diff --git a/DEPS b/DEPS
index 5b093d18..ed216bbb 100644
--- a/DEPS
+++ b/DEPS
@@ -209,11 +209,11 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Skia
   # and whatever else without interference from each other.
-  'skia_revision': '658d96662bc8b4a19cbd4c775e21ddde176f3d5d',
+  'skia_revision': '2a63f82422744f3e5a813de74f05e5e7282e3388',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
-  'v8_revision': 'bc9b57bdb0f2bc2896d490b13fa3b8cf4c6e1d1a',
+  'v8_revision': '60368d5347ca37d070e7592f9cbba494f2cb2ec2',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling swarming_client
   # and whatever else without interference from each other.
@@ -221,11 +221,11 @@
   # 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': '2622c7b049cd394eca9b12edddfb0bbe8c110475',
+  'angle_revision': 'ce7d80bd43eba819b9b895a80327cb5cade28b8a',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
-  'swiftshader_revision': '9dd7a5171793ca09a52bb5c9037a759f1e0b1ffa',
+  'swiftshader_revision': '21c3054afcd01aa1f142b91a965063abd563bcda',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling PDFium
   # and whatever else without interference from each other.
@@ -280,7 +280,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling catapult
   # and whatever else without interference from each other.
-  'catapult_revision': 'e83a92e7b8e7dc762352103a1b6103f0313255ca',
+  'catapult_revision': '8f9ae5d5d3347e0f1cc08b54272dfe7e0b4b0700',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
@@ -288,7 +288,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling devtools-frontend
   # and whatever else without interference from each other.
-  'devtools_frontend_revision': '07f7a07998c03b022c963ba8d286782189372186',
+  'devtools_frontend_revision': '045d6a80fb823342232c991069a80cad50041b64',
   # 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.
@@ -1332,7 +1332,7 @@
     Var('chromium_git') + '/external/github.com/cisco/openh264' + '@' + '3dd5b80bc4f172dd82925bb259cb7c82348409c5',
 
   'src/third_party/openscreen/src':
-    Var('chromium_git') + '/openscreen' + '@' + '6e5b900796102a16ef85f69ad4f7ef228d662219',
+    Var('chromium_git') + '/openscreen' + '@' + 'cacde3327295076c1e93d772ca38b115cf93bbd4',
 
   'src/third_party/openxr/src': {
     'url': Var('chromium_git') + '/external/github.com/KhronosGroup/OpenXR-SDK' + '@' + '97cfe495bb7a3853266b646d1c79e169387f9c7a',
@@ -1349,7 +1349,7 @@
   },
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' + 'e46c9b802f8761df0341aad1d7845b7732a48596',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' + 'b3f455e9edced2a776ddcb5a2cdf5ddcefa8776f',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '6f3e5028eb65d0b4c5fdd792106ac4c84eee1eb3',
@@ -1430,7 +1430,7 @@
               'version': 'r2LsKQPbfi0NYEO8tfocwaJ1MMACXPDLkgCI0IjJq-YC'
           },
       ],
-      'condition': 'host_os == "linux" and checkout_fuchsia',
+      'condition': 'host_os == "linux" and checkout_fuchsia_for_arm64_host',
       'dep_type': 'cipd',
   },
 
@@ -1547,7 +1547,7 @@
   'src/third_party/usrsctp/usrsctplib':
     Var('chromium_git') + '/external/github.com/sctplab/usrsctp' + '@' + '22ba62ffe79c3881581ab430368bf3764d9533eb',
 
-  'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@c47e201b15037753c0a089c09d561328b1e2b8ca',
+  'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@8ea5e4dc6455225072461adbb756c3e4f7c2b699',
 
   'src/third_party/vulkan_memory_allocator':
     Var('chromium_git') + '/external/github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git' + '@' + '732a76d9d3c70d6aa487216495eeb28518349c3a',
@@ -1635,7 +1635,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@ff155bec5f1fab5084ac9735280fead01162d13a',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@0cd4c506f8dd630e83a2e219bb8bd7ec97a46d2b',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index b43cbb49..2d8512a 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -1784,6 +1784,7 @@
 
   public_deps = [
     "//ash/public/cpp",
+    "//ash/public/cpp/app_list/vector_icons:vector_icons",
     "//ash/public/cpp/resources:ash_public_unscaled_resources",
     "//ash/resources/vector_icons",
     "//ash/strings",
diff --git a/ash/app_list/bubble/app_list_bubble_view.cc b/ash/app_list/bubble/app_list_bubble_view.cc
index c002aab..1983031 100644
--- a/ash/app_list/bubble/app_list_bubble_view.cc
+++ b/ash/app_list/bubble/app_list_bubble_view.cc
@@ -13,6 +13,7 @@
 #include "ash/public/cpp/shell_window_ids.h"
 #include "ash/shelf/shelf.h"
 #include "ash/shell.h"
+#include "ash/style/ash_color_provider.h"
 #include "base/bind.h"
 #include "base/i18n/rtl.h"
 #include "ui/base/ui_base_types.h"
@@ -78,6 +79,11 @@
   set_parent_window(
       Shell::GetContainer(root_window, kShellWindowId_AppListContainer));
 
+  // TODO(https://crbug.com/1204551): Add transparency and rounded corners.
+  // See TrayBubbleView and BubbleBorder.
+  set_color(AshColorProvider::Get()->GetBaseLayerColor(
+      AshColorProvider::BaseLayerType::kOpaque));
+
   auto* layout = SetLayoutManager(
       std::make_unique<BoxLayout>(BoxLayout::Orientation::kVertical));
   layout->set_cross_axis_alignment(BoxLayout::CrossAxisAlignment::kStretch);
diff --git a/ash/app_list/bubble/bubble_apps_page.cc b/ash/app_list/bubble/bubble_apps_page.cc
index 5b450941..97e70b2 100644
--- a/ash/app_list/bubble/bubble_apps_page.cc
+++ b/ash/app_list/bubble/bubble_apps_page.cc
@@ -6,10 +6,13 @@
 
 #include <limits>
 #include <memory>
+#include <string>
 #include <utility>
 
 #include "ash/app_list/bubble/scrollable_apps_grid_view.h"
+#include "ash/bubble/bubble_utils.h"
 #include "base/check.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 #include "ui/gfx/text_constants.h"
 #include "ui/views/controls/label.h"
 #include "ui/views/controls/scroll_view.h"
@@ -19,6 +22,15 @@
 using views::BoxLayout;
 
 namespace ash {
+namespace {
+
+std::unique_ptr<views::Label> CreateLabel(const std::u16string& text) {
+  auto label = std::make_unique<views::Label>(text);
+  bubble_utils::ApplyStyle(label.get(), bubble_utils::LabelStyle::kBody);
+  return label;
+}
+
+}  // namespace
 
 BubbleAppsPage::BubbleAppsPage(AppListViewDelegate* view_delegate) {
   DCHECK(view_delegate);
@@ -31,6 +43,8 @@
   scroll->SetDrawOverflowIndicator(false);
   scroll->SetHorizontalScrollBarMode(
       views::ScrollView::ScrollBarMode::kDisabled);
+  // Don't paint a background. The bubble already has one.
+  scroll->SetBackgroundColor(absl::nullopt);
 
   auto scroll_contents = std::make_unique<views::View>();
   auto* layout = scroll_contents->SetLayoutManager(
@@ -39,8 +53,7 @@
 
   // TODO(https://crbug.com/1204551): Localized strings.
   // TODO(https://crbug.com/1204551): Styling.
-  auto* continue_label =
-      scroll_contents->AddChildView(std::make_unique<views::Label>(u"Label"));
+  auto* continue_label = scroll_contents->AddChildView(CreateLabel(u"Label"));
   continue_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
 
   auto* continue_section =
@@ -54,7 +67,7 @@
   continue_layout->set_cross_axis_alignment(
       BoxLayout::CrossAxisAlignment::kStretch);
   for (int i = 0; i < 4; ++i) {
-    continue_section->AddChildView(std::make_unique<views::Label>(u"Item"));
+    continue_section->AddChildView(CreateLabel(u"Item"));
   }
 
   // TODO(https://crbug.com/1204551): Replace with real recent apps view.
@@ -68,7 +81,7 @@
   recent_apps_layout->set_main_axis_alignment(
       views::BoxLayout::MainAxisAlignment::kCenter);
   for (int i = 0; i < 5; ++i) {
-    recent_apps->AddChildView(std::make_unique<views::Label>(u"Item"));
+    recent_apps->AddChildView(CreateLabel(u"Item"));
   }
 
   // All apps section.
diff --git a/ash/app_list/bubble/bubble_search_page.cc b/ash/app_list/bubble/bubble_search_page.cc
index e00e4b7f..5c0e836 100644
--- a/ash/app_list/bubble/bubble_search_page.cc
+++ b/ash/app_list/bubble/bubble_search_page.cc
@@ -8,6 +8,8 @@
 #include <memory>
 #include <utility>
 
+#include "ash/bubble/bubble_utils.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 #include "ui/views/controls/label.h"
 #include "ui/views/controls/scroll_view.h"
 #include "ui/views/layout/box_layout.h"
@@ -25,6 +27,8 @@
   scroll->SetDrawOverflowIndicator(false);
   scroll->SetHorizontalScrollBarMode(
       views::ScrollView::ScrollBarMode::kDisabled);
+  // Don't paint a background. The bubble already has one.
+  scroll->SetBackgroundColor(absl::nullopt);
 
   auto scroll_contents = std::make_unique<views::View>();
   scroll_contents->SetLayoutManager(
@@ -38,7 +42,9 @@
   results->SetLayoutManager(std::make_unique<views::BoxLayout>(
       views::BoxLayout::Orientation::kVertical, gfx::Insets(), kSpacing));
   for (int i = 0; i < 20; ++i) {
-    results->AddChildView(std::make_unique<views::Label>(u"Result"));
+    auto label = std::make_unique<views::Label>(u"Result");
+    bubble_utils::ApplyStyle(label.get(), bubble_utils::LabelStyle::kBody);
+    results->AddChildView(std::move(label));
   }
 
   scroll->SetContents(std::move(scroll_contents));
diff --git a/ash/bubble/bubble_utils.cc b/ash/bubble/bubble_utils.cc
index 51149c2..f6f97a5 100644
--- a/ash/bubble/bubble_utils.cc
+++ b/ash/bubble/bubble_utils.cc
@@ -4,17 +4,51 @@
 
 #include "ash/bubble/bubble_utils.h"
 
+#include <memory>
+
 #include "ash/capture_mode/capture_mode_util.h"
 #include "ash/public/cpp/shell_window_ids.h"
 #include "ash/root_window_controller.h"
 #include "ash/shell.h"
+#include "ash/style/ash_color_provider.h"
+#include "base/bind.h"
+#include "base/callback.h"
 #include "base/check.h"
 #include "ui/aura/window.h"
 #include "ui/events/event.h"
 #include "ui/events/types/event_type.h"
+#include "ui/views/controls/label.h"
 
 namespace ash {
 namespace bubble_utils {
+namespace {
+
+// A label which invokes a constructor-specified callback in `OnThemeChanged()`.
+class LabelWithThemeChangedCallback : public views::Label {
+ public:
+  using ThemeChangedCallback = base::RepeatingCallback<void(views::Label*)>;
+
+  LabelWithThemeChangedCallback(const std::u16string& text,
+                                ThemeChangedCallback theme_changed_callback)
+      : views::Label(text),
+        theme_changed_callback_(std::move(theme_changed_callback)) {}
+
+  LabelWithThemeChangedCallback(const LabelWithThemeChangedCallback&) = delete;
+  LabelWithThemeChangedCallback& operator=(
+      const LabelWithThemeChangedCallback&) = delete;
+  ~LabelWithThemeChangedCallback() override = default;
+
+ private:
+  // views::Label:
+  void OnThemeChanged() override {
+    views::Label::OnThemeChanged();
+    theme_changed_callback_.Run(this);
+  }
+
+  ThemeChangedCallback theme_changed_callback_;
+};
+
+}  // namespace
 
 bool ShouldCloseBubbleForEvent(const ui::LocatedEvent& event) {
   // Should only be called for "press" type events.
@@ -60,5 +94,47 @@
   return true;
 }
 
+void ApplyStyle(views::Label* label, LabelStyle style) {
+  label->SetAutoColorReadabilityEnabled(false);
+  label->SetEnabledColor(AshColorProvider::Get()->GetContentLayerColor(
+      AshColorProvider::ContentLayerType::kTextColorPrimary));
+
+  switch (style) {
+    case LabelStyle::kBadge:
+      label->SetFontList(gfx::FontList({"Roboto"}, gfx::Font::NORMAL, 14,
+                                       gfx::Font::Weight::MEDIUM));
+      break;
+    case LabelStyle::kBody:
+      label->SetFontList(gfx::FontList({"Roboto"}, gfx::Font::NORMAL, 14,
+                                       gfx::Font::Weight::NORMAL));
+      break;
+    case LabelStyle::kChip:
+      label->SetFontList(gfx::FontList({"Roboto"}, gfx::Font::NORMAL, 13,
+                                       gfx::Font::Weight::NORMAL));
+      break;
+    case LabelStyle::kHeader:
+      label->SetFontList(gfx::FontList({"Roboto"}, gfx::Font::NORMAL, 16,
+                                       gfx::Font::Weight::MEDIUM));
+      break;
+  }
+}
+
+std::unique_ptr<views::Label> CreateLabel(LabelStyle style,
+                                          const std::u16string& text) {
+  auto label = std::make_unique<LabelWithThemeChangedCallback>(
+      text,
+      /*theme_changed_callback=*/base::BindRepeating(
+          [](LabelStyle style, views::Label* label) {
+            ApplyStyle(label, style);
+          },
+          style));
+  // Apply `style` to `label` manually in case the view is painted without ever
+  // having being added to the view hierarchy. In such cases, the `label` will
+  // not receive an `OnThemeChanged()` event. This occurs, for example, with
+  // holding space drag images.
+  ApplyStyle(label.get(), style);
+  return label;
+}
+
 }  // namespace bubble_utils
 }  // namespace ash
diff --git a/ash/bubble/bubble_utils.h b/ash/bubble/bubble_utils.h
index aee494c7..69bcb60c 100644
--- a/ash/bubble/bubble_utils.h
+++ b/ash/bubble/bubble_utils.h
@@ -5,12 +5,19 @@
 #ifndef ASH_BUBBLE_BUBBLE_UTILS_H_
 #define ASH_BUBBLE_BUBBLE_UTILS_H_
 
+#include <memory>
+#include <string>
+
 #include "ash/ash_export.h"
 
 namespace ui {
 class LocatedEvent;
 }  // namespace ui
 
+namespace views {
+class Label;
+}  // namespace views
+
 namespace ash {
 namespace bubble_utils {
 
@@ -20,6 +27,23 @@
 // bubble will close and immediately reopen).
 ASH_EXPORT bool ShouldCloseBubbleForEvent(const ui::LocatedEvent& event);
 
+// Enumeration of supported label styles.
+enum class LabelStyle {
+  kBadge,
+  kBody,
+  kChip,
+  kHeader,
+};
+
+// Applies the specified `style` to the given `label`.
+ASH_EXPORT void ApplyStyle(views::Label* label, LabelStyle style);
+
+// Creates a label with optional `text` matching the specified `style`. The
+// label will paint correctly even if it is not added to the view hierarchy.
+std::unique_ptr<views::Label> CreateLabel(
+    LabelStyle style,
+    const std::u16string& text = std::u16string());
+
 }  // namespace bubble_utils
 }  // namespace ash
 
diff --git a/ash/constants/ash_features.cc b/ash/constants/ash_features.cc
index 218a4f6f..331e399 100644
--- a/ash/constants/ash_features.cc
+++ b/ash/constants/ash_features.cc
@@ -650,7 +650,7 @@
 
 // Enables or disables sticky settings in the Scan app.
 const base::Feature kScanAppStickySettings{"ScanAppStickySettings",
-                                           base::FEATURE_DISABLED_BY_DEFAULT};
+                                           base::FEATURE_ENABLED_BY_DEFAULT};
 
 // Enables or disables long kill timeout for session manager daemon. When
 // enabled, session manager daemon waits for a longer time (e.g. 12s) for chrome
diff --git a/ash/content/common/resources/navigation_selector.html b/ash/content/common/resources/navigation_selector.html
index cc526678..7ab70008 100644
--- a/ash/content/common/resources/navigation_selector.html
+++ b/ash/content/common/resources/navigation_selector.html
@@ -52,7 +52,8 @@
   <div id="navigationSelectorMenu">
     <template id="menuItems" is="dom-repeat" items="{{menuItems}}">
       <template is="dom-if" if="[[!isCollapsible_(item)]]">
-        <div class="navigation-item" tabindex="0" on-click="onSelected_">
+        <div class$="{{computeInitialClass_(item.selectorItem)}}" tabindex="0"
+            on-click="onSelected_">
           <iron-icon class="icon" icon="[[item.selectorItem.icon]]" alt="">
           </iron-icon>
           [[item.selectorItem.name]]
@@ -65,7 +66,7 @@
         <iron-collapse opened="[[item.properties.isExpanded]]">
           <template class="collapsed-list" is="dom-repeat"
               items="{{item.properties.subMenuItems}}">
-            <div class="navigation-item nested-item" tabindex="0"
+            <div class$="{{computeInitialClass_(item)}}" tabindex="0"
                 on-click="onNestedSelected_">
               <iron-icon class="icon" icon="[[getIcon_(item)]]" alt="">
               </iron-icon>
diff --git a/ash/content/common/resources/navigation_selector.js b/ash/content/common/resources/navigation_selector.js
index 17f4dd20..cd88f80 100644
--- a/ash/content/common/resources/navigation_selector.js
+++ b/ash/content/common/resources/navigation_selector.js
@@ -152,6 +152,20 @@
   getIcon_(item) {
     return item.icon;
   }
+
+  /**
+   * @param {!SelectorItem} item
+   * @return {string}
+   * @protected
+   */
+  computeInitialClass_(item) {
+    let classList = "navigation-item";
+    if (!!this.selectedItem && item.name == this.selectedItem.name) {
+      // Add the initial .selected class to the currently selected entry.
+      classList += " selected";
+    }
+    return classList;
+  }
 }
 
 customElements.define(NavigationSelectorElement.is, NavigationSelectorElement);
\ No newline at end of file
diff --git a/ash/content/shimless_rma/resources/onboarding_update_page.html b/ash/content/shimless_rma/resources/onboarding_update_page.html
index c18d8da..d8fb1a8 100644
--- a/ash/content/shimless_rma/resources/onboarding_update_page.html
+++ b/ash/content/shimless_rma/resources/onboarding_update_page.html
@@ -12,12 +12,12 @@
     </p>
     <p hidden$="{{!checkInProgress_}}">Checking OS version...</p>
     <cr-button disabled="{{checkInProgress_}}"
-        hidden$="{{updateCheckBtnHidden_(networkAvailable, updateAvailable_)}}"
-        id="checkUpdate" on-click="onUpdateCheckBtnClicked_">
+        hidden$="{{updateCheckButtonHidden_(networkAvailable, updateAvailable_)}}"
+        id="checkUpdate" on-click="onUpdateCheckButtonClicked_">
       Check for updates
     </cr-button>
     <cr-button hidden$="{{!updateAvailable_}}"
-        id="performUpdate" on-click="onUpdateBtnClicked_">
+        id="performUpdate" on-click="onUpdateButtonClicked_">
       Update version and restart
     </cr-button>
   </div>
diff --git a/ash/content/shimless_rma/resources/onboarding_update_page.js b/ash/content/shimless_rma/resources/onboarding_update_page.js
index 9401520..845ff63 100644
--- a/ash/content/shimless_rma/resources/onboarding_update_page.js
+++ b/ash/content/shimless_rma/resources/onboarding_update_page.js
@@ -102,7 +102,7 @@
   }
 
   /** @protected */
-  onUpdateCheckBtnClicked_() {
+  onUpdateCheckButtonClicked_() {
     this.checkInProgress_ = true;
     this.shimlessRmaService_.checkForChromeUpdates().then((res) => {
       if (res.updateAvailable) {
@@ -120,14 +120,14 @@
   }
 
   /** @protected */
-  onUpdateBtnClicked_() {
+  onUpdateButtonClicked_() {
     // TODO(joonbug): trigger update
   }
 
   /**
    * @protected
    */
-  updateCheckBtnHidden_() {
+  updateCheckButtonHidden_() {
     return !this.networkAvailable || this.updateAvailable_;
   }
 };
diff --git a/ash/content/shimless_rma/resources/shimless_rma.html b/ash/content/shimless_rma/resources/shimless_rma.html
index 7d340c5..02c5689 100644
--- a/ash/content/shimless_rma/resources/shimless_rma.html
+++ b/ash/content/shimless_rma/resources/shimless_rma.html
@@ -2,10 +2,10 @@
 </style>
 <div id="shimlessRMAContainer">
 	<div class="shimlessHeader">
-		<cr-button 
-			id="back" on-click="onBackBtnClicked_"
-			disabled="[[isBtnDisabled_(currentPage_.btnBack)]]"
-			hidden$="[[isBtnHidden_(currentPage_.btnBack)]]">
+		<cr-button
+			id="back" on-click="onBackButtonClicked_"
+			disabled="[[isButtonDisabled_(currentPage_.buttonBack)]]"
+			hidden$="[[isButtonHidden_(currentPage_.buttonBack)]]">
 		  Back
 		</cr-button>
 	</div>
@@ -13,15 +13,15 @@
 	<div id="contentContainer"></div>
 	<div class="shimlessFooter">
 		<cr-button
-			id="cancel" on-click="onCancelBtnClicked_"
-			disabled="[[isBtnDisabled_(currentPage_.btnCancel)]]"
-			hidden$="[[isBtnHidden_(currentPage_.btnCancel)]]">
+			id="cancel" on-click="onCancelButtonClicked_"
+			disabled="[[isButtonDisabled_(currentPage_.buttonCancel)]]"
+			hidden$="[[isButtonHidden_(currentPage_.buttonCancel)]]">
 		  Cancel
 		</cr-button>
 		<cr-button
-			id="next" on-click="onNextBtnClicked_"
-			disabled="[[isBtnDisabled_(currentPage_.btnNext)]]"
-			hidden$="[[isBtnHidden_(currentPage_.btnNext)]]">
+			id="next" on-click="onNextButtonClicked_"
+			disabled="[[isButtonDisabled_(currentPage_.buttonNext)]]"
+			hidden$="[[isButtonHidden_(currentPage_.buttonNext)]]">
 		  Next
 		</cr-button>
 	</div>
diff --git a/ash/content/shimless_rma/resources/shimless_rma.js b/ash/content/shimless_rma/resources/shimless_rma.js
index b1e07640..081d95df 100644
--- a/ash/content/shimless_rma/resources/shimless_rma.js
+++ b/ash/content/shimless_rma/resources/shimless_rma.js
@@ -17,7 +17,7 @@
  * Enum for button states.
  * @enum {string}
  */
-export const BtnState = {
+export const ButtonState = {
   VISIBLE: 'visible',
   DISABLED: 'disable',
   HIDDEN: 'hidden'
@@ -26,10 +26,10 @@
 /**
  * @typedef {{
  *  componentIs: string,
- *  btnNext: !BtnState,
- *  btnNextLabel: string,
- *  btnCancel: !BtnState,
- *  btnBack: !BtnState,
+ *  buttonNext: !ButtonState,
+ *  buttonNextLabel: string,
+ *  buttonCancel: !ButtonState,
+ *  buttonBack: !ButtonState,
  * }}
  */
 let PageInfo;
@@ -40,28 +40,28 @@
 const StateComponentMapping = {
   [RmaState.kUnknown]: {
     componentIs: 'badcomponent',
-    btnNext: BtnState.HIDDEN,
-    btnCancel: BtnState.VISIBLE,
-    btnBack: BtnState.HIDDEN,
+    buttonNext: ButtonState.HIDDEN,
+    buttonCancel: ButtonState.VISIBLE,
+    buttonBack: ButtonState.HIDDEN,
   },
   [RmaState.kWelcomeScreen]: {
     componentIs: 'onboarding-landing-page',
-    btnNext: BtnState.VISIBLE,
-    btnCancel: BtnState.VISIBLE,
-    btnBack: BtnState.HIDDEN,
+    buttonNext: ButtonState.VISIBLE,
+    buttonCancel: ButtonState.VISIBLE,
+    buttonBack: ButtonState.HIDDEN,
   },
   // TODO(joonbug): update to correct RmaState
   [RmaState.kSelectComponents]: {
     componentIs: 'onboarding-update-page',
-    btnNext: BtnState.HIDDEN,
-    btnCancel: BtnState.VISIBLE,
-    btnBack: BtnState.VISIBLE,
+    buttonNext: ButtonState.HIDDEN,
+    buttonCancel: ButtonState.VISIBLE,
+    buttonBack: ButtonState.VISIBLE,
   },
   [RmaState.kUpdateChrome]: {
     componentIs: 'onboarding-update-page',
-    btnNext: BtnState.HIDDEN,
-    btnCancel: BtnState.VISIBLE,
-    btnBack: BtnState.VISIBLE,
+    buttonNext: ButtonState.HIDDEN,
+    buttonCancel: ButtonState.VISIBLE,
+    buttonBack: ButtonState.VISIBLE,
   },
 };
 
@@ -190,38 +190,38 @@
   }
 
   /** @protected */
-  isBtnHidden_(btn) {
-    return btn === 'hidden';
+  isButtonHidden_(button) {
+    return button === 'hidden';
   }
 
   /** @protected */
-  isBtnDisabled_(btn) {
-    return btn === 'disabled';
+  isButtonDisabled_(button) {
+    return button === 'disabled';
   }
 
   /**
-   * @param {string} btnName
-   * @param {!BtnState} btnState
+   * @param {string} buttonName
+   * @param {!ButtonState} buttonState
    */
-  updateBtnState(btnName, btnState) {
-    assert(this.currentPage_.hasOwnProperty(btnName));
-    this.set(`currentPage_.${btnName}`, btnState);
+  updateButtonState(buttonName, buttonState) {
+    assert(this.currentPage_.hasOwnProperty(buttonName));
+    this.set(`currentPage_.${buttonName}`, buttonState);
   }
 
   /** @protected */
-  onBackBtnClicked_() {
+  onBackButtonClicked_() {
     // TODO(joonbug): error handling based on state.error
     this.fetchPrevState_().then((state) => this.loadState_(state.prevState));
   }
 
   /** @protected */
-  onNextBtnClicked_() {
+  onNextButtonClicked_() {
     const page = this.shadowRoot.querySelector(this.currentPage_.componentIs);
     assert(page);
 
     // Acquire promise to check whether current page is ready for next page.
     const prepPageAdvance =
-        page.onNextBtnClick || (() => Promise.resolve(RmaState.kUnknown));
+        page.onNextButtonClick || (() => Promise.resolve(RmaState.kUnknown));
     assert(typeof prepPageAdvance === 'function');
 
     prepPageAdvance()
@@ -233,7 +233,7 @@
   }
 
   /** @protected */
-  onCancelBtnClicked_() {
+  onCancelButtonClicked_() {
     this.loadState_(assert(this.initialState_));
   }
 };
diff --git a/ash/fast_ink/view_tree_host_root_view.cc b/ash/fast_ink/view_tree_host_root_view.cc
index d1c7254..8786ee0 100644
--- a/ash/fast_ink/view_tree_host_root_view.cc
+++ b/ash/fast_ink/view_tree_host_root_view.cc
@@ -300,8 +300,10 @@
 }
 
 ViewTreeHostRootView::~ViewTreeHostRootView() {
-  LayerTreeViewTreeFrameSinkHolder::DeleteWhenLastResourceHasBeenReclaimed(
-      std::move(frame_sink_holder_));
+  if (frame_sink_holder_) {
+    LayerTreeViewTreeFrameSinkHolder::DeleteWhenLastResourceHasBeenReclaimed(
+        std::move(frame_sink_holder_));
+  }
 }
 
 void ViewTreeHostRootView::Paint() {
diff --git a/ash/quick_answers/quick_answers_ui_controller.cc b/ash/quick_answers/quick_answers_ui_controller.cc
index fd58ff44..795b1a6a 100644
--- a/ash/quick_answers/quick_answers_ui_controller.cc
+++ b/ash/quick_answers/quick_answers_ui_controller.cc
@@ -4,7 +4,9 @@
 
 #include "ash/quick_answers/quick_answers_ui_controller.h"
 
+#include "ash/constants/ash_features.h"
 #include "ash/public/cpp/assistant/controller/assistant_interaction_controller.h"
+#include "ash/public/cpp/new_window_delegate.h"
 #include "ash/quick_answers/quick_answers_controller_impl.h"
 #include "ash/quick_answers/ui/quick_answers_view.h"
 #include "ash/quick_answers/ui/user_notice_view.h"
@@ -14,6 +16,7 @@
 #include "chromeos/components/quick_answers/quick_answers_model.h"
 #include "chromeos/services/assistant/public/cpp/assistant_service.h"
 #include "mojo/public/cpp/bindings/remote.h"
+#include "net/base/escape.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "ui/aura/client/aura_constants.h"
 #include "ui/base/l10n/l10n_util.h"
@@ -22,6 +25,12 @@
 using chromeos::quick_answers::QuickAnswer;
 namespace ash {
 
+namespace {
+
+constexpr char kGoogleSearchUrlPrefix[] = "https://www.google.com/search?q=";
+
+}  // namespace
+
 QuickAnswersUiController::QuickAnswersUiController(
     QuickAnswersControllerImpl* controller)
     : controller_(controller) {}
@@ -53,9 +62,16 @@
   // Route dismissal through |controller_| for logging impressions.
   controller_->DismissQuickAnswers(/*is_active=*/true);
 
-  ash::AssistantInteractionController::Get()->StartTextInteraction(
-      query_, /*allow_tts=*/false,
-      chromeos::assistant::AssistantQuerySource::kQuickAnswers);
+  if (chromeos::features::IsQuickAnswersStandaloneSettingsEnabled()) {
+    NewWindowDelegate::GetInstance()->NewTabWithUrl(
+        GURL(kGoogleSearchUrlPrefix +
+             net::EscapeUrlEncodedData(query_, /*use_plus=*/true)),
+        /*from_user_interaction=*/true);
+  } else {
+    ash::AssistantInteractionController::Get()->StartTextInteraction(
+        query_, /*allow_tts=*/false,
+        chromeos::assistant::AssistantQuerySource::kQuickAnswers);
+  }
   controller_->OnQuickAnswerClick();
 }
 
diff --git a/ash/quick_answers/ui/quick_answers_view.cc b/ash/quick_answers/ui/quick_answers_view.cc
index c5caa58e..2db2d84 100644
--- a/ash/quick_answers/ui/quick_answers_view.cc
+++ b/ash/quick_answers/ui/quick_answers_view.cc
@@ -5,6 +5,7 @@
 #include "ash/quick_answers/ui/quick_answers_view.h"
 
 #include "ash/constants/ash_features.h"
+#include "ash/public/cpp/app_list/vector_icons/vector_icons.h"
 #include "ash/public/cpp/assistant/assistant_interface_binder.h"
 #include "ash/quick_answers/quick_answers_ui_controller.h"
 #include "ash/quick_answers/ui/quick_answers_pre_target_handler.h"
@@ -55,6 +56,10 @@
 constexpr int kAssistantIconSizeDip = 16;
 constexpr gfx::Insets kAssistantIconInsets(10, 10, 0, 8);
 
+// Google icon.
+constexpr int kGoogleIconSizeDip = 16;
+constexpr gfx::Insets kGoogleIconInsets(10, 10, 0, 8);
+
 // Spacing between lines in the main view.
 constexpr int kLineSpacingDip = 4;
 constexpr int kLineHeightDip = 20;
@@ -283,6 +288,15 @@
       chromeos::kAssistantIcon, kAssistantIconSizeDip, gfx::kPlaceholderColor));
 }
 
+void QuickAnswersView::AddGoogleIcon() {
+  // Add Google icon.
+  auto* google_icon =
+      main_view_->AddChildView(std::make_unique<views::ImageView>());
+  google_icon->SetBorder(views::CreateEmptyBorder(kGoogleIconInsets));
+  google_icon->SetImage(gfx::CreateVectorIcon(
+      kGoogleColorIcon, kGoogleIconSizeDip, gfx::kPlaceholderColor));
+}
+
 void QuickAnswersView::AddDogfoodButton() {
   auto* dogfood_view = AddChildView(std::make_unique<View>());
   auto* layout =
@@ -334,8 +348,12 @@
   layout->set_cross_axis_alignment(
       views::BoxLayout::CrossAxisAlignment::kStart);
 
-  // Add Assistant icon.
-  AddAssistantIcon();
+  // Add branding icon.
+  if (chromeos::features::IsQuickAnswersStandaloneSettingsEnabled()) {
+    AddGoogleIcon();
+  } else {
+    AddAssistantIcon();
+  }
 
   // Add content view.
   content_view_ = main_view_->AddChildView(std::make_unique<View>());
diff --git a/ash/quick_answers/ui/quick_answers_view.h b/ash/quick_answers/ui/quick_answers_view.h
index 22e91035..926c681 100644
--- a/ash/quick_answers/ui/quick_answers_view.h
+++ b/ash/quick_answers/ui/quick_answers_view.h
@@ -68,6 +68,7 @@
   void AddDogfoodButton();
   void AddSettingsButton();
   void AddAssistantIcon();
+  void AddGoogleIcon();
   void ResetContentView();
   void SetBackgroundState(bool highlight);
   void UpdateBounds();
diff --git a/ash/system/holding_space/downloads_section.cc b/ash/system/holding_space/downloads_section.cc
index 648e364..580cd03d7 100644
--- a/ash/system/holding_space/downloads_section.cc
+++ b/ash/system/holding_space/downloads_section.cc
@@ -4,6 +4,7 @@
 
 #include "ash/system/holding_space/downloads_section.h"
 
+#include "ash/bubble/bubble_utils.h"
 #include "ash/public/cpp/holding_space/holding_space_client.h"
 #include "ash/public/cpp/holding_space/holding_space_constants.h"
 #include "ash/public/cpp/holding_space/holding_space_controller.h"
@@ -13,7 +14,6 @@
 #include "ash/style/ash_color_provider.h"
 #include "ash/system/holding_space/holding_space_item_chip_view.h"
 #include "ash/system/holding_space/holding_space_item_chips_container.h"
-#include "ash/system/holding_space/holding_space_util.h"
 #include "base/bind.h"
 #include "base/callback_helpers.h"
 #include "ui/base/l10n/l10n_util.h"
@@ -66,8 +66,8 @@
         kHoldingSpaceDownloadsHeaderSpacing));
 
     // Label.
-    auto* label = AddChildView(holding_space_util::CreateLabel(
-        holding_space_util::LabelStyle::kHeader,
+    auto* label = AddChildView(bubble_utils::CreateLabel(
+        bubble_utils::LabelStyle::kHeader,
         l10n_util::GetStringUTF16(IDS_ASH_HOLDING_SPACE_DOWNLOADS_TITLE)));
     label->SetHorizontalAlignment(gfx::HorizontalAlignment::ALIGN_LEFT);
     layout->SetFlexForView(label, 1);
diff --git a/ash/system/holding_space/holding_space_drag_util.cc b/ash/system/holding_space/holding_space_drag_util.cc
index ff99388..a9621df6 100644
--- a/ash/system/holding_space/holding_space_drag_util.cc
+++ b/ash/system/holding_space/holding_space_drag_util.cc
@@ -6,6 +6,7 @@
 
 #include <memory>
 
+#include "ash/bubble/bubble_utils.h"
 #include "ash/public/cpp/ash_features.h"
 #include "ash/public/cpp/holding_space/holding_space_image.h"
 #include "ash/public/cpp/holding_space/holding_space_item.h"
@@ -13,7 +14,6 @@
 #include "ash/style/ash_color_provider.h"
 #include "ash/style/scoped_light_mode_as_default.h"
 #include "ash/system/holding_space/holding_space_item_view.h"
-#include "ash/system/holding_space/holding_space_util.h"
 #include "base/containers/adapters.h"
 #include "base/i18n/rtl.h"
 #include "ui/compositor/canvas_painter.h"
@@ -223,7 +223,8 @@
 
     // Label.
     ScopedLightModeAsDefault scoped_light_mode;
-    auto* label = AddChildView(CreateLabel(LabelStyle::kChip, item->text()));
+    auto* label = AddChildView(bubble_utils::CreateLabel(
+        bubble_utils::LabelStyle::kChip, item->text()));
     label->SetElideBehavior(gfx::ElideBehavior::ELIDE_MIDDLE);
     label->SetHorizontalAlignment(gfx::HorizontalAlignment::ALIGN_LEFT);
     layout->SetFlexForView(label, 1);
@@ -310,7 +311,8 @@
         views::BoxLayout::MainAxisAlignment::kCenter);
 
     // Label.
-    auto* label = AddChildView(CreateLabel(LabelStyle::kBadge));
+    auto* label = AddChildView(
+        bubble_utils::CreateLabel(bubble_utils::LabelStyle::kBadge));
     label->SetEnabledColor(AshColorProvider::Get()->IsDarkModeEnabled()
                                ? gfx::kGoogleGrey900
                                : gfx::kGoogleGrey200);
diff --git a/ash/system/holding_space/holding_space_item_chip_view.cc b/ash/system/holding_space/holding_space_item_chip_view.cc
index 2c04acd..40f4e7a9 100644
--- a/ash/system/holding_space/holding_space_item_chip_view.cc
+++ b/ash/system/holding_space/holding_space_item_chip_view.cc
@@ -6,12 +6,12 @@
 
 #include <algorithm>
 
+#include "ash/bubble/bubble_utils.h"
 #include "ash/public/cpp/holding_space/holding_space_constants.h"
 #include "ash/public/cpp/holding_space/holding_space_item.h"
 #include "ash/public/cpp/rounded_image_view.h"
 #include "ash/style/ash_color_provider.h"
 #include "ash/system/holding_space/holding_space_item_view.h"
-#include "ash/system/holding_space/holding_space_util.h"
 #include "ash/system/holding_space/holding_space_view_delegate.h"
 #include "base/bind.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
@@ -124,7 +124,7 @@
   label_->SetPaintToLayer();
   label_->layer()->SetFillsBoundsOpaquely(false);
 
-  holding_space_util::ApplyStyle(label_, holding_space_util::LabelStyle::kChip);
+  bubble_utils::ApplyStyle(label_, bubble_utils::LabelStyle::kChip);
 
   // Pin.
   views::View* pin_button_container =
diff --git a/ash/system/holding_space/holding_space_util.cc b/ash/system/holding_space/holding_space_util.cc
index 59d48fb..ccae26f 100644
--- a/ash/system/holding_space/holding_space_util.cc
+++ b/ash/system/holding_space/holding_space_util.cc
@@ -4,8 +4,9 @@
 
 #include "ash/system/holding_space/holding_space_util.h"
 
-#include "ash/style/ash_color_provider.h"
-#include "base/bind.h"
+#include <memory>
+
+#include "third_party/abseil-cpp/absl/types/optional.h"
 #include "ui/compositor/layer.h"
 #include "ui/compositor/layer_animation_element.h"
 #include "ui/compositor/layer_animation_observer.h"
@@ -13,8 +14,8 @@
 #include "ui/compositor/layer_animator.h"
 #include "ui/gfx/canvas.h"
 #include "ui/views/background.h"
-#include "ui/views/controls/label.h"
 #include "ui/views/painter.h"
+#include "ui/views/view.h"
 
 namespace ash {
 namespace holding_space_util {
@@ -62,33 +63,6 @@
   const absl::optional<gfx::InsetsF> insets_;
 };
 
-// LabelWithThemeChangedCallback -----------------------------------------------
-
-// A label which invokes a constructor-specified callback in `OnThemeChanged()`.
-class LabelWithThemeChangedCallback : public views::Label {
- public:
-  using ThemeChangedCallback = base::RepeatingCallback<void(views::Label*)>;
-
-  LabelWithThemeChangedCallback(const std::u16string& text,
-                                ThemeChangedCallback theme_changed_callback)
-      : views::Label(text),
-        theme_changed_callback_(std::move(theme_changed_callback)) {}
-
-  LabelWithThemeChangedCallback(const LabelWithThemeChangedCallback&) = delete;
-  LabelWithThemeChangedCallback& operator=(
-      const LabelWithThemeChangedCallback&) = delete;
-  ~LabelWithThemeChangedCallback() override = default;
-
- private:
-  // views::Label:
-  void OnThemeChanged() override {
-    views::Label::OnThemeChanged();
-    theme_changed_callback_.Run(this);
-  }
-
-  ThemeChangedCallback theme_changed_callback_;
-};
-
 // Helpers ---------------------------------------------------------------------
 
 // Creates a `ui::LayerAnimationSequence` for the specified `element` with
@@ -148,48 +122,6 @@
             observer);
 }
 
-void ApplyStyle(views::Label* label, LabelStyle style) {
-  label->SetAutoColorReadabilityEnabled(false);
-  label->SetEnabledColor(AshColorProvider::Get()->GetContentLayerColor(
-      AshColorProvider::ContentLayerType::kTextColorPrimary));
-
-  switch (style) {
-    case LabelStyle::kBadge:
-      label->SetFontList(gfx::FontList({"Roboto"}, gfx::Font::NORMAL, 14,
-                                       gfx::Font::Weight::MEDIUM));
-      break;
-    case LabelStyle::kBody:
-      label->SetFontList(gfx::FontList({"Roboto"}, gfx::Font::NORMAL, 14,
-                                       gfx::Font::Weight::NORMAL));
-      break;
-    case LabelStyle::kChip:
-      label->SetFontList(gfx::FontList({"Roboto"}, gfx::Font::NORMAL, 13,
-                                       gfx::Font::Weight::NORMAL));
-      break;
-    case LabelStyle::kHeader:
-      label->SetFontList(gfx::FontList({"Roboto"}, gfx::Font::NORMAL, 16,
-                                       gfx::Font::Weight::MEDIUM));
-      break;
-  }
-}
-
-std::unique_ptr<views::Label> CreateLabel(LabelStyle style,
-                                          const std::u16string& text) {
-  auto label = std::make_unique<LabelWithThemeChangedCallback>(
-      text,
-      /*theme_changed_callback=*/base::BindRepeating(
-          [](LabelStyle style, views::Label* label) {
-            ApplyStyle(label, style);
-          },
-          style));
-  // Apply `style` to `label` manually in case the view is painted without ever
-  // having being added to the view hierarchy. In such cases, the `label` will
-  // not receive an `OnThemeChanged()` event. This occurs, for example, with
-  // holding space drag images.
-  ApplyStyle(label.get(), style);
-  return label;
-}
-
 std::unique_ptr<views::Background> CreateCircleBackground(SkColor color,
                                                           size_t fixed_size) {
   return views::CreateBackgroundFromPainter(
diff --git a/ash/system/holding_space/holding_space_util.h b/ash/system/holding_space/holding_space_util.h
index f55d2b2..140a6ac 100644
--- a/ash/system/holding_space/holding_space_util.h
+++ b/ash/system/holding_space/holding_space_util.h
@@ -5,7 +5,7 @@
 #ifndef ASH_SYSTEM_HOLDING_SPACE_HOLDING_SPACE_UTIL_H_
 #define ASH_SYSTEM_HOLDING_SPACE_HOLDING_SPACE_UTIL_H_
 
-#include <string>
+#include <memory>
 
 #include "base/time/time.h"
 #include "third_party/skia/include/core/SkColor.h"
@@ -17,7 +17,6 @@
 
 namespace views {
 class Background;
-class Label;
 class View;
 }  // namespace views
 
@@ -37,23 +36,6 @@
                 base::TimeDelta duration,
                 ui::LayerAnimationObserver* observer);
 
-// TODO(crbug.com/1199925): Move to ash typography.
-// Enumeration of supported label styles.
-enum class LabelStyle {
-  kBadge,
-  kBody,
-  kChip,
-  kHeader,
-};
-
-// Applies the specified `style` to the given `label`.
-void ApplyStyle(views::Label* label, LabelStyle style);
-
-// Creates a label with optional `text` matching the specified `style`.
-std::unique_ptr<views::Label> CreateLabel(
-    LabelStyle style,
-    const std::u16string& text = std::u16string());
-
 // Creates a circular background of the specified `color` and `fixed_size`.
 std::unique_ptr<views::Background> CreateCircleBackground(SkColor color,
                                                           size_t fixed_size);
diff --git a/ash/system/holding_space/pinned_files_section.cc b/ash/system/holding_space/pinned_files_section.cc
index c43f338..8614b89c 100644
--- a/ash/system/holding_space/pinned_files_section.cc
+++ b/ash/system/holding_space/pinned_files_section.cc
@@ -4,6 +4,7 @@
 
 #include "ash/system/holding_space/pinned_files_section.h"
 
+#include "ash/bubble/bubble_utils.h"
 #include "ash/public/cpp/holding_space/holding_space_client.h"
 #include "ash/public/cpp/holding_space/holding_space_constants.h"
 #include "ash/public/cpp/holding_space/holding_space_controller.h"
@@ -17,7 +18,6 @@
 #include "ash/style/ash_color_provider.h"
 #include "ash/system/holding_space/holding_space_item_chip_view.h"
 #include "ash/system/holding_space/holding_space_item_chips_container.h"
-#include "ash/system/holding_space/holding_space_util.h"
 #include "ash/system/holding_space/holding_space_view_delegate.h"
 #include "base/bind.h"
 #include "base/callback_helpers.h"
@@ -113,7 +113,7 @@
 
     // Label.
     auto* label = AddChildView(
-        holding_space_util::CreateLabel(holding_space_util::LabelStyle::kChip));
+        bubble_utils::CreateLabel(bubble_utils::LabelStyle::kChip));
     label->SetText(l10n_util::GetStringUTF16(
         IDS_ASH_HOLDING_SPACE_PINNED_FILES_APP_CHIP_TEXT));
     layout->SetFlexForView(label, 1);
@@ -154,8 +154,8 @@
 }
 
 std::unique_ptr<views::View> PinnedFilesSection::CreateHeader() {
-  auto header = holding_space_util::CreateLabel(
-      holding_space_util::LabelStyle::kHeader,
+  auto header = bubble_utils::CreateLabel(
+      bubble_utils::LabelStyle::kHeader,
       l10n_util::GetStringUTF16(IDS_ASH_HOLDING_SPACE_PINNED_TITLE));
   header->SetHorizontalAlignment(gfx::HorizontalAlignment::ALIGN_LEFT);
   header->SetPaintToLayer();
@@ -193,8 +193,8 @@
       views::BoxLayout::CrossAxisAlignment::kStart);
 
   // Prompt.
-  auto* prompt = placeholder->AddChildView(holding_space_util::CreateLabel(
-      holding_space_util::LabelStyle::kBody,
+  auto* prompt = placeholder->AddChildView(bubble_utils::CreateLabel(
+      bubble_utils::LabelStyle::kBody,
       l10n_util::GetStringUTF16(IDS_ASH_HOLDING_SPACE_PINNED_EMPTY_PROMPT)));
   prompt->SetHorizontalAlignment(gfx::HorizontalAlignment::ALIGN_LEFT);
   prompt->SetMultiLine(true);
diff --git a/ash/system/holding_space/screen_captures_section.cc b/ash/system/holding_space/screen_captures_section.cc
index 559e949..ed42760 100644
--- a/ash/system/holding_space/screen_captures_section.cc
+++ b/ash/system/holding_space/screen_captures_section.cc
@@ -4,11 +4,11 @@
 
 #include "ash/system/holding_space/screen_captures_section.h"
 
+#include "ash/bubble/bubble_utils.h"
 #include "ash/public/cpp/holding_space/holding_space_constants.h"
 #include "ash/public/cpp/holding_space/holding_space_item.h"
 #include "ash/strings/grit/ash_strings.h"
 #include "ash/system/holding_space/holding_space_item_screen_capture_view.h"
-#include "ash/system/holding_space/holding_space_util.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/compositor/layer.h"
 #include "ui/views/accessibility/view_accessibility.h"
@@ -35,8 +35,8 @@
 }
 
 std::unique_ptr<views::View> ScreenCapturesSection::CreateHeader() {
-  auto header = holding_space_util::CreateLabel(
-      holding_space_util::LabelStyle::kHeader,
+  auto header = bubble_utils::CreateLabel(
+      bubble_utils::LabelStyle::kHeader,
       l10n_util::GetStringUTF16(IDS_ASH_HOLDING_SPACE_SCREEN_CAPTURES_TITLE));
   header->SetHorizontalAlignment(gfx::HorizontalAlignment::ALIGN_LEFT);
   header->SetPaintToLayer();
diff --git a/ash/wm/window_cycle/window_cycle_list.cc b/ash/wm/window_cycle/window_cycle_list.cc
index eca54039..56ff1a5f 100644
--- a/ash/wm/window_cycle/window_cycle_list.cc
+++ b/ash/wm/window_cycle/window_cycle_list.cc
@@ -1010,6 +1010,7 @@
            ->IsInteractiveAltTabModeAllowed()) {
     return windows_.size() > 1u;
   }
+
   int total_window_in_all_desks = GetNumberOfWindowsAllDesks();
   return windows_.size() > 1u ||
          (windows_.size() <= 1u &&
@@ -1160,30 +1161,25 @@
 }
 
 void WindowCycleList::Scroll(int offset) {
-  const bool is_interactive_alt_tab_mode_allowed =
-      Shell::Get()->window_cycle_controller()->IsInteractiveAltTabModeAllowed();
-  if (windows_.empty() && (!is_interactive_alt_tab_mode_allowed ||
-                           GetNumberOfWindowsAllDesks() == 0))
-    return;
-
-  // When there is only one window, we should give feedback to the user. If
-  // the window is minimized, we should also show it.
-  if (windows_.size() == 1 &&
-      !Shell::Get()->window_cycle_controller()->IsSwitchingMode()) {
-    ::wm::AnimateWindow(windows_[0], ::wm::WINDOW_ANIMATION_TYPE_BOUNCE);
+  if (windows_.size() == 1)
     SelectWindow(windows_[0]);
+
+  if (!ShouldShowUi()) {
+    // When there is only one window, we should give feedback to the user. If
+    // the window is minimized, we should also show it.
+    if (windows_.size() == 1)
+      ::wm::AnimateWindow(windows_[0], ::wm::WINDOW_ANIMATION_TYPE_BOUNCE);
+    return;
   }
 
   DCHECK(static_cast<size_t>(current_index_) < windows_.size());
-
   current_index_ = GetOffsettedWindowIndex(offset);
-  if (ShouldShowUi()) {
-    if (current_index_ > 1)
-      InitWindowCycleView();
 
-    if (cycle_view_)
-      cycle_view_->ScrollToWindow(windows_[current_index_]);
-  }
+  if (current_index_ > 1)
+    InitWindowCycleView();
+
+  if (cycle_view_)
+    cycle_view_->ScrollToWindow(windows_[current_index_]);
 }
 
 int WindowCycleList::GetOffsettedWindowIndex(int offset) const {
diff --git a/ash/wm/window_state.cc b/ash/wm/window_state.cc
index 844910c..0ddba1b 100644
--- a/ash/wm/window_state.cc
+++ b/ash/wm/window_state.cc
@@ -145,7 +145,7 @@
 float GetCurrentSnappedWidthRatio(aura::Window* window) {
   gfx::Rect maximized_bounds =
       screen_util::GetMaximizedWindowBoundsInParent(window);
-  return static_cast<float>(window->bounds().width()) /
+  return static_cast<float>(window->GetTargetBounds().width()) /
          static_cast<float>(maximized_bounds.width());
 }
 
diff --git a/ash/wm/window_state_unittest.cc b/ash/wm/window_state_unittest.cc
index cf9953d..38105f7 100644
--- a/ash/wm/window_state_unittest.cc
+++ b/ash/wm/window_state_unittest.cc
@@ -11,6 +11,7 @@
 #include "ash/public/cpp/shelf_config.h"
 #include "ash/public/cpp/window_properties.h"
 #include "ash/test/ash_test_base.h"
+#include "ash/test/test_window_builder.h"
 #include "ash/wm/pip/pip_positioner.h"
 #include "ash/wm/window_state_util.h"
 #include "ash/wm/window_util.h"
@@ -21,6 +22,7 @@
 #include "ui/aura/window.h"
 #include "ui/base/hit_test.h"
 #include "ui/compositor/layer.h"
+#include "ui/compositor/scoped_animation_duration_scale_mode.h"
 #include "ui/display/screen.h"
 #include "ui/events/test/event_generator.h"
 #include "ui/views/widget/widget.h"
@@ -412,6 +414,57 @@
   EXPECT_EQ(0.5f, *window_state->snapped_width_ratio());
 }
 
+// Tests that dragging and snapping the snapped window update the width ratio
+// correctly (crbug.com/1208969).
+TEST_F(WindowStateTest, SnapSnappedWindow) {
+  ui::ScopedAnimationDurationScaleMode test_duration_mode(
+      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
+  UpdateDisplay("800x600");
+  const gfx::Rect kWorkAreaBounds =
+      display::Screen::GetScreen()->GetPrimaryDisplay().work_area();
+  aura::test::TestWindowDelegate delegate;
+  gfx::Size window_normal_size = gfx::Size(800, 100);
+  std::unique_ptr<aura::Window> window =
+      TestWindowBuilder()
+          .SetBounds(gfx::Rect(window_normal_size))
+          .SetDelegate(&delegate)
+          .AllowAllWindowStates()
+          .Build();
+  delegate.set_window_component(HTCAPTION);
+  WindowState* window_state = WindowState::Get(window.get());
+  const WMEvent cycle_snap_left(WM_EVENT_CYCLE_SNAP_LEFT);
+  window_state->OnWMEvent(&cycle_snap_left);
+
+  // Snap window to the left.
+  EXPECT_EQ(WindowStateType::kLeftSnapped, window_state->GetStateType());
+  gfx::Rect expected =
+      gfx::Rect(kWorkAreaBounds.x(), kWorkAreaBounds.y(),
+                kWorkAreaBounds.width() / 2, kWorkAreaBounds.height());
+  // Wait for the snapped animation to complete and test that the window bound
+  // is left-snapped and the snap width ratio is updated.
+  window->layer()->GetAnimator()->Step(base::TimeTicks::Now() +
+                                       base::TimeDelta::FromSeconds(1));
+  EXPECT_EQ(expected, window->GetBoundsInScreen());
+  EXPECT_EQ(0.5f, *window_state->snapped_width_ratio());
+
+  // Drag the window to unsnap but do not release.
+  ui::test::EventGenerator* generator = GetEventGenerator();
+  generator->MoveMouseTo(window->bounds().CenterPoint());
+  generator->PressLeftButton();
+  generator->MoveMouseBy(5, 0);
+  // While dragged, the window size should restore to its normal bound.
+  EXPECT_EQ(window_normal_size, window->bounds().size());
+  EXPECT_EQ(1.0f, *window_state->snapped_width_ratio());
+
+  // Continue dragging the window and snap it back to the same position.
+  generator->MoveMouseBy(-405, 0);
+  generator->ReleaseLeftButton();
+
+  // The snapped ratio should be correct regardless of whether the animation
+  // is finished or not.
+  EXPECT_EQ(0.5f, *window_state->snapped_width_ratio());
+}
+
 // Test that snapping left/right preserves the restore bounds.
 TEST_F(WindowStateTest, RestoreBounds) {
   std::unique_ptr<aura::Window> window(
diff --git a/base/test/scoped_feature_list.cc b/base/test/scoped_feature_list.cc
index 7ada1d5..4b9c09f8 100644
--- a/base/test/scoped_feature_list.cc
+++ b/base/test/scoped_feature_list.cc
@@ -136,6 +136,10 @@
 
 ScopedFeatureList::ScopedFeatureList() = default;
 
+ScopedFeatureList::ScopedFeatureList(const Feature& enable_feature) {
+  InitAndEnableFeature(enable_feature);
+}
+
 ScopedFeatureList::~ScopedFeatureList() {
   Reset();
 }
diff --git a/base/test/scoped_feature_list.h b/base/test/scoped_feature_list.h
index 6de0305..05f1e98 100644
--- a/base/test/scoped_feature_list.h
+++ b/base/test/scoped_feature_list.h
@@ -41,7 +41,12 @@
 // initialization in the test harness's constructor.
 class ScopedFeatureList final {
  public:
+  // Constructs the instance in a non-initialized state.
   ScopedFeatureList();
+
+  // Shorthand for immediately initializing with InitAndEnableFeature().
+  explicit ScopedFeatureList(const Feature& enable_feature);
+
   ~ScopedFeatureList();
 
   struct FeatureAndParams {
diff --git a/build/android/gyp/compile_resources.py b/build/android/gyp/compile_resources.py
index 48409de..8a668e7 100755
--- a/build/android/gyp/compile_resources.py
+++ b/build/android/gyp/compile_resources.py
@@ -121,6 +121,11 @@
       'must be identical.')
 
   input_opts.add_argument(
+      '--support-zh-hk',
+      action='store_true',
+      help='Use zh-rTW resources for zh-rHK.')
+
+  input_opts.add_argument(
       '--debuggable',
       action='store_true',
       help='Whether to add android:debuggable="true".')
@@ -283,6 +288,20 @@
       yield os.path.join(root, f)
 
 
+def _DuplicateZhResources(resource_dirs, path_info):
+  """Duplicate Taiwanese resources into Hong-Kong specific directory."""
+  for resource_dir in resource_dirs:
+    # We use zh-TW resources for zh-HK (if we have zh-TW resources).
+    for path in _IterFiles(resource_dir):
+      if 'zh-rTW' in path:
+        hk_path = path.replace('zh-rTW', 'zh-rHK')
+        build_utils.MakeDirectory(os.path.dirname(hk_path))
+        shutil.copyfile(path, hk_path)
+        path_info.RegisterRename(
+            os.path.relpath(path, resource_dir),
+            os.path.relpath(hk_path, resource_dir))
+
+
 def _RenameLocaleResourceDirs(resource_dirs, path_info):
   """Rename locale resource directories into standard names when necessary.
 
@@ -338,11 +357,13 @@
             os.path.relpath(path2, resource_dir))
 
 
-def _ToAndroidLocales(locale_allowlist):
+def _ToAndroidLocales(locale_allowlist, support_zh_hk):
   """Converts the list of Chrome locales to Android config locale qualifiers.
 
   Args:
     locale_allowlist: A list of Chromium locale names.
+    support_zh_hk: True if we need to support zh-HK by duplicating
+      the zh-TW strings.
   Returns:
     A set of matching Android config locale qualifier names.
   """
@@ -356,7 +377,14 @@
     language = locale.split('-')[0]
     ret.add(language)
 
-  return ret
+  # We don't actually support zh-HK in Chrome on Android, but we mimic the
+  # native side behavior where we use zh-TW resources when the locale is set to
+  # zh-HK. See https://crbug.com/780847.
+  if support_zh_hk:
+    assert not any('HK' in l for l in locale_allowlist), (
+        'Remove special logic if zh-HK is now supported (crbug.com/780847).')
+    ret.add('zh-rHK')
+  return set(ret)
 
 
 def _MoveImagesToNonMdpiFolders(res_root, path_info):
@@ -682,7 +710,8 @@
   # list provided by --locale-allowlist.
   wanted_locales = all_locales
   if options.locale_allowlist:
-    wanted_locales = _ToAndroidLocales(options.locale_allowlist)
+    wanted_locales = _ToAndroidLocales(options.locale_allowlist,
+                                       options.support_zh_hk)
 
   # Set B: shared resources locales, which is either set A
   # or the list provided by --shared-resources-allowlist-locales
@@ -694,7 +723,7 @@
             options.shared_resources_allowlist))
 
     shared_resources_locales = _ToAndroidLocales(
-        options.shared_resources_allowlist_locales)
+        options.shared_resources_allowlist_locales, options.support_zh_hk)
 
   # Remove any file that belongs to a locale not covered by
   # either A or B.
@@ -754,6 +783,8 @@
 
   logging.debug('Applying locale transformations')
   path_info = resource_utils.ResourceInfoFile()
+  if options.support_zh_hk:
+    _DuplicateZhResources(dep_subdirs, path_info)
   _RenameLocaleResourceDirs(dep_subdirs, path_info)
 
   logging.debug('Applying file-based exclusions')
diff --git a/build/config/android/internal_rules.gni b/build/config/android/internal_rules.gni
index 11eb48cd..5fd989b 100644
--- a/build/config/android/internal_rules.gni
+++ b/build/config/android/internal_rules.gni
@@ -2212,6 +2212,10 @@
   #     resources to put in the final output, even if aapt_locale_allowlist
   #     is defined to a smaller subset.
   #
+  #   support_zh_hk: (optional)
+  #     If true, support zh-HK in Chrome on Android by using the resources
+  #     from zh-TW. See https://crbug.com/780847.
+  #
   #   aapt_locale_allowlist: (optional)
   #     Restrict compiled locale-dependent resources to a specific allowlist.
   #     NOTE: This is a list of Chromium locale names, not Android ones.
@@ -2524,6 +2528,10 @@
           [ "--values-filter-rules=${invoker.resource_values_filter_rules}" ]
     }
 
+    if (defined(invoker.support_zh_hk) && invoker.support_zh_hk) {
+      _args += [ "--support-zh-hk" ]
+    }
+
     if (defined(invoker.include_resource)) {
       _rebased_include_resources =
           rebase_path(invoker.include_resource, root_build_dir)
diff --git a/build/config/android/rules.gni b/build/config/android/rules.gni
index d33240d..c65ab979 100644
--- a/build/config/android/rules.gni
+++ b/build/config/android/rules.gni
@@ -2482,6 +2482,7 @@
                                "resources_config_paths",
                                "shared_resources",
                                "shared_resources_allowlist_locales",
+                               "support_zh_hk",
                                "uses_split",
                              ])
       short_resource_paths = _short_resource_paths
@@ -3497,6 +3498,7 @@
                                "static_library_provider",
                                "static_library_synchronized_proguard",
                                "strip_resource_names",
+                               "support_zh_hk",
                                "target_sdk_version",
                                "testonly",
                                "uncompress_dex",
@@ -3624,6 +3626,7 @@
                                "static_library_provider",
                                "static_library_synchronized_proguard",
                                "strip_resource_names",
+                               "support_zh_hk",
                                "target_sdk_version",
                                "testonly",
                                "uncompress_shared_libraries",
diff --git a/build/config/fuchsia/generate_runner_scripts.gni b/build/config/fuchsia/generate_runner_scripts.gni
index d9a67521..3b83f3b 100644
--- a/build/config/fuchsia/generate_runner_scripts.gni
+++ b/build/config/fuchsia/generate_runner_scripts.gni
@@ -157,12 +157,8 @@
         "${boot_image_root}/qemu/storage-full.blk",
         "${boot_image_root}/qemu/zircon-a.zbi",
         "//third_party/qemu-${host_os}-${test_host_cpu}/",
+        "${aemu_root}/",
       ]
-
-      # Include AEMU for x64 emulator hosts and for arm64 hosts.
-      if (test_host_cpu == "x64" || test_host_cpu == "arm64") {
-        data += [ "${aemu_root}/" ]
-      }
     }
 
     foreach(fuchsia_additional_boot_image, fuchsia_additional_boot_images) {
diff --git a/build/config/locales.gni b/build/config/locales.gni
index 1d57b877..e94e162 100644
--- a/build/config/locales.gni
+++ b/build/config/locales.gni
@@ -14,11 +14,26 @@
 # |locales_without_pseudolocales|.
 
 # The following additional platform specific lists are created:
+# - |android_apk_locales| subset for Android based apk builds
 # - |android_bundle_locales_as_resources| locales formatted for XML output names
 # - |locales_as_mac_outputs| formated for mac output bundles
 # - |ios_packed_locales| subset for iOS
 # - |ios_packed_locales_as_mac_outputs| subset for iOS output
 
+# Android doesn't ship all locales in order to save space (but webview does).
+# http://crbug.com/369218
+android_apk_omitted_locales = [
+  "bn",
+  "et",
+  "gu",
+  "kn",
+  "ml",
+  "mr",
+  "ms",
+  "ta",
+  "te",
+]
+
 # Chrome on iOS only ships with a subset of the locales supported by other
 # version of Chrome as the corresponding locales are not supported by the
 # operating system (but for simplicity, the corresponding .pak files are
@@ -42,6 +57,7 @@
 # These list are defined even when not building for Android or iOS for the
 # sake of build/locale_tool.py. Ensure that GN doesn't complain about them
 # being unused.
+not_needed([ "android_apk_omitted_locales" ])
 not_needed([ "ios_unsupported_locales" ])
 
 # Superset of all locales used in Chrome with platform specific changes noted.
@@ -167,6 +183,11 @@
 if (is_android) {
   locales = all_chrome_locales
 
+  # Android doesn't ship all locales on KitKat in order to save space
+  # (but webview does). http://crbug.com/369218
+  android_apk_locales = all_chrome_locales - android_bundle_only_locales -
+                        android_apk_omitted_locales
+
   # List for Android locale names in .xml exports. Note: needs to stay in sync
   # with |ToAndroidLocaleName| in build/android/gyp/util/resource_utils.py.
   #  - add r: (e.g. zh-HK -> zh-rHK )
diff --git a/build/fuchsia/linux.sdk.sha1 b/build/fuchsia/linux.sdk.sha1
index 896c5554..cb5229fa 100644
--- a/build/fuchsia/linux.sdk.sha1
+++ b/build/fuchsia/linux.sdk.sha1
@@ -1 +1 @@
-4.20210527.1.1
+4.20210527.3.1
diff --git a/build/fuchsia/mac.sdk.sha1 b/build/fuchsia/mac.sdk.sha1
index e204c51..cb5229fa 100644
--- a/build/fuchsia/mac.sdk.sha1
+++ b/build/fuchsia/mac.sdk.sha1
@@ -1 +1 @@
-4.20210527.0.1
+4.20210527.3.1
diff --git a/build/locale_tool.py b/build/locale_tool.py
index b5729d8..cad51908 100755
--- a/build/locale_tool.py
+++ b/build/locale_tool.py
@@ -322,9 +322,10 @@
 ##########################################################################
 
 # Various list of locales that will be extracted from build/config/locales.gni
-# Do not use these directly, use ChromeLocales(), and IosUnsupportedLocales()
-# instead to access these lists.
+# Do not use these directly, use ChromeLocales(), AndroidAPKOmittedLocales() and
+# IosUnsupportedLocales() instead to access these lists.
 _INTERNAL_CHROME_LOCALES = []
+_INTERNAL_ANDROID_APK_OMITTED_LOCALES = []
 _INTERNAL_IOS_UNSUPPORTED_LOCALES = []
 
 
@@ -335,6 +336,13 @@
   return _INTERNAL_CHROME_LOCALES
 
 
+def AndroidAPKOmittedLocales():
+  """Return the list of locales omitted from Android APKs."""
+  if not _INTERNAL_ANDROID_APK_OMITTED_LOCALES:
+    _ExtractAllChromeLocalesLists()
+  return _INTERNAL_ANDROID_APK_OMITTED_LOCALES
+
+
 def IosUnsupportedLocales():
   """Return the list of locales that are unsupported on iOS."""
   if not _INTERNAL_IOS_UNSUPPORTED_LOCALES:
@@ -396,6 +404,9 @@
 # Write the locales lists to files in the output directory.
 _filename = root_build_dir + "/foo"
 write_file(_filename + ".locales", locales, "json")
+write_file(_filename + ".android_apk_omitted_locales",
+            android_apk_omitted_locales,
+            "json")
 write_file(_filename + ".ios_unsupported_locales",
             ios_unsupported_locales,
             "json")
@@ -450,6 +461,10 @@
     _INTERNAL_CHROME_LOCALES = _ReadJsonList(
         os.path.join(out_path, 'foo.locales'))
 
+    global _INTERNAL_ANDROID_APK_OMITTED_LOCALES
+    _INTERNAL_ANDROID_APK_OMITTED_LOCALES = _ReadJsonList(
+        os.path.join(out_path, 'foo.android_apk_omitted_locales'))
+
     global _INTERNAL_IOS_UNSUPPORTED_LOCALES
     _INTERNAL_IOS_UNSUPPORTED_LOCALES = _ReadJsonList(
         os.path.join(out_path, 'foo.ios_unsupported_locales'))
@@ -1271,8 +1286,9 @@
   description = 'List supported Chrome locales'
   long_description = r'''
 List locales of interest, by default this prints all locales supported by
-Chrome, but `--type=ios_unsupported` can be used for the list of locales
-unsupported on iOS.
+Chrome, but `--type=android_apk_omitted` can be used to print the list of
+locales omitted from Android APKs (but not app bundles), and
+`--type=ios_unsupported` for the list of locales unsupported on iOS.
 
 These values are extracted directly from build/config/locales.gni.
 
@@ -1283,6 +1299,7 @@
   # Maps type argument to a function returning the corresponding locales list.
   TYPE_MAP = {
       'all': ChromeLocales,
+      'android_apk_omitted': AndroidAPKOmittedLocales,
       'ios_unsupported': IosUnsupportedLocales,
   }
 
diff --git a/chrome/VERSION b/chrome/VERSION
index e82286f..49264b155 100644
--- a/chrome/VERSION
+++ b/chrome/VERSION
@@ -1,4 +1,4 @@
 MAJOR=93
 MINOR=0
-BUILD=4525
+BUILD=4526
 PATCH=0
diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn
index 0988287..93ac6234 100644
--- a/chrome/android/BUILD.gn
+++ b/chrome/android/BUILD.gn
@@ -2004,7 +2004,13 @@
       renaming_sources = []
       renaming_destinations = []
 
-      foreach(_locale, locales) {
+      # Only include all Android locales on bundle builds.
+      if (_is_bundle_module) {
+        _locales_list = locales
+      } else {
+        _locales_list = android_apk_locales
+      }
+      foreach(_locale, _locales_list) {
         renaming_sources +=
             [ "$target_gen_dir/${_variant}_paks/locales/$_locale.pak" ]
         renaming_destinations += [ "locales/$_locale.pak" ]
diff --git a/chrome/android/chrome_java_sources.gni b/chrome/android/chrome_java_sources.gni
index ca1ae15f..f9c8d98 100644
--- a/chrome/android/chrome_java_sources.gni
+++ b/chrome/android/chrome_java_sources.gni
@@ -432,7 +432,6 @@
   "java/src/org/chromium/chrome/browser/customtabs/CustomTabActivityLifecycleUmaTracker.java",
   "java/src/org/chromium/chrome/browser/customtabs/CustomTabAppMenuPropertiesDelegate.java",
   "java/src/org/chromium/chrome/browser/customtabs/CustomTabBottomBarDelegate.java",
-  "java/src/org/chromium/chrome/browser/customtabs/CustomTabColorProvider.java",
   "java/src/org/chromium/chrome/browser/customtabs/CustomTabColorProviderImpl.java",
   "java/src/org/chromium/chrome/browser/customtabs/CustomTabCompositorContentInitializer.java",
   "java/src/org/chromium/chrome/browser/customtabs/CustomTabDelegateFactory.java",
diff --git a/chrome/android/chrome_public_apk_tmpl.gni b/chrome/android/chrome_public_apk_tmpl.gni
index 1f3e94f..f1cb16b 100644
--- a/chrome/android/chrome_public_apk_tmpl.gni
+++ b/chrome/android/chrome_public_apk_tmpl.gni
@@ -173,6 +173,11 @@
       product_config_java_packages = [ "org.chromium.chrome.browser" ]
     }
 
+    # Use zh-TW strings for zh-HK (https://crbug.com/780847).
+    if (!defined(support_zh_hk)) {
+      support_zh_hk = true
+    }
+
     # Android supports webp transparent resources properly since API level 18,
     # so this can only be activated for modern ones (which target API >= 21).
     if (!defined(png_to_webp)) {
@@ -185,9 +190,18 @@
     short_resource_paths = true
 
     if (!defined(aapt_locale_allowlist)) {
-      # For bundles, only include resource strings files from our full
-      # locale list, but nothing more.
-      aapt_locale_allowlist = locales
+      if (target_type == "android_apk") {
+        # For APKs, do not include the resource strings files from our
+        # omitted locale list in order to save size.
+        aapt_locale_allowlist = android_apk_locales
+      } else {
+        # For bundles, only include resource strings files from our full
+        # locale list, but nothing more.
+        aapt_locale_allowlist = locales
+
+        # zh_hk is supported in Android bundles.
+        support_zh_hk = false
+      }
     }
 
     if (!defined(use_chromium_linker)) {
@@ -573,6 +587,13 @@
         uncompress_shared_libraries = true
       }
 
+      # Android N+ better supports multiple locales (https://crbug.com/780847).
+      if (defined(invoker.support_zh_hk)) {
+        support_zh_hk = invoker.support_zh_hk
+      } else {
+        support_zh_hk = false
+      }
+
       if (_is_bundle_module) {
         _pak_prefix += "_bundle_module"
       } else {
@@ -623,6 +644,7 @@
       "secondary_abi_shared_libraries",
       "secondary_native_lib_placeholders",
       "shared_libraries",
+      "support_zh_hk",
       "uncompress_shared_libraries",
       "use_chromium_linker",
       "use_modern_linker",
diff --git a/chrome/android/features/cablev2_authenticator/native/cablev2_authenticator_android.cc b/chrome/android/features/cablev2_authenticator/native/cablev2_authenticator_android.cc
index 64faf63..4f3a773 100644
--- a/chrome/android/features/cablev2_authenticator/native/cablev2_authenticator_android.cc
+++ b/chrome/android/features/cablev2_authenticator/native/cablev2_authenticator_android.cc
@@ -574,8 +574,8 @@
     return 0;
   }
 
-  DCHECK(event->source !=
-             device::cablev2::authenticator::Registration::Type::LINKING ||
+  DCHECK((event->source ==
+          device::cablev2::authenticator::Registration::Type::LINKING) ==
          event->contact_id.has_value());
 
   GlobalData& global_data = GetGlobalData();
diff --git a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedStreamViewResizer.java b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedStreamViewResizer.java
new file mode 100644
index 0000000..d468c5e
--- /dev/null
+++ b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedStreamViewResizer.java
@@ -0,0 +1,110 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.feed;
+
+import android.app.Activity;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.core.view.ViewCompat;
+
+import org.chromium.chrome.R;
+import org.chromium.components.browser_ui.widget.displaystyle.UiConfig;
+import org.chromium.components.browser_ui.widget.displaystyle.ViewResizer;
+
+/**
+ * Updates the paddings used to display the feed stream when switching to landscape mode. Due to the
+ * fact that the search bar is floating at the top, the entire feed stream needs to shrink a little
+ * bit in order to have large image or video preview fit in the viewport.
+ */
+public class FeedStreamViewResizer extends ViewResizer {
+    // The aspect ratio of large images or video previews, computed based on 1280:720.
+    private static final float FEED_IMAGE_OR_VIDEO_ASPECT_RATIO = 1.778f;
+
+    private final Activity mActivity;
+
+    /**
+     * @param activity The activity displays the view.
+     * @param view The view that will have its padding resized.
+     * @param config The UiConfig object to subscribe to.
+     * @param defaultPaddingPixels Padding to use in {@link HorizontalDisplayStyle#REGULAR}.
+     * @param minWidePaddingPixels Minimum lateral padding to use in {@link
+     *         HorizontalDisplayStyle#WIDE}.
+     */
+    public FeedStreamViewResizer(Activity activity, View view, UiConfig config,
+            int defaultPaddingPixels, int minWidePaddingPixels) {
+        super(view, config, defaultPaddingPixels, minWidePaddingPixels);
+        mActivity = activity;
+    }
+
+    /**
+     * Convenience method to create a new ViewResizer and immediately attach it to a {@link
+     * UiConfig}. If the {@link UiConfig} can outlive the view, the regular constructor should be
+     * used, so it can be detached to avoid memory leaks.
+     * @param activity The activity displays the view.
+     * @param view The view that will have its padding resized.
+     * @param config The UiConfig object to subscribe to.
+     * @param defaultPaddingPixels Padding to use in {@link HorizontalDisplayStyle#REGULAR}.
+     * @param minWidePaddingPixels Minimum lateral padding to use in {@link
+     *         HorizontalDisplayStyle#WIDE}.
+     * @return The {@link ViewResizer} that is created and attached.
+     */
+    public static FeedStreamViewResizer createAndAttach(Activity activity, View view,
+            UiConfig config, int defaultPaddingPixels, int minWidePaddingPixels) {
+        FeedStreamViewResizer viewResizer = new FeedStreamViewResizer(
+                activity, view, config, defaultPaddingPixels, minWidePaddingPixels);
+        viewResizer.attach();
+        return viewResizer;
+    }
+
+    @Override
+    public void onDisplayStyleChanged(UiConfig.DisplayStyle newDisplayStyle) {
+        if (mUiConfig.getContext().getResources().getConfiguration().orientation
+                != Configuration.ORIENTATION_LANDSCAPE) {
+            super.onDisplayStyleChanged(newDisplayStyle);
+            return;
+        }
+
+        updatePaddingForLandscapeMode();
+    }
+
+    /**
+     * In landscape mode, the entire large image or video preview cannot fit in the viewport because
+     * the floating search bar at the top reduces the user's visible area. To deal with this, we
+     * add the left and right paddings to all items in the RecyclerView in order to shrink all card
+     * images a little bit so that they can fit in the viewport.
+     */
+    private void updatePaddingForLandscapeMode() {
+        ViewGroup contentContainer = mActivity.findViewById(android.R.id.content);
+        if (contentContainer == null) {
+            return;
+        }
+        View toolbarView = contentContainer.findViewById(R.id.toolbar_container);
+        if (toolbarView == null) {
+            return;
+        }
+        int toolbarHeight = toolbarView.getHeight();
+
+        Resources resources = mUiConfig.getContext().getResources();
+        float dpToPx = resources.getDisplayMetrics().density;
+        float screenWidth = resources.getConfiguration().screenWidthDp * dpToPx;
+        float screenHeight = resources.getConfiguration().screenHeightDp * dpToPx;
+
+        float useableHeight = screenHeight - statusBarHeight() - toolbarHeight;
+        int padding = (int) ((screenWidth - useableHeight * FEED_IMAGE_OR_VIDEO_ASPECT_RATIO) / 2);
+
+        ViewCompat.setPaddingRelative(
+                mView, padding, mView.getPaddingTop(), padding, mView.getPaddingBottom());
+    }
+
+    private int statusBarHeight() {
+        Rect visibleContentRect = new Rect();
+        mActivity.getWindow().getDecorView().getWindowVisibleDisplayFrame(visibleContentRect);
+        return visibleContentRect.top;
+    }
+}
diff --git a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedSurfaceCoordinator.java b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedSurfaceCoordinator.java
index 554a072..36c8e6b 100644
--- a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedSurfaceCoordinator.java
+++ b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedSurfaceCoordinator.java
@@ -115,7 +115,7 @@
     private @Nullable NativePageNavigationDelegate mPageNavigationDelegate;
     private @Nullable FeedSurfaceLifecycleManager mFeedSurfaceLifecycleManager;
     private @Nullable PersonalizedSigninPromoView mSigninPromoView;
-    private @Nullable ViewResizer mStreamViewResizer;
+    private @Nullable FeedStreamViewResizer mStreamViewResizer;
     // This is the "default"/interest feed stream, not necessarily the current stream.
     // TODO(chili): Remove the necessity of this.
     private @Nullable FeedStream mStream;
@@ -498,8 +498,8 @@
         mRecyclerView.setBackgroundResource(R.color.default_bg_color);
 
         mRootView.addView(mRecyclerView);
-        mStreamViewResizer = ViewResizer.createAndAttach(
-                mRecyclerView, mUiConfig, mDefaultMarginPixels, mWideMarginPixels);
+        mStreamViewResizer = FeedStreamViewResizer.createAndAttach(
+                mActivity, mRecyclerView, mUiConfig, mDefaultMarginPixels, mWideMarginPixels);
 
         if (mNtpHeader != null) UiUtils.removeViewFromParent(mNtpHeader);
         if (mSectionHeaderView != null) UiUtils.removeViewFromParent(mSectionHeaderView);
diff --git a/chrome/android/feed/core/javatests/src/org/chromium/chrome/browser/feed/FeedV2NewTabPageTest.java b/chrome/android/feed/core/javatests/src/org/chromium/chrome/browser/feed/FeedV2NewTabPageTest.java
index fd99563d..46fb0457 100644
--- a/chrome/android/feed/core/javatests/src/org/chromium/chrome/browser/feed/FeedV2NewTabPageTest.java
+++ b/chrome/android/feed/core/javatests/src/org/chromium/chrome/browser/feed/FeedV2NewTabPageTest.java
@@ -4,6 +4,8 @@
 
 package org.chromium.chrome.browser.feed;
 
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+
 import static androidx.test.espresso.Espresso.onView;
 import static androidx.test.espresso.action.ViewActions.click;
 import static androidx.test.espresso.assertion.ViewAssertions.doesNotExist;
@@ -22,6 +24,7 @@
 import static org.chromium.chrome.test.util.ViewUtils.VIEW_NULL;
 import static org.chromium.chrome.test.util.ViewUtils.waitForView;
 
+import android.content.pm.ActivityInfo;
 import android.support.test.InstrumentationRegistry;
 import android.view.View;
 import android.view.ViewGroup;
@@ -55,7 +58,9 @@
 import org.chromium.base.test.util.CriteriaHelper;
 import org.chromium.base.test.util.DisabledTest;
 import org.chromium.base.test.util.Feature;
+import org.chromium.base.test.util.Restriction;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.ChromeTabbedActivity;
 import org.chromium.chrome.browser.feed.v2.FeedV2TestHelper;
 import org.chromium.chrome.browser.feed.v2.TestFeedServer;
 import org.chromium.chrome.browser.firstrun.FirstRunUtils;
@@ -70,6 +75,7 @@
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.test.ChromeJUnit4RunnerDelegate;
 import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
+import org.chromium.chrome.test.util.ChromeRenderTestRule;
 import org.chromium.chrome.test.util.ChromeTabUtils;
 import org.chromium.chrome.test.util.NewTabPageTestUtils;
 import org.chromium.chrome.test.util.ViewUtils;
@@ -83,7 +89,9 @@
 import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.net.NetworkChangeNotifier;
 import org.chromium.net.test.EmbeddedTestServer;
+import org.chromium.ui.test.util.UiRestriction;
 
+import java.io.IOException;
 import java.util.Collections;
 import java.util.List;
 
@@ -118,6 +126,10 @@
     private final ChromeTabbedActivityTestRule mActivityTestRule =
             new ChromeTabbedActivityTestRule();
 
+    @Rule
+    public final ChromeRenderTestRule mRenderTestRule =
+            ChromeRenderTestRule.Builder.withPublicCorpus().build();
+
     private final AccountManagerTestRule mAccountManagerTestRule =
             new AccountManagerTestRule(new FakeAccountManagerFacade(null) {
                 @Override
@@ -359,6 +371,33 @@
                 headerStatusView.getText());
     }
 
+    @Test
+    @MediumTest
+    @Feature({"RenderTest"})
+    @Restriction({UiRestriction.RESTRICTION_TYPE_PHONE})
+    public void testLoadFeedContent_Landscape() throws IOException {
+        ChromeTabbedActivity chromeActivity = mActivityTestRule.getActivity();
+        chromeActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
+        CriteriaHelper.pollUiThread(() -> {
+            Criteria.checkThat(chromeActivity.getResources().getConfiguration().orientation,
+                    is(ORIENTATION_LANDSCAPE));
+        });
+
+        openNewTabPage();
+
+        CriteriaHelper.pollUiThread(() -> {
+            Criteria.checkThat(FeedV2TestHelper.getFeedUserActionsHistogramValues(),
+                    Matchers.hasEntry("kOpenedFeedSurface", 1));
+            Criteria.checkThat(FeedV2TestHelper.getLoadStreamStatusInitialValues(),
+                    Matchers.hasEntry("kLoadedFromNetwork", 1));
+        });
+
+        RecyclerView recyclerView = getRecyclerView();
+        FeedV2TestHelper.waitForRecyclerItems(MIN_ITEMS_AFTER_LOAD, recyclerView);
+
+        mRenderTestRule.render(recyclerView, "feedContent_landscape");
+    }
+
     /**
      * Toggles the header and checks whether the header has the right status.
      * @param expanded Whether the header should be expanded.
diff --git a/chrome/android/feed/feed_java_sources.gni b/chrome/android/feed/feed_java_sources.gni
index 53b6918c..de145734 100644
--- a/chrome/android/feed/feed_java_sources.gni
+++ b/chrome/android/feed/feed_java_sources.gni
@@ -20,6 +20,7 @@
 ]
 
 feed_java_sources = [
+  "//chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedStreamViewResizer.java",
   "//chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedSurfaceCoordinator.java",
   "//chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedSurfaceLifecycleManager.java",
   "//chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedSurfaceMediator.java",
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabActivity.java
index 3c11685f..7cfc41c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabActivity.java
@@ -444,8 +444,9 @@
     @Override
     public int getActivityThemeColor() {
         BrowserServicesIntentDataProvider intentDataProvider = getIntentDataProvider();
-        if (!intentDataProvider.isOpenedByChrome() && intentDataProvider.hasCustomToolbarColor()) {
-            return intentDataProvider.getToolbarColor();
+        if (!intentDataProvider.isOpenedByChrome()
+                && intentDataProvider.getColorProvider().hasCustomToolbarColor()) {
+            return intentDataProvider.getColorProvider().getToolbarColor();
         }
         return TabState.UNSPECIFIED_THEME_COLOR;
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabActivity.java
index 6ca4b58..729e2b3 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabActivity.java
@@ -86,7 +86,8 @@
 
     @Override
     protected Drawable getBackgroundDrawable() {
-        int initialBackgroundColor = mIntentDataProvider.getInitialBackgroundColor();
+        int initialBackgroundColor =
+                mIntentDataProvider.getColorProvider().getInitialBackgroundColor();
         if (mIntentDataProvider.isTrustedIntent() && initialBackgroundColor != Color.TRANSPARENT) {
             return new ColorDrawable(initialBackgroundColor);
         } else {
@@ -134,7 +135,7 @@
 
         // Setting task title and icon to be null will preserve the client app's title and icon.
         setTaskDescription(new ActivityManager.TaskDescription(
-                null, null, mIntentDataProvider.getToolbarColor()));
+                null, null, mIntentDataProvider.getColorProvider().getToolbarColor()));
 
         getComponent().resolveBottomBarDelegate().showBottomBarIfNecessary();
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabBottomBarDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabBottomBarDelegate.java
index 1025324..2a83a89 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabBottomBarDelegate.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabBottomBarDelegate.java
@@ -151,7 +151,7 @@
         if (items.isEmpty()) return;
         LinearLayout layout = new LinearLayout(mActivity);
         layout.setId(R.id.custom_tab_bottom_bar_wrapper);
-        layout.setBackgroundColor(mDataProvider.getBottomBarColor());
+        layout.setBackgroundColor(mDataProvider.getColorProvider().getBottomBarColor());
         for (CustomButtonParams params : items) {
             if (params.showOnToolbar()) continue;
             final PendingIntent pendingIntent = params.getPendingIntent();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabColorProviderImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabColorProviderImpl.java
index ed02a78..ddc3b8f2 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabColorProviderImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabColorProviderImpl.java
@@ -18,14 +18,15 @@
 
 import org.chromium.base.IntentUtils;
 import org.chromium.base.Log;
+import org.chromium.chrome.browser.browserservices.intents.ColorProvider;
 import org.chromium.components.browser_ui.styles.ChromeColors;
 import org.chromium.ui.util.ColorUtils;
 
 /**
- * CustomTabColorProvider implementation used for normal profiles, in some cases incognito
+ * ColorProvider implementation used for normal profiles, in some cases incognito
  * profiles.
  */
-public final class CustomTabColorProviderImpl implements CustomTabColorProvider {
+public final class CustomTabColorProviderImpl implements ColorProvider {
     private static final String TAG = "CustomTabColorPrvdr";
 
     private final boolean mHasCustomToolbarColor;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabIntentDataProvider.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabIntentDataProvider.java
index 8347175..a939ebb 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabIntentDataProvider.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabIntentDataProvider.java
@@ -39,6 +39,7 @@
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.IntentHandler;
 import org.chromium.chrome.browser.browserservices.intents.BrowserServicesIntentDataProvider;
+import org.chromium.chrome.browser.browserservices.intents.ColorProvider;
 import org.chromium.chrome.browser.browserservices.intents.CustomButtonParams;
 import org.chromium.chrome.browser.flags.ActivityType;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
@@ -248,7 +249,7 @@
     private final int[] mGsaExperimentIds;
 
     @NonNull
-    private final CustomTabColorProvider mColorProvider;
+    private final ColorProvider mColorProvider;
 
     /**
      * Add extras to customize menu items for opening Reader Mode UI custom tab from Chrome.
@@ -470,8 +471,8 @@
         if (shareState == CustomTabsIntent.SHARE_STATE_ON
                 || shareState == CustomTabsIntent.SHARE_STATE_DEFAULT) {
             if (mToolbarButtons.isEmpty()) {
-                mToolbarButtons.add(
-                        CustomButtonParamsImpl.createShareButton(context, getToolbarColor()));
+                mToolbarButtons.add(CustomButtonParamsImpl.createShareButton(
+                        context, getColorProvider().getToolbarColor()));
                 logShareOptionLocation(ShareOptionLocation.TOOLBAR);
             } else if (mMenuEntries.isEmpty()) {
                 mShowShareItemInMenu = true;
@@ -631,25 +632,8 @@
     }
 
     @Override
-    public int getToolbarColor() {
-        return mColorProvider.getToolbarColor();
-    }
-
-    @Override
-    public boolean hasCustomToolbarColor() {
-        return mColorProvider.hasCustomToolbarColor();
-    }
-
-    @Override
-    @Nullable
-    public Integer getNavigationBarColor() {
-        return mColorProvider.getNavigationBarColor();
-    }
-
-    @Override
-    @Nullable
-    public Integer getNavigationBarDividerColor() {
-        return mColorProvider.getNavigationBarDividerColor();
+    public ColorProvider getColorProvider() {
+        return mColorProvider;
     }
 
     @Override
@@ -679,11 +663,6 @@
     }
 
     @Override
-    public int getBottomBarColor() {
-        return mColorProvider.getBottomBarColor();
-    }
-
-    @Override
     @Nullable
     public RemoteViews getBottomBarRemoteViews() {
         return mRemoteViews;
@@ -756,11 +735,6 @@
     }
 
     @Override
-    public int getInitialBackgroundColor() {
-        return mColorProvider.getInitialBackgroundColor();
-    }
-
-    @Override
     public boolean shouldShowStarButton() {
         return !mDisableStar;
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabStatusBarColorProvider.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabStatusBarColorProvider.java
index 06f2d7b..842e27d 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabStatusBarColorProvider.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabStatusBarColorProvider.java
@@ -56,7 +56,7 @@
             case ToolbarColorType.DEFAULT_COLOR:
                 return DEFAULT_STATUS_BAR_COLOR;
             case ToolbarColorType.INTENT_TOOLBAR_COLOR:
-                return mIntentDataProvider.getToolbarColor();
+                return mIntentDataProvider.getColorProvider().getToolbarColor();
         }
         return DEFAULT_STATUS_BAR_COLOR;
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabTaskDescriptionHelper.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabTaskDescriptionHelper.java
index 36b9e470..2f75665 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabTaskDescriptionHelper.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabTaskDescriptionHelper.java
@@ -96,8 +96,8 @@
         mDefaultThemeColor = ApiCompatibilityUtils.getColor(
                 mActivity.getResources(), R.color.default_primary_color);
         if (webappExtras != null) {
-            if (mIntentDataProvider.hasCustomToolbarColor()) {
-                mDefaultThemeColor = mIntentDataProvider.getToolbarColor();
+            if (mIntentDataProvider.getColorProvider().hasCustomToolbarColor()) {
+                mDefaultThemeColor = mIntentDataProvider.getColorProvider().getToolbarColor();
             }
             mForceIcon = webappExtras.icon.bitmap();
             mForceTitle = webappExtras.shortName;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/IncognitoCustomTabColorProvider.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/IncognitoCustomTabColorProvider.java
index fe9454e..0d4ab42 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/IncognitoCustomTabColorProvider.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/IncognitoCustomTabColorProvider.java
@@ -9,13 +9,13 @@
 
 import androidx.annotation.Nullable;
 
+import org.chromium.chrome.browser.browserservices.intents.ColorProvider;
 import org.chromium.components.browser_ui.styles.ChromeColors;
 
 /**
- * CustomTabColorProvider implementation used for normal provides, and some times incognito
- * profiles.
+ * ColorProvider implementation used for incognito profiles.
  */
-public final class IncognitoCustomTabColorProvider implements CustomTabColorProvider {
+public final class IncognitoCustomTabColorProvider implements ColorProvider {
     private final int mToolbarColor;
     private final int mBottomBarColor;
     private final int mNavigationBarColor;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/IncognitoCustomTabIntentDataProvider.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/IncognitoCustomTabIntentDataProvider.java
index 0bfaff4c..4ad4e519 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/IncognitoCustomTabIntentDataProvider.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/IncognitoCustomTabIntentDataProvider.java
@@ -28,6 +28,7 @@
 import org.chromium.chrome.browser.ChromeApplicationImpl;
 import org.chromium.chrome.browser.IntentHandler;
 import org.chromium.chrome.browser.browserservices.intents.BrowserServicesIntentDataProvider;
+import org.chromium.chrome.browser.browserservices.intents.ColorProvider;
 import org.chromium.chrome.browser.flags.ActivityType;
 import org.chromium.chrome.browser.flags.CachedFeatureFlags;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
@@ -64,7 +65,7 @@
     private final CustomTabsSessionToken mSession;
     private final boolean mIsTrustedIntent;
     private final Bundle mAnimationBundle;
-    private final CustomTabColorProvider mColorProvider;
+    private final ColorProvider mColorProvider;
     private final int mTitleVisibilityState;
     private final Drawable mCloseButtonIcon;
     private final boolean mShowShareItem;
@@ -291,13 +292,8 @@
     }
 
     @Override
-    public int getToolbarColor() {
-        return mColorProvider.getToolbarColor();
-    }
-
-    @Override
-    public boolean hasCustomToolbarColor() {
-        return mColorProvider.hasCustomToolbarColor();
+    public ColorProvider getColorProvider() {
+        return mColorProvider;
     }
 
     @Override
@@ -312,27 +308,6 @@
     }
 
     @Override
-    public int getBottomBarColor() {
-        return mColorProvider.getBottomBarColor();
-    }
-
-    @Override
-    public int getInitialBackgroundColor() {
-        return mColorProvider.getInitialBackgroundColor();
-    }
-
-    @Override
-    public Integer getNavigationBarColor() {
-        return mColorProvider.getNavigationBarColor();
-    }
-
-    @Override
-    @Nullable
-    public Integer getNavigationBarDividerColor() {
-        return mColorProvider.getNavigationBarDividerColor();
-    }
-
-    @Override
     public int getTitleVisibilityState() {
         return mTitleVisibilityState;
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityTabController.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityTabController.java
index 62638b3..832082c2 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityTabController.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityTabController.java
@@ -488,7 +488,7 @@
     private void prepareTabBackground(final Tab tab) {
         if (!CustomTabIntentDataProvider.isTrustedCustomTab(mIntent, mSession)) return;
 
-        int backgroundColor = mIntentDataProvider.getInitialBackgroundColor();
+        int backgroundColor = mIntentDataProvider.getColorProvider().getInitialBackgroundColor();
         if (backgroundColor == Color.TRANSPARENT) return;
 
         // Set the background color.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/CustomTabNavigationBarController.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/CustomTabNavigationBarController.java
index 636060f1..8e945a6 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/CustomTabNavigationBarController.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/CustomTabNavigationBarController.java
@@ -27,8 +27,9 @@
      */
     public static void update(Window window, BrowserServicesIntentDataProvider intentDataProvider,
             Resources resources) {
-        Integer navigationBarColor = intentDataProvider.getNavigationBarColor();
-        Integer navigationBarDividerColor = intentDataProvider.getNavigationBarDividerColor();
+        Integer navigationBarColor = intentDataProvider.getColorProvider().getNavigationBarColor();
+        Integer navigationBarDividerColor =
+                intentDataProvider.getColorProvider().getNavigationBarDividerColor();
 
         int lightBackgroundDividerColor = ApiCompatibilityUtils.getColor(
                 resources, org.chromium.chrome.R.color.black_alpha_12);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbarColorController.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbarColorController.java
index 09ff4e6c..21d7468 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbarColorController.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbarColorController.java
@@ -89,8 +89,9 @@
             return ToolbarColorType.THEME_COLOR;
         }
 
-        return intentDataProvider.hasCustomToolbarColor() ? ToolbarColorType.INTENT_TOOLBAR_COLOR
-                                                          : ToolbarColorType.DEFAULT_COLOR;
+        return intentDataProvider.getColorProvider().hasCustomToolbarColor()
+                ? ToolbarColorType.INTENT_TOOLBAR_COLOR
+                : ToolbarColorType.DEFAULT_COLOR;
     }
 
     /**
@@ -170,7 +171,7 @@
             case ToolbarColorType.DEFAULT_COLOR:
                 return getDefaultColor();
             case ToolbarColorType.INTENT_TOOLBAR_COLOR:
-                return mIntentDataProvider.getToolbarColor();
+                return mIntentDataProvider.getColorProvider().getToolbarColor();
         }
         return getDefaultColor();
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappInfo.java b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappInfo.java
index 8addf21..2d30a61c4 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappInfo.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappInfo.java
@@ -103,7 +103,7 @@
      * ShortcutHelper.MANIFEST_COLOR_INVALID_OR_MISSING otherwise.
      */
     public long toolbarColor() {
-        return hasValidToolbarColor() ? mProvider.getToolbarColor()
+        return hasValidToolbarColor() ? mProvider.getColorProvider().getToolbarColor()
                                       : ShortcutHelper.MANIFEST_COLOR_INVALID_OR_MISSING;
     }
 
@@ -111,7 +111,7 @@
      * Returns whether the toolbar color specified in the Intent is valid.
      */
     public boolean hasValidToolbarColor() {
-        return mProvider.hasCustomToolbarColor();
+        return mProvider.getColorProvider().hasCustomToolbarColor();
     }
 
     /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappIntentDataProvider.java b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappIntentDataProvider.java
index ec253b92..464adfd 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappIntentDataProvider.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappIntentDataProvider.java
@@ -21,6 +21,7 @@
 import org.chromium.base.ContextUtils;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.browserservices.intents.BrowserServicesIntentDataProvider;
+import org.chromium.chrome.browser.browserservices.intents.ColorProvider;
 import org.chromium.chrome.browser.browserservices.intents.WebApkExtras;
 import org.chromium.chrome.browser.browserservices.intents.WebDisplayMode;
 import org.chromium.chrome.browser.browserservices.intents.WebappExtras;
@@ -31,8 +32,6 @@
  * Stores info about a web app.
  */
 public class WebappIntentDataProvider extends BrowserServicesIntentDataProvider {
-    private final int mToolbarColor;
-    private final boolean mHasCustomToolbarColor;
     private final Drawable mCloseButtonIcon;
     private final TrustedWebActivityDisplayMode mTwaDisplayMode;
     private final ShareData mShareData;
@@ -40,6 +39,7 @@
     private final @Nullable WebApkExtras mWebApkExtras;
     private final @ActivityType int mActivityType;
     private final Intent mIntent;
+    private final ColorProviderImpl mColorProvider;
 
     /**
      * Returns the toolbar color to use if a custom color is not specified by the webapp.
@@ -52,8 +52,7 @@
             boolean hasCustomToolbarColor, @Nullable ShareData shareData,
             @NonNull WebappExtras webappExtras, @Nullable WebApkExtras webApkExtras) {
         mIntent = intent;
-        mToolbarColor = toolbarColor;
-        mHasCustomToolbarColor = hasCustomToolbarColor;
+        mColorProvider = new ColorProviderImpl(toolbarColor, hasCustomToolbarColor);
         mCloseButtonIcon = TintedDrawable.constructTintedDrawable(
                 ContextUtils.getApplicationContext(), R.drawable.btn_close);
         mTwaDisplayMode = (webappExtras.displayMode == WebDisplayMode.FULLSCREEN)
@@ -92,13 +91,9 @@
     }
 
     @Override
-    public int getToolbarColor() {
-        return mToolbarColor;
-    }
-
-    @Override
-    public boolean hasCustomToolbarColor() {
-        return mHasCustomToolbarColor;
+    @NonNull
+    public ColorProvider getColorProvider() {
+        return mColorProvider;
     }
 
     @Override
@@ -159,4 +154,46 @@
     public int getDefaultOrientation() {
         return mWebappExtras.orientation;
     }
+
+    private static final class ColorProviderImpl implements ColorProvider {
+        private final int mToolbarColor;
+        private final boolean mHasCustomToolbarColor;
+
+        ColorProviderImpl(int toolbarColor, boolean hasCustomToolbarColor) {
+            mToolbarColor = toolbarColor;
+            mHasCustomToolbarColor = hasCustomToolbarColor;
+        }
+
+        @Override
+        public int getToolbarColor() {
+            return mToolbarColor;
+        }
+
+        @Override
+        public boolean hasCustomToolbarColor() {
+            return mHasCustomToolbarColor;
+        }
+
+        @Override
+        @Nullable
+        public Integer getNavigationBarColor() {
+            return null;
+        }
+
+        @Override
+        @Nullable
+        public Integer getNavigationBarDividerColor() {
+            return null;
+        }
+
+        @Override
+        public int getBottomBarColor() {
+            return getToolbarColor();
+        }
+
+        @Override
+        public int getInitialBackgroundColor() {
+            return Color.TRANSPARENT;
+        }
+    }
 }
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/CustomTabIntentDataProviderTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/CustomTabIntentDataProviderTest.java
index dcbd5c0..7629730 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/CustomTabIntentDataProviderTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/CustomTabIntentDataProviderTest.java
@@ -35,6 +35,7 @@
 import org.chromium.base.IntentUtils;
 import org.chromium.base.test.BaseRobolectricTestRunner;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.browserservices.intents.ColorProvider;
 import org.chromium.chrome.browser.document.ChromeLauncherActivity;
 import org.chromium.chrome.test.util.browser.Features;
 import org.chromium.device.mojom.ScreenOrientationLockType;
@@ -69,10 +70,12 @@
                 .build()
                 .intent;
 
-        CustomTabIntentDataProvider lightProvider = new CustomTabIntentDataProvider(
-                intent, ApplicationProvider.getApplicationContext(), COLOR_SCHEME_LIGHT);
-        CustomTabIntentDataProvider darkProvider = new CustomTabIntentDataProvider(
-                intent, ApplicationProvider.getApplicationContext(), COLOR_SCHEME_DARK);
+        ColorProvider lightProvider = new CustomTabIntentDataProvider(
+                intent, ApplicationProvider.getApplicationContext(), COLOR_SCHEME_LIGHT)
+                                              .getColorProvider();
+        ColorProvider darkProvider = new CustomTabIntentDataProvider(
+                intent, ApplicationProvider.getApplicationContext(), COLOR_SCHEME_DARK)
+                                             .getColorProvider();
 
         assertEquals((int) lightParams.toolbarColor, lightProvider.getToolbarColor());
         assertEquals((int) darkParams.toolbarColor, darkProvider.getToolbarColor());
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/CustomTabNavigationBarControllerTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/CustomTabNavigationBarControllerTest.java
index cd5d7f1e..05aa042 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/CustomTabNavigationBarControllerTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/CustomTabNavigationBarControllerTest.java
@@ -26,6 +26,7 @@
 
 import org.chromium.base.ApiCompatibilityUtils;
 import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.chrome.browser.browserservices.intents.ColorProvider;
 import org.chromium.chrome.browser.customtabs.features.CustomTabNavigationBarController;
 import org.chromium.ui.util.ColorUtils;
 
@@ -34,6 +35,8 @@
 @Config(manifest = Config.NONE)
 public class CustomTabNavigationBarControllerTest {
     @Mock
+    private ColorProvider mColorProvider;
+    @Mock
     private CustomTabIntentDataProvider mCustomTabIntentDataProvider;
     private Window mWindow;
     private Resources mResources;
@@ -44,11 +47,12 @@
         Activity activity = Robolectric.buildActivity(Activity.class).get();
         mWindow = spy(activity.getWindow());
         mResources = activity.getResources();
+        when(mCustomTabIntentDataProvider.getColorProvider()).thenReturn(mColorProvider);
     }
 
     @Test
     public void doesNotSetBarColorWhenNull() {
-        when(mCustomTabIntentDataProvider.getNavigationBarColor()).thenReturn(null);
+        when(mColorProvider.getNavigationBarColor()).thenReturn(null);
         CustomTabNavigationBarController.update(mWindow, mCustomTabIntentDataProvider, mResources);
 
         verify(mWindow, never()).setNavigationBarColor(Mockito.anyInt());
@@ -57,10 +61,10 @@
     @Test
     @Config(sdk = Build.VERSION_CODES.P) // Android P+ (>=28) is needed for setting divider color.
     public void doesNotSetDividerColorWhenNull() {
-        when(mCustomTabIntentDataProvider.getNavigationBarDividerColor()).thenReturn(null);
+        when(mColorProvider.getNavigationBarDividerColor()).thenReturn(null);
         // Bar color needs to be null. Otherwise the divider color could still be set if
         // needsDarkButtons is true.
-        when(mCustomTabIntentDataProvider.getNavigationBarColor()).thenReturn(null);
+        when(mColorProvider.getNavigationBarColor()).thenReturn(null);
 
         CustomTabNavigationBarController.update(mWindow, mCustomTabIntentDataProvider, mResources);
         verify(mWindow, never()).setNavigationBarDividerColor(Mockito.anyInt());
@@ -70,8 +74,8 @@
     @Config(sdk = Build.VERSION_CODES.O_MR1)
     // Android P+ (>=28) is needed for setting the divider color.
     public void doesNotSetDividerColorWhenSdkLow() {
-        when(mCustomTabIntentDataProvider.getNavigationBarDividerColor()).thenReturn(Color.RED);
-        when(mCustomTabIntentDataProvider.getNavigationBarColor()).thenReturn(Color.GREEN);
+        when(mColorProvider.getNavigationBarDividerColor()).thenReturn(Color.RED);
+        when(mColorProvider.getNavigationBarColor()).thenReturn(Color.GREEN);
 
         // Make sure calling the line below does not throw an exception, because the method does not
         // exist in android P+.
@@ -81,15 +85,15 @@
     @Test
     @Config(sdk = Build.VERSION_CODES.N_MR1) // SDK 25 is used to trigger supportsDarkButtons=false.
     public void setsCorrectBarColor() {
-        when(mCustomTabIntentDataProvider.getNavigationBarDividerColor()).thenReturn(Color.RED);
+        when(mColorProvider.getNavigationBarDividerColor()).thenReturn(Color.RED);
 
         // The case when needsDarkButtons=true.
-        when(mCustomTabIntentDataProvider.getNavigationBarColor()).thenReturn(Color.WHITE);
+        when(mColorProvider.getNavigationBarColor()).thenReturn(Color.WHITE);
         CustomTabNavigationBarController.update(mWindow, mCustomTabIntentDataProvider, mResources);
         verify(mWindow).setNavigationBarColor(ColorUtils.getDarkenedColorForStatusBar(Color.WHITE));
 
         // The case when needsDarkButtons=false.
-        when(mCustomTabIntentDataProvider.getNavigationBarColor()).thenReturn(Color.BLACK);
+        when(mColorProvider.getNavigationBarColor()).thenReturn(Color.BLACK);
         CustomTabNavigationBarController.update(mWindow, mCustomTabIntentDataProvider, mResources);
         verify(mWindow).setNavigationBarColor(Color.BLACK);
     }
@@ -97,11 +101,11 @@
     @Test
     @Config(sdk = Build.VERSION_CODES.O) // SDK 26 is used to trigger supportDarkButtons=true.
     public void setsCorrectBarColorWhenDarkButtonsSupported() {
-        when(mCustomTabIntentDataProvider.getNavigationBarDividerColor()).thenReturn(Color.RED);
-        when(mCustomTabIntentDataProvider.getNavigationBarColor()).thenReturn(Color.GREEN);
+        when(mColorProvider.getNavigationBarDividerColor()).thenReturn(Color.RED);
+        when(mColorProvider.getNavigationBarColor()).thenReturn(Color.GREEN);
 
         // The case when needsDarkButtons=true
-        when(mCustomTabIntentDataProvider.getNavigationBarColor()).thenReturn(Color.WHITE);
+        when(mColorProvider.getNavigationBarColor()).thenReturn(Color.WHITE);
         CustomTabNavigationBarController.update(mWindow, mCustomTabIntentDataProvider, mResources);
         verify(mWindow).setNavigationBarColor(Color.WHITE);
     }
@@ -110,15 +114,15 @@
     @Config(sdk = Build.VERSION_CODES.P) // Android P+ (>=28) needed for setting divider color.
     public void setsCorrectDividerColor() {
         // The case when divider color is set explicitly.
-        when(mCustomTabIntentDataProvider.getNavigationBarDividerColor()).thenReturn(Color.RED);
-        when(mCustomTabIntentDataProvider.getNavigationBarColor()).thenReturn(Color.BLACK);
+        when(mColorProvider.getNavigationBarDividerColor()).thenReturn(Color.RED);
+        when(mColorProvider.getNavigationBarColor()).thenReturn(Color.BLACK);
 
         CustomTabNavigationBarController.update(mWindow, mCustomTabIntentDataProvider, mResources);
         verify(mWindow).setNavigationBarDividerColor(Color.RED);
 
         // The case when divider color is set implicitly due to needsDarkButtons=true.
-        when(mCustomTabIntentDataProvider.getNavigationBarDividerColor()).thenReturn(null);
-        when(mCustomTabIntentDataProvider.getNavigationBarColor()).thenReturn(Color.WHITE);
+        when(mColorProvider.getNavigationBarDividerColor()).thenReturn(null);
+        when(mColorProvider.getNavigationBarColor()).thenReturn(Color.WHITE);
         CustomTabNavigationBarController.update(mWindow, mCustomTabIntentDataProvider, mResources);
         verify(mWindow).setNavigationBarDividerColor(ApiCompatibilityUtils.getColor(
                 mResources, org.chromium.chrome.R.color.black_alpha_12));
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/CustomTabStatusBarColorProviderTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/CustomTabStatusBarColorProviderTest.java
index ad9edd50..6ed6b39d 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/CustomTabStatusBarColorProviderTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/CustomTabStatusBarColorProviderTest.java
@@ -21,6 +21,7 @@
 import org.robolectric.annotation.Config;
 
 import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.chrome.browser.browserservices.intents.ColorProvider;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tab.TabImpl;
 import org.chromium.chrome.browser.ui.system.StatusBarColorController;
@@ -39,17 +40,21 @@
     @Mock public StatusBarColorController mStatusBarColorController;
     @Mock
     public TabImpl mTab;
-    private CustomTabStatusBarColorProvider mColorProvider;
+    private CustomTabStatusBarColorProvider mStatusBarColorProvider;
+    @Mock
+    private ColorProvider mColorProvider;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
 
-        mColorProvider = Mockito.spy(new CustomTabStatusBarColorProvider(
+        mStatusBarColorProvider = Mockito.spy(new CustomTabStatusBarColorProvider(
                 mCustomTabIntentDataProvider, mStatusBarColorController));
 
-        when(mCustomTabIntentDataProvider.getToolbarColor()).thenReturn(USER_PROVIDED_COLOR);
-        when(mCustomTabIntentDataProvider.hasCustomToolbarColor()).thenReturn(true);
+        when(mCustomTabIntentDataProvider.getColorProvider()).thenReturn(mColorProvider);
+
+        when(mColorProvider.getToolbarColor()).thenReturn(USER_PROVIDED_COLOR);
+        when(mColorProvider.hasCustomToolbarColor()).thenReturn(true);
     }
 
     @Test
@@ -61,48 +66,48 @@
 
     @Test
     public void useTabThemeColor_enable() {
-        mColorProvider.setUseTabThemeColor(true);
+        mStatusBarColorProvider.setUseTabThemeColor(true);
         Assert.assertEquals(UNDEFINED_STATUS_BAR_COLOR, getStatusBarColor(mTab));
         verify(mStatusBarColorController).updateStatusBarColor();
     }
 
     @Test
     public void useTabThemeColor_enable_nullTab() {
-        mColorProvider.setUseTabThemeColor(true);
+        mStatusBarColorProvider.setUseTabThemeColor(true);
         Assert.assertEquals(USER_PROVIDED_COLOR, getStatusBarColor(null));
 
-        when(mCustomTabIntentDataProvider.hasCustomToolbarColor()).thenReturn(false);
+        when(mColorProvider.hasCustomToolbarColor()).thenReturn(false);
         Assert.assertEquals(DEFAULT_STATUS_BAR_COLOR, getStatusBarColor(null));
     }
 
     @Test
     public void useTabThemeColor_disable() {
-        mColorProvider.setUseTabThemeColor(true);
+        mStatusBarColorProvider.setUseTabThemeColor(true);
         Assert.assertEquals(UNDEFINED_STATUS_BAR_COLOR, getStatusBarColor(mTab));
         verify(mStatusBarColorController).updateStatusBarColor();
 
-        mColorProvider.setUseTabThemeColor(false);
+        mStatusBarColorProvider.setUseTabThemeColor(false);
         Assert.assertEquals(USER_PROVIDED_COLOR, getStatusBarColor(mTab));
         verify(mStatusBarColorController, times(2)).updateStatusBarColor();
     }
 
     @Test
     public void useTabThemeColor_disable_noCustomColor() {
-        when(mCustomTabIntentDataProvider.hasCustomToolbarColor()).thenReturn(false);
-        mColorProvider.setUseTabThemeColor(false);
+        when(mColorProvider.hasCustomToolbarColor()).thenReturn(false);
+        mStatusBarColorProvider.setUseTabThemeColor(false);
         Assert.assertEquals(DEFAULT_STATUS_BAR_COLOR, getStatusBarColor(mTab));
     }
 
     @Test
     public void useTabThemeColor_idempotent() {
-        mColorProvider.setUseTabThemeColor(true);
-        mColorProvider.setUseTabThemeColor(true);
+        mStatusBarColorProvider.setUseTabThemeColor(true);
+        mStatusBarColorProvider.setUseTabThemeColor(true);
 
         Assert.assertEquals(UNDEFINED_STATUS_BAR_COLOR, getStatusBarColor(mTab));
         verify(mStatusBarColorController).updateStatusBarColor();
     }
 
     private int getStatusBarColor(Tab tab) {
-        return mColorProvider.getBaseStatusBarColor(tab, FALLBACK_COLOR);
+        return mStatusBarColorProvider.getBaseStatusBarColor(tab, FALLBACK_COLOR);
     }
 }
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityContentTestEnvironment.java b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityContentTestEnvironment.java
index 274dca3a..2084793 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityContentTestEnvironment.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityContentTestEnvironment.java
@@ -33,6 +33,7 @@
 import org.chromium.chrome.browser.app.ChromeActivity;
 import org.chromium.chrome.browser.app.tab_activity_glue.ReparentingTask;
 import org.chromium.chrome.browser.app.tabmodel.CustomTabsTabModelOrchestrator;
+import org.chromium.chrome.browser.browserservices.intents.ColorProvider;
 import org.chromium.chrome.browser.compositor.CompositorViewHolder;
 import org.chromium.chrome.browser.customtabs.CloseButtonNavigator;
 import org.chromium.chrome.browser.customtabs.CustomTabDelegateFactory;
@@ -77,6 +78,7 @@
     @Mock public CustomTabDelegateFactory customTabDelegateFactory;
     @Mock public ChromeActivity activity;
     @Mock public CustomTabsConnection connection;
+  @Mock public ColorProvider colorProvider;
     @Mock public CustomTabIntentDataProvider intentDataProvider;
     @Mock public TabObserverRegistrar tabObserverRegistrar;
     @Mock public CompositorViewHolder compositorViewHolder;
@@ -143,6 +145,7 @@
         when(startupTabPreloader.takeTabIfMatchingOrDestroy(any(), anyInt())).thenReturn(null);
         when(reparentingTaskProvider.get(any())).thenReturn(reparentingTask);
         when(activityTabProvider.addObserver(activityTabObserverCaptor.capture())).thenReturn(null);
+        when(intentDataProvider.getColorProvider()).thenReturn(colorProvider);
     }
 
     @Override
diff --git a/chrome/android/modules/chrome_bundle_tmpl.gni b/chrome/android/modules/chrome_bundle_tmpl.gni
index 18233436..8ffac28 100644
--- a/chrome/android/modules/chrome_bundle_tmpl.gni
+++ b/chrome/android/modules/chrome_bundle_tmpl.gni
@@ -164,7 +164,7 @@
     proguard_enabled = !is_java_debug
     enable_language_splits = true
     extra_modules = _extra_modules
-    system_image_locale_allowlist = locales - android_bundle_only_locales
+    system_image_locale_allowlist = android_apk_locales
     is_multi_abi = _is_multi_abi
     validate_services = _enable_chrome_module
 
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 63411e7..4f9ad9f 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -4488,6 +4488,8 @@
       "nearby_sharing/nearby_receive_manager.h",
       "nearby_sharing/nearby_share_delegate_impl.cc",
       "nearby_sharing/nearby_share_delegate_impl.h",
+      "nearby_sharing/nearby_share_feature_usage_metrics.cc",
+      "nearby_sharing/nearby_share_feature_usage_metrics.h",
       "nearby_sharing/nearby_share_metrics_logger.cc",
       "nearby_sharing/nearby_share_metrics_logger.h",
       "nearby_sharing/nearby_share_profile_info_provider_impl.cc",
@@ -4630,6 +4632,7 @@
       "//chrome/services/sharing/public/proto",
       "//chrome/services/speech:lib",
       "//chromeos/components/cdm_factory_daemon:cdm_factory_daemon_browser",
+      "//chromeos/components/feature_usage",
       "//chromeos/components/quick_answers",
       "//chromeos/components/sync_wifi",
       "//chromeos/crosapi/cpp",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index d732a99..edb817a 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -5374,6 +5374,13 @@
      FEATURE_VALUE_TYPE(app_list_features::kEnableAssistantSearch)},
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+    {"strict-extension-isolation",
+     flag_descriptions::kStrictExtensionIsolationName,
+     flag_descriptions::kStrictExtensionIsolationDescription, kOsDesktop,
+     FEATURE_VALUE_TYPE(extensions_features::kStrictExtensionIsolation)},
+#endif  // BUILDFLAG(ENABLE_EXTENSIONS)
+
     {"strict-origin-isolation", flag_descriptions::kStrictOriginIsolationName,
      flag_descriptions::kStrictOriginIsolationDescription, kOsAll,
      FEATURE_VALUE_TYPE(features::kStrictOriginIsolation)},
@@ -5506,6 +5513,10 @@
      flag_descriptions::kEnableShortcutCustomizationAppName,
      flag_descriptions::kEnableShortcutCustomizationAppDescription, kOsCrOS,
      FEATURE_VALUE_TYPE(features::kShortcutCustomizationApp)},
+
+    {"enhanced-network-voices", flag_descriptions::kEnhancedNetworkVoicesName,
+     flag_descriptions::kEnhancedNetworkVoicesDescription, kOsCrOS,
+     FEATURE_VALUE_TYPE(features::kEnhancedNetworkVoices)},
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
     {"enable-fenced-frames", flag_descriptions::kEnableFencedFramesName,
@@ -6390,7 +6401,7 @@
     {"conversion-measurement-api",
      flag_descriptions::kConversionMeasurementApiName,
      flag_descriptions::kConversionMeasurementApiDescription, kOsAll,
-     FEATURE_VALUE_TYPE(features::kConversionMeasurement)},
+     FEATURE_VALUE_TYPE(blink::features::kConversionMeasurement)},
     {"conversion-measurement-debug-mode",
      flag_descriptions::kConversionMeasurementDebugModeName,
      flag_descriptions::kConversionMeasurementDebugModeDescription, kOsAll,
diff --git a/chrome/browser/android/browserservices/intents/BUILD.gn b/chrome/browser/android/browserservices/intents/BUILD.gn
index a668c3fc..d13d0d5 100644
--- a/chrome/browser/android/browserservices/intents/BUILD.gn
+++ b/chrome/browser/android/browserservices/intents/BUILD.gn
@@ -8,6 +8,7 @@
   sources = [
     "java/src/org/chromium/chrome/browser/browserservices/intents/BitmapHelper.java",
     "java/src/org/chromium/chrome/browser/browserservices/intents/BrowserServicesIntentDataProvider.java",
+    "java/src/org/chromium/chrome/browser/browserservices/intents/ColorProvider.java",
     "java/src/org/chromium/chrome/browser/browserservices/intents/CustomButtonParams.java",
     "java/src/org/chromium/chrome/browser/browserservices/intents/WebApkExtras.java",
     "java/src/org/chromium/chrome/browser/browserservices/intents/WebApkShareTarget.java",
diff --git a/chrome/browser/android/browserservices/intents/java/src/org/chromium/chrome/browser/browserservices/intents/BrowserServicesIntentDataProvider.java b/chrome/browser/android/browserservices/intents/java/src/org/chromium/chrome/browser/browserservices/intents/BrowserServicesIntentDataProvider.java
index df7bf30..07bb9eb4 100644
--- a/chrome/browser/android/browserservices/intents/java/src/org/chromium/chrome/browser/browserservices/intents/BrowserServicesIntentDataProvider.java
+++ b/chrome/browser/android/browserservices/intents/java/src/org/chromium/chrome/browser/browserservices/intents/BrowserServicesIntentDataProvider.java
@@ -7,11 +7,11 @@
 import android.app.PendingIntent;
 import android.content.ComponentName;
 import android.content.Intent;
-import android.graphics.Color;
 import android.graphics.drawable.Drawable;
 import android.widget.RemoteViews;
 
 import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.browser.customtabs.CustomTabsIntent;
 import androidx.browser.customtabs.CustomTabsSessionToken;
@@ -145,35 +145,8 @@
         return true;
     }
 
-    /**
-     * @return The toolbar color.
-     */
-    public int getToolbarColor() {
-        return Color.WHITE;
-    }
-
-    /**
-     * @return Whether the intent specifies a custom toolbar color.
-     */
-    public boolean hasCustomToolbarColor() {
-        return false;
-    }
-
-    /**
-     * @return The navigation bar color specified in the intent, or null if not specified.
-     */
-    @Nullable
-    public Integer getNavigationBarColor() {
-        return null;
-    }
-
-    /**
-     * @return The navigation bar divider color specified in the intent, or null if not specified.
-     */
-    @Nullable
-    public Integer getNavigationBarDividerColor() {
-        return null;
-    }
+    @NonNull
+    public abstract ColorProvider getColorProvider();
 
     /**
      * @return The drawable of the icon of close button shown in the custom tab toolbar.
@@ -212,13 +185,6 @@
     }
 
     /**
-     * @return The color of the bottom bar.
-     */
-    public int getBottomBarColor() {
-        return getToolbarColor();
-    }
-
-    /**
      * @return The {@link RemoteViews} to show on the bottom bar, or null if the extra is not
      *         specified.
      */
@@ -290,13 +256,6 @@
     }
 
     /**
-     * @return Initial RGB background color.
-     */
-    public int getInitialBackgroundColor() {
-        return Color.TRANSPARENT;
-    }
-
-    /**
      * @return Whether there should be a star button in the menu.
      */
     public boolean shouldShowStarButton() {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabColorProvider.java b/chrome/browser/android/browserservices/intents/java/src/org/chromium/chrome/browser/browserservices/intents/ColorProvider.java
similarity index 72%
rename from chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabColorProvider.java
rename to chrome/browser/android/browserservices/intents/java/src/org/chromium/chrome/browser/browserservices/intents/ColorProvider.java
index ebe3c9f..05730e48 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabColorProvider.java
+++ b/chrome/browser/android/browserservices/intents/java/src/org/chromium/chrome/browser/browserservices/intents/ColorProvider.java
@@ -1,17 +1,15 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
+// Copyright 2021 The Chromium Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-package org.chromium.chrome.browser.customtabs;
+package org.chromium.chrome.browser.browserservices.intents;
 
 import androidx.annotation.Nullable;
 
 /**
- * Provides the set of colors used by custom tabs.
+ * Provides the set of colors used by BrowserServicesIntentDataProvider.
  */
-// TODO(sky): make BrowserServicesIntentDataProvider own this and move methods from
-// BrowserServicesIntentDataProvider onto this.
-public interface CustomTabColorProvider {
+public interface ColorProvider {
     /**
      * @return The color of the bottom bar.
      */
diff --git a/chrome/browser/apps/icon_standardizer.cc b/chrome/browser/apps/icon_standardizer.cc
index 68433fcb..4c46b97 100644
--- a/chrome/browser/apps/icon_standardizer.cc
+++ b/chrome/browser/apps/icon_standardizer.cc
@@ -21,6 +21,8 @@
 
 constexpr float kBackgroundCircleScale = 176.0f / 192.0f;
 
+constexpr float kMinimumVisibleCircularIconSizeRatio = 0.625f;
+
 // Returns the bounding rect for the opaque part of the icon.
 gfx::Rect GetVisibleIconBounds(const SkBitmap& bitmap) {
   const SkPixmap pixmap = bitmap.pixmap();
@@ -152,13 +154,13 @@
     preview.eraseColor(SK_ColorTRANSPARENT);
 
     // |preview| will be the original icon with all visible pixels colored red.
-    for (int x = 0; x < width; x++) {
-      for (int y = 0; y < height; y++) {
-        const SkColor* src_color =
-            reinterpret_cast<SkColor*>(bitmap.getAddr32(0, y));
-        SkColor* preview_color =
-            reinterpret_cast<SkColor*>(preview.getAddr32(0, y));
+    for (int y = 0; y < height; y++) {
+      const SkColor* src_color =
+          reinterpret_cast<SkColor*>(bitmap.getAddr32(0, y));
+      SkColor* preview_color =
+          reinterpret_cast<SkColor*>(preview.getAddr32(0, y));
 
+      for (int x = 0; x < width; x++) {
         SkColor target_color;
 
         if (SkColorGetA(src_color[x]) < 1) {
@@ -172,37 +174,15 @@
     }
 
     gfx::Rect visible_preview_bounds = GetVisibleIconBounds(preview);
-    float visible_icon_diagonal = std::sqrt(
-        visible_preview_bounds.height() * visible_preview_bounds.height() +
-        visible_preview_bounds.width() * visible_preview_bounds.width());
 
-    float preview_diagonal = std::sqrt(preview.height() * preview.height() +
-                                       preview.width() * preview.width());
-
-    float scale = preview_diagonal / visible_icon_diagonal;
-
-    // If the visible icon requires too large of a scale, then the icon is small
-    // enough that it should not be considered circular. This also serves as a
-    // speculative crash fix by setting an upper limit on |scale| in the case it
-    // is too large. (crbug.com/1162155)
-    if (scale >= 1.6f)
+    float visible_icon_size_ratio =
+        static_cast<float>(visible_preview_bounds.width()) /
+        static_cast<float>(width);
+    // If the visible icon is too small then it should not be considered
+    // circular.
+    if (visible_icon_size_ratio < kMinimumVisibleCircularIconSizeRatio)
       return false;
 
-    gfx::Size scaled_icon_size =
-        gfx::ScaleToRoundedSize(rep.pixel_size(), scale);
-
-    // To detect a circle shaped icon of any size, resize and scale |preview| so
-    // the visible icon bounds match the maximum width and height of the bitmap.
-    const SkBitmap scaled_preview = skia::ImageOperations::Resize(
-        preview, skia::ImageOperations::RESIZE_BEST, scaled_icon_size.width(),
-        scaled_icon_size.height());
-
-    preview.eraseColor(SK_ColorTRANSPARENT);
-    SkCanvas canvas1(preview);
-    canvas1.drawImage(scaled_preview.asImage(),
-                      -visible_preview_bounds.x() * scale,
-                      -visible_preview_bounds.y() * scale);
-
     // Use a canvas to perform XOR and DST_OUT operations, which should
     // generate a transparent bitmap for |preview| if the original icon is
     // shaped like a circle.
@@ -214,14 +194,15 @@
 
     // XOR operation to remove a circle.
     paint_circle_mask.setBlendMode(SkBlendMode::kXor);
-    canvas.drawCircle(SkPoint::Make(width / 2.0f, height / 2.0f), width / 2.0,
-                      paint_circle_mask);
+    canvas.drawCircle(SkPoint::Make(width / 2.0f, height / 2.0f),
+                      visible_preview_bounds.width() / 2.0f, paint_circle_mask);
 
     SkPaint paint_outline;
     paint_outline.setColor(SK_ColorGREEN);
     paint_outline.setStyle(SkPaint::kStroke_Style);
 
-    const float outline_stroke_width = width * kCircleOutlineStrokeWidthRatio;
+    const float outline_stroke_width =
+        visible_preview_bounds.width() * kCircleOutlineStrokeWidthRatio;
     const float radius_offset = outline_stroke_width / 8.0f;
 
     paint_outline.setStrokeWidth(outline_stroke_width);
@@ -230,7 +211,8 @@
     // DST_OUT operation to remove an extra circle outline.
     paint_outline.setBlendMode(SkBlendMode::kDstOut);
     canvas.drawCircle(SkPoint::Make(width / 2.0f, height / 2.0f),
-                      width / 2.0f + radius_offset, paint_outline);
+                      visible_preview_bounds.width() / 2.0f + radius_offset,
+                      paint_outline);
 
     // Compute the total pixel difference between the circle mask and the
     // original icon.
@@ -244,7 +226,8 @@
     }
 
     float percentage_diff_pixels =
-        static_cast<float>(total_pixel_difference) / (width * height);
+        static_cast<float>(total_pixel_difference) /
+        (visible_preview_bounds.width() * visible_preview_bounds.height());
 
     // If the pixel difference between a circle and the original icon is small
     // enough, then the icon can be considered circle shaped.
diff --git a/chrome/browser/ash/login/ui/captive_portal_window_browsertest.cc b/chrome/browser/ash/login/ui/captive_portal_window_browsertest.cc
index c8652d09..710c539 100644
--- a/chrome/browser/ash/login/ui/captive_portal_window_browsertest.cc
+++ b/chrome/browser/ash/login/ui/captive_portal_window_browsertest.cc
@@ -215,7 +215,9 @@
   DISALLOW_COPY_AND_ASSIGN(CaptivePortalWindowCtorDtorTest);
 };
 
-IN_PROC_BROWSER_TEST_F(CaptivePortalWindowCtorDtorTest, OpenPortalDialog) {
+// TODO(https://crbug.com/1213549): Flaky on linux-chromeos-rel.
+IN_PROC_BROWSER_TEST_F(CaptivePortalWindowCtorDtorTest,
+                       DISABLED_OpenPortalDialog) {
   LoginDisplayHost* host = LoginDisplayHost::default_host();
   ASSERT_TRUE(host);
   OobeUI* oobe = host->GetOobeUI();
diff --git a/chrome/browser/attribution_reporting/chrome_attribution_browsertest.cc b/chrome/browser/attribution_reporting/chrome_attribution_browsertest.cc
index 028ca2939..ec3af52 100644
--- a/chrome/browser/attribution_reporting/chrome_attribution_browsertest.cc
+++ b/chrome/browser/attribution_reporting/chrome_attribution_browsertest.cc
@@ -4,14 +4,12 @@
 
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/metrics/histogram_tester.h"
-#include "base/test/scoped_feature_list.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_list.h"
 #include "chrome/test/base/in_process_browser_test.h"
 #include "chrome/test/base/ui_test_utils.h"
 #include "components/page_load_metrics/browser/page_load_metrics_test_waiter.h"
 #include "content/public/browser/web_contents.h"
-#include "content/public/common/content_features.h"
 #include "content/public/common/content_switches.h"
 #include "content/public/test/browser_test.h"
 #include "content/public/test/browser_test_utils.h"
@@ -26,9 +24,7 @@
 // content shell.
 class ChromeAttributionBrowserTest : public InProcessBrowserTest {
  public:
-  ChromeAttributionBrowserTest() {
-    feature_list_.InitAndEnableFeature(features::kConversionMeasurement);
-  }
+  ChromeAttributionBrowserTest() = default;
 
   void SetUpCommandLine(base::CommandLine* command_line) override {
     // Sets up the blink runtime feature for ConversionMeasurement.
@@ -47,9 +43,6 @@
 
  protected:
   net::EmbeddedTestServer server_{net::EmbeddedTestServer::TYPE_HTTPS};
-
- private:
-  base::test::ScopedFeatureList feature_list_;
 };
 
 IN_PROC_BROWSER_TEST_F(ChromeAttributionBrowserTest,
diff --git a/chrome/browser/attribution_reporting/conversions_usage_restriction_trial_browsertest.cc b/chrome/browser/attribution_reporting/conversions_usage_restriction_trial_browsertest.cc
index 2fd44aa..d7c8601 100644
--- a/chrome/browser/attribution_reporting/conversions_usage_restriction_trial_browsertest.cc
+++ b/chrome/browser/attribution_reporting/conversions_usage_restriction_trial_browsertest.cc
@@ -74,8 +74,7 @@
     // Disable the alternative usage feature, this should prevent the OT token
     // from enabling the API.
     feature_list_.InitWithFeatures(
-        {features::kConversionMeasurement},
-        {embedder_support::kConversionMeasurementAPIAlternativeUsage});
+        {}, {embedder_support::kConversionMeasurementAPIAlternativeUsage});
   }
 
  private:
@@ -100,9 +99,7 @@
     // Enable the alternative usage feature, this should allow the OT token to
     // enable the API.
     feature_list_.InitWithFeatures(
-        {features::kConversionMeasurement,
-         embedder_support::kConversionMeasurementAPIAlternativeUsage},
-        {});
+        {embedder_support::kConversionMeasurementAPIAlternativeUsage}, {});
   }
 
  private:
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index e6ba098..c2169a0c 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -1963,6 +1963,15 @@
   return true;
 }
 
+size_t ChromeContentBrowserClient::GetProcessCountToIgnoreForLimit() {
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+  return ChromeContentBrowserClientExtensionsPart::
+      GetProcessCountToIgnoreForLimit();
+#else
+  return 0;
+#endif
+}
+
 bool ChromeContentBrowserClient::ShouldTryToUseExistingProcessHost(
     content::BrowserContext* browser_context,
     const GURL& url) {
diff --git a/chrome/browser/chrome_content_browser_client.h b/chrome/browser/chrome_content_browser_client.h
index 8a2e7ea..b0d91a2 100644
--- a/chrome/browser/chrome_content_browser_client.h
+++ b/chrome/browser/chrome_content_browser_client.h
@@ -199,6 +199,7 @@
   bool IsSuitableHost(content::RenderProcessHost* process_host,
                       const GURL& site_url) override;
   bool MayReuseHost(content::RenderProcessHost* process_host) override;
+  size_t GetProcessCountToIgnoreForLimit() override;
   bool ShouldTryToUseExistingProcessHost(
       content::BrowserContext* browser_context,
       const GURL& url) override;
diff --git a/chrome/browser/extensions/api/declarative_content/declarative_content_apitest.cc b/chrome/browser/extensions/api/declarative_content/declarative_content_apitest.cc
index 49c40fc..170ba95e 100644
--- a/chrome/browser/extensions/api/declarative_content/declarative_content_apitest.cc
+++ b/chrome/browser/extensions/api/declarative_content/declarative_content_apitest.cc
@@ -24,6 +24,9 @@
 #include "content/public/browser/storage_partition.h"
 #include "content/public/test/browser_test.h"
 #include "content/public/test/browser_test_utils.h"
+#include "extensions/browser/api/declarative/rules_registry.h"
+#include "extensions/browser/api/declarative/rules_registry_service.h"
+#include "extensions/browser/api/declarative_webrequest/webrequest_constants.h"
 #include "extensions/browser/extension_action.h"
 #include "extensions/browser/extension_action_manager.h"
 #include "extensions/browser/extension_registry.h"
@@ -1029,6 +1032,52 @@
             ExecuteScriptInBackgroundPage(extension->id(), kRemoveTestRule1));
 }
 
+// Test that an extension with a RequestContentScript rule in its manifest can
+// be loaded.
+// Regression for crbug.com/1211316, which could cause this test to flake if
+// RulesRegistry::OnExtensionLoaded() was called before
+// UserScriptmanager::OnExtensionLoaded().
+IN_PROC_BROWSER_TEST_F(DeclarativeContentApiTest, RequestContentScriptRule) {
+  constexpr char kManifest[] = R"(
+      {
+        "name": "Declarative Content apitest",
+        "version": "0.1",
+        "manifest_version": 2,
+        "page_action": {},
+        "permissions": [
+          "declarativeContent"
+        ],
+        "event_rules": [{
+          "event": "declarativeContent.onPageChanged",
+          "actions": [{
+            "type": "declarativeContent.RequestContentScript",
+            "js": ["asdf.js"]
+          }],
+          "conditions": [{
+            "type": "declarativeContent.PageStateMatcher",
+            "pageUrl": {"hostPrefix": "test1"}
+          }]
+        }]
+      }
+  )";
+
+  ext_dir_.WriteManifest(kManifest);
+  const Extension* extension = LoadExtension(ext_dir_.UnpackedPath());
+  ASSERT_TRUE(extension);
+
+  RulesRegistryService* rules_registry_service =
+      extensions::RulesRegistryService::Get(browser()->profile());
+  scoped_refptr<RulesRegistry> rules_registry =
+      rules_registry_service->GetRulesRegistry(
+          RulesRegistryService::kDefaultRulesRegistryID,
+          "declarativeContent.onPageChanged");
+  DCHECK(rules_registry);
+
+  std::vector<const api::events::Rule*> rules;
+  rules_registry->GetAllRules(extension->id(), &rules);
+  EXPECT_EQ(1u, rules.size());
+}
+
 // TODO(wittman): Once ChromeContentRulesRegistry operates on condition and
 // action interfaces, add a test that checks that a navigation always evaluates
 // consistent URL state for all conditions. i.e.: if condition1 evaluates to
diff --git a/chrome/browser/extensions/api/socket/socket_api_unittest.cc b/chrome/browser/extensions/api/socket/socket_api_unittest.cc
index 59475e98..d9cba341 100644
--- a/chrome/browser/extensions/api/socket/socket_api_unittest.cc
+++ b/chrome/browser/extensions/api/socket/socket_api_unittest.cc
@@ -39,7 +39,6 @@
 TEST_F(SocketUnitTest, Create) {
   // Create SocketCreateFunction and put it on BrowserThread
   SocketCreateFunction* function = new SocketCreateFunction();
-  function->set_work_task_runner(base::SequencedTaskRunnerHandle::Get());
 
   // Run tests
   std::unique_ptr<base::DictionaryValue> result(
diff --git a/chrome/browser/extensions/api/sockets_tcp_server/sockets_tcp_server_api_unittest.cc b/chrome/browser/extensions/api/sockets_tcp_server/sockets_tcp_server_api_unittest.cc
index 40210ce..066526e 100644
--- a/chrome/browser/extensions/api/sockets_tcp_server/sockets_tcp_server_api_unittest.cc
+++ b/chrome/browser/extensions/api/sockets_tcp_server/sockets_tcp_server_api_unittest.cc
@@ -54,7 +54,6 @@
   // Create SocketCreateFunction and put it on BrowserThread
   SocketsTcpServerCreateFunction* function =
       new SocketsTcpServerCreateFunction();
-  function->set_work_task_runner(base::SequencedTaskRunnerHandle::Get());
 
   // Run tests
   std::unique_ptr<base::DictionaryValue> result(RunFunctionAndReturnDictionary(
diff --git a/chrome/browser/extensions/chrome_app_sorting.cc b/chrome/browser/extensions/chrome_app_sorting.cc
index 4430a8e..12bba92 100644
--- a/chrome/browser/extensions/chrome_app_sorting.cc
+++ b/chrome/browser/extensions/chrome_app_sorting.cc
@@ -81,7 +81,14 @@
     : browser_context_(browser_context),
       default_ordinals_created_(false) {
   ExtensionIdList extensions;
-  ExtensionPrefs::Get(browser_context_)->GetExtensions(&extensions);
+  ExtensionPrefs* prefs = ExtensionPrefs::Get(browser_context_);
+  std::unique_ptr<extensions::ExtensionPrefs::ExtensionsInfo> extensions_info =
+      prefs->GetInstalledExtensionsInfo();
+  for (size_t i = 0; i < extensions_info->size(); ++i) {
+    ExtensionInfo* info = extensions_info->at(i).get();
+    if (!prefs->IsFromBookmark(info->extension_id))
+      extensions.push_back(info->extension_id);
+  }
   InitializePageOrdinalMap(extensions);
   MigrateAppIndex(extensions);
 }
@@ -201,6 +208,16 @@
   InitializePageOrdinalMap(web_app_registrar_->GetAppIds());
 }
 
+void ChromeAppSorting::SetWebAppRegistrarForTesting(
+    const web_app::WebAppRegistrar* web_app_registrar) {
+  web_app_registrar_ = web_app_registrar;
+}
+
+void ChromeAppSorting::SetWebAppSyncBridgeForTesting(
+    web_app::WebAppSyncBridge* sync_bridge) {
+  web_app_sync_bridge_ = sync_bridge;
+}
+
 void ChromeAppSorting::FixNTPOrdinalCollisions() {
   for (auto page_it = ntp_ordinal_map_.begin();
        page_it != ntp_ordinal_map_.end(); ++page_it) {
diff --git a/chrome/browser/extensions/chrome_app_sorting.h b/chrome/browser/extensions/chrome_app_sorting.h
index 7d28ea1..fb574569 100644
--- a/chrome/browser/extensions/chrome_app_sorting.h
+++ b/chrome/browser/extensions/chrome_app_sorting.h
@@ -96,6 +96,7 @@
   friend class ChromeAppSortingInitializeWithNoApps;
   friend class ChromeAppSortingPageOrdinalMapping;
   friend class ChromeAppSortingSetExtensionVisible;
+  friend class ChromeAppSortingMigratedBookmarkApp;
 
   // An enum used by GetMinOrMaxAppLaunchOrdinalsOnPage to specify which
   // value should be returned.
@@ -165,6 +166,13 @@
   // Returns the number of items in |m| visible on the new tab page.
   size_t CountItemsVisibleOnNtp(const AppLaunchOrdinalMap& m) const;
 
+  // Sets |web_app_registrar_|. Only for use by tests.
+  void SetWebAppRegistrarForTesting(
+      const web_app::WebAppRegistrar* web_app_registrar);
+
+  // Sets |web_app_sync_bridge_|. Only for use by tests.
+  void SetWebAppSyncBridgeForTesting(web_app::WebAppSyncBridge* sync_bridge);
+
   content::BrowserContext* const browser_context_ = nullptr;
   const web_app::WebAppRegistrar* web_app_registrar_ = nullptr;
   web_app::WebAppSyncBridge* web_app_sync_bridge_ = nullptr;
diff --git a/chrome/browser/extensions/chrome_app_sorting_unittest.cc b/chrome/browser/extensions/chrome_app_sorting_unittest.cc
index 1a75eec..4526c2e 100644
--- a/chrome/browser/extensions/chrome_app_sorting_unittest.cc
+++ b/chrome/browser/extensions/chrome_app_sorting_unittest.cc
@@ -7,6 +7,8 @@
 #include <memory>
 
 #include "chrome/browser/extensions/extension_prefs_unittest.h"
+#include "chrome/browser/web_applications/test/test_web_app_registry_controller.h"
+#include "chrome/browser/web_applications/web_app.h"
 #include "components/crx_file/id_util.h"
 #include "components/sync/model/string_ordinal.h"
 #include "extensions/common/constants.h"
@@ -938,4 +940,180 @@
        ChromeAppSortingSetExtensionVisible) {
 }
 
+class ChromeAppSortingMigratedBookmarkApp : public ExtensionPrefsTest {
+ public:
+  ChromeAppSortingMigratedBookmarkApp() {}
+  ~ChromeAppSortingMigratedBookmarkApp() override {}
+
+  void Initialize() override {
+    base::DictionaryValue simple_dict;
+    std::string error;
+
+    simple_dict.SetString(manifest_keys::kVersion, "1.0.0.0");
+    simple_dict.SetInteger(manifest_keys::kManifestVersion, 2);
+
+    // Say that ext1_ is a from_bookmark app that has been migrated to webapp.
+    // It should not be added to page ordinal map, since bookmark apps migrated
+    // to webapps are no longer used, but are still present. The web app added
+    // below will be used instead.
+    simple_dict.SetString(manifest_keys::kName, "ext1_");
+    extension1_ = prefs_.AddExtensionWithManifestAndFlags(
+        simple_dict, ManifestLocation::kExternalPref, Extension::FROM_BOOKMARK);
+
+    simple_dict.SetString(manifest_keys::kName, "ext2_");
+    extension2_ = prefs_.AddExtensionWithManifestAndFlags(
+        simple_dict, ManifestLocation::kExternalPref, Extension::NO_FLAGS);
+
+    simple_dict.SetString(manifest_keys::kName, "ext3_");
+    extension3_ = prefs_.AddExtensionWithManifestAndFlags(
+        simple_dict, ManifestLocation::kExternalPref, Extension::NO_FLAGS);
+
+    repeated_ordinal_ = syncer::StringOrdinal::CreateInitialOrdinal();
+    second_ordinal_ = repeated_ordinal_.CreateAfter();
+    third_ordinal_ = second_ordinal_.CreateAfter();
+
+    // A preference determining the order of which the apps appear on the NTP.
+    const char kPrefAppLaunchOrdinal[] = "app_launcher_ordinal";
+    // A preference determining the page on which an app appears in the NTP.
+    const char kPrefPageOrdinal[] = "page_ordinal";
+
+    // Set the pref values for which ordinals the extensions should use. This
+    // intentionally does not use ChromeAppSorting::SetAppLaunchOrdinal and
+    // SetPageOrdinal since those also insert entries into ntp_ordinal_map_.
+
+    // Old ordinals still exist for the ext1_ bookmark app that was migrated to
+    // webapp. These would create a collision with ext2, but should be ignored.
+    prefs()->UpdateExtensionPref(
+        extension1_->id(), kPrefAppLaunchOrdinal,
+        std::make_unique<base::Value>(repeated_ordinal_.ToInternalValue()));
+    prefs()->UpdateExtensionPref(
+        extension1_->id(), kPrefPageOrdinal,
+        std::make_unique<base::Value>(repeated_ordinal_.ToInternalValue()));
+
+    prefs()->UpdateExtensionPref(
+        extension2_->id(), kPrefAppLaunchOrdinal,
+        std::make_unique<base::Value>(repeated_ordinal_.ToInternalValue()));
+    prefs()->UpdateExtensionPref(
+        extension2_->id(), kPrefPageOrdinal,
+        std::make_unique<base::Value>(repeated_ordinal_.ToInternalValue()));
+
+    prefs()->UpdateExtensionPref(
+        extension3_->id(), kPrefAppLaunchOrdinal,
+        std::make_unique<base::Value>(third_ordinal_.ToInternalValue()));
+    prefs()->UpdateExtensionPref(
+        extension3_->id(), kPrefPageOrdinal,
+        std::make_unique<base::Value>(third_ordinal_.ToInternalValue()));
+
+    // Double check that the prefs set above are read correctly by
+    // ChromeAppSorting.
+    EXPECT_TRUE(repeated_ordinal_.Equals(
+        app_sorting()->GetAppLaunchOrdinal(extension1_->id())));
+    EXPECT_TRUE(repeated_ordinal_.Equals(
+        app_sorting()->GetPageOrdinal(extension1_->id())));
+
+    EXPECT_TRUE(repeated_ordinal_.Equals(
+        app_sorting()->GetAppLaunchOrdinal(extension2_->id())));
+    EXPECT_TRUE(repeated_ordinal_.Equals(
+        app_sorting()->GetPageOrdinal(extension2_->id())));
+
+    EXPECT_TRUE(third_ordinal_.Equals(
+        app_sorting()->GetAppLaunchOrdinal(extension3_->id())));
+    EXPECT_TRUE(third_ordinal_.Equals(
+        app_sorting()->GetPageOrdinal(extension3_->id())));
+
+    // Recreate ExtensionPrefs, which recreates app_sorting, which should now
+    // find the above created extensions and insert them into ntp_ordinal_map_.
+    prefs_.RecreateExtensionPrefs();
+
+    // Only two items should be in ordinal map, since ext1_ was ignored.
+    EXPECT_EQ(2U, CountElementsInOrdinalMap());
+
+    // Webapps are added to ChromeAppSorting asynchronously sometime after it
+    // is created. Simulate that now.
+    test_registry_controller_ =
+        std::make_unique<web_app::TestWebAppRegistryController>();
+    test_registry_controller_->SetUp(prefs_.profile());
+    test_registry_controller_->Init();
+
+    auto web_app = std::make_unique<web_app::WebApp>(extension1_->id());
+    web_app->SetName("name1");
+    web_app->SetStartUrl(GURL("https://example.com/path"));
+    web_app->SetDisplayMode(web_app::DisplayMode::kStandalone);
+    web_app->SetUserDisplayMode(web_app::DisplayMode::kStandalone);
+    web_app->AddSource(web_app::Source::kSync);
+    web_app->SetUserLaunchOrdinal(second_ordinal_);
+    web_app->SetUserPageOrdinal(repeated_ordinal_);
+    test_registry_controller_->RegisterApp(std::move(web_app));
+
+    // Emulate app_sorting()->InitializePageOrdinalMapFromWebApps() without
+    // needing a WebAppProvider:
+    app_sorting()->SetWebAppRegistrarForTesting(
+        &test_registry_controller_->registrar());
+    app_sorting()->SetWebAppSyncBridgeForTesting(
+        &test_registry_controller_->sync_bridge());
+    app_sorting()->InitializePageOrdinalMap(
+        test_registry_controller_->registrar().GetAppIds());
+
+    // Now that the bookmark app that was migrated to a webapp was added, there
+    // should be three items in ordinal map.
+    EXPECT_EQ(3U, CountElementsInOrdinalMap());
+
+    // Now call FixNTPOrdinalCollisions and see what happens.
+    app_sorting()->FixNTPOrdinalCollisions();
+
+    // Verification is done here rather than in Verify() since
+    // ExtensionPrefsTest verifies twice, once after recreating the
+    // ExtensionPrefs, which won't include the web_app we registered here.
+
+    // The ordinals should be unchanged since old bookmark app ordinals were
+    // ignored, there were not actually any collisions.
+    EXPECT_EQ(3U, CountElementsInOrdinalMap());
+    EXPECT_TRUE(app_sorting()
+                    ->GetAppLaunchOrdinal(extension1_->id())
+                    .Equals(second_ordinal_));
+    EXPECT_TRUE(app_sorting()
+                    ->GetPageOrdinal(extension1_->id())
+                    .Equals(repeated_ordinal_));
+    EXPECT_TRUE(app_sorting()
+                    ->GetAppLaunchOrdinal(extension2_->id())
+                    .Equals(repeated_ordinal_));
+    EXPECT_TRUE(app_sorting()
+                    ->GetPageOrdinal(extension2_->id())
+                    .Equals(repeated_ordinal_));
+    EXPECT_TRUE(app_sorting()
+                    ->GetAppLaunchOrdinal(extension3_->id())
+                    .Equals(third_ordinal_));
+    EXPECT_TRUE(app_sorting()
+                    ->GetPageOrdinal(extension3_->id())
+                    .Equals(third_ordinal_));
+  }
+
+  void Verify() override {
+    // Verification is done at the end of Initialize(), see comment there for
+    // details.
+  }
+
+ private:
+  size_t CountElementsInOrdinalMap() {
+    size_t count = 0;
+    for (const auto& page : app_sorting()->ntp_ordinal_map_) {
+      count += page.second.size();
+    }
+    return count;
+  }
+
+  syncer::StringOrdinal repeated_ordinal_;
+  syncer::StringOrdinal second_ordinal_;
+  syncer::StringOrdinal third_ordinal_;
+
+  scoped_refptr<Extension> extension1_;
+  scoped_refptr<Extension> extension2_;
+  scoped_refptr<Extension> extension3_;
+
+  std::unique_ptr<web_app::TestWebAppRegistryController>
+      test_registry_controller_;
+};
+TEST_F(ChromeAppSortingMigratedBookmarkApp,
+       ChromeAppSortingMigratedBookmarkApp) {}
+
 }  // namespace extensions
diff --git a/chrome/browser/extensions/chrome_content_browser_client_extensions_part.cc b/chrome/browser/extensions/chrome_content_browser_client_extensions_part.cc
index f37a1c5..0ef90ac3 100644
--- a/chrome/browser/extensions/chrome_content_browser_client_extensions_part.cc
+++ b/chrome/browser/extensions/chrome_content_browser_client_extensions_part.cc
@@ -6,6 +6,7 @@
 
 #include <stddef.h>
 
+#include <algorithm>
 #include <memory>
 #include <set>
 #include <string>
@@ -13,6 +14,7 @@
 
 #include "base/bind.h"
 #include "base/command_line.h"
+#include "base/feature_list.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/strings/string_piece.h"
 #include "build/chromeos_buildflags.h"
@@ -63,6 +65,7 @@
 #include "extensions/browser/url_request_util.h"
 #include "extensions/browser/view_type_utils.h"
 #include "extensions/common/constants.h"
+#include "extensions/common/extension_features.h"
 #include "extensions/common/extension_urls.h"
 #include "extensions/common/manifest_constants.h"
 #include "extensions/common/manifest_handlers/app_isolation_info.h"
@@ -195,6 +198,26 @@
   return script_url == extension->GetResourceURL(sw_script);
 }
 
+// Returns the number of processes containing extension background pages across
+// all profiles. If this is large enough (e.g., at browser startup time), it can
+// pose a risk that normal web processes will be overly constrained by the
+// browser's process limit.
+size_t GetExtensionBackgroundProcessCount() {
+  std::set<int> process_ids;
+
+  // Go through all profiles to ensure we have total count of extension
+  // processes containing background pages, otherwise one profile can
+  // starve the other. See https://crbug.com/98737.
+  std::vector<Profile*> profiles =
+      g_browser_process->profile_manager()->GetLoadedProfiles();
+  for (Profile* profile : profiles) {
+    ProcessManager* epm = ProcessManager::Get(profile);
+    for (ExtensionHost* host : epm->background_hosts())
+      process_ids.insert(host->render_process_host()->GetID());
+  }
+  return process_ids.size();
+}
+
 }  // namespace
 
 ChromeContentBrowserClientExtensionsPart::
@@ -331,6 +354,13 @@
 bool ChromeContentBrowserClientExtensionsPart::ShouldLockProcessToSite(
     content::BrowserContext* browser_context,
     const GURL& effective_site_url) {
+  // When strict extension isolation is enabled, all extension processes should
+  // be locked.
+  if (base::FeatureList::IsEnabled(
+          extensions_features::kStrictExtensionIsolation)) {
+    return true;
+  }
+
   if (!effective_site_url.SchemeIs(kExtensionScheme))
     return true;
 
@@ -472,6 +502,13 @@
 bool
 ChromeContentBrowserClientExtensionsPart::ShouldTryToUseExistingProcessHost(
     Profile* profile, const GURL& url) {
+  // When strict extension isolation is enabled, no extensions need to reuse an
+  // existing process.
+  if (base::FeatureList::IsEnabled(
+          extensions_features::kStrictExtensionIsolation)) {
+    return false;
+  }
+
   // This function is trying to limit the amount of processes used by extensions
   // with background pages. It uses a globally set percentage of processes to
   // run such extensions and if the limit is exceeded, it returns true, to
@@ -489,23 +526,34 @@
   if (!BackgroundInfo::HasBackgroundPage(extension))
     return false;
 
-  std::set<int> process_ids;
+  size_t max_process_count =
+      content::RenderProcessHost::GetMaxRendererProcessCount();
+  return (GetExtensionBackgroundProcessCount() >
+          (max_process_count * chrome::kMaxShareOfExtensionProcesses));
+}
+
+size_t
+ChromeContentBrowserClientExtensionsPart::GetProcessCountToIgnoreForLimit() {
+  // If strict extension isolation is enabled, ignore any extension processes
+  // that are beyond the extension-specific process limit when considering
+  // whether processes should be reused for other types of pages.
+
+  // If this is a unit test with no profile manager, or if strict extension
+  // isolation is disabled, there is no need to ignore any processes.
+  if (!g_browser_process->profile_manager() ||
+      !base::FeatureList::IsEnabled(
+          extensions_features::kStrictExtensionIsolation)) {
+    return 0;
+  }
+
   size_t max_process_count =
       content::RenderProcessHost::GetMaxRendererProcessCount();
 
-  // Go through all profiles to ensure we have total count of extension
-  // processes containing background pages, otherwise one profile can
-  // starve the other.
-  std::vector<Profile*> profiles = g_browser_process->profile_manager()->
-      GetLoadedProfiles();
-  for (size_t i = 0; i < profiles.size(); ++i) {
-    ProcessManager* epm = ProcessManager::Get(profiles[i]);
-    for (ExtensionHost* host : epm->background_hosts())
-      process_ids.insert(host->render_process_host()->GetID());
-  }
-
-  return (process_ids.size() >
-          (max_process_count * chrome::kMaxShareOfExtensionProcesses));
+  // Ignore any extension background processes over the extension portion of the
+  // process limit when deciding whether to reuse other renderer processes.
+  return std::max(0, static_cast<int>(GetExtensionBackgroundProcessCount() -
+                                      (max_process_count *
+                                       chrome::kMaxShareOfExtensionProcesses)));
 }
 
 // static
diff --git a/chrome/browser/extensions/chrome_content_browser_client_extensions_part.h b/chrome/browser/extensions/chrome_content_browser_client_extensions_part.h
index eefa114..7ed95d5 100644
--- a/chrome/browser/extensions/chrome_content_browser_client_extensions_part.h
+++ b/chrome/browser/extensions/chrome_content_browser_client_extensions_part.h
@@ -71,6 +71,7 @@
                              const GURL& site_url);
   static bool ShouldTryToUseExistingProcessHost(Profile* profile,
                                                 const GURL& url);
+  static size_t GetProcessCountToIgnoreForLimit();
   static bool ShouldSubframesTryToReuseExistingProcess(
       content::RenderFrameHost* main_frame);
   static bool ShouldSwapBrowsingInstancesForNavigation(
diff --git a/chrome/browser/extensions/navigation_observer_browsertest.cc b/chrome/browser/extensions/navigation_observer_browsertest.cc
index b2f4801..8e8bdeb 100644
--- a/chrome/browser/extensions/navigation_observer_browsertest.cc
+++ b/chrome/browser/extensions/navigation_observer_browsertest.cc
@@ -4,6 +4,7 @@
 
 #include "base/bind.h"
 #include "base/callback_helpers.h"
+#include "base/feature_list.h"
 #include "base/run_loop.h"
 #include "chrome/browser/extensions/chrome_test_extension_loader.h"
 #include "chrome/browser/extensions/extension_browsertest.h"
@@ -11,6 +12,7 @@
 #include "chrome/browser/extensions/navigation_observer.h"
 #include "chrome/common/url_constants.h"
 #include "chrome/test/base/ui_test_utils.h"
+#include "content/public/common/content_features.h"
 #include "content/public/test/browser_test.h"
 #include "content/public/test/no_renderer_crashes_assertion.h"
 #include "content/public/test/test_navigation_observer.h"
@@ -18,9 +20,19 @@
 #include "extensions/browser/extension_prefs.h"
 #include "extensions/browser/extension_registry.h"
 #include "extensions/common/extension.h"
+#include "extensions/common/extension_features.h"
 
 namespace extensions {
 
+namespace {
+
+bool IsStrictExtensionIsolationEnabled() {
+  return base::FeatureList::IsEnabled(
+      extensions_features::kStrictExtensionIsolation);
+}
+
+}  // namespace
+
 // A class for testing various scenarios of disabled extensions.
 class DisableExtensionBrowserTest : public ExtensionBrowserTest {
  protected:
@@ -184,9 +196,11 @@
   scoped_refptr<content::SiteInstance> extension_site_instance =
       subframe->GetSiteInstance();
 
-  // The extension process shouldn't be locked, since multiple extensions are
-  // allowed to reuse the same extension process.
-  EXPECT_FALSE(subframe->GetProcess()->IsProcessLockedToSiteForTesting());
+  // The extension process should only be locked if strict extension isolation
+  // is enabled, since multiple extensions are normally allowed to reuse the
+  // same extension process.
+  EXPECT_EQ(IsStrictExtensionIsolationEnabled(),
+            subframe->GetProcess()->IsProcessLockedToSiteForTesting());
 
   // Disable the extension.
   extension_service()->DisableExtension(extension->id(),
@@ -212,8 +226,10 @@
   EXPECT_EQ(subframe->GetSiteInstance()->GetSiteURL(),
             GURL(chrome::kExtensionInvalidRequestURL));
 
-  // The disabled extension process also shouldn't be locked.
-  EXPECT_FALSE(subframe->GetProcess()->IsProcessLockedToSiteForTesting());
+  // The disabled extension process should only be locked if strict extension
+  // isolation is enabled.
+  EXPECT_EQ(IsStrictExtensionIsolationEnabled(),
+            subframe->GetProcess()->IsProcessLockedToSiteForTesting());
 
   // Re-enable the extension.
   extension_service()->EnableExtension(extension->id());
@@ -226,7 +242,8 @@
   subframe = ChildFrameAt(web_contents->GetMainFrame(), 0);
   EXPECT_TRUE(subframe->IsRenderFrameLive());
   EXPECT_EQ(subframe->GetSiteInstance(), extension_site_instance);
-  EXPECT_FALSE(subframe->GetProcess()->IsProcessLockedToSiteForTesting());
+  EXPECT_EQ(IsStrictExtensionIsolationEnabled(),
+            subframe->GetProcess()->IsProcessLockedToSiteForTesting());
 }
 
 IN_PROC_BROWSER_TEST_F(ExtensionBrowserTest, NoExtensionsInRefererHeader) {
diff --git a/chrome/browser/extensions/process_management_browsertest.cc b/chrome/browser/extensions/process_management_browsertest.cc
index 4a668cb..bebf41b 100644
--- a/chrome/browser/extensions/process_management_browsertest.cc
+++ b/chrome/browser/extensions/process_management_browsertest.cc
@@ -4,6 +4,7 @@
 
 #include <stddef.h>
 
+#include "base/feature_list.h"
 #include "base/strings/stringprintf.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/scoped_feature_list.h"
@@ -33,6 +34,7 @@
 #include "extensions/browser/extension_host.h"
 #include "extensions/browser/process_manager.h"
 #include "extensions/browser/process_map.h"
+#include "extensions/common/extension_features.h"
 #include "extensions/common/manifest_handlers/web_accessible_resources_info.h"
 #include "extensions/common/switches.h"
 #include "net/dns/mock_host_resolver.h"
@@ -46,9 +48,8 @@
 namespace {
 
 bool IsExtensionProcessSharingAllowed() {
-  // TODO(nick): Currently, process sharing is allowed even in
-  // --site-per-process. Lock this down.  https://crbug.com/766267
-  return true;
+  return !base::FeatureList::IsEnabled(
+      extensions_features::kStrictExtensionIsolation);
 }
 
 class ProcessManagementTest : public ExtensionBrowserTest {
@@ -386,6 +387,144 @@
   EXPECT_EQ(5u, process_map->size());
 }
 
+// Parameterized tests that run with and without StrictExtensionIsolation, to
+// ensure we have test coverage for both paths.
+class ProcessManagementExtensionIsolationTest
+    : public ProcessManagementTest,
+      public ::testing::WithParamInterface<bool> {
+ public:
+  ProcessManagementExtensionIsolationTest() {
+    if (GetParam()) {
+      feature_list_.InitAndEnableFeature(
+          extensions_features::kStrictExtensionIsolation);
+    } else {
+      feature_list_.InitAndDisableFeature(
+          extensions_features::kStrictExtensionIsolation);
+    }
+  }
+
+  // Provides meaningful param names instead of /0, /1, ...
+  static std::string DescribeParams(
+      const testing::TestParamInfo<ParamType>& info) {
+    return info.param ? "StrictExtensionIsolation" : "ExtensionSharing";
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
+// Test that pushing both extensions and web processes past the limit creates
+// the expected number of processes.
+//
+// Sets the process limit to 3, with 1 expected extension process when sharing
+// is allowed between extensions. The test then creates 3 separate extensions,
+// 3 same-site web pages, and 1 cross-site web page.
+//
+// With extension process sharing, there should be 1 process for all extensions,
+// 2 processes for the same-site pages, and an extra process for the cross-site
+// page due to Site Isolation.
+//
+// Without extension process sharing, there should be 3 processes for the
+// extensions. The web pages should act as if there were only 1 process used by
+// the extensions, so there are 2 web processes for the same-site pages, and an
+// extra process for the cross-site page due to Site Isolation.
+IN_PROC_BROWSER_TEST_P(ProcessManagementExtensionIsolationTest,
+                       ExtensionAndWebProcessOverflow) {
+  // Set max renderers to 3, to expect a single extension process when sharing
+  // is allowed.
+  content::RenderProcessHost::SetMaxRendererProcessCount(3);
+
+  ASSERT_TRUE(embedded_test_server()->Start());
+
+  // Load 3 extensions with background processes, similar to Chrome startup.
+  ASSERT_TRUE(LoadExtension(
+      test_data_dir_.AppendASCII("api_test/browser_action/none")));
+  ASSERT_TRUE(LoadExtension(
+      test_data_dir_.AppendASCII("api_test/browser_action/basics")));
+  ASSERT_TRUE(LoadExtension(
+      test_data_dir_.AppendASCII("api_test/browser_action/add_popup")));
+
+  // Verify the number of extension processes.
+  std::set<int> process_ids;
+  Profile* profile = browser()->profile();
+  ProcessManager* epm = ProcessManager::Get(profile);
+  for (ExtensionHost* host : epm->background_hosts()) {
+    SCOPED_TRACE(testing::Message()
+                 << "When testing extension: " << host->extension_id());
+    // The process should be locked iff process sharing is not allowed.
+    EXPECT_NE(IsExtensionProcessSharingAllowed(),
+              host->render_process_host()->IsProcessLockedToSiteForTesting());
+    process_ids.insert(host->render_process_host()->GetID());
+  }
+  if (IsExtensionProcessSharingAllowed()) {
+    // All extensions share a single process, since this is 1/3 the process
+    // limit.
+    EXPECT_EQ(1u, process_ids.size());
+  } else {
+    // Each extension is in a locked process, unavailable for sharing.
+    EXPECT_EQ(3u, process_ids.size());
+  }
+
+  // Load 3 same-site tabs after the extensions.
+  GURL web_url1(embedded_test_server()->GetURL("foo.com", "/title1.html"));
+  GURL web_url2(embedded_test_server()->GetURL("foo.com", "/title2.html"));
+  GURL web_url3(embedded_test_server()->GetURL("foo.com", "/title3.html"));
+  ui_test_utils::NavigateToURLWithDisposition(
+      browser(), web_url1, WindowOpenDisposition::CURRENT_TAB,
+      ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
+  WebContents* web_contents1 =
+      browser()->tab_strip_model()->GetActiveWebContents();
+  ui_test_utils::NavigateToURLWithDisposition(
+      browser(), web_url2, WindowOpenDisposition::NEW_FOREGROUND_TAB,
+      ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
+  WebContents* web_contents2 =
+      browser()->tab_strip_model()->GetActiveWebContents();
+  ui_test_utils::NavigateToURLWithDisposition(
+      browser(), web_url3, WindowOpenDisposition::NEW_FOREGROUND_TAB,
+      ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
+  WebContents* web_contents3 =
+      browser()->tab_strip_model()->GetActiveWebContents();
+
+  // Verify the number of processes across extensions and tabs.
+  process_ids.insert(web_contents1->GetMainFrame()->GetProcess()->GetID());
+  process_ids.insert(web_contents2->GetMainFrame()->GetProcess()->GetID());
+  process_ids.insert(web_contents3->GetMainFrame()->GetProcess()->GetID());
+  if (IsExtensionProcessSharingAllowed()) {
+    // The web pages share the 2 remaining processes to stay under the limit.
+    EXPECT_EQ(3u, process_ids.size());
+  } else {
+    // The web processes still share 2 processes as if there were a single
+    // extension process (making a total of 5 processes counting the existing 3
+    // extension processes). This avoids starving the web pages with a single
+    // process (if the extensions pushed us past the limit on their own), or
+    // increasing the process count further (if all extension processes were
+    // ignored).
+    EXPECT_EQ(5u, process_ids.size());
+  }
+
+  // Add a cross-site web process.
+  GURL cross_site_url(
+      embedded_test_server()->GetURL("bar.com", "/title1.html"));
+  ui_test_utils::NavigateToURLWithDisposition(
+      browser(), cross_site_url, WindowOpenDisposition::NEW_FOREGROUND_TAB,
+      ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
+  WebContents* web_contents4 =
+      browser()->tab_strip_model()->GetActiveWebContents();
+  process_ids.insert(web_contents4->GetMainFrame()->GetProcess()->GetID());
+  // The cross-site process adds 1 more process to the total, to avoid sharing
+  // with the existing web renderer processes (due to Site Isolation).
+  if (IsExtensionProcessSharingAllowed())
+    EXPECT_EQ(4u, process_ids.size());
+  else
+    EXPECT_EQ(6u, process_ids.size());
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    All,
+    ProcessManagementExtensionIsolationTest,
+    testing::Bool(),
+    ProcessManagementExtensionIsolationTest::DescribeParams);
+
 IN_PROC_BROWSER_TEST_F(ProcessManagementTest,
                        NavigateExtensionTabToWebViaPost) {
   ASSERT_TRUE(embedded_test_server()->Start());
diff --git a/chrome/browser/extensions/process_manager_browsertest.cc b/chrome/browser/extensions/process_manager_browsertest.cc
index 70e35cb0..b31717be 100644
--- a/chrome/browser/extensions/process_manager_browsertest.cc
+++ b/chrome/browser/extensions/process_manager_browsertest.cc
@@ -10,6 +10,7 @@
 #include "base/bind.h"
 #include "base/callback.h"
 #include "base/containers/contains.h"
+#include "base/feature_list.h"
 #include "base/path_service.h"
 #include "base/run_loop.h"
 #include "base/strings/strcat.h"
@@ -47,6 +48,7 @@
 #include "extensions/browser/app_window/app_window.h"
 #include "extensions/browser/app_window/app_window_registry.h"
 #include "extensions/browser/process_manager.h"
+#include "extensions/common/extension_features.h"
 #include "extensions/common/manifest_handlers/background_info.h"
 #include "extensions/common/manifest_handlers/web_accessible_resources_info.h"
 #include "extensions/common/permissions/permissions_data.h"
@@ -66,9 +68,8 @@
 namespace {
 
 bool IsExtensionProcessSharingAllowed() {
-  // TODO(nick): Currently, process sharing is allowed even in
-  // --site-per-process. Lock this down.  https://crbug.com/766267
-  return true;
+  return !base::FeatureList::IsEnabled(
+      extensions_features::kStrictExtensionIsolation);
 }
 
 void AddFrameToSet(std::set<content::RenderFrameHost*>* frames,
diff --git a/chrome/browser/feed/android/BUILD.gn b/chrome/browser/feed/android/BUILD.gn
index 6791519..d80acd94 100644
--- a/chrome/browser/feed/android/BUILD.gn
+++ b/chrome/browser/feed/android/BUILD.gn
@@ -66,6 +66,7 @@
     "java/res/layout/feed_management_activity.xml",
     "java/res/layout/feed_management_list_item.xml",
     "java/res/layout/follow_management_activity.xml",
+    "java/res/layout/follow_management_empty_state.xml",
     "java/res/layout/follow_management_item.xml",
     "java/res/layout/radio_button_group_video_previews_preference.xml",
     "java/res/layout/web_feed_dialog.xml",
diff --git a/chrome/browser/feed/android/java/res/layout/follow_management_activity.xml b/chrome/browser/feed/android/java/res/layout/follow_management_activity.xml
index b101dad..0e6899f 100644
--- a/chrome/browser/feed/android/java/res/layout/follow_management_activity.xml
+++ b/chrome/browser/feed/android/java/res/layout/follow_management_activity.xml
@@ -40,7 +40,7 @@
   <androidx.recyclerview.widget.RecyclerView
       android:id="@+id/follow_management_list"
       android:layout_width="match_parent"
-      android:layout_height="wrap_content" />
+      android:layout_height="match_parent" />
 
   <!-- TODO(petewil) - After UXR, show all button -->
 
diff --git a/chrome/browser/feed/android/java/res/layout/follow_management_empty_state.xml b/chrome/browser/feed/android/java/res/layout/follow_management_empty_state.xml
new file mode 100644
index 0000000..a31eb00
--- /dev/null
+++ b/chrome/browser/feed/android/java/res/layout/follow_management_empty_state.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2021 The Chromium Authors. All rights reserved.
+     Use of this source code is governed by a BSD-style license that can be
+     found in the LICENSE file.
+-->
+
+
+<!-- The empty state will only appear if the recycler view is empty. -->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/follow_management_empty_state"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:gravity="center"
+    android:orientation="vertical" >
+
+  <TextView
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:gravity="center"
+      android:layout_margin="@dimen/follow_empty_margin"
+      style="@style/TextAppearance.TextMedium.Secondary"
+      android:padding="@dimen/follow_empty_margin"
+      android:background="@drawable/hairline_border_card_background"
+      android:text="@string/follow_manage_following_empty_state" />
+
+</LinearLayout>
diff --git a/chrome/browser/feed/android/java/res/values/dimens.xml b/chrome/browser/feed/android/java/res/values/dimens.xml
index d144509e..0c6120a9 100644
--- a/chrome/browser/feed/android/java/res/values/dimens.xml
+++ b/chrome/browser/feed/android/java/res/values/dimens.xml
@@ -26,4 +26,5 @@
     <dimen name="follow_arrow_size">24dp</dimen>
     <dimen name="follow_margin">16dp</dimen>
     <dimen name="follow_item_margin">10dp</dimen>
+    <dimen name="follow_empty_margin">24dp</dimen>
 </resources>
diff --git a/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/followmanagement/FollowManagementCoordinator.java b/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/followmanagement/FollowManagementCoordinator.java
index f7d02d24..3da0e213 100644
--- a/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/followmanagement/FollowManagementCoordinator.java
+++ b/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/followmanagement/FollowManagementCoordinator.java
@@ -8,6 +8,7 @@
 import android.view.LayoutInflater;
 import android.view.View;
 import android.widget.ImageView;
+import android.widget.LinearLayout;
 
 import androidx.recyclerview.widget.LinearLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
@@ -23,6 +24,7 @@
  * https://chromium.googlesource.com/chromium/src/+/HEAD/docs/ui/android/mvc_simple_list_tutorial.md
  */
 public class FollowManagementCoordinator {
+    private static final String TAG = "FollowMMCoordinator";
     private FollowManagementMediator mMediator;
     private Activity mActivity;
     private final View mView;
@@ -32,9 +34,13 @@
         ModelList listItems = new ModelList();
 
         SimpleRecyclerViewAdapter adapter = new SimpleRecyclerViewAdapter(listItems);
+        // Register types for both the full and empty states.
         adapter.registerType(FollowManagementItemProperties.DEFAULT_ITEM_TYPE,
                 new LayoutViewBuilder<FollowManagementItemView>(R.layout.follow_management_item),
                 FollowManagementItemViewBinder::bind);
+        adapter.registerType(FollowManagementItemProperties.EMPTY_ITEM_TYPE,
+                new LayoutViewBuilder<LinearLayout>(R.layout.follow_management_empty_state),
+                (unusedModel, unusedView, unusedKey) -> {});
 
         // Inflate the XML for the activity.
         mView = LayoutInflater.from(activity).inflate(R.layout.follow_management_activity, null);
diff --git a/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/followmanagement/FollowManagementItemProperties.java b/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/followmanagement/FollowManagementItemProperties.java
index e074eecd..8952d07 100644
--- a/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/followmanagement/FollowManagementItemProperties.java
+++ b/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/followmanagement/FollowManagementItemProperties.java
@@ -15,6 +15,7 @@
  */
 public class FollowManagementItemProperties {
     public static final int DEFAULT_ITEM_TYPE = 0;
+    public static final int EMPTY_ITEM_TYPE = 1;
 
     public static final WritableObjectPropertyKey<String> TITLE_KEY =
             new WritableObjectPropertyKey<>();
diff --git a/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/followmanagement/FollowManagementMediator.java b/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/followmanagement/FollowManagementMediator.java
index 2750c724..f49f659 100644
--- a/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/followmanagement/FollowManagementMediator.java
+++ b/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/followmanagement/FollowManagementMediator.java
@@ -166,6 +166,8 @@
     private void fillRecyclerView(List<WebFeedMetadata> followedWebFeeds) {
         String updatesUnavailable =
                 mContext.getResources().getString(R.string.follow_manage_updates_unavailable);
+
+        // Add the list items (if any) to the recycler view.
         for (WebFeedMetadata page : followedWebFeeds) {
             String title = page.title;
             GURL url = page.visitUrl;
@@ -195,6 +197,14 @@
             // getFavicon is async.  We'll get the favicon, then add it to the model.
             faviconProvider.startFaviconFetch();
         }
+        // If there are no subscribed feeds, show the empty state instead.
+        if (followedWebFeeds.isEmpty()) {
+            // Inflate and show the empty state view inside the recycler view.
+            PropertyModel pageModel = new PropertyModel();
+            SimpleRecyclerViewAdapter.ListItem listItem = new SimpleRecyclerViewAdapter.ListItem(
+                    FollowManagementItemProperties.EMPTY_ITEM_TYPE, pageModel);
+            mModelList.add(listItem);
+        }
     }
 
     // Generate a list item for the recycler vivew for a followed page.
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index 1860175..513222e 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -2739,6 +2739,11 @@
     "expiry_milestone": 90
   },
   {
+    "name": "enhanced-network-voices",
+    "owners": ["joelriley@google.com", "leileilei@google.com", "dmazzoni"],
+    "expiry_milestone": 95
+  },
+  {
     "name": "enterprise-realtime-extension-request",
     "owners": ["zmin", "parastoog"],
     "expiry_milestone": 98
@@ -4876,6 +4881,14 @@
     "expiry_milestone": 90
   },
   {
+    "name": "strict-extension-isolation",
+    "owners": [ "creis" ],
+    // This prevents extensions from sharing a process with each other. It is
+    // useful for improving security but launching it will depend on trials that
+    // will monitor the effect on process count, etc.
+    "expiry_milestone": 100
+  },
+  {
     "name": "strict-origin-isolation",
     "owners": [ "wjmaclean", "alexmos", "creis" ],
     // This can be used to opt in to origin isolation which isolates full
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index 6db86b1..081e83c5 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -800,6 +800,11 @@
     "during the handshake when resuming a connection to a compatible TLS 1.3 "
     "server.";
 
+const char kEnhancedNetworkVoicesName[] = "Enhanced network voices";
+const char kEnhancedNetworkVoicesDescription[] =
+    "This option enables high-quality, network-based voices in "
+    "Select-to-speak.";
+
 const char kPostQuantumCECPQ2Name[] = "TLS Post-Quantum Confidentiality";
 const char kPostQuantumCECPQ2Description[] =
     "This option enables a post-quantum (i.e. resistent to quantum computers) "
@@ -2290,6 +2295,11 @@
     "Partitions the HTTP Cache by (top-level site, current-frame site) to "
     "disallow cross-site tracking.";
 
+const char kStrictExtensionIsolationName[] = "Strict Extension Isolation";
+const char kStrictExtensionIsolationDescription[] =
+    "Experimental security mode that prevents extensions from sharing a "
+    "process with each other.";
+
 const char kStrictOriginIsolationName[] = "Strict-Origin-Isolation";
 const char kStrictOriginIsolationDescription[] =
     "Experimental security mode that strengthens the site isolation policy. "
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index e24aba2c..292b983c 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -479,6 +479,9 @@
 extern const char kEnableTLS13EarlyDataName[];
 extern const char kEnableTLS13EarlyDataDescription[];
 
+extern const char kEnhancedNetworkVoicesName[];
+extern const char kEnhancedNetworkVoicesDescription[];
+
 extern const char kPostQuantumCECPQ2Name[];
 extern const char kPostQuantumCECPQ2Description[];
 
@@ -1324,6 +1327,9 @@
 extern const char kStoreHoursAndroidName[];
 extern const char kStoreHoursAndroidDescription[];
 
+extern const char kStrictExtensionIsolationName[];
+extern const char kStrictExtensionIsolationDescription[];
+
 extern const char kStrictOriginIsolationName[];
 extern const char kStrictOriginIsolationDescription[];
 
diff --git a/chrome/browser/metrics/process_memory_metrics_emitter_browsertest.cc b/chrome/browser/metrics/process_memory_metrics_emitter_browsertest.cc
index 5735127..51d0397 100644
--- a/chrome/browser/metrics/process_memory_metrics_emitter_browsertest.cc
+++ b/chrome/browser/metrics/process_memory_metrics_emitter_browsertest.cc
@@ -39,6 +39,7 @@
 #if BUILDFLAG(ENABLE_EXTENSIONS)
 #include "extensions/browser/process_manager.h"
 #include "extensions/common/extension.h"
+#include "extensions/common/extension_features.h"
 #include "extensions/test/background_page_watcher.h"
 #include "extensions/test/test_extension_dir.h"
 #endif
@@ -663,6 +664,13 @@
 #endif
 IN_PROC_BROWSER_TEST_F(ProcessMemoryMetricsEmitterTest,
                        MAYBE_FetchAndEmitMetricsWithExtensionsAndHostReuse) {
+  // When strict extension isolation is enabled, there is no process reuse for
+  // extensions, and this becomes the same as FetchAndEmitMetricsWithExtensions.
+  if (base::FeatureList::IsEnabled(
+          extensions_features::kStrictExtensionIsolation)) {
+    return;
+  }
+
   // Limit the number of renderer processes to force reuse.
   content::RenderProcessHost::SetMaxRendererProcessCount(1);
   const Extension* extension1 = CreateExtension("Extension 1");
diff --git a/chrome/browser/nearby_sharing/nearby_share_feature_usage_metrics.cc b/chrome/browser/nearby_sharing/nearby_share_feature_usage_metrics.cc
new file mode 100644
index 0000000..d273f41
--- /dev/null
+++ b/chrome/browser/nearby_sharing/nearby_share_feature_usage_metrics.cc
@@ -0,0 +1,63 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/nearby_sharing/nearby_share_feature_usage_metrics.h"
+#include "chrome/browser/nearby_sharing/common/nearby_share_prefs.h"
+#include "chromeos/components/feature_usage/feature_usage_metrics.h"
+#include "components/prefs/pref_service.h"
+
+namespace {
+const char kNearbyShareUmaFeatureName[] = "NearbyShare";
+}  // namespace
+
+NearbyShareFeatureUsageMetrics::NearbyShareFeatureUsageMetrics(
+    PrefService* pref_service)
+    : pref_service_(pref_service),
+      feature_usage_metrics_(kNearbyShareUmaFeatureName, this) {}
+
+NearbyShareFeatureUsageMetrics::~NearbyShareFeatureUsageMetrics() = default;
+
+NearbyShareFeatureUsageMetrics::NearbyShareEnabledState
+NearbyShareFeatureUsageMetrics::GetNearbyShareEnabledState() const {
+  bool is_enabled =
+      pref_service_->GetBoolean(prefs::kNearbySharingEnabledPrefName);
+  bool is_managed =
+      pref_service_->IsManagedPreference(prefs::kNearbySharingEnabledPrefName);
+  bool is_onboarded = pref_service_->GetBoolean(
+      prefs::kNearbySharingOnboardingCompletePrefName);
+
+  if (is_enabled) {
+    return is_onboarded ? NearbyShareEnabledState::kEnabledAndOnboarded
+                        : NearbyShareEnabledState::kEnabledAndNotOnboarded;
+  }
+
+  if (is_managed) {
+    return NearbyShareEnabledState::kDisallowedByPolicy;
+  }
+
+  return is_onboarded ? NearbyShareEnabledState::kDisabledAndOnboarded
+                      : NearbyShareEnabledState::kDisabledAndNotOnboarded;
+}
+
+void NearbyShareFeatureUsageMetrics::RecordUsage(bool success) {
+  feature_usage_metrics_.RecordUsage(success);
+}
+
+bool NearbyShareFeatureUsageMetrics::IsEligible() const {
+  // This class is only created if the Nearby Share service is started, which
+  // only occurs for eligible users.
+  return true;
+}
+
+bool NearbyShareFeatureUsageMetrics::IsEnabled() const {
+  switch (GetNearbyShareEnabledState()) {
+    case NearbyShareEnabledState::kEnabledAndOnboarded:
+    case NearbyShareEnabledState::kEnabledAndNotOnboarded:
+      return true;
+    case NearbyShareEnabledState::kDisabledAndOnboarded:
+    case NearbyShareEnabledState::kDisabledAndNotOnboarded:
+    case NearbyShareEnabledState::kDisallowedByPolicy:
+      return false;
+  }
+}
diff --git a/chrome/browser/nearby_sharing/nearby_share_feature_usage_metrics.h b/chrome/browser/nearby_sharing/nearby_share_feature_usage_metrics.h
new file mode 100644
index 0000000..2d69c5e
--- /dev/null
+++ b/chrome/browser/nearby_sharing/nearby_share_feature_usage_metrics.h
@@ -0,0 +1,44 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_NEARBY_SHARING_NEARBY_SHARE_FEATURE_USAGE_METRICS_H_
+#define CHROME_BROWSER_NEARBY_SHARING_NEARBY_SHARE_FEATURE_USAGE_METRICS_H_
+
+#include "chromeos/components/feature_usage/feature_usage_metrics.h"
+
+class PrefService;
+
+// Tracks Nearby Share feature usage for the Standard Feature Usage Logging
+// (SFUL) framework.
+class NearbyShareFeatureUsageMetrics
+    : public feature_usage::FeatureUsageMetrics::Delegate {
+ public:
+  // These values are persisted to logs. Entries should not be renumbered and
+  // numeric values should never be reused. If entries are added, kMaxValue
+  // should be updated.
+  enum class NearbyShareEnabledState {
+    kEnabledAndOnboarded = 0,
+    kEnabledAndNotOnboarded = 1,
+    kDisabledAndOnboarded = 2,
+    kDisabledAndNotOnboarded = 3,
+    kDisallowedByPolicy = 4,
+    kMaxValue = kDisallowedByPolicy
+  };
+
+  explicit NearbyShareFeatureUsageMetrics(PrefService* pref_service);
+  ~NearbyShareFeatureUsageMetrics() final;
+
+  // feature_usage::FeatureUsageMetrics::Delegate:
+  bool IsEligible() const final;
+  bool IsEnabled() const final;
+
+  NearbyShareEnabledState GetNearbyShareEnabledState() const;
+  void RecordUsage(bool success);
+
+ private:
+  PrefService* pref_service_;
+  feature_usage::FeatureUsageMetrics feature_usage_metrics_;
+};
+
+#endif  // CHROME_BROWSER_NEARBY_SHARING_NEARBY_SHARE_FEATURE_USAGE_METRICS_H_
diff --git a/chrome/browser/nearby_sharing/nearby_share_feature_usage_metrics_unittest.cc b/chrome/browser/nearby_sharing/nearby_share_feature_usage_metrics_unittest.cc
new file mode 100644
index 0000000..cde8f16
--- /dev/null
+++ b/chrome/browser/nearby_sharing/nearby_share_feature_usage_metrics_unittest.cc
@@ -0,0 +1,144 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <memory>
+
+#include "base/test/metrics/histogram_tester.h"
+#include "base/test/task_environment.h"
+#include "chrome/browser/nearby_sharing/common/nearby_share_prefs.h"
+#include "chrome/browser/nearby_sharing/nearby_share_feature_usage_metrics.h"
+#include "chromeos/components/feature_usage/feature_usage_metrics.h"
+#include "components/prefs/testing_pref_service.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+class NearbyShareFeatureUsageMetricsTest : public ::testing::Test {
+ protected:
+  NearbyShareFeatureUsageMetricsTest() = default;
+  ~NearbyShareFeatureUsageMetricsTest() override = default;
+
+  void SetUp() override {
+    RegisterNearbySharingPrefs(pref_service_.registry());
+  }
+
+  void SetUnmanagedEnabled(bool is_enabled) {
+    pref_service_.SetBoolean(prefs::kNearbySharingEnabledPrefName, is_enabled);
+  }
+  void SetManagedEnabled(bool is_enabled) {
+    pref_service_.SetManagedPref(prefs::kNearbySharingEnabledPrefName,
+                                 std::make_unique<base::Value>(is_enabled));
+    ASSERT_TRUE(pref_service_.IsManagedPreference(
+        prefs::kNearbySharingEnabledPrefName));
+  }
+  void SetOnboarded(bool is_onboarded) {
+    pref_service_.SetBoolean(prefs::kNearbySharingOnboardingCompletePrefName,
+                             is_onboarded);
+  }
+
+  base::test::TaskEnvironment task_environment_;
+  TestingPrefServiceSimple pref_service_;
+};
+
+TEST_F(NearbyShareFeatureUsageMetricsTest, Enabled_Unmanaged) {
+  NearbyShareFeatureUsageMetrics feature_usage_metrics(&pref_service_);
+  SetUnmanagedEnabled(false);
+  SetOnboarded(false);
+  EXPECT_TRUE(feature_usage_metrics.IsEligible());
+  EXPECT_FALSE(feature_usage_metrics.IsEnabled());
+  EXPECT_EQ(NearbyShareFeatureUsageMetrics::NearbyShareEnabledState::
+                kDisabledAndNotOnboarded,
+            feature_usage_metrics.GetNearbyShareEnabledState());
+
+  SetUnmanagedEnabled(false);
+  SetOnboarded(true);
+  EXPECT_TRUE(feature_usage_metrics.IsEligible());
+  EXPECT_FALSE(feature_usage_metrics.IsEnabled());
+  EXPECT_EQ(NearbyShareFeatureUsageMetrics::NearbyShareEnabledState::
+                kDisabledAndOnboarded,
+            feature_usage_metrics.GetNearbyShareEnabledState());
+
+  // Note: This should never happen in practice.
+  SetUnmanagedEnabled(true);
+  SetOnboarded(false);
+  EXPECT_TRUE(feature_usage_metrics.IsEligible());
+  EXPECT_TRUE(feature_usage_metrics.IsEnabled());
+  EXPECT_EQ(NearbyShareFeatureUsageMetrics::NearbyShareEnabledState::
+                kEnabledAndNotOnboarded,
+            feature_usage_metrics.GetNearbyShareEnabledState());
+
+  SetUnmanagedEnabled(true);
+  SetOnboarded(true);
+  EXPECT_TRUE(feature_usage_metrics.IsEligible());
+  EXPECT_TRUE(feature_usage_metrics.IsEnabled());
+  EXPECT_EQ(NearbyShareFeatureUsageMetrics::NearbyShareEnabledState::
+                kEnabledAndOnboarded,
+            feature_usage_metrics.GetNearbyShareEnabledState());
+}
+
+TEST_F(NearbyShareFeatureUsageMetricsTest, Enabled_Managed) {
+  NearbyShareFeatureUsageMetrics feature_usage_metrics(&pref_service_);
+
+  SetManagedEnabled(false);
+  SetOnboarded(false);
+  EXPECT_TRUE(feature_usage_metrics.IsEligible());
+  EXPECT_FALSE(feature_usage_metrics.IsEnabled());
+  EXPECT_EQ(NearbyShareFeatureUsageMetrics::NearbyShareEnabledState::
+                kDisallowedByPolicy,
+            feature_usage_metrics.GetNearbyShareEnabledState());
+
+  SetManagedEnabled(false);
+  SetOnboarded(true);
+  EXPECT_TRUE(feature_usage_metrics.IsEligible());
+  EXPECT_FALSE(feature_usage_metrics.IsEnabled());
+  EXPECT_EQ(NearbyShareFeatureUsageMetrics::NearbyShareEnabledState::
+                kDisallowedByPolicy,
+            feature_usage_metrics.GetNearbyShareEnabledState());
+
+  // Note: This should never happen in practice.
+  SetManagedEnabled(true);
+  SetOnboarded(false);
+  EXPECT_TRUE(feature_usage_metrics.IsEligible());
+  EXPECT_TRUE(feature_usage_metrics.IsEnabled());
+  EXPECT_EQ(NearbyShareFeatureUsageMetrics::NearbyShareEnabledState::
+                kEnabledAndNotOnboarded,
+            feature_usage_metrics.GetNearbyShareEnabledState());
+
+  SetManagedEnabled(true);
+  SetOnboarded(true);
+  EXPECT_TRUE(feature_usage_metrics.IsEligible());
+  EXPECT_TRUE(feature_usage_metrics.IsEnabled());
+  EXPECT_EQ(NearbyShareFeatureUsageMetrics::NearbyShareEnabledState::
+                kEnabledAndOnboarded,
+            feature_usage_metrics.GetNearbyShareEnabledState());
+}
+
+TEST_F(NearbyShareFeatureUsageMetricsTest, RecordUsage) {
+  // Note: The feature must be enabled to use RecordUsage().
+  NearbyShareFeatureUsageMetrics feature_usage_metrics(&pref_service_);
+  SetUnmanagedEnabled(true);
+  SetOnboarded(true);
+
+  base::HistogramTester histograms;
+  histograms.ExpectBucketCount(
+      "ChromeOS.FeatureUsage.NearbyShare",
+      feature_usage::FeatureUsageMetrics::Event::kUsedWithSuccess, 0);
+  histograms.ExpectBucketCount(
+      "ChromeOS.FeatureUsage.NearbyShare",
+      feature_usage::FeatureUsageMetrics::Event::kUsedWithFailure, 0);
+
+  feature_usage_metrics.RecordUsage(/*success=*/true);
+  histograms.ExpectBucketCount(
+      "ChromeOS.FeatureUsage.NearbyShare",
+      feature_usage::FeatureUsageMetrics::Event::kUsedWithSuccess, 1);
+  histograms.ExpectBucketCount(
+      "ChromeOS.FeatureUsage.NearbyShare",
+      feature_usage::FeatureUsageMetrics::Event::kUsedWithFailure, 0);
+
+  feature_usage_metrics.RecordUsage(/*success=*/false);
+  histograms.ExpectBucketCount(
+      "ChromeOS.FeatureUsage.NearbyShare",
+      feature_usage::FeatureUsageMetrics::Event::kUsedWithSuccess, 1);
+  histograms.ExpectBucketCount(
+      "ChromeOS.FeatureUsage.NearbyShare",
+      feature_usage::FeatureUsageMetrics::Event::kUsedWithFailure, 1);
+}
diff --git a/chrome/browser/nearby_sharing/nearby_share_metrics_logger.cc b/chrome/browser/nearby_sharing/nearby_share_metrics_logger.cc
index 21ac976..a2d5c0c 100644
--- a/chrome/browser/nearby_sharing/nearby_share_metrics_logger.cc
+++ b/chrome/browser/nearby_sharing/nearby_share_metrics_logger.cc
@@ -6,12 +6,10 @@
 
 #include "base/metrics/histogram_functions.h"
 #include "base/numerics/safe_conversions.h"
-#include "chrome/browser/nearby_sharing/common/nearby_share_prefs.h"
 #include "chromeos/services/nearby/public/mojom/nearby_connections_types.mojom.h"
 #include "chromeos/services/nearby/public/mojom/nearby_decoder_types.mojom.h"
 #include "components/policy/core/common/policy_service.h"
 #include "components/policy/policy_constants.h"
-#include "components/prefs/pref_service.h"
 
 namespace {
 
@@ -21,18 +19,6 @@
 // These values are persisted to logs. Entries should not be renumbered and
 // numeric values should never be reused. If entries are added, kMaxValue should
 // be updated.
-enum class NearbyShareEnabledState {
-  kEnabledAndOnboarded = 0,
-  kEnabledAndNotOnboarded = 1,
-  kDisabledAndOnboarded = 2,
-  kDisabledAndNotOnboarded = 3,
-  kDisallowedByPolicy = 4,
-  kMaxValue = kDisallowedByPolicy
-};
-
-// These values are persisted to logs. Entries should not be renumbered and
-// numeric values should never be reused. If entries are added, kMaxValue should
-// be updated.
 enum class TransferFinalStatus {
   kComplete = 0,
   kUnknown = 1,
@@ -362,26 +348,8 @@
 
 }  // namespace
 
-void RecordNearbyShareEnabledMetric(const PrefService* pref_service) {
-  NearbyShareEnabledState state;
-
-  bool is_managed =
-      pref_service->IsManagedPreference(prefs::kNearbySharingEnabledPrefName);
-  bool is_enabled =
-      pref_service->GetBoolean(prefs::kNearbySharingEnabledPrefName);
-  bool is_onboarded =
-      pref_service->GetBoolean(prefs::kNearbySharingOnboardingCompletePrefName);
-
-  if (is_enabled) {
-    state = is_onboarded ? NearbyShareEnabledState::kEnabledAndOnboarded
-                         : NearbyShareEnabledState::kEnabledAndNotOnboarded;
-  } else if (is_managed) {
-    state = NearbyShareEnabledState::kDisallowedByPolicy;
-  } else {  // !is_enabled && !is_managed
-    state = is_onboarded ? NearbyShareEnabledState::kDisabledAndOnboarded
-                         : NearbyShareEnabledState::kDisabledAndNotOnboarded;
-  }
-
+void RecordNearbyShareEnabledMetric(
+    NearbyShareFeatureUsageMetrics::NearbyShareEnabledState state) {
   base::UmaHistogramEnumeration("Nearby.Share.Enabled", state);
 }
 
@@ -552,12 +520,26 @@
 }
 
 void RecordNearbyShareTransferFinalStatusMetric(
+    NearbyShareFeatureUsageMetrics* feature_usage_metrics,
     bool is_incoming,
     nearby_share::mojom::ShareTargetType type,
     TransferMetadata::Status status,
     bool is_known) {
   DCHECK(TransferMetadata::IsFinalStatus(status));
 
+  // Emit success/failure to Standard Feature Usage Logging if there was a
+  // definitive result.
+  switch (TransferMetadata::ToResult(status)) {
+    case TransferMetadata::Result::kSuccess:
+      feature_usage_metrics->RecordUsage(/*success=*/true);
+      break;
+    case TransferMetadata::Result::kFailure:
+      feature_usage_metrics->RecordUsage(/*success=*/false);
+      break;
+    case TransferMetadata::Result::kIndeterminate:
+      break;
+  }
+
   base::UmaHistogramBoolean("Nearby.Share.IsKnownContact", is_known);
 
   std::string send_or_receive = GetDirectionSubcategoryName(is_incoming);
diff --git a/chrome/browser/nearby_sharing/nearby_share_metrics_logger.h b/chrome/browser/nearby_sharing/nearby_share_metrics_logger.h
index 5c19dfb3..83224ca 100644
--- a/chrome/browser/nearby_sharing/nearby_share_metrics_logger.h
+++ b/chrome/browser/nearby_sharing/nearby_share_metrics_logger.h
@@ -6,15 +6,15 @@
 #define CHROME_BROWSER_NEARBY_SHARING_NEARBY_SHARE_METRICS_LOGGER_H_
 
 #include "base/time/time.h"
+#include "chrome/browser/nearby_sharing/nearby_share_feature_usage_metrics.h"
 #include "chrome/browser/nearby_sharing/transfer_metadata.h"
 #include "chromeos/services/nearby/public/mojom/nearby_connections_types.mojom.h"
 #include "chromeos/services/nearby/public/mojom/nearby_decoder_types.mojom.h"
 #include "chromeos/services/nearby/public/mojom/nearby_share_target_types.mojom.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
-class PrefService;
-
-void RecordNearbyShareEnabledMetric(const PrefService* pref_service);
+void RecordNearbyShareEnabledMetric(
+    NearbyShareFeatureUsageMetrics::NearbyShareEnabledState state);
 
 void RecordNearbyShareEstablishConnectionMetrics(
     bool success,
@@ -71,6 +71,7 @@
     location::nearby::connections::mojom::Status status);
 
 void RecordNearbyShareTransferFinalStatusMetric(
+    NearbyShareFeatureUsageMetrics* feature_usage_metrics,
     bool is_incoming,
     nearby_share::mojom::ShareTargetType type,
     TransferMetadata::Status status,
diff --git a/chrome/browser/nearby_sharing/nearby_sharing_service_impl.cc b/chrome/browser/nearby_sharing/nearby_sharing_service_impl.cc
index 5e70c43b..3fcf3ae 100644
--- a/chrome/browser/nearby_sharing/nearby_sharing_service_impl.cc
+++ b/chrome/browser/nearby_sharing/nearby_sharing_service_impl.cc
@@ -293,6 +293,7 @@
           profile->GetPath(),
           http_client_factory_.get())),
       settings_(prefs, local_device_data_manager_.get()),
+      feature_usage_metrics_(prefs),
       on_network_changed_delay_timer_(
           FROM_HERE,
           kProcessNetworkChangeTimerDelay,
@@ -303,7 +304,8 @@
   DCHECK(nearby_connections_manager_);
   DCHECK(power_client_);
 
-  RecordNearbyShareEnabledMetric(prefs_);
+  RecordNearbyShareEnabledMetric(
+      feature_usage_metrics_.GetNearbyShareEnabledState());
 
   auto* session_controller = ash::SessionController::Get();
   if (session_controller) {
@@ -1143,7 +1145,8 @@
 
 void NearbySharingServiceImpl::OnEnabledChanged(bool enabled) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  RecordNearbyShareEnabledMetric(prefs_);
+  RecordNearbyShareEnabledMetric(
+      feature_usage_metrics_.GetNearbyShareEnabledState());
   base::UmaHistogramBoolean("Nearby.Share.EnabledStateChanged", enabled);
   if (enabled) {
     NS_LOG(VERBOSE) << __func__ << ": Nearby sharing enabled!";
@@ -2733,6 +2736,7 @@
 
   if (metadata.is_final_status()) {
     RecordNearbyShareTransferFinalStatusMetric(
+        &feature_usage_metrics_,
         /*is_incoming=*/true, share_target.type, metadata.status(),
         share_target.is_known);
     OnTransferComplete();
@@ -2769,6 +2773,7 @@
   if (metadata.is_final_status()) {
     is_connecting_ = false;
     RecordNearbyShareTransferFinalStatusMetric(
+        &feature_usage_metrics_,
         /*is_incoming=*/false, share_target.type, metadata.status(),
         share_target.is_known);
     OnTransferComplete();
diff --git a/chrome/browser/nearby_sharing/nearby_sharing_service_impl.h b/chrome/browser/nearby_sharing/nearby_sharing_service_impl.h
index 7d22ba38..060db47 100644
--- a/chrome/browser/nearby_sharing/nearby_sharing_service_impl.h
+++ b/chrome/browser/nearby_sharing/nearby_sharing_service_impl.h
@@ -32,6 +32,7 @@
 #include "chrome/browser/nearby_sharing/nearby_connections_manager.h"
 #include "chrome/browser/nearby_sharing/nearby_file_handler.h"
 #include "chrome/browser/nearby_sharing/nearby_notification_manager.h"
+#include "chrome/browser/nearby_sharing/nearby_share_feature_usage_metrics.h"
 #include "chrome/browser/nearby_sharing/nearby_share_profile_info_provider_impl.h"
 #include "chrome/browser/nearby_sharing/nearby_share_settings.h"
 #include "chrome/browser/nearby_sharing/nearby_sharing_service.h"
@@ -410,6 +411,7 @@
   std::unique_ptr<NearbyShareContactManager> contact_manager_;
   std::unique_ptr<NearbyShareCertificateManager> certificate_manager_;
   NearbyShareSettings settings_;
+  NearbyShareFeatureUsageMetrics feature_usage_metrics_;
   NearbyFileHandler file_handler_;
   bool is_screen_locked_ = false;
   base::OneShotTimer rotate_background_advertisement_timer_;
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 434d7bf..2ea70bc 100644
--- a/chrome/browser/nearby_sharing/nearby_sharing_service_impl_unittest.cc
+++ b/chrome/browser/nearby_sharing/nearby_sharing_service_impl_unittest.cc
@@ -18,6 +18,7 @@
 #include "base/strings/string_number_conversions.h"
 #include "base/system/sys_info.h"
 #include "base/test/bind.h"
+#include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/threading/thread_restrictions.h"
 #include "build/chromeos_buildflags.h"
@@ -52,6 +53,7 @@
 #include "chrome/test/base/testing_browser_process.h"
 #include "chrome/test/base/testing_profile.h"
 #include "chrome/test/base/testing_profile_manager.h"
+#include "chromeos/components/feature_usage/feature_usage_metrics.h"
 #include "chromeos/services/nearby/public/cpp/mock_nearby_process_manager.h"
 #include "chromeos/services/nearby/public/cpp/mock_nearby_sharing_decoder.h"
 #include "chromeos/services/nearby/public/mojom/nearby_connections_types.mojom.h"
@@ -836,15 +838,31 @@
     auto barrier = base::BarrierClosure(updates.size(), std::move(callback));
     auto& expectation =
         EXPECT_CALL(transfer_callback, OnTransferUpdate).Times(updates.size());
+
     for (TransferMetadata::Status status : updates) {
-      expectation.WillOnce(testing::Invoke(
-          [=](const ShareTarget& share_target, TransferMetadata metadata) {
-            EXPECT_EQ(target.id, share_target.id);
-            EXPECT_EQ(status, metadata.status());
-            if (new_share_target)
-              *new_share_target = share_target;
-            barrier.Run();
-          }));
+      expectation.WillOnce(testing::Invoke([=](const ShareTarget& share_target,
+                                               TransferMetadata metadata) {
+        EXPECT_EQ(target.id, share_target.id);
+        EXPECT_EQ(status, metadata.status());
+        if (new_share_target)
+          *new_share_target = share_target;
+
+        // Though this is indirect, verify that the highest level
+        // success/failure metric was logged. We expect transfer updates to
+        // be a few indeterminate status then only one success or failure
+        // status.
+        TransferMetadata::Result result = TransferMetadata::ToResult(status);
+        histogram_tester_.ExpectBucketCount(
+            "ChromeOS.FeatureUsage.NearbyShare",
+            feature_usage::FeatureUsageMetrics::Event::kUsedWithSuccess,
+            result == TransferMetadata::Result::kSuccess ? 1 : 0);
+        histogram_tester_.ExpectBucketCount(
+            "ChromeOS.FeatureUsage.NearbyShare",
+            feature_usage::FeatureUsageMetrics::Event::kUsedWithFailure,
+            result == TransferMetadata::Result::kFailure ? 1 : 0);
+
+        barrier.Run();
+      }));
     }
   }
 
@@ -1046,6 +1064,7 @@
   int64_t last_advertising_interval_max_ = 0;
   chromeos::nearby::NearbyProcessManager::NearbyProcessStoppedCallback
       process_stopped_callback_;
+  base::HistogramTester histogram_tester_;
 };
 
 struct ValidSendSurfaceTestData {
diff --git a/chrome/browser/net/system_network_context_manager.cc b/chrome/browser/net/system_network_context_manager.cc
index 7904a05..9188fe3 100644
--- a/chrome/browser/net/system_network_context_manager.cc
+++ b/chrome/browser/net/system_network_context_manager.cc
@@ -499,23 +499,7 @@
   network_service->ConfigureHttpAuthPrefs(
       CreateHttpAuthDynamicParams(local_state_));
 
-  int max_connections_per_proxy =
-      local_state_->GetInteger(prefs::kMaxConnectionsPerProxy);
-  if (max_connections_per_proxy != -1)
-    network_service->SetMaxConnectionsPerProxy(max_connections_per_proxy);
-
-  network_service_network_context_.reset();
-  network_service->CreateNetworkContext(
-      network_service_network_context_.BindNewPipeAndPassReceiver(),
-      CreateNetworkContextParams());
-
-  mojo::PendingRemote<network::mojom::NetworkContextClient> client_remote;
-  mojo::MakeSelfOwnedReceiver(
-      std::make_unique<content::NetworkContextClientBase>(),
-      client_remote.InitWithNewPipeAndPassReceiver());
-  network_service_network_context_->SetClient(std::move(client_remote));
-
-// Configure the Certificate Transparency logs.
+  // Configure the Certificate Transparency logs.
 #if !defined(OS_ANDROID)
   if (g_enable_certificate_transparency) {
     std::vector<std::string> operated_by_google_logs =
@@ -546,6 +530,22 @@
   }
 #endif
 
+  int max_connections_per_proxy =
+      local_state_->GetInteger(prefs::kMaxConnectionsPerProxy);
+  if (max_connections_per_proxy != -1)
+    network_service->SetMaxConnectionsPerProxy(max_connections_per_proxy);
+
+  network_service_network_context_.reset();
+  network_service->CreateNetworkContext(
+      network_service_network_context_.BindNewPipeAndPassReceiver(),
+      CreateNetworkContextParams());
+
+  mojo::PendingRemote<network::mojom::NetworkContextClient> client_remote;
+  mojo::MakeSelfOwnedReceiver(
+      std::make_unique<content::NetworkContextClientBase>(),
+      client_remote.InitWithNewPipeAndPassReceiver());
+  network_service_network_context_->SetClient(std::move(client_remote));
+
   // Configure the stub resolver. This must be done after the system
   // NetworkContext is created, but before anything has the chance to use it.
   stub_resolver_config_reader_.UpdateNetworkService(true /* record_metrics */);
diff --git a/chrome/browser/optimization_guide/android/BUILD.gn b/chrome/browser/optimization_guide/android/BUILD.gn
index 024b5a2..a0f7aab 100644
--- a/chrome/browser/optimization_guide/android/BUILD.gn
+++ b/chrome/browser/optimization_guide/android/BUILD.gn
@@ -48,10 +48,12 @@
     ":java",
     "//base:base_java",
     "//chrome/browser/flags:java",
+    "//chrome/browser/profiles/android:java",
     "//components/optimization_guide/proto:optimization_guide_proto_java",
     "//content/public/android:content_java",
     "//third_party/android_deps:protobuf_lite_runtime_java",
     "//third_party/junit",
+    "//third_party/mockito:mockito_java",
     "//url:gurl_java",
   ]
 
diff --git a/chrome/browser/optimization_guide/android/android_push_notification_manager_unittest.cc b/chrome/browser/optimization_guide/android/android_push_notification_manager_unittest.cc
index 324eaa5..e5c9e4c3 100644
--- a/chrome/browser/optimization_guide/android/android_push_notification_manager_unittest.cc
+++ b/chrome/browser/optimization_guide/android/android_push_notification_manager_unittest.cc
@@ -11,11 +11,17 @@
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
 #include "chrome/browser/optimization_guide/android/native_j_unittests_jni_headers/OptimizationGuidePushNotificationTestHelper_jni.h"
+#include "chrome/browser/optimization_guide/android/optimization_guide_bridge.h"
+#include "chrome/browser/optimization_guide/optimization_guide_hints_manager.h"
+#include "chrome/browser/optimization_guide/optimization_guide_keyed_service.h"
+#include "chrome/browser/optimization_guide/optimization_guide_keyed_service_factory.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/common/chrome_constants.h"
+#include "chrome/test/base/chrome_render_view_host_test_harness.h"
 #include "chrome/test/base/testing_browser_process.h"
 #include "chrome/test/base/testing_profile.h"
 #include "chrome/test/base/testing_profile_manager.h"
+#include "components/optimization_guide/core/hint_cache.h"
 #include "components/optimization_guide/core/hints_fetcher.h"
 #include "components/optimization_guide/core/optimization_guide_features.h"
 #include "components/optimization_guide/core/optimization_guide_prefs.h"
@@ -78,7 +84,9 @@
 class AndroidPushNotificationManagerJavaTest : public testing::Test {
  public:
   AndroidPushNotificationManagerJavaTest()
-      : env_(base::android::AttachCurrentThread()),
+      : j_test_(Java_OptimizationGuidePushNotificationTestHelper_Constructor(
+            base::android::AttachCurrentThread())),
+        env_(base::android::AttachCurrentThread()),
         profile_manager_(TestingBrowserProcess::GetGlobal()) {
     scoped_feature_list_.InitAndEnableFeature(
         optimization_guide::features::kPushNotifications);
@@ -90,21 +98,52 @@
     ASSERT_TRUE(profile_manager_.SetUp(temp_dir_.GetPath()));
     profile_ = profile_manager_.CreateTestingProfile(chrome::kInitialProfile);
 
-    // It takes two session starts for experimental params to be picked up by
-    // Java, so override it manually.
+    service_ = static_cast<OptimizationGuideKeyedService*>(
+        OptimizationGuideKeyedServiceFactory::GetInstance()
+            ->SetTestingFactoryAndUse(
+                profile(),
+                base::BindRepeating(&AndroidPushNotificationManagerJavaTest::
+                                        CreateServiceForProfile,
+                                    base::Unretained(this))));
+
+    Java_OptimizationGuidePushNotificationTestHelper_setUpMocks(env_, j_test_);
+
+    // It takes two session starts for experimental params and feature flags to
+    // be picked up by Java, so override them manually.
     Java_OptimizationGuidePushNotificationTestHelper_setOverflowSizeForTesting(
         env_, kOverflowSize);
+    Java_OptimizationGuidePushNotificationTestHelper_setFeatureEnabled(env_);
   }
 
   void TearDown() override {
     Java_OptimizationGuidePushNotificationTestHelper_clearAllCaches(env_);
   }
 
-  JNIEnv* env() { return env_; }
+  std::unique_ptr<KeyedService> CreateServiceForProfile(
+      content::BrowserContext* browser_context) {
+    return std::make_unique<OptimizationGuideKeyedService>(
+        Profile::FromBrowserContext(browser_context));
+  }
 
-  Profile* profile() { return profile_; }
+  void PushNotificationNative(
+      const proto::HintNotificationPayload& notification) {
+    std::string encoded_notification;
+    notification.SerializeToString(&encoded_notification);
 
-  PrefService* prefs() { return profile()->GetPrefs(); }
+    OptimizationGuideBridge bridge(service());
+    bridge.OnNewPushNotification(
+        env_, base::android::ToJavaByteArray(env_, encoded_notification));
+  }
+
+  bool PushNotificationJava(
+      const proto::HintNotificationPayload& notification) {
+    std::string encoded_notification;
+    if (!notification.SerializeToString(&encoded_notification))
+      return false;
+
+    return Java_OptimizationGuidePushNotificationTestHelper_pushNotification(
+        env_, base::android::ToJavaByteArray(env_, encoded_notification));
+  }
 
   void CauseOverflow(proto::OptimizationType opt_type) {
     for (int i = 0; i < kOverflowSize + 1; i++) {
@@ -126,15 +165,6 @@
         env_, base::android::ToJavaByteArray(env_, encoded_notification));
   }
 
-  bool PushNotification(const proto::HintNotificationPayload& notification) {
-    std::string encoded_notification;
-    if (!notification.SerializeToString(&encoded_notification))
-      return false;
-
-    return Java_OptimizationGuidePushNotificationTestHelper_pushNotification(
-        env_, base::android::ToJavaByteArray(env_, encoded_notification));
-  }
-
   bool DidOverflow(proto::OptimizationType opt_type) {
     return Java_OptimizationGuidePushNotificationTestHelper_didOverflow(
         env_, static_cast<int>(opt_type));
@@ -145,12 +175,26 @@
         env_, static_cast<int>(opt_type));
   }
 
+  OptimizationGuideKeyedService* service() { return service_; }
+
+  OptimizationGuideHintsManager* hints_manager() {
+    return service()->GetHintsManager();
+  }
+
+  JNIEnv* env() { return env_; }
+
+  TestingProfile* profile() { return profile_; }
+
+  PrefService* prefs() { return profile()->GetPrefs(); }
+
  private:
   content::BrowserTaskEnvironment task_environment_{
       base::test::TaskEnvironment::MainThreadType::UI};
+  base::android::ScopedJavaGlobalRef<jobject> j_test_;
   JNIEnv* env_;
   TestingProfileManager profile_manager_;
   TestingProfile* profile_;
+  OptimizationGuideKeyedService* service_;
   base::ScopedTempDir temp_dir_;
   base::test::ScopedFeatureList scoped_feature_list_;
 };
@@ -191,7 +235,7 @@
 }
 
 TEST_F(AndroidPushNotificationManagerJavaTest,
-       SingleCachedNotification_FailedCallback) {
+       Cached_SingleNotification_FailedCallback) {
   base::HistogramTester histogram_tester;
   TestDelegate delegate;
   delegate.SetRunSuccessCallbacks(false);
@@ -270,7 +314,7 @@
       true, 2);
 }
 
-TEST_F(AndroidPushNotificationManagerJavaTest, Overflow_HandledSuccess) {
+TEST_F(AndroidPushNotificationManagerJavaTest, Cached_Overflow_HandledSuccess) {
   base::HistogramTester histogram_tester;
   TestDelegate delegate;
   delegate.SetRunSuccessCallbacks(true);
@@ -296,7 +340,7 @@
       0);
 }
 
-TEST_F(AndroidPushNotificationManagerJavaTest, Overflow_HandledFailure) {
+TEST_F(AndroidPushNotificationManagerJavaTest, Cached_Overflow_HandledFailure) {
   TestDelegate delegate;
   delegate.SetRunSuccessCallbacks(false);
 
@@ -312,7 +356,7 @@
   EXPECT_TRUE(DidOverflow(proto::OptimizationType::PERFORMANCE_HINTS));
 }
 
-TEST_F(AndroidPushNotificationManagerJavaTest, OverflowPurgesAllTypes) {
+TEST_F(AndroidPushNotificationManagerJavaTest, Cached_OverflowPurgesAllTypes) {
   base::HistogramTester histogram_tester;
   TestDelegate delegate;
   delegate.SetRunSuccessCallbacks(true);
@@ -430,7 +474,8 @@
       "OptimizationGuide.PushNotifications.GotPushNotification", true, 1);
 }
 
-TEST_F(AndroidPushNotificationManagerJavaTest, MultipleKeyRepresentations) {
+TEST_F(AndroidPushNotificationManagerJavaTest,
+       Cached_MultipleKeyRepresentations) {
   base::HistogramTester histogram_tester;
   TestDelegate delegate;
   delegate.SetRunSuccessCallbacks(true);
@@ -481,5 +526,318 @@
       true, 1);
 }
 
+TEST_F(AndroidPushNotificationManagerJavaTest, Pushed_URL_SuccessCase) {
+  // Pre-populate the store with some hints.
+  int cache_duration_in_secs = 60;
+  GURL url("https://host.com/r/cats");
+
+  std::unique_ptr<proto::GetHintsResponse> get_hints_response =
+      std::make_unique<proto::GetHintsResponse>();
+
+  proto::Hint* hint = get_hints_response->add_hints();
+  hint->set_key(url.spec());
+  hint->set_key_representation(proto::FULL_URL);
+  hint->mutable_max_cache_duration()->set_seconds(cache_duration_in_secs);
+  proto::PageHint* page_hint = hint->add_page_hints();
+  page_hint->add_whitelisted_optimizations()->set_optimization_type(
+      proto::PERFORMANCE_HINTS);
+  page_hint->set_page_pattern("whatever/*");
+
+  hint = get_hints_response->add_hints();
+  hint->set_key_representation(proto::HOST);
+  hint->set_key(url.host());
+  page_hint = hint->add_page_hints();
+  page_hint->set_page_pattern("page/*");
+
+  std::unique_ptr<base::RunLoop> run_loop = std::make_unique<base::RunLoop>();
+  hints_manager()->hint_cache()->UpdateFetchedHints(
+      std::move(get_hints_response), base::Time().Now(), {url.host()}, {url},
+      run_loop->QuitClosure());
+  run_loop->Run();
+
+  EXPECT_TRUE(hints_manager()->hint_cache()->HasHint(url.host()));
+  EXPECT_TRUE(hints_manager()->hint_cache()->HasURLKeyedEntryForURL(url));
+
+  proto::HintNotificationPayload notification;
+  notification.set_optimization_type(
+      proto::OptimizationType::PERFORMANCE_HINTS);
+  notification.set_key_representation(proto::KeyRepresentation::FULL_URL);
+  notification.set_hint_key(url.spec());
+
+  PushNotificationNative(notification);
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_FALSE(hints_manager()->hint_cache()->HasHint(url.host()));
+  EXPECT_FALSE(hints_manager()->hint_cache()->HasURLKeyedEntryForURL(url));
+}
+
+TEST_F(AndroidPushNotificationManagerJavaTest, Pushed_Host_SuccessCase) {
+  // Pre-populate the store with some hints.
+  int cache_duration_in_secs = 60;
+  GURL url("https://host.com/r/cats");
+
+  std::unique_ptr<proto::GetHintsResponse> get_hints_response =
+      std::make_unique<proto::GetHintsResponse>();
+
+  proto::Hint* hint = get_hints_response->add_hints();
+  hint->set_key(url.spec());
+  hint->set_key_representation(proto::FULL_URL);
+  hint->mutable_max_cache_duration()->set_seconds(cache_duration_in_secs);
+  proto::PageHint* page_hint = hint->add_page_hints();
+  page_hint->add_whitelisted_optimizations()->set_optimization_type(
+      proto::PERFORMANCE_HINTS);
+  page_hint->set_page_pattern("whatever/*");
+
+  hint = get_hints_response->add_hints();
+  hint->set_key_representation(proto::HOST);
+  hint->set_key(url.host());
+  page_hint = hint->add_page_hints();
+  page_hint->set_page_pattern("page/*");
+
+  std::unique_ptr<base::RunLoop> run_loop = std::make_unique<base::RunLoop>();
+  hints_manager()->hint_cache()->UpdateFetchedHints(
+      std::move(get_hints_response), base::Time().Now(), {url.host()}, {url},
+      run_loop->QuitClosure());
+  run_loop->Run();
+
+  EXPECT_TRUE(hints_manager()->hint_cache()->HasHint(url.host()));
+  EXPECT_TRUE(hints_manager()->hint_cache()->HasURLKeyedEntryForURL(url));
+
+  proto::HintNotificationPayload notification;
+  notification.set_optimization_type(
+      proto::OptimizationType::PERFORMANCE_HINTS);
+  notification.set_key_representation(proto::KeyRepresentation::HOST);
+  notification.set_hint_key(url.host());
+
+  PushNotificationNative(notification);
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_FALSE(hints_manager()->hint_cache()->HasHint(url.host()));
+  EXPECT_TRUE(hints_manager()->hint_cache()->HasURLKeyedEntryForURL(url));
+}
+
+TEST_F(AndroidPushNotificationManagerJavaTest, PushedJava_URL_SuccessCase) {
+  // Pre-populate the store with some hints.
+  int cache_duration_in_secs = 60;
+  GURL url("https://host.com/r/cats");
+
+  std::unique_ptr<proto::GetHintsResponse> get_hints_response =
+      std::make_unique<proto::GetHintsResponse>();
+
+  proto::Hint* hint = get_hints_response->add_hints();
+  hint->set_key(url.spec());
+  hint->set_key_representation(proto::FULL_URL);
+  hint->mutable_max_cache_duration()->set_seconds(cache_duration_in_secs);
+  proto::PageHint* page_hint = hint->add_page_hints();
+  page_hint->add_whitelisted_optimizations()->set_optimization_type(
+      proto::PERFORMANCE_HINTS);
+  page_hint->set_page_pattern("whatever/*");
+
+  hint = get_hints_response->add_hints();
+  hint->set_key_representation(proto::HOST);
+  hint->set_key(url.host());
+  page_hint = hint->add_page_hints();
+  page_hint->set_page_pattern("page/*");
+
+  std::unique_ptr<base::RunLoop> run_loop = std::make_unique<base::RunLoop>();
+  hints_manager()->hint_cache()->UpdateFetchedHints(
+      std::move(get_hints_response), base::Time().Now(), {url.host()}, {url},
+      run_loop->QuitClosure());
+  run_loop->Run();
+
+  EXPECT_TRUE(hints_manager()->hint_cache()->HasHint(url.host()));
+  EXPECT_TRUE(hints_manager()->hint_cache()->HasURLKeyedEntryForURL(url));
+
+  proto::HintNotificationPayload notification;
+  notification.set_optimization_type(
+      proto::OptimizationType::PERFORMANCE_HINTS);
+  notification.set_key_representation(proto::KeyRepresentation::FULL_URL);
+  notification.set_hint_key(url.spec());
+
+  PushNotificationJava(notification);
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_FALSE(hints_manager()->hint_cache()->HasHint(url.host()));
+  EXPECT_FALSE(hints_manager()->hint_cache()->HasURLKeyedEntryForURL(url));
+}
+
+TEST_F(AndroidPushNotificationManagerJavaTest, PushedJava_Host_SuccessCase) {
+  // Pre-populate the store with some hints.
+  int cache_duration_in_secs = 60;
+  GURL url("https://host.com/r/cats");
+
+  std::unique_ptr<proto::GetHintsResponse> get_hints_response =
+      std::make_unique<proto::GetHintsResponse>();
+
+  proto::Hint* hint = get_hints_response->add_hints();
+  hint->set_key(url.spec());
+  hint->set_key_representation(proto::FULL_URL);
+  hint->mutable_max_cache_duration()->set_seconds(cache_duration_in_secs);
+  proto::PageHint* page_hint = hint->add_page_hints();
+  page_hint->add_whitelisted_optimizations()->set_optimization_type(
+      proto::PERFORMANCE_HINTS);
+  page_hint->set_page_pattern("whatever/*");
+
+  hint = get_hints_response->add_hints();
+  hint->set_key_representation(proto::HOST);
+  hint->set_key(url.host());
+  page_hint = hint->add_page_hints();
+  page_hint->set_page_pattern("page/*");
+
+  std::unique_ptr<base::RunLoop> run_loop = std::make_unique<base::RunLoop>();
+  hints_manager()->hint_cache()->UpdateFetchedHints(
+      std::move(get_hints_response), base::Time().Now(), {url.host()}, {url},
+      run_loop->QuitClosure());
+  run_loop->Run();
+
+  EXPECT_TRUE(hints_manager()->hint_cache()->HasHint(url.host()));
+  EXPECT_TRUE(hints_manager()->hint_cache()->HasURLKeyedEntryForURL(url));
+
+  proto::HintNotificationPayload notification;
+  notification.set_optimization_type(
+      proto::OptimizationType::PERFORMANCE_HINTS);
+  notification.set_key_representation(proto::KeyRepresentation::HOST);
+  notification.set_hint_key(url.host());
+
+  PushNotificationJava(notification);
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_FALSE(hints_manager()->hint_cache()->HasHint(url.host()));
+  EXPECT_TRUE(hints_manager()->hint_cache()->HasURLKeyedEntryForURL(url));
+}
+
+TEST_F(AndroidPushNotificationManagerJavaTest,
+       Pushed_KeyRepresentationRequired) {
+  // Pre-populate the store with some hints.
+  int cache_duration_in_secs = 60;
+  GURL url("https://host.com/r/cats");
+
+  std::unique_ptr<proto::GetHintsResponse> get_hints_response =
+      std::make_unique<proto::GetHintsResponse>();
+
+  proto::Hint* hint = get_hints_response->add_hints();
+  hint->set_key(url.spec());
+  hint->set_key_representation(proto::FULL_URL);
+  hint->mutable_max_cache_duration()->set_seconds(cache_duration_in_secs);
+  proto::PageHint* page_hint = hint->add_page_hints();
+  page_hint->add_whitelisted_optimizations()->set_optimization_type(
+      proto::PERFORMANCE_HINTS);
+  page_hint->set_page_pattern("whatever/*");
+
+  hint = get_hints_response->add_hints();
+  hint->set_key_representation(proto::HOST);
+  hint->set_key(url.host());
+  page_hint = hint->add_page_hints();
+  page_hint->set_page_pattern("page/*");
+
+  std::unique_ptr<base::RunLoop> run_loop = std::make_unique<base::RunLoop>();
+  hints_manager()->hint_cache()->UpdateFetchedHints(
+      std::move(get_hints_response), base::Time().Now(), {url.host()}, {url},
+      run_loop->QuitClosure());
+  run_loop->Run();
+
+  EXPECT_TRUE(hints_manager()->hint_cache()->HasHint(url.host()));
+  EXPECT_TRUE(hints_manager()->hint_cache()->HasURLKeyedEntryForURL(url));
+
+  proto::HintNotificationPayload notification;
+  notification.set_optimization_type(
+      proto::OptimizationType::PERFORMANCE_HINTS);
+  notification.set_hint_key(url.spec());
+
+  PushNotificationNative(notification);
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_TRUE(hints_manager()->hint_cache()->HasHint(url.host()));
+  EXPECT_TRUE(hints_manager()->hint_cache()->HasURLKeyedEntryForURL(url));
+}
+
+TEST_F(AndroidPushNotificationManagerJavaTest,
+       Pushed_OptimizationTypeNotRequired) {
+  // Pre-populate the store with some hints.
+  int cache_duration_in_secs = 60;
+  GURL url("https://host.com/r/cats");
+
+  std::unique_ptr<proto::GetHintsResponse> get_hints_response =
+      std::make_unique<proto::GetHintsResponse>();
+
+  proto::Hint* hint = get_hints_response->add_hints();
+  hint->set_key(url.spec());
+  hint->set_key_representation(proto::FULL_URL);
+  hint->mutable_max_cache_duration()->set_seconds(cache_duration_in_secs);
+  proto::PageHint* page_hint = hint->add_page_hints();
+  page_hint->add_whitelisted_optimizations()->set_optimization_type(
+      proto::PERFORMANCE_HINTS);
+  page_hint->set_page_pattern("whatever/*");
+
+  hint = get_hints_response->add_hints();
+  hint->set_key_representation(proto::HOST);
+  hint->set_key(url.host());
+  page_hint = hint->add_page_hints();
+  page_hint->set_page_pattern("page/*");
+
+  std::unique_ptr<base::RunLoop> run_loop = std::make_unique<base::RunLoop>();
+  hints_manager()->hint_cache()->UpdateFetchedHints(
+      std::move(get_hints_response), base::Time().Now(), {url.host()}, {url},
+      run_loop->QuitClosure());
+  run_loop->Run();
+
+  EXPECT_TRUE(hints_manager()->hint_cache()->HasHint(url.host()));
+  EXPECT_TRUE(hints_manager()->hint_cache()->HasURLKeyedEntryForURL(url));
+
+  proto::HintNotificationPayload notification;
+  notification.set_key_representation(proto::KeyRepresentation::FULL_URL);
+  notification.set_hint_key(url.spec());
+
+  PushNotificationNative(notification);
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_FALSE(hints_manager()->hint_cache()->HasHint(url.host()));
+  EXPECT_FALSE(hints_manager()->hint_cache()->HasURLKeyedEntryForURL(url));
+}
+
+TEST_F(AndroidPushNotificationManagerJavaTest, Pushed_HintKeyRequired) {
+  // Pre-populate the store with some hints.
+  int cache_duration_in_secs = 60;
+  GURL url("https://host.com/r/cats");
+
+  std::unique_ptr<proto::GetHintsResponse> get_hints_response =
+      std::make_unique<proto::GetHintsResponse>();
+
+  proto::Hint* hint = get_hints_response->add_hints();
+  hint->set_key(url.spec());
+  hint->set_key_representation(proto::FULL_URL);
+  hint->mutable_max_cache_duration()->set_seconds(cache_duration_in_secs);
+  proto::PageHint* page_hint = hint->add_page_hints();
+  page_hint->add_whitelisted_optimizations()->set_optimization_type(
+      proto::PERFORMANCE_HINTS);
+  page_hint->set_page_pattern("whatever/*");
+
+  hint = get_hints_response->add_hints();
+  hint->set_key_representation(proto::HOST);
+  hint->set_key(url.host());
+  page_hint = hint->add_page_hints();
+  page_hint->set_page_pattern("page/*");
+
+  std::unique_ptr<base::RunLoop> run_loop = std::make_unique<base::RunLoop>();
+  hints_manager()->hint_cache()->UpdateFetchedHints(
+      std::move(get_hints_response), base::Time().Now(), {url.host()}, {url},
+      run_loop->QuitClosure());
+  run_loop->Run();
+
+  EXPECT_TRUE(hints_manager()->hint_cache()->HasHint(url.host()));
+  EXPECT_TRUE(hints_manager()->hint_cache()->HasURLKeyedEntryForURL(url));
+
+  proto::HintNotificationPayload notification;
+  notification.set_optimization_type(
+      proto::OptimizationType::PERFORMANCE_HINTS);
+  notification.set_key_representation(proto::KeyRepresentation::FULL_URL);
+
+  PushNotificationNative(notification);
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_TRUE(hints_manager()->hint_cache()->HasHint(url.host()));
+  EXPECT_TRUE(hints_manager()->hint_cache()->HasURLKeyedEntryForURL(url));
+}
+
 }  // namespace android
 }  // namespace optimization_guide
diff --git a/chrome/browser/optimization_guide/android/java/src/org/chromium/chrome/browser/optimization_guide/OptimizationGuideBridge.java b/chrome/browser/optimization_guide/android/java/src/org/chromium/chrome/browser/optimization_guide/OptimizationGuideBridge.java
index f0ba493b..08789e2 100644
--- a/chrome/browser/optimization_guide/android/java/src/org/chromium/chrome/browser/optimization_guide/OptimizationGuideBridge.java
+++ b/chrome/browser/optimization_guide/android/java/src/org/chromium/chrome/browser/optimization_guide/OptimizationGuideBridge.java
@@ -116,6 +116,17 @@
                 mNativeOptimizationGuideBridge, url, optimizationType.getNumber(), callback);
     }
 
+    public void onNewPushNotification(HintNotificationPayload notification) {
+        ThreadUtils.assertOnUiThread();
+        if (mNativeOptimizationGuideBridge == 0) {
+            OptimizationGuidePushNotificationManager.onPushNotificationNotHandledByNative(
+                    notification);
+            return;
+        }
+        OptimizationGuideBridgeJni.get().onNewPushNotification(
+                mNativeOptimizationGuideBridge, notification.toByteArray());
+    }
+
     @CalledByNative
     private static void onOptimizationGuideDecision(OptimizationGuideCallback callback,
             @OptimizationGuideDecision int optimizationGuideDecision,
diff --git a/chrome/browser/optimization_guide/android/java/src/org/chromium/chrome/browser/optimization_guide/OptimizationGuideBridgeFactory.java b/chrome/browser/optimization_guide/android/java/src/org/chromium/chrome/browser/optimization_guide/OptimizationGuideBridgeFactory.java
index f7bec5e8..1c90ce04 100644
--- a/chrome/browser/optimization_guide/android/java/src/org/chromium/chrome/browser/optimization_guide/OptimizationGuideBridgeFactory.java
+++ b/chrome/browser/optimization_guide/android/java/src/org/chromium/chrome/browser/optimization_guide/OptimizationGuideBridgeFactory.java
@@ -10,6 +10,7 @@
 import org.chromium.chrome.browser.profiles.ProfileManager;
 import org.chromium.components.optimization_guide.proto.HintsProto;
 
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -25,6 +26,13 @@
     private ProfileManager.Observer mProfileManagerObserver;
 
     /**
+     * Creates an instance of this class with no observed optimization types.
+     */
+    public OptimizationGuideBridgeFactory() {
+        this(new ArrayList<HintsProto.OptimizationType>());
+    }
+
+    /**
      * @param optimizationTypes list of {@link HintsProto.OptimizationType} the {@link
      * OptimizationGuideBridge} is initialized with.
      */
@@ -56,7 +64,9 @@
                 mProfileToOptimizationGuideBridgeMap.get(profile);
         if (optimizationGuideBridge == null) {
             optimizationGuideBridge = new OptimizationGuideBridge();
-            optimizationGuideBridge.registerOptimizationTypes(mOptimizationTypes);
+            if (mOptimizationTypes.size() > 0) {
+                optimizationGuideBridge.registerOptimizationTypes(mOptimizationTypes);
+            }
             mProfileToOptimizationGuideBridgeMap.put(profile, optimizationGuideBridge);
         }
         return optimizationGuideBridge;
diff --git a/chrome/browser/optimization_guide/android/java/src/org/chromium/chrome/browser/optimization_guide/OptimizationGuidePushNotificationManager.java b/chrome/browser/optimization_guide/android/java/src/org/chromium/chrome/browser/optimization_guide/OptimizationGuidePushNotificationManager.java
index cc1a276..99c2d8a 100644
--- a/chrome/browser/optimization_guide/android/java/src/org/chromium/chrome/browser/optimization_guide/OptimizationGuidePushNotificationManager.java
+++ b/chrome/browser/optimization_guide/android/java/src/org/chromium/chrome/browser/optimization_guide/OptimizationGuidePushNotificationManager.java
@@ -29,6 +29,7 @@
  */
 public class OptimizationGuidePushNotificationManager {
     private static Boolean sNativeIsInitialized;
+    private static OptimizationGuideBridgeFactory sBridgeFactory;
 
     // All logic here is static, so no instances of this class are needed.
     private OptimizationGuidePushNotificationManager() {}
@@ -54,7 +55,10 @@
         }
 
         if (nativeIsInitialized()) {
-            // TODO(crbug/1199123): Push the notification to native.
+            if (sBridgeFactory == null) {
+                sBridgeFactory = new OptimizationGuideBridgeFactory();
+            }
+            sBridgeFactory.create().onNewPushNotification(payload);
             return;
         }
 
diff --git a/chrome/browser/optimization_guide/android/java/src/org/chromium/chrome/browser/optimization_guide/OptimizationGuidePushNotificationTestHelper.java b/chrome/browser/optimization_guide/android/java/src/org/chromium/chrome/browser/optimization_guide/OptimizationGuidePushNotificationTestHelper.java
index 8b33e78..9e444ab 100644
--- a/chrome/browser/optimization_guide/android/java/src/org/chromium/chrome/browser/optimization_guide/OptimizationGuidePushNotificationTestHelper.java
+++ b/chrome/browser/optimization_guide/android/java/src/org/chromium/chrome/browser/optimization_guide/OptimizationGuidePushNotificationTestHelper.java
@@ -4,7 +4,13 @@
 
 package org.chromium.chrome.browser.optimization_guide;
 
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
 import org.chromium.base.annotations.CalledByNative;
+import org.chromium.chrome.browser.flags.CachedFeatureFlags;
+import org.chromium.chrome.browser.flags.ChromeFeatureList;
+import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.components.optimization_guide.proto.HintsProto.OptimizationType;
 import org.chromium.components.optimization_guide.proto.PushNotificationProto.HintNotificationPayload;
 
@@ -12,6 +18,18 @@
  * Unit test helper for OptimizationGuidePushNotificationManager.
  */
 public class OptimizationGuidePushNotificationTestHelper {
+    @Mock
+    private Profile mProfile;
+
+    @CalledByNative
+    private OptimizationGuidePushNotificationTestHelper() {}
+
+    @CalledByNative
+    public void setUpMocks() {
+        MockitoAnnotations.initMocks(this);
+        Profile.setLastUsedProfileForTesting(mProfile);
+    }
+
     @CalledByNative
     public static boolean cacheNotification(byte[] encodedNotification) {
         HintNotificationPayload notification;
@@ -27,18 +45,6 @@
     }
 
     @CalledByNative
-    public static boolean pushNotification(byte[] encodedNotification) {
-        HintNotificationPayload notification;
-        try {
-            notification = HintNotificationPayload.parseFrom(encodedNotification);
-            OptimizationGuidePushNotificationManager.onPushNotification(notification);
-        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
-            return false;
-        }
-        return true;
-    }
-
-    @CalledByNative
     public static int countCachedNotifications(int optType) {
         HintNotificationPayload[] payloads =
                 OptimizationGuidePushNotificationManager.getNotificationCacheForOptimizationType(
@@ -62,4 +68,23 @@
     public static void clearAllCaches() {
         OptimizationGuidePushNotificationManager.clearCacheForAllTypes();
     }
+
+    @CalledByNative
+    public static void setFeatureEnabled() {
+        CachedFeatureFlags.setForTesting(
+                ChromeFeatureList.OPTIMIZATION_GUIDE_PUSH_NOTIFICATIONS, true);
+    }
+
+    @CalledByNative
+    public static boolean pushNotification(byte[] encodedNotification) {
+        HintNotificationPayload notification;
+        try {
+            notification = HintNotificationPayload.parseFrom(encodedNotification);
+            OptimizationGuidePushNotificationManager.onPushNotification(notification);
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+            return false;
+        }
+
+        return true;
+    }
 }
diff --git a/chrome/browser/optimization_guide/android/javatests/src/org/chromium/chrome/browser/optimization_guide/OptimizationGuidePushNotificationManagerUnitTest.java b/chrome/browser/optimization_guide/android/javatests/src/org/chromium/chrome/browser/optimization_guide/OptimizationGuidePushNotificationManagerUnitTest.java
index 222fca8..acc5661 100644
--- a/chrome/browser/optimization_guide/android/javatests/src/org/chromium/chrome/browser/optimization_guide/OptimizationGuidePushNotificationManagerUnitTest.java
+++ b/chrome/browser/optimization_guide/android/javatests/src/org/chromium/chrome/browser/optimization_guide/OptimizationGuidePushNotificationManagerUnitTest.java
@@ -4,6 +4,12 @@
 
 package org.chromium.chrome.browser.optimization_guide;
 
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
 import androidx.test.filters.SmallTest;
 
 import com.google.protobuf.ByteString;
@@ -11,14 +17,20 @@
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 
 import org.chromium.base.FeatureList;
 import org.chromium.base.test.BaseJUnit4ClassRunner;
+import org.chromium.base.test.UiThreadTest;
 import org.chromium.base.test.util.Batch;
+import org.chromium.base.test.util.JniMocker;
 import org.chromium.chrome.browser.flags.CachedFeatureFlags;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
+import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.components.optimization_guide.proto.CommonTypesProto.Any;
 import org.chromium.components.optimization_guide.proto.HintsProto.KeyRepresentation;
 import org.chromium.components.optimization_guide.proto.HintsProto.OptimizationType;
@@ -34,6 +46,15 @@
 // Batch this per class since the test is setting global feature state.
 @Batch(Batch.PER_CLASS)
 public class OptimizationGuidePushNotificationManagerUnitTest {
+    @Rule
+    public JniMocker mocker = new JniMocker();
+
+    @Mock
+    private Profile mProfile;
+
+    @Mock
+    OptimizationGuideBridge.Natives mOptimizationGuideBridgeJniMock;
+
     private static final String TEST_URL = "https://testurl.com/";
 
     private static final HintNotificationPayload NOTIFICATION_WITH_PAYLOAD =
@@ -60,8 +81,22 @@
     }
 
     @Before
+    public void setUp() {
+        resetFeatureFlags();
+
+        MockitoAnnotations.initMocks(this);
+        mocker.mock(OptimizationGuideBridgeJni.TEST_HOOKS, mOptimizationGuideBridgeJniMock);
+        when(mOptimizationGuideBridgeJniMock.init()).thenReturn(1L);
+
+        Profile.setLastUsedProfileForTesting(mProfile);
+    }
+
     @After
-    public void reset() {
+    public void tearDown() {
+        resetFeatureFlags();
+    }
+
+    public void resetFeatureFlags() {
         CachedFeatureFlags.resetFlagsForTesting();
         OptimizationGuidePushNotificationManager.clearCacheForAllTypes();
         OptimizationGuidePushNotificationManager.setNativeIsInitializedForTesting(null);
@@ -102,6 +137,25 @@
 
     @Test
     @SmallTest
+    @UiThreadTest
+    public void testNativeCalled() {
+        setFeatureStatusForTest(true);
+        OptimizationGuidePushNotificationManager.setNativeIsInitializedForTesting(true);
+
+        OptimizationGuidePushNotificationManager.onPushNotification(NOTIFICATION_WITHOUT_PAYLOAD);
+
+        HintNotificationPayload[] cached =
+                OptimizationGuidePushNotificationManager.getNotificationCacheForOptimizationType(
+                        OptimizationType.PERFORMANCE_HINTS);
+        Assert.assertNotNull(cached);
+        Assert.assertEquals(0, cached.length);
+
+        verify(mOptimizationGuideBridgeJniMock, times(1))
+                .onNewPushNotification(anyLong(), eq(NOTIFICATION_WITHOUT_PAYLOAD.toByteArray()));
+    }
+
+    @Test
+    @SmallTest
     public void testFeatureDisabled() {
         setFeatureStatusForTest(false);
         OptimizationGuidePushNotificationManager.setNativeIsInitializedForTesting(false);
diff --git a/chrome/browser/optimization_guide/android/optimization_guide_bridge.cc b/chrome/browser/optimization_guide/android/optimization_guide_bridge.cc
index 11df0e72..32afba97 100644
--- a/chrome/browser/optimization_guide/android/optimization_guide_bridge.cc
+++ b/chrome/browser/optimization_guide/android/optimization_guide_bridge.cc
@@ -191,7 +191,7 @@
 
 void OptimizationGuideBridge::OnNewPushNotification(
     JNIEnv* env,
-    const JavaParamRef<jbyteArray>& j_encoded_notification) {
+    const JavaRef<jbyteArray>& j_encoded_notification) {
   if (!j_encoded_notification)
     return;
 
diff --git a/chrome/browser/optimization_guide/android/optimization_guide_bridge.h b/chrome/browser/optimization_guide/android/optimization_guide_bridge.h
index 6bdd53e6..4fa5e7f3 100644
--- a/chrome/browser/optimization_guide/android/optimization_guide_bridge.h
+++ b/chrome/browser/optimization_guide/android/optimization_guide_bridge.h
@@ -43,7 +43,7 @@
       const base::android::JavaParamRef<jobject>& java_callback);
   void OnNewPushNotification(
       JNIEnv* env,
-      const base::android::JavaParamRef<jbyteArray>& j_encoded_notification);
+      const base::android::JavaRef<jbyteArray>& j_encoded_notification);
 
  private:
   OptimizationGuideKeyedService* optimization_guide_keyed_service_;
diff --git a/chrome/browser/optimization_guide/optimization_guide_keyed_service.h b/chrome/browser/optimization_guide/optimization_guide_keyed_service.h
index 0c42266..2ef9d45a7 100644
--- a/chrome/browser/optimization_guide/optimization_guide_keyed_service.h
+++ b/chrome/browser/optimization_guide/optimization_guide_keyed_service.h
@@ -26,6 +26,7 @@
 
 namespace optimization_guide {
 namespace android {
+class AndroidPushNotificationManagerJavaTest;
 class OptimizationGuideBridge;
 }  // namespace android
 class OptimizationGuideStore;
@@ -114,6 +115,8 @@
   friend class OptimizationGuideWebContentsObserver;
   friend class optimization_guide::PredictionModelDownloadClient;
   friend class optimization_guide::PredictionManagerBrowserTestBase;
+  friend class optimization_guide::android::
+      AndroidPushNotificationManagerJavaTest;
   friend class optimization_guide::android::OptimizationGuideBridge;
 
   // Initializes |this|.
diff --git a/chrome/browser/privacy_sandbox/privacy_sandbox_settings_browsertest.cc b/chrome/browser/privacy_sandbox/privacy_sandbox_settings_browsertest.cc
index 8b99606..760ac73 100644
--- a/chrome/browser/privacy_sandbox/privacy_sandbox_settings_browsertest.cc
+++ b/chrome/browser/privacy_sandbox/privacy_sandbox_settings_browsertest.cc
@@ -45,7 +45,8 @@
  public:
   PrivacySandboxSettingsBrowserTest() {
     feature_list()->InitWithFeatures(
-        {features::kPrivacySandboxSettings, features::kConversionMeasurement,
+        {features::kPrivacySandboxSettings,
+         blink::features::kConversionMeasurement,
          blink::features::kInterestCohortAPIOriginTrial},
         {});
   }
diff --git a/chrome/browser/privacy_sandbox/privacy_sandbox_settings_unittest.cc b/chrome/browser/privacy_sandbox/privacy_sandbox_settings_unittest.cc
index b854027..08d0ceec 100644
--- a/chrome/browser/privacy_sandbox/privacy_sandbox_settings_unittest.cc
+++ b/chrome/browser/privacy_sandbox/privacy_sandbox_settings_unittest.cc
@@ -161,7 +161,8 @@
     feature_list()->Reset();
     if (privacy_sandbox_available) {
       feature_list()->InitWithFeatures(
-          {features::kPrivacySandboxSettings, features::kConversionMeasurement,
+          {features::kPrivacySandboxSettings,
+           blink::features::kConversionMeasurement,
            blink::features::kInterestCohortAPIOriginTrial},
           {});
     } else {
@@ -198,7 +199,7 @@
 
 TEST_F(PrivacySandboxSettingsTest, PrivacySandboxSettingsFunctional) {
   feature_list()->InitWithFeatures(
-      {features::kConversionMeasurement,
+      {blink::features::kConversionMeasurement,
        blink::features::kInterestCohortAPIOriginTrial},
       {features::kPrivacySandboxSettings});
   EXPECT_FALSE(privacy_sandbox_settings()->PrivacySandboxSettingsFunctional());
@@ -206,7 +207,7 @@
 
   feature_list()->InitWithFeatures(
       {features::kPrivacySandboxSettings},
-      {features::kConversionMeasurement,
+      {blink::features::kConversionMeasurement,
        blink::features::kInterestCohortAPIOriginTrial});
   EXPECT_TRUE(privacy_sandbox_settings()->PrivacySandboxSettingsFunctional());
 }
diff --git a/chrome/browser/resources/chromeos/accessibility/switch_access/item_scan_manager.js b/chrome/browser/resources/chromeos/accessibility/switch_access/item_scan_manager.js
index 9af930f6..9b7df388 100644
--- a/chrome/browser/resources/chromeos/accessibility/switch_access/item_scan_manager.js
+++ b/chrome/browser/resources/chromeos/accessibility/switch_access/item_scan_manager.js
@@ -255,6 +255,7 @@
       // Clearing the focus rings avoids having them re-animate to the same
       // position.
       FocusRingManager.clearAll();
+      this.history_.save(new FocusData(this.group_, this.node_));
       this.loadFromData_(this.suspendedGroup_);
     }
   }
diff --git a/chrome/browser/resources/download_shelf/BUILD.gn b/chrome/browser/resources/download_shelf/BUILD.gn
index 7761ea3..0e63e05 100644
--- a/chrome/browser/resources/download_shelf/BUILD.gn
+++ b/chrome/browser/resources/download_shelf/BUILD.gn
@@ -164,6 +164,7 @@
     "//ui/webui/resources/cr_elements/cr_icon_button:cr_icon_button.m",
     "//ui/webui/resources/js:custom_element",
     "//ui/webui/resources/js:icon.m",
+    "//ui/webui/resources/js:load_time_data.m",
   ]
 }
 
diff --git a/chrome/browser/resources/download_shelf/app.html b/chrome/browser/resources/download_shelf/app.html
index e0f6226..c96bea3d 100644
--- a/chrome/browser/resources/download_shelf/app.html
+++ b/chrome/browser/resources/download_shelf/app.html
@@ -23,7 +23,6 @@
 
 </style>
 <download-list></download-list>
-<cr-button id="show-all-button">$i18n{showAll}</cr-button>
-<cr-icon-button id="close-button" iron-icon="cr:close"
-    aria-label="$i18n{close}">
+<cr-button id="show-all-button"></cr-button>
+<cr-icon-button id="close-button" iron-icon="cr:close">
 </cr-icon-button>
diff --git a/chrome/browser/resources/download_shelf/app.js b/chrome/browser/resources/download_shelf/app.js
index 7dbf78e..81489e8 100644
--- a/chrome/browser/resources/download_shelf/app.js
+++ b/chrome/browser/resources/download_shelf/app.js
@@ -11,6 +11,7 @@
 import './strings.m.js';
 
 import {CustomElement} from 'chrome://resources/js/custom_element.js';
+import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
 import {DownloadShelfApiProxy, DownloadShelfApiProxyImpl} from './download_shelf_api_proxy.js';
 
 export class DownloadShelfAppElement extends CustomElement {
@@ -24,9 +25,13 @@
     /** @private {!DownloadShelfApiProxy} */
     this.apiProxy_ = DownloadShelfApiProxyImpl.getInstance();
 
-    this.$('#show-all-button')
-        .addEventListener('click', e => this.onShowAll_());
-    this.$('#close-button').addEventListener('click', e => this.onClose_());
+    const showAllButton = this.$('#show-all-button');
+    showAllButton.innerText = loadTimeData.getString('showAll');
+    showAllButton.addEventListener('click', e => this.onShowAll_());
+
+    const closeButton = this.$('#close-button');
+    closeButton.setAttribute('aria-label', loadTimeData.getString('close'));
+    closeButton.addEventListener('click', e => this.onClose_());
   }
 
   /** @private */
diff --git a/chrome/browser/resources/new_tab_page/realbox/realbox_match.html b/chrome/browser/resources/new_tab_page/realbox/realbox_match.html
index 4dd4fa5c..65e2d091 100644
--- a/chrome/browser/resources/new_tab_page/realbox/realbox_match.html
+++ b/chrome/browser/resources/new_tab_page/realbox/realbox_match.html
@@ -78,6 +78,7 @@
     --cr-icon-button-margin-end: 0;
     --cr-icon-button-margin-start: 0;
     --cr-icon-button-size: 24px;
+    margin-inline-end: 1px;
     opacity: 0; /* Hides the button while keeping it in tab order. */
   }
 
diff --git a/chrome/browser/resources/settings/a11y_page/captions_subpage.html b/chrome/browser/resources/settings/a11y_page/captions_subpage.html
index a9076161..86ef0284 100644
--- a/chrome/browser/resources/settings/a11y_page/captions_subpage.html
+++ b/chrome/browser/resources/settings/a11y_page/captions_subpage.html
@@ -1,7 +1,11 @@
     <style include="cr-shared-style settings-shared">
+      .cr-row-no-top-gap {
+        margin-bottom: var(--cr-section-vertical-margin);
+        min-height: auto;
+      }
       .preview-box {
-        all: initial;
         align-items: center;
+        all: initial;
         background-image:
           url(chrome://theme/IDR_ACCESSIBILITY_CAPTIONS_PREVIEW_BACKGROUND);
         background-position: center;
@@ -21,7 +25,7 @@
     <div class="cr-row">
       <h2 class="start">$i18n{captionsPreferencesTitle}</h2>
     </div>
-    <div class="cr-row first">
+    <div class="cr-row first cr-row-no-top-gap">
       <div class="start">$i18n{captionsPreferencesSubtitle}</div>
     </div>
     <div class="preview-box">
diff --git a/chrome/browser/resources/settings/chromeos/internet_page/network_summary_item.html b/chrome/browser/resources/settings/chromeos/internet_page/network_summary_item.html
index 5fc0a9a..f226563d 100644
--- a/chrome/browser/resources/settings/chromeos/internet_page/network_summary_item.html
+++ b/chrome/browser/resources/settings/chromeos/internet_page/network_summary_item.html
@@ -43,7 +43,7 @@
     <div class="settings-box two-line no-padding">
       <div actionable$="[[isItemActionable_(activeNetworkState,
                             deviceState, networkStateList)]]"
-           class="flex layout horizontal center link-wrapper"
+          class="flex layout horizontal center link-wrapper"
           on-click="onShowDetailsTap_">
         <div id="details" no-flex$="[[showSimInfo_(deviceState)]]"
              aria-hidden="true">
diff --git a/chrome/browser/resources/settings/chromeos/internet_page/network_summary_item.js b/chrome/browser/resources/settings/chromeos/internet_page/network_summary_item.js
index a244b9a..d52d266 100644
--- a/chrome/browser/resources/settings/chromeos/internet_page/network_summary_item.js
+++ b/chrome/browser/resources/settings/chromeos/internet_page/network_summary_item.js
@@ -478,10 +478,10 @@
           return true;
         }
         // When network type is Cellular and |updatedCellularActivationUi| is
-        // enabled, always show "Mobile data" subpage, when eSim is available
-        // or multiple pSimSlots are available
+        // enabled, always show "Mobile data" subpage, when at least one eSIM
+        // or pSIM slot is available
         const {pSimSlots, eSimSlots} = getSimSlotCount(deviceState);
-        if (eSimSlots > 0 || pSimSlots > 1) {
+        if (eSimSlots > 0 || pSimSlots > 0) {
           return true;
         }
       } else if (this.simLockedOrAbsent_(deviceState)) {
diff --git a/chrome/browser/sharing_hub/sharing_hub_model.cc b/chrome/browser/sharing_hub/sharing_hub_model.cc
index 26cd1fb..0436df32 100644
--- a/chrome/browser/sharing_hub/sharing_hub_model.cc
+++ b/chrome/browser/sharing_hub/sharing_hub_model.cc
@@ -4,15 +4,20 @@
 
 #include "chrome/browser/sharing_hub/sharing_hub_model.h"
 
+#include "base/strings/utf_string_conversions.h"
 #include "chrome/app/chrome_command_ids.h"
 #include "chrome/app/vector_icons/vector_icons.h"
 #include "chrome/browser/media/router/media_router_feature.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/send_tab_to_self/send_tab_to_self_util.h"
+#include "chrome/browser/ui/browser_navigator.h"
+#include "chrome/browser/ui/browser_navigator_params.h"
 #include "chrome/browser/ui/qrcode_generator/qrcode_generator_bubble_controller.h"
 #include "chrome/grit/generated_resources.h"
 #include "components/vector_icons/vector_icons.h"
 #include "content/public/browser/browser_context.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "url/gurl.h"
 
 namespace sharing_hub {
 
@@ -24,9 +29,10 @@
 
 SharingHubModel::~SharingHubModel() = default;
 
-void SharingHubModel::GetActionList(content::WebContents* web_contents,
-                                    std::vector<SharingHubAction>* list) {
-  for (const auto& action : action_list_) {
+void SharingHubModel::GetFirstPartyActionList(
+    content::WebContents* web_contents,
+    std::vector<SharingHubAction>* list) {
+  for (const auto& action : first_party_action_list_) {
     if (action.command_id == IDC_SEND_TAB_TO_SELF) {
       if (DoShowSendTabToSelfForWebContents(web_contents)) {
         list->push_back(action);
@@ -42,29 +48,66 @@
   }
 }
 
+void SharingHubModel::GetThirdPartyActionList(
+    content::WebContents* web_contents,
+    std::vector<SharingHubAction>* list) {
+  for (const auto& action : third_party_action_list_) {
+    list->push_back(action);
+  }
+}
+
+void SharingHubModel::ExecuteThirdPartyAction(Profile* profile, int id) {
+  auto url_it = third_party_action_urls_.find(id);
+  if (url_it == third_party_action_urls_.end())
+    return;
+
+  NavigateParams params(profile, url_it->second, ui::PAGE_TRANSITION_LINK);
+  params.disposition = WindowOpenDisposition::NEW_FOREGROUND_TAB;
+  params.tabstrip_add_types = TabStripModel::ADD_ACTIVE;
+  Navigate(&params);
+}
+
 void SharingHubModel::PopulateFirstPartyActions() {
-  action_list_.push_back(
-      {IDC_COPY_URL, IDS_SHARING_HUB_COPY_LINK_LABEL, kCopyIcon, true});
+  first_party_action_list_.push_back(
+      {IDC_COPY_URL, l10n_util::GetStringUTF16(IDS_SHARING_HUB_COPY_LINK_LABEL),
+       kCopyIcon, true});
 
-  action_list_.push_back({IDC_QRCODE_GENERATOR,
-                          IDS_OMNIBOX_QRCODE_GENERATOR_ICON_LABEL,
-                          kQrcodeGeneratorIcon, true});
+  first_party_action_list_.push_back(
+      {IDC_QRCODE_GENERATOR,
+       l10n_util::GetStringUTF16(IDS_OMNIBOX_QRCODE_GENERATOR_ICON_LABEL),
+       kQrcodeGeneratorIcon, true});
 
-  action_list_.push_back({IDC_SEND_TAB_TO_SELF,
-                          IDS_CONTEXT_MENU_SEND_TAB_TO_SELF, kSendTabToSelfIcon,
-                          true});
+  first_party_action_list_.push_back(
+      {IDC_SEND_TAB_TO_SELF,
+       l10n_util::GetStringUTF16(IDS_CONTEXT_MENU_SEND_TAB_TO_SELF),
+       kSendTabToSelfIcon, true});
 
-  action_list_.push_back(
-      {IDC_SAVE_PAGE, IDS_SHARING_HUB_SAVE_PAGE_LABEL, kSavePageIcon, true});
+  first_party_action_list_.push_back(
+      {IDC_SAVE_PAGE,
+       l10n_util::GetStringUTF16(IDS_SHARING_HUB_SAVE_PAGE_LABEL),
+       kSavePageIcon, true});
 
   if (media_router::MediaRouterEnabled(context_)) {
-    action_list_.push_back({IDC_ROUTE_MEDIA, IDS_SHARING_HUB_MEDIA_ROUTER_LABEL,
-                            vector_icons::kMediaRouterIdleIcon, true});
+    first_party_action_list_.push_back(
+        {IDC_ROUTE_MEDIA,
+         l10n_util::GetStringUTF16(IDS_SHARING_HUB_MEDIA_ROUTER_LABEL),
+         vector_icons::kMediaRouterIdleIcon, true});
   }
 }
 
 void SharingHubModel::PopulateThirdPartyActions() {
-  // TODO(1186833): add third party actions
+  // Note: The third party action id must be greater than 0, otherwise the
+  // action will be disabled in the app menu.
+  // TODO(1186833): Replace with actual 3P data.
+  std::string title = "title";
+  third_party_action_list_.push_back(
+      {1, base::ASCIIToUTF16(title), kQrcodeGeneratorIcon, false});
+  third_party_action_urls_[1] = GURL(u"https://www.google.com");
+
+  std::string title2 = "title2";
+  third_party_action_list_.push_back(
+      {2, base::ASCIIToUTF16(title2), kQrcodeGeneratorIcon, false});
+  third_party_action_urls_[2] = GURL(u"https://www.twitter.com");
 }
 
 bool SharingHubModel::DoShowSendTabToSelfForWebContents(
diff --git a/chrome/browser/sharing_hub/sharing_hub_model.h b/chrome/browser/sharing_hub/sharing_hub_model.h
index b2792d2..76bfbf0 100644
--- a/chrome/browser/sharing_hub/sharing_hub_model.h
+++ b/chrome/browser/sharing_hub/sharing_hub_model.h
@@ -5,10 +5,15 @@
 #ifndef CHROME_BROWSER_SHARING_HUB_SHARING_HUB_MODEL_H_
 #define CHROME_BROWSER_SHARING_HUB_SHARING_HUB_MODEL_H_
 
+#include <map>
+#include <string>
 #include <vector>
 
 #include "base/macros.h"
 
+class GURL;
+class Profile;
+
 namespace content {
 class BrowserContext;
 class WebContents;
@@ -22,7 +27,7 @@
 
 struct SharingHubAction {
   int command_id;
-  int title;
+  std::u16string title;
   const gfx::VectorIcon& icon;
   bool is_first_party;
 };
@@ -35,11 +40,19 @@
   explicit SharingHubModel(content::BrowserContext* context);
   ~SharingHubModel();
 
-  // Populates the vector with Sharing Hub actions, ordered by appearance in the
-  // dialog. Some actions (i.e. send tab to self) may not be shown for some
-  // URLs.
-  void GetActionList(content::WebContents* web_contents,
-                     std::vector<SharingHubAction>* list);
+  // Populates the vector with first party Sharing Hub actions, ordered by
+  // appearance in the dialog. Some actions (i.e. send tab to self) may not be
+  // shown for some URLs.
+  void GetFirstPartyActionList(content::WebContents* web_contents,
+                               std::vector<SharingHubAction>* list);
+  // Populates the vector with third party Sharing Hub actions, ordered by
+  // appearance in the dialog.
+  void GetThirdPartyActionList(content::WebContents* web_contents,
+                               std::vector<SharingHubAction>* list);
+
+  // Executes the third party action indicated by |id|, i.e. opens a new tab to
+  // the corresponding webpage.
+  void ExecuteThirdPartyAction(Profile* profile, int id);
 
  private:
   void PopulateFirstPartyActions();
@@ -47,8 +60,13 @@
 
   bool DoShowSendTabToSelfForWebContents(content::WebContents* web_contents);
 
-  // A list of Sharing Hub actions in order in which they appear.
-  std::vector<SharingHubAction> action_list_;
+  // A list of Sharing Hub first party actions in order in which they appear.
+  std::vector<SharingHubAction> first_party_action_list_;
+  // A list of Sharing Hub third party actions in order in which they appear.
+  std::vector<SharingHubAction> third_party_action_list_;
+
+  // A list of third party action URLs mapped to action id.
+  std::map<int, GURL> third_party_action_urls_;
 
   content::BrowserContext* context_;
 
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings.grd b/chrome/browser/ui/android/strings/android_chrome_strings.grd
index 75416271..b3d44cde 100644
--- a/chrome/browser/ui/android/strings/android_chrome_strings.grd
+++ b/chrome/browser/ui/android/strings/android_chrome_strings.grd
@@ -2846,6 +2846,9 @@
       <message name="IDS_FOLLOW_MANAGE_FOLLOWING" desc="Header for Following Management activity.">
         Following
       </message>
+      <message name="IDS_FOLLOW_MANAGE_FOLLOWING_EMPTY_STATE" desc="Message for Following Management activity when there are no followed sites to show.">
+        You'll find sites you follow here
+      </message>
       <message name="IDS_FOLLOW_MANAGE_UPDATES_UNAVAILABLE" desc="Label this web feed source as unavailable so the user will not expect updates from it.">
         Updates Unavailable
       </message>
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_FOLLOW_MANAGE_FOLLOWING_EMPTY_STATE.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_FOLLOW_MANAGE_FOLLOWING_EMPTY_STATE.png.sha1
new file mode 100644
index 0000000..6b33c43
--- /dev/null
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_FOLLOW_MANAGE_FOLLOWING_EMPTY_STATE.png.sha1
@@ -0,0 +1 @@
+3de06623921cbf71a9d4feaf3f1683194560599f
\ No newline at end of file
diff --git a/chrome/browser/ui/ash/assistant/device_actions.cc b/chrome/browser/ui/ash/assistant/device_actions.cc
index 91d7f8e..ca21860 100644
--- a/chrome/browser/ui/ash/assistant/device_actions.cc
+++ b/chrome/browser/ui/ash/assistant/device_actions.cc
@@ -19,6 +19,7 @@
 #include "chrome/browser/ui/app_list/arc/arc_app_utils.h"
 #include "chromeos/dbus/power/power_manager_client.h"
 #include "chromeos/dbus/power_manager/backlight.pb.h"
+#include "chromeos/network/network_event_log.h"
 #include "chromeos/network/network_state_handler.h"
 #include "components/arc/arc_service_manager.h"
 #include "components/arc/mojom/intent_helper.mojom.h"
@@ -112,6 +113,7 @@
 DeviceActions::~DeviceActions() = default;
 
 void DeviceActions::SetWifiEnabled(bool enabled) {
+  NET_LOG(USER) << __func__ << ":" << enabled;
   NetworkHandler::Get()->network_state_handler()->SetTechnologyEnabled(
       NetworkTypePattern::WiFi(), enabled,
       chromeos::network_handler::ErrorCallback());
diff --git a/chrome/browser/ui/sharing_hub/sharing_hub_bubble_controller.cc b/chrome/browser/ui/sharing_hub/sharing_hub_bubble_controller.cc
index af88603..4e37311 100644
--- a/chrome/browser/ui/sharing_hub/sharing_hub_bubble_controller.cc
+++ b/chrome/browser/ui/sharing_hub/sharing_hub_bubble_controller.cc
@@ -117,15 +117,24 @@
   return true;
 }
 
-std::vector<SharingHubAction> SharingHubBubbleController::GetActions() const {
-  SharingHubService* const service =
-      SharingHubServiceFactory::GetForProfile(GetProfile());
-  SharingHubModel* const model =
-      service ? service->GetSharingHubModel() : nullptr;
-
+std::vector<SharingHubAction>
+SharingHubBubbleController::GetFirstPartyActions() {
   std::vector<SharingHubAction> actions;
+
+  SharingHubModel* model = GetSharingHubModel();
   if (model)
-    model->GetActionList(web_contents_, &actions);
+    model->GetFirstPartyActionList(web_contents_, &actions);
+
+  return actions;
+}
+
+std::vector<SharingHubAction>
+SharingHubBubbleController::GetThirdPartyActions() {
+  std::vector<SharingHubAction> actions;
+
+  SharingHubModel* model = GetSharingHubModel();
+  if (model)
+    model->GetThirdPartyActionList(web_contents_, &actions);
 
   return actions;
 }
@@ -140,7 +149,9 @@
   if (is_first_party) {
     chrome::ExecuteCommand(browser, command_id);
   } else {
-    // TODO(1186833): execute 3p action
+    SharingHubModel* model = GetSharingHubModel();
+    DCHECK(model);
+    model->ExecuteThirdPartyAction(GetProfile(), command_id);
   }
 }
 
@@ -148,6 +159,17 @@
   sharing_hub_bubble_view_ = nullptr;
 }
 
+SharingHubModel* SharingHubBubbleController::GetSharingHubModel() {
+  if (!sharing_hub_model_) {
+    SharingHubService* const service =
+        SharingHubServiceFactory::GetForProfile(GetProfile());
+    if (!service)
+      return nullptr;
+    sharing_hub_model_ = service->GetSharingHubModel();
+  }
+  return sharing_hub_model_;
+}
+
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 void SharingHubBubbleController::ShowSharesheet() {
   if (!base::FeatureList::IsEnabled(features::kSharesheet) ||
diff --git a/chrome/browser/ui/sharing_hub/sharing_hub_bubble_controller.h b/chrome/browser/ui/sharing_hub/sharing_hub_bubble_controller.h
index 215a1de..149f592 100644
--- a/chrome/browser/ui/sharing_hub/sharing_hub_bubble_controller.h
+++ b/chrome/browser/ui/sharing_hub/sharing_hub_bubble_controller.h
@@ -17,6 +17,7 @@
 namespace sharing_hub {
 
 class SharingHubBubbleView;
+class SharingHubModel;
 struct SharingHubAction;
 
 // Controller component of the Sharing Hub dialog bubble.
@@ -43,8 +44,10 @@
   // Returns true if the omnibox icon should be shown.
   bool ShouldOfferOmniboxIcon();
 
-  // Returns the list of Sharing Hub actions.
-  virtual std::vector<SharingHubAction> GetActions() const;
+  // Returns the list of Sharing Hub first party actions.
+  virtual std::vector<SharingHubAction> GetFirstPartyActions();
+  // Returns the list of Sharing Hub third party actions.
+  virtual std::vector<SharingHubAction> GetThirdPartyActions();
 
   // Handles when the user clicks on a Sharing Hub action. If this is a first
   // party action, executes the appropriate browser command. If this is a third
@@ -60,6 +63,8 @@
  private:
   friend class content::WebContentsUserData<SharingHubBubbleController>;
 
+  SharingHubModel* GetSharingHubModel();
+
 #if BUILDFLAG(IS_CHROMEOS_ASH)
   void ShowSharesheet();
   void OnSharesheetShown(sharesheet::SharesheetResult result);
@@ -69,6 +74,8 @@
   content::WebContents* web_contents_;
   // Weak reference. Will be nullptr if no bubble is currently shown.
   SharingHubBubbleView* sharing_hub_bubble_view_ = nullptr;
+  // Cached reference to the model.
+  SharingHubModel* sharing_hub_model_ = nullptr;
 
   WEB_CONTENTS_USER_DATA_KEY_DECL();
 
diff --git a/chrome/browser/ui/sharing_hub/sharing_hub_sub_menu_model.cc b/chrome/browser/ui/sharing_hub/sharing_hub_sub_menu_model.cc
index cb35d31..b0e331d6 100644
--- a/chrome/browser/ui/sharing_hub/sharing_hub_sub_menu_model.cc
+++ b/chrome/browser/ui/sharing_hub/sharing_hub_sub_menu_model.cc
@@ -21,33 +21,70 @@
 
 namespace sharing_hub {
 
-SharingHubSubMenuModel::SharingHubSubMenuModel(
-    ui::SimpleMenuModel::Delegate* delegate,
-    content::WebContents* web_contents)
-    : SimpleMenuModel(delegate) {
-  Build(web_contents);
+SharingHubSubMenuModel::SharingHubSubMenuModel(Browser* browser)
+    : SimpleMenuModel(this), browser_(browser) {
+  Build(browser_->tab_strip_model()->GetActiveWebContents());
+}
+
+SharingHubSubMenuModel::~SharingHubSubMenuModel() = default;
+
+bool SharingHubSubMenuModel::IsCommandIdEnabled(int command_id) const {
+  return true;
+}
+
+void SharingHubSubMenuModel::ExecuteCommand(int command_id, int event_flags) {
+  if (IsThirdPartyAction(command_id)) {
+    SharingHubModel* const model = GetSharingHubModel();
+    if (!model)
+      return;
+    model->ExecuteThirdPartyAction(browser_->profile(), command_id);
+  } else {
+    GlobalError* error =
+        GlobalErrorServiceFactory::GetForProfile(browser_->profile())
+            ->GetGlobalErrorByMenuItemCommandID(command_id);
+    if (error) {
+      error->ExecuteMenuItem(browser_);
+      return;
+    }
+    // TODO crbug.com/1186848  Log metrics per command_id;
+    chrome::ExecuteCommand(browser_, command_id);
+  }
+}
+
+SharingHubModel* SharingHubSubMenuModel::GetSharingHubModel() const {
+  SharingHubService* const service =
+      SharingHubServiceFactory::GetForProfile(browser_->profile());
+  return service ? service->GetSharingHubModel() : nullptr;
 }
 
 void SharingHubSubMenuModel::Build(content::WebContents* web_contents) {
   if (!web_contents)
     return;
 
-  Profile* profile =
-      Profile::FromBrowserContext(web_contents->GetBrowserContext());
-  SharingHubService* const service =
-      SharingHubServiceFactory::GetForProfile(profile);
-  SharingHubModel* const model =
-      service ? service->GetSharingHubModel() : nullptr;
-  std::vector<SharingHubAction> actions;
-  if (model) {
-    model->GetActionList(web_contents, &actions);
+  SharingHubModel* const model = GetSharingHubModel();
+  if (!model)
+    return;
 
-    for (std::vector<SharingHubAction>::const_iterator it = actions.begin();
-         it != actions.end(); ++it) {
-      AddItemWithStringId(it->command_id, it->title);
-    }
+  std::vector<SharingHubAction> first_party_actions;
+  std::vector<SharingHubAction> third_party_actions;
+  model->GetFirstPartyActionList(web_contents, &first_party_actions);
+  model->GetThirdPartyActionList(web_contents, &third_party_actions);
+
+  for (auto action : first_party_actions) {
+    AddItem(action.command_id, action.title);
   }
   AddSeparator(ui::NORMAL_SEPARATOR);
+  for (auto action : third_party_actions) {
+    AddItemWithIcon(action.command_id, action.title,
+                    ui::ImageModel::FromVectorIcon(action.icon));
+    third_party_action_ids_.push_back(action.command_id);
+  }
+}
+
+bool SharingHubSubMenuModel::IsThirdPartyAction(int id) {
+  return std::find(third_party_action_ids_.begin(),
+                   third_party_action_ids_.end(),
+                   id) != third_party_action_ids_.end();
 }
 
 }  // namespace sharing_hub
diff --git a/chrome/browser/ui/sharing_hub/sharing_hub_sub_menu_model.h b/chrome/browser/ui/sharing_hub/sharing_hub_sub_menu_model.h
index 5a505e9..c8b3cff 100644
--- a/chrome/browser/ui/sharing_hub/sharing_hub_sub_menu_model.h
+++ b/chrome/browser/ui/sharing_hub/sharing_hub_sub_menu_model.h
@@ -8,15 +8,29 @@
 #include "content/public/browser/web_contents.h"
 #include "ui/base/models/simple_menu_model.h"
 
+class Browser;
+
 namespace sharing_hub {
 
-class SharingHubSubMenuModel : public ui::SimpleMenuModel {
+class SharingHubModel;
+
+class SharingHubSubMenuModel : public ui::SimpleMenuModel,
+                               public ui::SimpleMenuModel::Delegate {
  public:
-  SharingHubSubMenuModel(ui::SimpleMenuModel::Delegate* delegate,
-                         content::WebContents* web_contents);
+  explicit SharingHubSubMenuModel(Browser* browser);
+  ~SharingHubSubMenuModel() override;
+
+  // Overridden from ui::SimpleMenuModel::Delegate:
+  bool IsCommandIdEnabled(int command_id) const override;
+  void ExecuteCommand(int command_id, int event_flags) override;
 
  private:
+  SharingHubModel* GetSharingHubModel() const;
   void Build(content::WebContents* web_contents);
+  bool IsThirdPartyAction(int id);
+
+  Browser* browser_;
+  std::vector<int> third_party_action_ids_;
 
   DISALLOW_COPY_AND_ASSIGN(SharingHubSubMenuModel);
 };
diff --git a/chrome/browser/ui/toolbar/app_menu_model.cc b/chrome/browser/ui/toolbar/app_menu_model.cc
index 77d8373..075abaf 100644
--- a/chrome/browser/ui/toolbar/app_menu_model.cc
+++ b/chrome/browser/ui/toolbar/app_menu_model.cc
@@ -826,8 +826,8 @@
   AddSeparator(ui::UPPER_SEPARATOR);
 
   if (base::FeatureList::IsEnabled(sharing_hub::kSharingHubDesktopAppMenu)) {
-    sub_menus_.push_back(std::make_unique<sharing_hub::SharingHubSubMenuModel>(
-        this, browser_->tab_strip_model()->GetActiveWebContents()));
+    sub_menus_.push_back(
+        std::make_unique<sharing_hub::SharingHubSubMenuModel>(browser_));
     AddSubMenuWithStringId(IDC_SHARING_HUB_MENU, IDS_SHARING_HUB_TITLE,
                            sub_menus_.back().get());
   }
diff --git a/chrome/browser/ui/views/bookmarks/bookmark_bar_view.cc b/chrome/browser/ui/views/bookmarks/bookmark_bar_view.cc
index 7305203..cbfa041 100644
--- a/chrome/browser/ui/views/bookmarks/bookmark_bar_view.cc
+++ b/chrome/browser/ui/views/bookmarks/bookmark_bar_view.cc
@@ -1669,7 +1669,12 @@
 
   views::LabelButton* button = bookmark_buttons_[index];
   bookmark_buttons_.erase(bookmark_buttons_.cbegin() + index);
-  delete button;
+  // Set not visible before removing to advance focus if needed. See
+  // crbug.com/1183980. TODO(crbug.com/1189729): remove this workaround if
+  // FocusManager behavior is changed.
+  button->SetVisible(false);
+  RemoveChildViewT(button);
+
   return true;
 }
 
diff --git a/chrome/browser/ui/views/frame/browser_view.cc b/chrome/browser/ui/views/frame/browser_view.cc
index bfa6016a..cad9e986 100644
--- a/chrome/browser/ui/views/frame/browser_view.cc
+++ b/chrome/browser/ui/views/frame/browser_view.cc
@@ -3307,6 +3307,7 @@
 
   // Toggle fullscreen mode; move the window between displays as needed.
   // TODO(crbug.com/1034783): Implement at lower layers to avoid transitions.
+  bool entering_cross_screen_fullscreen = false;
   if (fullscreen && display_id != display::kInvalidDisplayId) {
     display::Screen* screen = display::Screen::GetScreen();
     display::Display display;
@@ -3335,19 +3336,23 @@
       } else {
         frame_->SetFullscreen(false);
       }
+      entering_cross_screen_fullscreen = true;
       frame_->SetBounds({display.work_area().origin(),
                          frame_->GetWindowBoundsInScreen().size()});
     }
   }
 
+#if defined(OS_MAC)
+  // On Mac, the fullscreen state change must be requested with a delay after
+  // moving the window to the target display; see http://crbug.com/1210548
+  frame_->SetFullscreen(fullscreen, entering_cross_screen_fullscreen);
+#else   // OS_MAC
   frame_->SetFullscreen(fullscreen);
-
-#if !defined(OS_MAC)
   // On Mac, the pre-fullscreen bounds must be restored after an asynchronous
   // transition out of the fullscreen workspace; see http://crbug.com/1039874
   if (!fullscreen && restore_pre_fullscreen_bounds_callback_)
     std::move(restore_pre_fullscreen_bounds_callback_).Run();
-#endif  // !OS_MAC
+#endif  // OS_MAC
 
   // Enable immersive before the browser refreshes its list of enabled commands.
   const bool should_stay_in_immersive =
diff --git a/chrome/browser/ui/views/frame/browser_view.h b/chrome/browser/ui/views/frame/browser_view.h
index 6b777fd..17968cd 100644
--- a/chrome/browser/ui/views/frame/browser_view.h
+++ b/chrome/browser/ui/views/frame/browser_view.h
@@ -228,6 +228,10 @@
   // Accessor for the contents WebView.
   views::WebView* contents_web_view() { return contents_web_view_; }
 
+  base::WeakPtr<BrowserView> GetAsWeakPtr() {
+    return weak_ptr_factory_.GetWeakPtr();
+  }
+
   // Accessor for the BrowserView's TabSearchButton instance.
   TabSearchButton* GetTabSearchButton();
 
diff --git a/chrome/browser/ui/views/sharing_hub/sharing_hub_bubble_action_button.cc b/chrome/browser/ui/views/sharing_hub/sharing_hub_bubble_action_button.cc
index e8587f5..eebf2e56 100644
--- a/chrome/browser/ui/views/sharing_hub/sharing_hub_bubble_action_button.cc
+++ b/chrome/browser/ui/views/sharing_hub/sharing_hub_bubble_action_button.cc
@@ -36,7 +36,7 @@
                               base::Unretained(bubble),
                               base::Unretained(this)),
           CreateIcon(action_info.icon),
-          l10n_util::GetStringUTF16(action_info.title)) {
+          action_info.title) {
   action_command_id_ = action_info.command_id;
   action_is_first_party_ = action_info.is_first_party;
   SetEnabled(true);
diff --git a/chrome/browser/ui/views/sharing_hub/sharing_hub_bubble_view_impl.cc b/chrome/browser/ui/views/sharing_hub/sharing_hub_bubble_view_impl.cc
index f682597..3d04cda 100644
--- a/chrome/browser/ui/views/sharing_hub/sharing_hub_bubble_view_impl.cc
+++ b/chrome/browser/ui/views/sharing_hub/sharing_hub_bubble_view_impl.cc
@@ -111,23 +111,27 @@
   scroll_view_ = AddChildView(std::make_unique<views::ScrollView>());
   scroll_view_->ClipHeightTo(0, kActionButtonHeight * kMaximumButtons);
 
-  PopulateScrollView(controller_->GetActions());
+  PopulateScrollView(controller_->GetFirstPartyActions(),
+                     controller_->GetThirdPartyActions());
 }
 
 void SharingHubBubbleViewImpl::PopulateScrollView(
-    const std::vector<SharingHubAction>& actions) {
+    const std::vector<SharingHubAction>& first_party_actions,
+    const std::vector<SharingHubAction>& third_party_actions) {
   auto* action_list_view =
       scroll_view_->SetContents(std::make_unique<views::View>());
   action_list_view->SetLayoutManager(std::make_unique<views::BoxLayout>(
       views::BoxLayout::Orientation::kVertical));
 
-  bool separator_added = false;
-  for (const auto& action : actions) {
-    if (!separator_added && !action.is_first_party) {
-      action_list_view->AddChildView(GetSeparator());
-      separator_added = true;
-    }
+  for (const auto& action : first_party_actions) {
+    auto* view = action_list_view->AddChildView(
+        std::make_unique<SharingHubBubbleActionButton>(this, action));
+    view->SetGroup(kActionButtonGroup);
+  }
 
+  action_list_view->AddChildView(GetSeparator());
+
+  for (const auto& action : third_party_actions) {
     auto* view = action_list_view->AddChildView(
         std::make_unique<SharingHubBubbleActionButton>(this, action));
     view->SetGroup(kActionButtonGroup);
diff --git a/chrome/browser/ui/views/sharing_hub/sharing_hub_bubble_view_impl.h b/chrome/browser/ui/views/sharing_hub/sharing_hub_bubble_view_impl.h
index 4302a440..e3bcee5 100644
--- a/chrome/browser/ui/views/sharing_hub/sharing_hub_bubble_view_impl.h
+++ b/chrome/browser/ui/views/sharing_hub/sharing_hub_bubble_view_impl.h
@@ -61,7 +61,9 @@
   void CreateScrollView();
 
   // Populates the scroll view containing sharing actions.
-  void PopulateScrollView(const std::vector<SharingHubAction>& actions);
+  void PopulateScrollView(
+      const std::vector<SharingHubAction>& first_party_actions,
+      const std::vector<SharingHubAction>& third_party_actions);
 
   // Resizes and potentially moves the bubble to fit the content's preferred
   // size.
diff --git a/chrome/browser/ui/views/tabs/tab_drag_controller.cc b/chrome/browser/ui/views/tabs/tab_drag_controller.cc
index fbda064f7..14e0f078 100644
--- a/chrome/browser/ui/views/tabs/tab_drag_controller.cc
+++ b/chrome/browser/ui/views/tabs/tab_drag_controller.cc
@@ -1488,6 +1488,12 @@
           ? views::Widget::MoveLoopEscapeBehavior::kHide
           : views::Widget::MoveLoopEscapeBehavior::kDontHide;
 
+  // This code isn't set up to handle nested run loops. Nested run loops may
+  // lead to all sorts of interesting crashes, and generally indicate a bug
+  // lower in the stack. This is a CHECK() as there may be security
+  // implications to attempting a nested run loop.
+  CHECK(!in_move_loop_);
+  in_move_loop_ = true;
   views::Widget::MoveLoopResult result = move_loop_widget_->RunMoveLoop(
       drag_offset, move_loop_source, escape_behavior);
   content::NotificationService::current()->Notify(
@@ -1498,10 +1504,8 @@
   if (!ref)
     return;
 
-  if (move_loop_widget_ &&
-      widget_observation_.IsObservingSource(move_loop_widget_)) {
-    widget_observation_.Reset();
-  }
+  in_move_loop_ = false;
+  widget_observation_.Reset();
   move_loop_widget_ = nullptr;
 
   if (current_state_ == DragState::kDraggingWindow) {
diff --git a/chrome/browser/ui/views/tabs/tab_drag_controller.h b/chrome/browser/ui/views/tabs/tab_drag_controller.h
index ef8d13d..6cbf15f 100644
--- a/chrome/browser/ui/views/tabs/tab_drag_controller.h
+++ b/chrome/browser/ui/views/tabs/tab_drag_controller.h
@@ -718,6 +718,9 @@
   base::ScopedObservation<views::Widget, views::WidgetObserver>
       widget_observation_{this};
 
+  // True while RunMoveLoop() has been called on a widget.
+  bool in_move_loop_ = false;
+
   base::WeakPtrFactory<TabDragController> weak_factory_{this};
 };
 
diff --git a/chrome/browser/ui/views/toolbar/chrome_labs_button.cc b/chrome/browser/ui/views/toolbar/chrome_labs_button.cc
index 05be16f..c17e709 100644
--- a/chrome/browser/ui/views/toolbar/chrome_labs_button.cc
+++ b/chrome/browser/ui/views/toolbar/chrome_labs_button.cc
@@ -8,6 +8,7 @@
 #include "chrome/browser/about_flags.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/views/frame/browser_view.h"
 #include "chrome/browser/ui/views/toolbar/chrome_labs_bubble_view.h"
 #include "chrome/browser/ui/webui/flags/flags_ui.h"
 #include "chrome/common/channel_info.h"
@@ -26,11 +27,11 @@
 #include "chrome/browser/ash/settings/owner_flags_storage.h"
 #endif
 
-ChromeLabsButton::ChromeLabsButton(Browser* browser,
+ChromeLabsButton::ChromeLabsButton(BrowserView* browser_view,
                                    const ChromeLabsBubbleViewModel* model)
     : ToolbarButton(base::BindRepeating(&ChromeLabsButton::ButtonPressed,
                                         base::Unretained(this))),
-      browser_(browser),
+      browser_view_(browser_view),
       model_(model) {
   SetVectorIcons(kChromeLabsIcon, kChromeLabsTouchIcon);
   SetAccessibleName(l10n_util::GetStringUTF16(IDS_ACCNAME_CHROMELABS_BUTTON));
@@ -47,6 +48,14 @@
 }
 
 void ChromeLabsButton::ButtonPressed() {
+  // On Chrome OS if we are still waiting for IsOwnerAsync to return abort
+  // button clicks.
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+  if (is_waiting_to_show) {
+    return;
+  }
+#endif
+
   if (ChromeLabsBubbleView::IsShowing()) {
     ChromeLabsBubbleView::Hide();
     return;
@@ -60,20 +69,31 @@
   // Reset timer.
   ash_owner_check_timer_ = nullptr;
   // Bypass possible incognito profile same as chrome://flags does.
-  Profile* original_profile = browser_->profile()->GetOriginalProfile();
-  if (base::SysInfo::IsRunningOnChromeOS() &&
+  Profile* original_profile =
+      browser_view_->browser()->profile()->GetOriginalProfile();
+  if ((base::SysInfo::IsRunningOnChromeOS() ||
+       should_circumvent_device_check_for_testing_) &&
       ash::OwnerSettingsServiceAshFactory::GetForBrowserContext(
           original_profile)) {
     ash::OwnerSettingsServiceAsh* service =
         ash::OwnerSettingsServiceAshFactory::GetForBrowserContext(
             original_profile);
     ash_owner_check_timer_ = std::make_unique<base::ElapsedTimer>();
-    service->IsOwnerAsync(
-        base::BindOnce(&ChromeLabsBubbleView::Show, this, browser_, model_));
+    is_waiting_to_show = true;
+    service->IsOwnerAsync(base::BindOnce(
+        [](ChromeLabsButton* button, base::WeakPtr<BrowserView> browser_view,
+           const ChromeLabsBubbleViewModel* model, bool is_owner) {
+          if (!browser_view)
+            return;
+          ChromeLabsBubbleView::Show(button, browser_view->browser(), model,
+                                     is_owner);
+          button->is_waiting_to_show = false;
+        },
+        this, browser_view_->GetAsWeakPtr(), model_));
     return;
   }
 #endif
-  ChromeLabsBubbleView::Show(this, browser_, model_,
+  ChromeLabsBubbleView::Show(this, browser_view_->browser(), model_,
                              /*user_is_chromeos_owner=*/false);
 }
 
diff --git a/chrome/browser/ui/views/toolbar/chrome_labs_button.h b/chrome/browser/ui/views/toolbar/chrome_labs_button.h
index 285c248..40c440c 100644
--- a/chrome/browser/ui/views/toolbar/chrome_labs_button.h
+++ b/chrome/browser/ui/views/toolbar/chrome_labs_button.h
@@ -13,13 +13,13 @@
 class ElapsedTimer;
 }
 
-class Browser;
+class BrowserView;
 class Profile;
 
 class ChromeLabsButton : public ToolbarButton {
  public:
   METADATA_HEADER(ChromeLabsButton);
-  explicit ChromeLabsButton(Browser* browser,
+  explicit ChromeLabsButton(BrowserView* browser_view,
                             const ChromeLabsBubbleViewModel* model);
   ChromeLabsButton(const ChromeLabsButton&) = delete;
   ChromeLabsButton& operator=(const ChromeLabsButton&) = delete;
@@ -32,6 +32,10 @@
   base::ElapsedTimer* GetAshOwnerCheckTimer() {
     return ash_owner_check_timer_.get();
   }
+
+  void SetShouldCircumventDeviceCheckForTesting(bool should_circumvent) {
+    should_circumvent_device_check_for_testing_ = should_circumvent;
+  }
 #endif
 
  private:
@@ -44,7 +48,13 @@
   std::unique_ptr<base::ElapsedTimer> ash_owner_check_timer_;
 #endif
 
-  Browser* browser_;
+  BrowserView* browser_view_;
+
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+  bool is_waiting_to_show = false;
+  // Used to circumvent the IsRunningOnChromeOS() check in ash-chrome tests.
+  bool should_circumvent_device_check_for_testing_ = false;
+#endif
 
   const ChromeLabsBubbleViewModel* model_;
 };
diff --git a/chrome/browser/ui/views/toolbar/chrome_labs_button_unittest.cc b/chrome/browser/ui/views/toolbar/chrome_labs_button_unittest.cc
index 82d7853..0a8e798a 100644
--- a/chrome/browser/ui/views/toolbar/chrome_labs_button_unittest.cc
+++ b/chrome/browser/ui/views/toolbar/chrome_labs_button_unittest.cc
@@ -22,6 +22,8 @@
 #include "base/command_line.h"
 #include "base/memory/ptr_util.h"
 #include "chrome/browser/ash/login/users/fake_chrome_user_manager.h"
+#include "chrome/browser/ash/ownership/owner_settings_service_ash.h"
+#include "chrome/browser/ash/ownership/owner_settings_service_ash_factory.h"
 #include "chrome/common/pref_names.h"
 #include "components/user_manager/scoped_user_manager.h"
 #endif
@@ -89,12 +91,23 @@
 TEST_F(ChromeLabsButtonTest, ShowAndHideChromeLabsBubbleOnPress) {
   ChromeLabsButton* labs_button =
       browser_view()->toolbar()->chrome_labs_button();
+
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+  ash::OwnerSettingsServiceAsh* service_ =
+      ash::OwnerSettingsServiceAshFactory::GetForBrowserContext(GetProfile());
+  labs_button->SetShouldCircumventDeviceCheckForTesting(true);
+#endif
+
   EXPECT_FALSE(ChromeLabsBubbleView::IsShowing());
   ui::MouseEvent e(ui::ET_MOUSE_PRESSED, gfx::Point(), gfx::Point(),
                    ui::EventTimeForNow(), 0, 0);
   views::test::ButtonTestApi test_api(labs_button);
   test_api.NotifyClick(e);
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+  service_->RunPendingIsOwnerCallbacksForTesting(/*is_owner=*/false);
+#endif
   EXPECT_TRUE(ChromeLabsBubbleView::IsShowing());
+
   views::test::WidgetDestroyedWaiter destroyed_waiter(
       ChromeLabsBubbleView::GetChromeLabsBubbleViewForTesting()->GetWidget());
   test_api.NotifyClick(e);
diff --git a/chrome/browser/ui/views/toolbar/toolbar_view.cc b/chrome/browser/ui/views/toolbar/toolbar_view.cc
index 2c5c301..1ebaac5d 100644
--- a/chrome/browser/ui/views/toolbar/toolbar_view.cc
+++ b/chrome/browser/ui/views/toolbar/toolbar_view.cc
@@ -312,7 +312,7 @@
     if (ChromeLabsButton::ShouldShowButton(chrome_labs_model_.get(),
                                            browser_->profile())) {
       chrome_labs_button_ = AddChildView(std::make_unique<ChromeLabsButton>(
-          browser_, chrome_labs_model_.get()));
+          browser_view_, chrome_labs_model_.get()));
 
       show_chrome_labs_button_.Init(
           chrome_labs_prefs::kBrowserLabsEnabled,
diff --git a/chrome/browser/ui/web_applications/web_app_browsertest.cc b/chrome/browser/ui/web_applications/web_app_browsertest.cc
index 2ed89371..e85f0f5 100644
--- a/chrome/browser/ui/web_applications/web_app_browsertest.cc
+++ b/chrome/browser/ui/web_applications/web_app_browsertest.cc
@@ -186,13 +186,11 @@
 // line switch to enable manifest parsing.
 class WebAppBrowserTest_WindowControlsOverlay : public WebAppBrowserTest {
  public:
-  WebAppBrowserTest_WindowControlsOverlay() {
-    scoped_feature_list_.InitWithFeatures(
-        {features::kWebAppWindowControlsOverlay}, {});
-  }
+  WebAppBrowserTest_WindowControlsOverlay() = default;
 
  private:
-  base::test::ScopedFeatureList scoped_feature_list_;
+  base::test::ScopedFeatureList scoped_feature_list_{
+      features::kWebAppWindowControlsOverlay};
 };
 
 using WebAppTabRestoreBrowserTest = WebAppBrowserTest;
@@ -1145,12 +1143,11 @@
 
 class WebAppBrowserTest_HideOrigin : public WebAppBrowserTest {
  public:
-  WebAppBrowserTest_HideOrigin() {
-    scoped_feature_list_.InitAndEnableFeature(features::kHideWebAppOriginText);
-  }
+  WebAppBrowserTest_HideOrigin() = default;
 
  private:
-  base::test::ScopedFeatureList scoped_feature_list_;
+  base::test::ScopedFeatureList scoped_feature_list_{
+      features::kHideWebAppOriginText};
 };
 
 // WebApps should not have origin text with this feature on.
@@ -1163,13 +1160,11 @@
 
 class WebAppBrowserTest_AppNameInsteadOfOrigin : public WebAppBrowserTest {
  public:
-  WebAppBrowserTest_AppNameInsteadOfOrigin() {
-    scoped_feature_list_.InitAndEnableFeature(
-        features::kDesktopPWAsFlashAppNameInsteadOfOrigin);
-  }
+  WebAppBrowserTest_AppNameInsteadOfOrigin() = default;
 
  private:
-  base::test::ScopedFeatureList scoped_feature_list_;
+  base::test::ScopedFeatureList scoped_feature_list_{
+      features::kDesktopPWAsFlashAppNameInsteadOfOrigin};
 };
 
 // Web apps should flash the app name with this feature on.
@@ -1316,13 +1311,11 @@
 
 class WebAppBrowserTest_RemoveStatusBar : public WebAppBrowserTest {
  public:
-  WebAppBrowserTest_RemoveStatusBar() {
-    scoped_feature_list_.InitAndEnableFeature(
-        features::kRemoveStatusBarInWebApps);
-  }
+  WebAppBrowserTest_RemoveStatusBar() = default;
 
  private:
-  base::test::ScopedFeatureList scoped_feature_list_;
+  base::test::ScopedFeatureList scoped_feature_list_{
+      features::kRemoveStatusBarInWebApps};
 };
 
 IN_PROC_BROWSER_TEST_F(WebAppBrowserTest_RemoveStatusBar, RemoveStatusBar) {
diff --git a/chrome/browser/ui/webui/settings/site_settings_handler_unittest.cc b/chrome/browser/ui/webui/settings/site_settings_handler_unittest.cc
index 132d2d6..98c96b29 100644
--- a/chrome/browser/ui/webui/settings/site_settings_handler_unittest.cc
+++ b/chrome/browser/ui/webui/settings/site_settings_handler_unittest.cc
@@ -32,7 +32,8 @@
 #include "chrome/browser/usb/usb_chooser_context.h"
 #include "chrome/browser/usb/usb_chooser_context_factory.h"
 #include "chrome/browser/web_applications/components/web_app_helpers.h"
-#include "chrome/browser/web_applications/test/test_app_registrar.h"
+#include "chrome/browser/web_applications/test/test_web_app_registry_controller.h"
+#include "chrome/browser/web_applications/web_app.h"
 #include "chrome/common/pref_names.h"
 #include "chrome/grit/generated_resources.h"
 #include "chrome/test/base/browser_with_test_window_test.h"
@@ -106,10 +107,6 @@
     {{"http://127.0.0.1", "location"}, {true, ""}},  // Localhost is secure.
     {{"http://[::1]", "location"}, {true, ""}}};
 
-std::string GenerateFakeAppId(const GURL& url) {
-  return web_app::GenerateAppIdFromURL(url);
-}
-
 }  // namespace
 
 namespace settings {
@@ -171,8 +168,13 @@
   }
 
   void SetUp() override {
+    test_registry_controller_ =
+        std::make_unique<web_app::TestWebAppRegistryController>();
+    test_registry_controller_->SetUp(profile());
+    controller().Init();
+
     handler_ =
-        std::make_unique<SiteSettingsHandler>(profile_.get(), app_registrar_);
+        std::make_unique<SiteSettingsHandler>(profile_.get(), app_registrar());
     handler()->set_web_ui(web_ui());
     handler()->AllowJavascript();
     // AllowJavascript() adds a callback to create leveldb_env::ChromiumEnv
@@ -193,9 +195,26 @@
     }
   }
 
+  std::unique_ptr<web_app::WebApp> CreateWebApp() {
+    const GURL app_url = GURL("http://abc.example.com/path");
+    const web_app::AppId app_id = web_app::GenerateAppIdFromURL(app_url);
+
+    auto web_app = std::make_unique<web_app::WebApp>(app_id);
+    web_app->AddSource(web_app::Source::kSync);
+    web_app->SetDisplayMode(web_app::DisplayMode::kStandalone);
+    web_app->SetUserDisplayMode(web_app::DisplayMode::kStandalone);
+    web_app->SetName("Name");
+    web_app->SetStartUrl(app_url);
+
+    return web_app;
+  }
+
+  web_app::TestWebAppRegistryController& controller() {
+    return *test_registry_controller_;
+  }
+  web_app::WebAppRegistrar& app_registrar() { return controller().registrar(); }
   TestingProfile* profile() { return profile_.get(); }
   TestingProfile* incognito_profile() { return incognito_profile_; }
-  web_app::TestAppRegistrar& app_registrar() { return app_registrar_; }
   content::TestWebUI* web_ui() { return &web_ui_; }
   SiteSettingsHandler* handler() { return handler_.get(); }
 
@@ -487,7 +506,8 @@
   content::BrowserTaskEnvironment task_environment_;
   std::unique_ptr<TestingProfile> profile_;
   TestingProfile* incognito_profile_;
-  web_app::TestAppRegistrar app_registrar_;
+  std::unique_ptr<web_app::TestWebAppRegistryController>
+      test_registry_controller_;
   content::TestWebUI web_ui_;
   std::unique_ptr<SiteSettingsHandler> handler_;
 #if BUILDFLAG(IS_CHROMEOS_ASH)
@@ -906,9 +926,8 @@
 }
 
 TEST_F(SiteSettingsHandlerTest, InstalledApps) {
-  web_app::TestAppRegistrar& registrar = app_registrar();
-  const GURL url("http://abc.example.com/");
-  registrar.AddExternalApp(GenerateFakeAppId(url), {url});
+  auto web_app = CreateWebApp();
+  controller().RegisterApp(std::move(web_app));
 
   SetUpCookiesTreeModel();
 
@@ -1533,7 +1552,13 @@
       const SiteSettingsHandlerInfobarTest&) = delete;
   void SetUp() override {
     BrowserWithTestWindowTest::SetUp();
-    handler_ = std::make_unique<SiteSettingsHandler>(profile(), app_registrar_);
+
+    test_registry_controller_ =
+        std::make_unique<web_app::TestWebAppRegistryController>();
+    test_registry_controller_->SetUp(profile());
+
+    handler_ =
+        std::make_unique<SiteSettingsHandler>(profile(), app_registrar());
     handler()->set_web_ui(web_ui());
     handler()->AllowJavascript();
     web_ui()->ClearTrackedCalls();
@@ -1547,6 +1572,9 @@
             extensions::ExtensionSystem::Get(profile()));
     extension_system->CreateExtensionService(
         base::CommandLine::ForCurrentProcess(), base::FilePath(), false);
+
+    // Wait for the sync bridge to be ready synchronously.
+    controller().Init();
   }
 
   void TearDown() override {
@@ -1577,10 +1605,17 @@
 
   Browser* browser2() { return browser2_.get(); }
 
+  web_app::TestWebAppRegistryController& controller() {
+    return *test_registry_controller_;
+  }
+
+  web_app::WebAppRegistrar& app_registrar() { return controller().registrar(); }
+
   const std::string kNotifications;
 
  private:
-  web_app::TestAppRegistrar app_registrar_;
+  std::unique_ptr<web_app::TestWebAppRegistryController>
+      test_registry_controller_;
   content::TestWebUI web_ui_;
   std::unique_ptr<SiteSettingsHandler> handler_;
   std::unique_ptr<BrowserWindow> window2_;
diff --git a/chrome/browser/webauthn/authenticator_request_dialog_model.cc b/chrome/browser/webauthn/authenticator_request_dialog_model.cc
index 2e515843..1bb8956 100644
--- a/chrome/browser/webauthn/authenticator_request_dialog_model.cc
+++ b/chrome/browser/webauthn/authenticator_request_dialog_model.cc
@@ -843,6 +843,12 @@
 
   if (cable_ui_type_) {
     switch (*cable_ui_type_) {
+      case AuthenticatorRequestDialogModel::CableUIType::CABLE_V2_2ND_FACTOR:
+        if (!base::FeatureList::IsEnabled(device::kWebAuthPhoneSupport)) {
+          break;
+        }
+        [[fallthrough]];
+
       case AuthenticatorRequestDialogModel::CableUIType::CABLE_V2_SERVER_LINK:
         transports_to_list_if_active.push_back(
             AuthenticatorTransport::kAndroidAccessory);
@@ -854,16 +860,14 @@
         if (base::Contains(transport_availability_.available_transports,
                            cable)) {
           transports_to_list_if_active.push_back(cable);
-          DCHECK(is_get_assertion);
+          DCHECK(is_get_assertion ||
+                 base::FeatureList::IsEnabled(device::kWebAuthPhoneSupport));
           if (!priority_transport) {
             priority_transport = cable;
           }
         }
         break;
       }
-
-      case AuthenticatorRequestDialogModel::CableUIType::CABLE_V2_2ND_FACTOR:
-        break;
     }
   }
 
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index aeafbb9..7d868a7 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-master-1622115746-16aae4983a3b79f0983927a7f29da7d36068f03d.profdata
+chrome-win64-master-1622159822-454cea1e0e4715322c0a3ea42d011f3c9a700300.profdata
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index c3bb583..6fd5f654 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -5612,6 +5612,7 @@
       "../browser/nearby_sharing/nearby_notification_manager_unittest.cc",
       "../browser/nearby_sharing/nearby_per_session_discovery_manager_unittest.cc",
       "../browser/nearby_sharing/nearby_receive_manager_unittest.cc",
+      "../browser/nearby_sharing/nearby_share_feature_usage_metrics_unittest.cc",
       "../browser/nearby_sharing/nearby_share_profile_info_provider_impl_unittest.cc",
       "../browser/nearby_sharing/nearby_share_settings_unittest.cc",
       "../browser/nearby_sharing/nearby_sharing_service_impl_unittest.cc",
diff --git a/chrome/test/data/webui/chromeos/diagnostics/routine_section_test.js b/chrome/test/data/webui/chromeos/diagnostics/routine_section_test.js
index a7554d1..0944530 100644
--- a/chrome/test/data/webui/chromeos/diagnostics/routine_section_test.js
+++ b/chrome/test/data/webui/chromeos/diagnostics/routine_section_test.js
@@ -226,6 +226,15 @@
     return flushTasks();
   }
 
+  /**
+   * @param {boolean} isActive
+   * @return {!Promise}
+   */
+  function setIsActive(isActive) {
+    routineSectionElement.isActive = isActive;
+    return flushTasks();
+  }
+
   test('ElementRenders', () => {
     return initializeRoutineSection([]).then(() => {
       // Verify the element rendered.
@@ -850,4 +859,38 @@
           resetMockTime();
         });
   });
+
+  test('PageChangeStopsRunningTest', () => {
+    /** @type {!Array<!RoutineType>} */
+    const routines = [RoutineType.kMemory];
+
+    routineController.setFakeStandardRoutineResult(
+        RoutineType.kMemory, StandardRoutineResult.kTestPassed);
+    return initializeRoutineSection(routines)
+        .then(() => clickRunTestsButton())
+        .then(() => {
+          // Badge is visible with test running.
+          assertFalse(getStatusBadge().hidden);
+          assertEquals(getStatusBadge().badgeType, BadgeType.RUNNING);
+          dx_utils.assertTextContains(
+              getStatusBadge().value, loadTimeData.getString('testRunning'));
+
+          // Text is visible describing which test is being run.
+          assertFalse(getStatusTextElement().hidden);
+          dx_utils.assertElementContainsText(
+              getStatusTextElement(),
+              loadTimeData.getString('memoryRoutineText').toLowerCase());
+
+          // Simulate a navigation page change event.
+          return setIsActive(false);
+        })
+        .then(() => flushTasks())
+        .then(() => {
+          // Result list is no longer visible.
+          assertFalse(isVisible(getResultList()));
+          // Memory routine should be cancelled.
+          assertEquals(
+              ExecutionProgress.kCancelled, getEntries()[0].item.progress);
+        });
+  });
 }
diff --git a/chrome/test/data/webui/chromeos/shimless_rma/onboarding_update_page_test.js b/chrome/test/data/webui/chromeos/shimless_rma/onboarding_update_page_test.js
index ea9536ff..553c8d8 100644
--- a/chrome/test/data/webui/chromeos/shimless_rma/onboarding_update_page_test.js
+++ b/chrome/test/data/webui/chromeos/shimless_rma/onboarding_update_page_test.js
@@ -54,9 +54,10 @@
   /**
    * @return {!Promise}
    */
-  function clickCheckUpdateBtn() {
-    const checkUpdateBtn = component.shadowRoot.querySelector('#checkUpdate');
-    checkUpdateBtn.click();
+  function clickCheckUpdateButton() {
+    const checkUpdateButton =
+        component.shadowRoot.querySelector('#checkUpdate');
+    checkUpdateButton.click();
     return flushTasks();
   }
 
@@ -67,9 +68,9 @@
     return initializeUpdatePage(version, update).then(() => {
       const versionComponent =
           component.shadowRoot.querySelector('#versionInfo');
-      const updateBtn = component.shadowRoot.querySelector('#performUpdate');
+      const updateButton = component.shadowRoot.querySelector('#performUpdate');
       assertTrue(versionComponent.textContent.trim().indexOf(version) !== -1);
-      assertTrue(updateBtn.hidden);
+      assertTrue(updateButton.hidden);
     });
   });
 
@@ -85,11 +86,11 @@
         .then(() => {
           const networkUnavailable =
               component.shadowRoot.querySelector('#networkUnavailable');
-          const checkUpdateBtn =
+          const checkUpdateButton =
               component.shadowRoot.querySelector('#checkUpdate');
 
           assertFalse(networkUnavailable.hidden);
-          assertTrue(checkUpdateBtn.hidden);
+          assertTrue(checkUpdateButton.hidden);
         });
   });
 
@@ -102,7 +103,7 @@
           component.networkAvailable = true;
           return flushTasks();
         })
-        .then(() => clickCheckUpdateBtn())
+        .then(() => clickCheckUpdateButton())
         .then(() => {
           const versionComponent =
               component.shadowRoot.querySelector('#versionInfo');
@@ -122,7 +123,7 @@
           component.networkAvailable = true;
           return flushTasks();
         })
-        .then(() => clickCheckUpdateBtn())
+        .then(() => clickCheckUpdateButton())
         .then(() => {
           const versionComponent =
               component.shadowRoot.querySelector('#versionInfo');
@@ -132,9 +133,9 @@
               versionComponent.textContent.trim().indexOf(uptoDateMsg) !== -1);
         })
         .then(() => {
-          const updateBtn =
+          const updateButton =
               component.shadowRoot.querySelector('#performUpdate');
-          assertFalse(updateBtn.hidden);
+          assertFalse(updateButton.hidden);
         });
   });
 }
diff --git a/chrome/test/data/webui/chromeos/shimless_rma/shimless_rma_app_test.js b/chrome/test/data/webui/chromeos/shimless_rma/shimless_rma_app_test.js
index 2a98864..8ee6018 100644
--- a/chrome/test/data/webui/chromeos/shimless_rma/shimless_rma_app_test.js
+++ b/chrome/test/data/webui/chromeos/shimless_rma/shimless_rma_app_test.js
@@ -6,7 +6,7 @@
 import {fakeChromeVersion, fakeStates} from 'chrome://shimless-rma/fake_data.js';
 import {FakeShimlessRmaService} from 'chrome://shimless-rma/fake_shimless_rma_service.js';
 import {setShimlessRmaServiceForTesting} from 'chrome://shimless-rma/mojo_interface_provider.js';
-import {BtnState, ShimlessRmaElement} from 'chrome://shimless-rma/shimless_rma.js';
+import {ButtonState, ShimlessRmaElement} from 'chrome://shimless-rma/shimless_rma.js';
 import {RmaState, State} from 'chrome://shimless-rma/shimless_rma_types.js';
 
 import {assertFalse, assertTrue} from '../../chai_assert.js';
@@ -56,15 +56,15 @@
 
   /**
    * Utility function to assert navigation buttons
-   * TODO(joonbug): expand to cover assertion of BtnState
+   * TODO(joonbug): expand to cover assertion of ButtonState
    */
   function assertNavButtons() {
-    const nextBtn = component.shadowRoot.querySelector('#back');
-    const prevBtn = component.shadowRoot.querySelector('#cancel');
-    const backBtn = component.shadowRoot.querySelector('#next');
-    assertTrue(!!nextBtn);
-    assertTrue(!!prevBtn);
-    assertTrue(!!backBtn);
+    const nextButton = component.shadowRoot.querySelector('#back');
+    const prevButton = component.shadowRoot.querySelector('#cancel');
+    const backButton = component.shadowRoot.querySelector('#next');
+    assertTrue(!!nextButton);
+    assertTrue(!!prevButton);
+    assertTrue(!!backButton);
   }
 
   /**
@@ -72,8 +72,8 @@
    * @return {Promise}
    */
   function clickNext() {
-    const nextBtn = component.shadowRoot.querySelector('#next');
-    nextBtn.click();
+    const nextButton = component.shadowRoot.querySelector('#next');
+    nextButton.click();
     return flushTasks();
   }
 
@@ -100,8 +100,8 @@
     assertTrue(!!initialPage);
     assertTrue(initialPage.hidden);
 
-    const prevBtn = component.shadowRoot.querySelector('#back');
-    prevBtn.click();
+    const prevButton = component.shadowRoot.querySelector('#back');
+    prevButton.click();
     await flushTasks();
 
     // components page should not be destroyed.
@@ -117,15 +117,15 @@
         component.shadowRoot.querySelector('onboarding-landing-page');
     await clickNext();
 
-    const cancelBtn = component.shadowRoot.querySelector('#cancel');
-    cancelBtn.click();
+    const cancelButton = component.shadowRoot.querySelector('#cancel');
+    cancelButton.click();
     await flushTasks();
 
     // back to initial page
     assertFalse(initialPage.hidden);
   });
 
-  test('NextBtnClickedOnReady', async () => {
+  test('NextButtonClickedOnReady', async () => {
     await initializeShimlessRMAApp(fakeStates, fakeChromeVersion[0]);
 
     const initialPage =
@@ -133,7 +133,7 @@
     assertTrue(!!initialPage);
 
     const resolver = new PromiseResolver();
-    initialPage.onNextBtnClick = () => resolver.promise;
+    initialPage.onNextButtonClick = () => resolver.promise;
 
     await clickNext();
     assertFalse(initialPage.hidden);
@@ -148,7 +148,7 @@
     assertTrue(initialPage.hidden);
   });
 
-  test('NextBtnClickedOnNotReady', async () => {
+  test('NextButtonClickedOnNotReady', async () => {
     await initializeShimlessRMAApp(fakeStates, fakeChromeVersion[0]);
 
     const initialPage =
@@ -156,7 +156,7 @@
     assertTrue(!!initialPage);
 
     const resolver = new PromiseResolver();
-    initialPage.onNextBtnClick = () => resolver.promise;
+    initialPage.onNextButtonClick = () => resolver.promise;
 
     await clickNext();
     assertFalse(initialPage.hidden);
@@ -170,13 +170,13 @@
   test('UpdateButtonState', async () => {
     await initializeShimlessRMAApp(fakeStates, fakeChromeVersion[0]);
 
-    const backBtn = component.shadowRoot.querySelector('#back');
-    assertTrue(!!backBtn);
-    assertTrue(backBtn.hidden);
+    const backButton = component.shadowRoot.querySelector('#back');
+    assertTrue(!!backButton);
+    assertTrue(backButton.hidden);
 
-    component.updateBtnState('btnBack', BtnState.VISIBLE);
+    component.updateButtonState('buttonBack', ButtonState.VISIBLE);
     await flushTasks();
 
-    assertFalse(backBtn.hidden);
+    assertFalse(backButton.hidden);
   });
 }
diff --git a/chrome/test/data/webui/settings/chromeos/network_summary_item_test.js b/chrome/test/data/webui/settings/chromeos/network_summary_item_test.js
index a69da17b..1ca11ab 100644
--- a/chrome/test/data/webui/settings/chromeos/network_summary_item_test.js
+++ b/chrome/test/data/webui/settings/chromeos/network_summary_item_test.js
@@ -6,6 +6,7 @@
 // #import 'chrome://os-settings/chromeos/os_settings.js';
 
 // #import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+// #import {eventToPromise} from 'chrome://test/test_util.m.js';
 // clang-format on
 
 suite('NetworkSummaryItem', function() {
@@ -285,4 +286,39 @@
         netSummaryItem.$$('#networkState').classList.contains('network-state'));
   });
 
+  test(
+      'Show networks list when only 1 pSIM network is available',
+      async function() {
+        const mojom = chromeos.networkConfig.mojom;
+
+        const showNetworksFiredPromise =
+            test_util.eventToPromise('show-networks', netSummaryItem);
+
+        // Simulate a device which has a single pSIM slot and no eSIM slots.
+        const simInfos = [{slotId: 1, iccid: '000', isPrimary: true, eid: ''}];
+
+        netSummaryItem.setProperties({
+          isUpdatedCellularUiEnabled_: true,
+          deviceState: {
+            deviceState: mojom.DeviceStateType.kEnabled,
+            type: mojom.NetworkType.kCellular,
+            simAbsent: false,
+            inhibitReason: mojom.InhibitReason.kNotInhibited,
+            simLockStatus: {lockEnabled: false},
+            simInfos: simInfos,
+          },
+          activeNetworkState: {
+            connectionState: mojom.ConnectionStateType.kNotConnected,
+            guid: '',
+            type: mojom.NetworkType.kCellular,
+            typeState: {cellular: {networkTechnology: ''}}
+          },
+        });
+        Polymer.dom.flush();
+        const networkState = netSummaryItem.$$('#networkState');
+        assertTrue(!!networkState);
+        networkState.click();
+        Polymer.dom.flush();
+        await showNetworksFiredPromise;
+      });
 });
diff --git a/chrome/test/data/webui/settings/privacy_sandbox_browsertest.js b/chrome/test/data/webui/settings/privacy_sandbox_browsertest.js
index ae53c20..be6bfd3 100644
--- a/chrome/test/data/webui/settings/privacy_sandbox_browsertest.js
+++ b/chrome/test/data/webui/settings/privacy_sandbox_browsertest.js
@@ -24,7 +24,7 @@
     return {
       enabled: [
         'blink::features::kInterestCohortAPIOriginTrial',
-        'features::kConversionMeasurement',
+        'blink::features::kConversionMeasurement',
         'features::kPrivacySandboxSettings',
       ]
     };
diff --git a/chromecast/cast_core/BUILD.gn b/chromecast/cast_core/BUILD.gn
index 4547ee7..5b0abbb 100644
--- a/chromecast/cast_core/BUILD.gn
+++ b/chromecast/cast_core/BUILD.gn
@@ -4,20 +4,35 @@
 
 import("//chromecast/chromecast.gni")
 
+cast_source_set("renderer") {
+  sources = [
+    "cast_runtime_content_renderer_client.cc",
+    "cast_runtime_content_renderer_client.h",
+  ]
+
+  deps = [
+    "//base",
+    "//chromecast/renderer",
+    "//components/cast_streaming/renderer",
+    "//content/public/renderer",
+    "//media",
+  ]
+}
+
 cast_source_set("cast_core") {
   sources = [
     "cast_runtime_service.cc",
     "cast_runtime_service.h",
   ]
-
-  if (!enable_cast_media_runtime) {
-    sources += [ "cast_runtime_service_simple.cc" ]
-  }
-
   deps = [
     "//base",
     "//chromecast/service",
   ]
-
   public_deps = [ "//chromecast/media/cma/backend/proxy:headers" ]
+
+  if (!enable_cast_media_runtime) {
+    sources += [ "cast_runtime_service_simple.cc" ]
+  } else {
+    deps += [ ":renderer" ]
+  }
 }
diff --git a/chromecast/cast_core/DEPS b/chromecast/cast_core/DEPS
index b5becb8..207af01 100644
--- a/chromecast/cast_core/DEPS
+++ b/chromecast/cast_core/DEPS
@@ -1,4 +1,8 @@
 include_rules = [
   "+chromecast/media",
+  "+chromecast/renderer",
   '+chromecast/service',
+  '+components/cast_streaming',
+  "+content/public",
+  "+media/base",
 ]
diff --git a/chromecast/cast_core/cast_runtime_content_renderer_client.cc b/chromecast/cast_core/cast_runtime_content_renderer_client.cc
new file mode 100644
index 0000000..d9e898f
--- /dev/null
+++ b/chromecast/cast_core/cast_runtime_content_renderer_client.cc
@@ -0,0 +1,43 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromecast/cast_core/cast_runtime_content_renderer_client.h"
+
+#include "content/public/renderer/render_frame.h"
+#include "content/public/renderer/render_frame_media_playback_options.h"
+#include "media/base/demuxer.h"
+
+namespace chromecast {
+
+// static
+std::unique_ptr<shell::CastContentRendererClient>
+shell::CastContentRendererClient::Create() {
+  return std::make_unique<CastRuntimeContentRendererClient>();
+}
+
+CastRuntimeContentRendererClient::CastRuntimeContentRendererClient() = default;
+
+CastRuntimeContentRendererClient::~CastRuntimeContentRendererClient() = default;
+
+void CastRuntimeContentRendererClient::RenderFrameCreated(
+    content::RenderFrame* render_frame) {
+  CastContentRendererClient::RenderFrameCreated(render_frame);
+  cast_streaming_renderer_client_.RenderFrameCreated(render_frame);
+}
+
+std::unique_ptr<::media::Demuxer>
+CastRuntimeContentRendererClient::OverrideDemuxerForUrl(
+    content::RenderFrame* render_frame,
+    const GURL& url,
+    scoped_refptr<base::SingleThreadTaskRunner> media_task_runner) {
+  if (!render_frame->GetRenderFrameMediaPlaybackOptions()
+           .is_remoting_renderer_enabled()) {
+    return nullptr;
+  }
+
+  return cast_streaming_renderer_client_.OverrideDemuxerForUrl(
+      render_frame, url, std::move(media_task_runner));
+}
+
+}  // namespace chromecast
diff --git a/chromecast/cast_core/cast_runtime_content_renderer_client.h b/chromecast/cast_core/cast_runtime_content_renderer_client.h
new file mode 100644
index 0000000..1c16508
--- /dev/null
+++ b/chromecast/cast_core/cast_runtime_content_renderer_client.h
@@ -0,0 +1,47 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMECAST_CAST_CORE_CAST_RUNTIME_CONTENT_RENDERER_CLIENT_H_
+#define CHROMECAST_CAST_CORE_CAST_RUNTIME_CONTENT_RENDERER_CLIENT_H_
+
+#include <memory>
+
+#include "chromecast/renderer/cast_content_renderer_client.h"
+#include "components/cast_streaming/renderer/public/cast_streaming_content_renderer_client.h"
+
+namespace media {
+class Demuxer;
+}
+
+namespace chromecast {
+
+class CastRuntimeContentRendererClient
+    : public shell::CastContentRendererClient {
+ public:
+  CastRuntimeContentRendererClient();
+  CastRuntimeContentRendererClient(const CastRuntimeContentRendererClient&) =
+      delete;
+  CastRuntimeContentRendererClient(CastRuntimeContentRendererClient&&) = delete;
+  ~CastRuntimeContentRendererClient() override;
+
+  CastRuntimeContentRendererClient& operator=(
+      const CastRuntimeContentRendererClient&) = delete;
+  CastRuntimeContentRendererClient& operator=(
+      CastRuntimeContentRendererClient&&) = delete;
+
+  // content::ContentRendererClient overrides.
+  void RenderFrameCreated(content::RenderFrame* render_frame) override;
+  std::unique_ptr<::media::Demuxer> OverrideDemuxerForUrl(
+      content::RenderFrame* render_frame,
+      const GURL& url,
+      scoped_refptr<base::SingleThreadTaskRunner> media_task_runner) override;
+
+ private:
+  cast_streaming::CastStreamingContentRendererClient
+      cast_streaming_renderer_client_;
+};
+
+}  // namespace chromecast
+
+#endif  // CHROMECAST_CAST_CORE_CAST_RUNTIME_CONTENT_RENDERER_CLIENT_H_
diff --git a/chromecast/renderer/BUILD.gn b/chromecast/renderer/BUILD.gn
index 53141dfb..96b9a256 100644
--- a/chromecast/renderer/BUILD.gn
+++ b/chromecast/renderer/BUILD.gn
@@ -26,6 +26,10 @@
 
 group("renderer") {
   public_deps = [ ":renderer_base" ]
+
+  if (chromecast_branding == "public" && !enable_cast_media_runtime) {
+    public_deps += [ ":simple_client" ]
+  }
 }
 
 cast_source_set("renderer_base") {
diff --git a/chromeos/components/diagnostics_ui/resources/battery_status_card.html b/chromeos/components/diagnostics_ui/resources/battery_status_card.html
index 8a2f1f2..981368e 100644
--- a/chromeos/components/diagnostics_ui/resources/battery_status_card.html
+++ b/chromeos/components/diagnostics_ui/resources/battery_status_card.html
@@ -46,6 +46,7 @@
     additional-message="[[getRunTestsAdditionalMessage(
       batteryChargeStatus_.chargeNowMilliampHours,
       batteryHealth_.chargeFullNowMilliampHours)]]"
-    learn-more-link-section="battery">
+    learn-more-link-section="battery"
+    is-active="[[isActive]]">
   </routine-section>
 </diagnostics-card>
diff --git a/chromeos/components/diagnostics_ui/resources/battery_status_card.js b/chromeos/components/diagnostics_ui/resources/battery_status_card.js
index ac46315..631333e 100644
--- a/chromeos/components/diagnostics_ui/resources/battery_status_card.js
+++ b/chromeos/components/diagnostics_ui/resources/battery_status_card.js
@@ -102,6 +102,11 @@
       type: String,
       computed: 'updateIconClassList_(batteryChargeStatus_.powerAdapterStatus)',
     },
+
+    /** @type {boolean} */
+    isActive: {
+      type: Boolean,
+    },
   },
 
   /** @override */
diff --git a/chromeos/components/diagnostics_ui/resources/connectivity_card.html b/chromeos/components/diagnostics_ui/resources/connectivity_card.html
index 09407be..94420755 100644
--- a/chromeos/components/diagnostics_ui/resources/connectivity_card.html
+++ b/chromeos/components/diagnostics_ui/resources/connectivity_card.html
@@ -8,6 +8,7 @@
   <routine-section slot="routines" routines="[[routines_]]"
   is-test-running="{{isTestRunning}}"
   run-tests-button-text="Run Network test"
-  routine-runtime="{{getEstimateRuntimeInMinutes_(routines_)}}">
+  routine-runtime="{{getEstimateRuntimeInMinutes_(routines_)}}"
+  is-active="[[isActive]]">
 </routine-section>
 </diagnostics-card>
diff --git a/chromeos/components/diagnostics_ui/resources/connectivity_card.js b/chromeos/components/diagnostics_ui/resources/connectivity_card.js
index fda60db..5370f0b 100644
--- a/chromeos/components/diagnostics_ui/resources/connectivity_card.js
+++ b/chromeos/components/diagnostics_ui/resources/connectivity_card.js
@@ -52,6 +52,11 @@
       type: String,
       value: '',
     },
+
+    /** @type {boolean} */
+    isActive: {
+      type: Boolean,
+    },
   },
 
   /** @protected */
diff --git a/chromeos/components/diagnostics_ui/resources/cpu_card.html b/chromeos/components/diagnostics_ui/resources/cpu_card.html
index ba72256..016a9d7 100644
--- a/chromeos/components/diagnostics_ui/resources/cpu_card.html
+++ b/chromeos/components/diagnostics_ui/resources/cpu_card.html
@@ -33,6 +33,7 @@
     is-test-running="{{isTestRunning}}"
     run-tests-button-text="[[i18n('runCpuTestText')]]"
     should-show-caution-banner="true"
-    learn-more-link-section="cpu">
+    learn-more-link-section="cpu"
+    is-active="[[isActive]]">
   </routine-section>
 </diagnostics-card>
diff --git a/chromeos/components/diagnostics_ui/resources/cpu_card.js b/chromeos/components/diagnostics_ui/resources/cpu_card.js
index 3c072a7b..165950e 100644
--- a/chromeos/components/diagnostics_ui/resources/cpu_card.js
+++ b/chromeos/components/diagnostics_ui/resources/cpu_card.js
@@ -71,6 +71,11 @@
       value: false,
       notify: true,
     },
+
+    /** @type {boolean} */
+    isActive: {
+      type: Boolean,
+    },
   },
 
   /** @override */
diff --git a/chromeos/components/diagnostics_ui/resources/memory_card.html b/chromeos/components/diagnostics_ui/resources/memory_card.html
index 29c67c8..d577101 100644
--- a/chromeos/components/diagnostics_ui/resources/memory_card.html
+++ b/chromeos/components/diagnostics_ui/resources/memory_card.html
@@ -13,6 +13,7 @@
     routine-runtime="{{getEstimateRuntimeInMinutes_(routines_, memoryUsage_)}}"
     is-test-running="{{isTestRunning}}"
     run-tests-button-text="[[i18n('runMemoryTestText')]]"
-    learn-more-link-section="memory">
+    learn-more-link-section="memory"
+    is-active="[[isActive]]">
   </routine-section>
 </diagnostics-card>
diff --git a/chromeos/components/diagnostics_ui/resources/memory_card.js b/chromeos/components/diagnostics_ui/resources/memory_card.js
index 7ed887b..64d1f18 100644
--- a/chromeos/components/diagnostics_ui/resources/memory_card.js
+++ b/chromeos/components/diagnostics_ui/resources/memory_card.js
@@ -63,7 +63,12 @@
       type: Boolean,
       value: false,
       notify: true,
-    }
+    },
+
+    /** @type {boolean} */
+    isActive: {
+      type: Boolean,
+    },
   },
 
   /** @override */
diff --git a/chromeos/components/diagnostics_ui/resources/network_list.html b/chromeos/components/diagnostics_ui/resources/network_list.html
index 2cd8a3f..39ffa346 100644
--- a/chromeos/components/diagnostics_ui/resources/network_list.html
+++ b/chromeos/components/diagnostics_ui/resources/network_list.html
@@ -4,7 +4,8 @@
 <div id="networkListContainer" class="diagnostics-cards-container">
   <div class="diagnostics-cards-container">
     <connectivity-card class="card-width" active-guid="[[activeGuid_]]"
-        is-test-running="{{isTestRunning}}">
+        is-test-running="{{isTestRunning}}"
+        is-active="[[isActive]]">
     </connectivity-card>
   </div>
   <dom-repeat id="networkCardList" items="[[otherNetworkGuids_]]" as="guid">
diff --git a/chromeos/components/diagnostics_ui/resources/network_list.js b/chromeos/components/diagnostics_ui/resources/network_list.js
index 81ef5e9..61bf01b 100644
--- a/chromeos/components/diagnostics_ui/resources/network_list.js
+++ b/chromeos/components/diagnostics_ui/resources/network_list.js
@@ -46,6 +46,12 @@
       type: String,
       value: '',
     },
+
+    /** @type {boolean} */
+    isActive: {
+      type: Boolean,
+      value: true,
+    },
   },
 
   /** @override */
@@ -72,4 +78,14 @@
         guid => guid !== networkGuidInfo.activeGuid);
     this.activeGuid_ = networkGuidInfo.activeGuid || '';
   },
+
+  /**
+   * 'navigation-view-panel' is responsible for calling this function when
+   * the active page changes.
+   * @param {{isActive: boolean}} isActive
+   * @public
+   */
+  onNavigationPageChanged({isActive}) {
+    this.isActive = isActive;
+  },
 });
diff --git a/chromeos/components/diagnostics_ui/resources/routine_section.js b/chromeos/components/diagnostics_ui/resources/routine_section.js
index a2b5a8a..3c03a0b 100644
--- a/chromeos/components/diagnostics_ui/resources/routine_section.js
+++ b/chromeos/components/diagnostics_ui/resources/routine_section.js
@@ -160,13 +160,39 @@
       type: Boolean,
       value: false,
     },
+
+    /** @type {boolean} */
+    isActive: {
+      type: Boolean,
+    },
+
+    /**
+     * Used to reset run button text to its initial state
+     * when a navigation page change event occurs.
+     *  @private {string}
+     */
+    initialButtonText_: {
+      type: String,
+      value: '',
+      computed: 'getInitialButtonText_(runTestsButtonText)',
+    },
   },
 
   observers: [
     'routineStatusChanged_(executionStatus_, currentTestName_,' +
         'additionalMessage)',
+    'onActivePageChanged_(isActive)',
   ],
 
+  /**
+   * @param {string} buttonText
+   * @return {string}
+   * @private
+   */
+  getInitialButtonText_(buttonText) {
+    return this.initialButtonText_ || buttonText;
+  },
+
   /** @private */
   getResultListElem_() {
     return /** @type {!RoutineResultListElement} */ (
@@ -450,6 +476,26 @@
         'dismiss-caution-banner', {bubbles: true, composed: true}));
   },
 
+  /** @private */
+  resetRoutineState_() {
+    this.setBadgeAndStatusText_(BadgeType.QUEUED, '', '');
+    this.runTestsButtonText = this.initialButtonText_;
+    this.hasTestFailure_ = false;
+    this.currentTestName_ = '';
+    this.executionStatus_ = ExecutionProgress.kNotStarted;
+    this.$.collapse.hide();
+  },
+
+  /**
+   * Stop any running tests and reset to initial routine state
+   * when the active navigation page changes.
+   * @private
+   */
+  onActivePageChanged_() {
+    this.stopTests_();
+    this.resetRoutineState_();
+  },
+
   /** @override */
   detached() {
     this.cleanUp_();
diff --git a/chromeos/components/diagnostics_ui/resources/system_page.html b/chromeos/components/diagnostics_ui/resources/system_page.html
index b7f0bcd..48788f2 100644
--- a/chromeos/components/diagnostics_ui/resources/system_page.html
+++ b/chromeos/components/diagnostics_ui/resources/system_page.html
@@ -77,18 +77,24 @@
     <template is="dom-if" if="[[showBatteryStatusCard_]]" restamp>
       <div class="card-width">
         <battery-status-card id="batteryStatusCard"
-          is-test-running="{{isTestRunning}}">
+          is-test-running="{{isTestRunning}}"
+          is-active="[[isActive]]"
+          >
         </battery-status-card>
       </div>
     </template>
     <div class="card-width">
       <cpu-card id="cpuCard"
-        is-test-running="{{isTestRunning}}">
+        is-test-running="{{isTestRunning}}"
+        is-active="[[isActive]]"
+        >
       </cpu-card>
     </div>
     <div class="card-width">
       <memory-card id="memoryCard"
-        is-test-running="{{isTestRunning}}">
+        is-test-running="{{isTestRunning}}"
+        is-active="[[isActive]]"
+        >
       </memory-card>
     </div>
   </div>
diff --git a/chromeos/components/diagnostics_ui/resources/system_page.js b/chromeos/components/diagnostics_ui/resources/system_page.js
index 5dbb894..c7be4a2 100644
--- a/chromeos/components/diagnostics_ui/resources/system_page.js
+++ b/chromeos/components/diagnostics_ui/resources/system_page.js
@@ -93,6 +93,12 @@
       type: Number,
       value: -1,
     },
+
+    /** @type {boolean} */
+    isActive: {
+      type: Boolean,
+      value: true,
+    },
   },
 
   /** @override */
@@ -171,4 +177,14 @@
           window.setTimeout(() => this.scrollingClass_ = '', 300);
     });
   },
+
+  /**
+   * 'navigation-view-panel' is responsible for calling this function when
+   * the active page changes.
+   * @param {{isActive: boolean}} isActive
+   * @public
+   */
+  onNavigationPageChanged({isActive}) {
+    this.isActive = isActive;
+  },
 });
diff --git a/chromeos/network/cellular_esim_uninstall_handler.cc b/chromeos/network/cellular_esim_uninstall_handler.cc
index 326f33c..9df59734 100644
--- a/chromeos/network/cellular_esim_uninstall_handler.cc
+++ b/chromeos/network/cellular_esim_uninstall_handler.cc
@@ -262,32 +262,8 @@
 
 void CellularESimUninstallHandler::AttemptDisableProfile() {
   DCHECK_EQ(state_, UninstallState::kDisablingProfile);
-
-  const dbus::ObjectPath& esim_profile_path =
-      *uninstall_requests_.front()->esim_profile_path;
-  HermesProfileClient::Properties* esim_profile_properties =
-      HermesProfileClient::Get()->GetProperties(esim_profile_path);
-
-  if (!esim_profile_properties) {
-    NET_LOG(ERROR) << "eSIM profile not exposed by Hermes. ICCID: "
-                   << GetIccidForCurrentRequest();
-    CompleteCurrentRequest(/*success=*/false);
-    return;
-  }
-
-  if (esim_profile_properties->state().value() !=
-      hermes::profile::State::kActive) {
-    NET_LOG(EVENT) << "eSIM profile with ICCID " << GetIccidForCurrentRequest()
-                   << "is not active and does "
-                   << "not need to be disabled. State: "
-                   << esim_profile_properties->state().value();
-    TransitionToUninstallState(UninstallState::kUninstallingProfile);
-    AttemptUninstallProfile();
-    return;
-  }
-
   HermesProfileClient::Get()->DisableCarrierProfile(
-      esim_profile_path,
+      *uninstall_requests_.front()->esim_profile_path,
       base::BindOnce(&CellularESimUninstallHandler::OnDisableProfile,
                      weak_ptr_factory_.GetWeakPtr()));
 }
diff --git a/chromeos/network/network_connect.cc b/chromeos/network/network_connect.cc
index df6e643d8..bc3fcb8 100644
--- a/chromeos/network/network_connect.cc
+++ b/chromeos/network/network_connect.cc
@@ -393,8 +393,9 @@
 void NetworkConnectImpl::SetTechnologyEnabled(
     const NetworkTypePattern& technology,
     bool enabled_state) {
+  const std::string technology_string = technology.ToDebugString();
   std::string log_string = base::StringPrintf(
-      "technology %s, target state: %s", technology.ToDebugString().c_str(),
+      "technology %s, target state: %s", technology_string.c_str(),
       (enabled_state ? "ENABLED" : "DISABLED"));
   NET_LOG(USER) << "SetTechnologyEnabled: " << log_string;
   NetworkStateHandler* handler = NetworkHandler::Get()->network_state_handler();
@@ -405,6 +406,7 @@
   }
   if (enabled) {
     // User requested to disable the technology.
+    NET_LOG(USER) << __func__ << " " << technology_string << ":" << false;
     handler->SetTechnologyEnabled(technology, false,
                                   network_handler::ErrorCallback());
     return;
@@ -434,6 +436,7 @@
       return;
     }
   }
+  NET_LOG(USER) << __func__ << " " << technology_string << ":" << true;
   handler->SetTechnologyEnabled(technology, true,
                                 network_handler::ErrorCallback());
 }
diff --git a/chromeos/services/BUILD.gn b/chromeos/services/BUILD.gn
index 26fa2c0..e42d1a0 100644
--- a/chromeos/services/BUILD.gn
+++ b/chromeos/services/BUILD.gn
@@ -23,6 +23,7 @@
     "//chromeos/services/cellular_setup:unit_tests",
     "//chromeos/services/cros_healthd/public/cpp:unit_tests",
     "//chromeos/services/device_sync:unit_tests",
+    "//chromeos/services/federated/public/cpp:unit_tests",
     "//chromeos/services/ime:services_unittests",
     "//chromeos/services/ime:unit_tests",
     "//chromeos/services/machine_learning/public/cpp:unit_tests",
diff --git a/chromeos/services/cellular_setup/euicc.cc b/chromeos/services/cellular_setup/euicc.cc
index 1e8afe6..d0eefef1 100644
--- a/chromeos/services/cellular_setup/euicc.cc
+++ b/chromeos/services/cellular_setup/euicc.cc
@@ -62,14 +62,14 @@
     Euicc::RequestPendingProfilesCallback callback) {
   return base::BindOnce(
       [](Euicc::RequestPendingProfilesCallback callback,
-         base::Time installation_start_time,
+         base::Time refresh_profile_start_time,
          mojom::ESimOperationResult result) -> void {
         std::move(callback).Run(result);
         if (result != mojom::ESimOperationResult::kSuccess)
           return;
         UMA_HISTOGRAM_MEDIUM_TIMES(
             "Network.Cellular.ESim.ProfileDiscovery.Latency",
-            base::Time::Now() - installation_start_time);
+            base::Time::Now() - refresh_profile_start_time);
       },
       std::move(callback), base::Time::Now());
 }
@@ -140,9 +140,13 @@
 }
 
 void Euicc::RequestPendingProfiles(RequestPendingProfilesCallback callback) {
-  NET_LOG(EVENT) << "Requesting Pending profiles";
-  esim_manager_->cellular_inhibitor()->InhibitCellularScanning(
-      CellularInhibitor::InhibitReason::kRefreshingProfileList,
+  // Before requesting pending profiles, we also request installed profiles.
+  // This ensures that if an error occurs and Chrome's installed profile cache
+  // goes out of sync with Hermes, we re-sync at this point. See b/187459880 for
+  // details.
+  NET_LOG(EVENT) << "Requesting installed and pending profiles";
+  esim_manager_->cellular_esim_profile_handler()->RefreshProfileList(
+      path_,
       base::BindOnce(
           &Euicc::PerformRequestPendingProfiles, weak_ptr_factory_.GetWeakPtr(),
           CreateTimedRequestPendingProfilesCallback(std::move(callback))));
@@ -346,11 +350,13 @@
     RequestPendingProfilesCallback callback,
     std::unique_ptr<CellularInhibitor::InhibitLock> inhibit_lock) {
   if (!inhibit_lock) {
-    NET_LOG(ERROR) << "Error inhibiting cellular device";
+    NET_LOG(ERROR) << "Error requesting installed profiles. Path: "
+                   << path_.value();
     std::move(callback).Run(mojom::ESimOperationResult::kFailure);
     return;
   }
 
+  NET_LOG(EVENT) << "Requesting pending profiles";
   HermesEuiccClient::Get()->RequestPendingProfiles(
       path_, /*root_smds=*/std::string(),
       base::BindOnce(&Euicc::OnRequestPendingProfilesResult,
diff --git a/chromeos/services/cellular_setup/euicc_unittest.cc b/chromeos/services/cellular_setup/euicc_unittest.cc
index d574fd9..0a04274 100644
--- a/chromeos/services/cellular_setup/euicc_unittest.cc
+++ b/chromeos/services/cellular_setup/euicc_unittest.cc
@@ -241,18 +241,20 @@
 
   base::HistogramTester histogram_tester;
 
-  const uint64_t profile_discovery_latency = 3000;
+  constexpr base::TimeDelta kHermesInteractiveDelay =
+      base::TimeDelta::FromMilliseconds(3000);
   HermesEuiccClient::Get()->GetTestInterface()->SetInteractiveDelay(
-      base::TimeDelta::FromMilliseconds(profile_discovery_latency));
+      kHermesInteractiveDelay);
 
   // Verify that successful request returns correct status code.
   EXPECT_EQ(mojom::ESimOperationResult::kSuccess,
             RequestPendingProfiles(euicc));
 
+  // Before requesting pending profiles, we request installed profiles, so we
+  // expect that there will be 2 delays (one for installed, one for pending).
   histogram_tester.ExpectTimeBucketCount(
       "Network.Cellular.ESim.ProfileDiscovery.Latency",
-      base::TimeDelta::FromMilliseconds(profile_discovery_latency), 1);
-
+      2 * kHermesInteractiveDelay, 1);
   histogram_tester.ExpectTotalCount(
       "Network.Cellular.ESim.ProfileDiscovery.Latency", 1);
 }
diff --git a/chromeos/services/federated/DEPS b/chromeos/services/federated/DEPS
new file mode 100644
index 0000000..2e2e8d99
--- /dev/null
+++ b/chromeos/services/federated/DEPS
@@ -0,0 +1,5 @@
+include_rules = [
+  "+chromeos/dbus",
+  "+mojo/core/embedder",
+  "+mojo/public",
+]
diff --git a/chromeos/services/federated/OWNERS b/chromeos/services/federated/OWNERS
new file mode 100644
index 0000000..e48073a8
--- /dev/null
+++ b/chromeos/services/federated/OWNERS
@@ -0,0 +1,3 @@
+alanlxl@chromium.org
+amoylan@chromium.org
+martis@chromium.org
diff --git a/chromeos/services/federated/public/cpp/BUILD.gn b/chromeos/services/federated/public/cpp/BUILD.gn
new file mode 100644
index 0000000..55d96ae
--- /dev/null
+++ b/chromeos/services/federated/public/cpp/BUILD.gn
@@ -0,0 +1,48 @@
+# Copyright 2021 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+assert(is_chromeos, "Non-ChromeOS builds cannot depend on //chromeos")
+
+source_set("cpp") {
+  sources = [
+    "federated_example_util.cc",
+    "federated_example_util.h",
+    "service_connection.cc",
+    "service_connection.h",
+  ]
+  deps = [
+    "//base",
+    "//chromeos/dbus/federated",
+    "//chromeos/services/federated/public/mojom",
+  ]
+}
+
+source_set("test_support") {
+  testonly = true
+  sources = [
+    "fake_service_connection.cc",
+    "fake_service_connection.h",
+  ]
+  deps = [
+    ":cpp",
+    "//base",
+    "//chromeos/services/federated/public/mojom",
+    "//mojo/public/cpp/bindings",
+  ]
+}
+
+source_set("unit_tests") {
+  testonly = true
+  sources = [ "service_connection_unittest.cc" ]
+  deps = [
+    ":cpp",
+    ":test_support",
+    "//base/test:test_support",
+    "//chromeos/dbus/federated",
+    "//chromeos/services/federated/public/mojom",
+    "//mojo/core/embedder",
+    "//mojo/public/cpp/bindings",
+    "//testing/gtest",
+  ]
+}
diff --git a/chromeos/services/federated/public/cpp/fake_service_connection.cc b/chromeos/services/federated/public/cpp/fake_service_connection.cc
new file mode 100644
index 0000000..6027764
--- /dev/null
+++ b/chromeos/services/federated/public/cpp/fake_service_connection.cc
@@ -0,0 +1,30 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromeos/services/federated/public/cpp/fake_service_connection.h"
+
+namespace chromeos {
+namespace federated {
+
+FakeServiceConnectionImpl::FakeServiceConnectionImpl() = default;
+FakeServiceConnectionImpl::~FakeServiceConnectionImpl() = default;
+
+void FakeServiceConnectionImpl::BindReceiver(
+    mojo::PendingReceiver<mojom::FederatedService> receiver) {
+  Clone(std::move(receiver));
+}
+
+void FakeServiceConnectionImpl::Clone(
+    mojo::PendingReceiver<mojom::FederatedService> receiver) {
+  receivers_.Add(this, std::move(receiver));
+}
+
+void FakeServiceConnectionImpl::ReportExample(const std::string& client_name,
+                                              mojom::ExamplePtr example) {
+  LOG(INFO) << "In FakeServiceConnectionImpl::ReportExample, does nothing";
+  return;
+}
+
+}  // namespace federated
+}  // namespace chromeos
diff --git a/chromeos/services/federated/public/cpp/fake_service_connection.h b/chromeos/services/federated/public/cpp/fake_service_connection.h
new file mode 100644
index 0000000..3db4aa3
--- /dev/null
+++ b/chromeos/services/federated/public/cpp/fake_service_connection.h
@@ -0,0 +1,48 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMEOS_SERVICES_FEDERATED_PUBLIC_CPP_FAKE_SERVICE_CONNECTION_H_
+#define CHROMEOS_SERVICES_FEDERATED_PUBLIC_CPP_FAKE_SERVICE_CONNECTION_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "chromeos/services/federated/public/cpp/service_connection.h"
+#include "chromeos/services/federated/public/mojom/example.mojom.h"
+#include "chromeos/services/federated/public/mojom/federated_service.mojom.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/receiver_set.h"
+
+namespace chromeos {
+namespace federated {
+
+// Fake implementation of chromeos::federated::ServiceConnection.
+// Handles BindReceiver by binding the receiver to itself.
+// For use with ServiceConnection::UseFakeServiceConnectionForTesting().
+class FakeServiceConnectionImpl : public ServiceConnection,
+                                  public mojom::FederatedService {
+ public:
+  FakeServiceConnectionImpl();
+  FakeServiceConnectionImpl(const FakeServiceConnectionImpl&) = delete;
+  FakeServiceConnectionImpl& operator=(const FakeServiceConnectionImpl&) =
+      delete;
+  ~FakeServiceConnectionImpl() override;
+
+  // ServiceConnection:
+  void BindReceiver(
+      mojo::PendingReceiver<mojom::FederatedService> receiver) override;
+
+  // mojom::FederatedService:
+  void Clone(mojo::PendingReceiver<mojom::FederatedService> receiver) override;
+  void ReportExample(const std::string& client_name,
+                     mojom::ExamplePtr example) override;
+
+ private:
+  mojo::ReceiverSet<mojom::FederatedService> receivers_;
+};
+
+}  // namespace federated
+}  // namespace chromeos
+
+#endif  // CHROMEOS_SERVICES_FEDERATED_PUBLIC_CPP_FAKE_SERVICE_CONNECTION_H_
diff --git a/chromeos/services/federated/public/cpp/federated_example_util.cc b/chromeos/services/federated/public/cpp/federated_example_util.cc
new file mode 100644
index 0000000..a46318bf
--- /dev/null
+++ b/chromeos/services/federated/public/cpp/federated_example_util.cc
@@ -0,0 +1,32 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromeos/services/federated/public/cpp/federated_example_util.h"
+
+namespace chromeos {
+namespace federated {
+
+mojom::ValueListPtr CreateInt64List(const std::vector<int64_t>& values) {
+  mojom::ValueListPtr value_list = mojom::ValueList::New();
+  value_list->set_int64_list(mojom::Int64List::New());
+  value_list->get_int64_list()->value = values;
+  return value_list;
+}
+
+mojom::ValueListPtr CreateFloatList(const std::vector<double>& values) {
+  mojom::ValueListPtr value_list = mojom::ValueList::New();
+  value_list->set_float_list(mojom::FloatList::New());
+  value_list->get_float_list()->value = values;
+  return value_list;
+}
+
+mojom::ValueListPtr CreateStringList(const std::vector<std::string>& values) {
+  mojom::ValueListPtr value_list = mojom::ValueList::New();
+  value_list->set_string_list(mojom::StringList::New());
+  value_list->get_string_list()->value = values;
+  return value_list;
+}
+
+}  // namespace federated
+}  // namespace chromeos
diff --git a/chromeos/services/federated/public/cpp/federated_example_util.h b/chromeos/services/federated/public/cpp/federated_example_util.h
new file mode 100644
index 0000000..cafb218
--- /dev/null
+++ b/chromeos/services/federated/public/cpp/federated_example_util.h
@@ -0,0 +1,24 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMEOS_SERVICES_FEDERATED_PUBLIC_CPP_FEDERATED_EXAMPLE_UTIL_H_
+#define CHROMEOS_SERVICES_FEDERATED_PUBLIC_CPP_FEDERATED_EXAMPLE_UTIL_H_
+
+#include <string>
+#include <vector>
+
+#include "chromeos/services/federated/public/mojom/example.mojom.h"
+
+namespace chromeos {
+namespace federated {
+
+// Helper functions for creating different ValueList.
+mojom::ValueListPtr CreateInt64List(const std::vector<int64_t>& values);
+mojom::ValueListPtr CreateFloatList(const std::vector<double>& values);
+mojom::ValueListPtr CreateStringList(const std::vector<std::string>& values);
+
+}  // namespace federated
+}  // namespace chromeos
+
+#endif  // CHROMEOS_SERVICES_FEDERATED_PUBLIC_CPP_FEDERATED_EXAMPLE_UTIL_H_
diff --git a/chromeos/services/federated/public/cpp/service_connection.cc b/chromeos/services/federated/public/cpp/service_connection.cc
new file mode 100644
index 0000000..505f0112
--- /dev/null
+++ b/chromeos/services/federated/public/cpp/service_connection.cc
@@ -0,0 +1,135 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromeos/services/federated/public/cpp/service_connection.h"
+
+#include "base/bind.h"
+#include "base/macros.h"
+#include "base/no_destructor.h"
+#include "base/sequence_checker.h"
+#include "chromeos/dbus/federated/federated_client.h"
+#include "chromeos/services/federated/public/mojom/federated_service.mojom.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "mojo/public/cpp/platform/platform_channel.h"
+#include "mojo/public/cpp/system/invitation.h"
+#include "third_party/cros_system_api/dbus/service_constants.h"
+
+namespace chromeos {
+namespace federated {
+
+namespace {
+
+ServiceConnection* g_fake_service_connection_for_testing = nullptr;
+
+// Real Impl of ServiceConnection
+class ServiceConnectionImpl : public ServiceConnection {
+ public:
+  ServiceConnectionImpl();
+  ServiceConnectionImpl(const ServiceConnectionImpl&) = delete;
+  ServiceConnectionImpl& operator=(const ServiceConnectionImpl&) = delete;
+  ~ServiceConnectionImpl() override = default;
+
+  // ServiceConnection:
+  void BindReceiver(
+      mojo::PendingReceiver<mojom::FederatedService> receiver) override;
+
+ private:
+  // Binds the top level interface |federated_service_| to an
+  // implementation in the Federated Service daemon, if it is not already bound.
+  // The binding is accomplished via D-Bus bootstrap.
+  void BindFederatedServiceIfNeeded();
+
+  // Mojo disconnect handler. Resets |federated_service_|, which
+  // will be reconnected upon next use.
+  void OnMojoDisconnect();
+
+  // Response callback for FederatedClient::BootstrapMojoConnection.
+  void OnBootstrapMojoConnectionResponse(bool success);
+
+  mojo::Remote<mojom::FederatedService> federated_service_;
+
+  SEQUENCE_CHECKER(sequence_checker_);
+};
+
+ServiceConnectionImpl::ServiceConnectionImpl() {
+  DETACH_FROM_SEQUENCE(sequence_checker_);
+}
+
+void ServiceConnectionImpl::BindReceiver(
+    mojo::PendingReceiver<mojom::FederatedService> receiver) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  BindFederatedServiceIfNeeded();
+  federated_service_->Clone(std::move(receiver));
+}
+
+void ServiceConnectionImpl::BindFederatedServiceIfNeeded() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (federated_service_) {
+    return;
+  }
+
+  mojo::PlatformChannel platform_channel;
+
+  // Prepare a Mojo invitation to send through |platform_channel|.
+  mojo::OutgoingInvitation invitation;
+  // Include an initial Mojo pipe in the invitation.
+  mojo::ScopedMessagePipeHandle pipe = invitation.AttachMessagePipe(
+      ::federated::kBootstrapMojoConnectionChannelToken);
+  mojo::OutgoingInvitation::Send(std::move(invitation),
+                                 base::kNullProcessHandle,
+                                 platform_channel.TakeLocalEndpoint());
+
+  // Bind our end of |pipe| to our mojo::Remote<FederatedService>. The daemon
+  // should bind its end to a FederatedService implementation.
+  federated_service_.Bind(mojo::PendingRemote<mojom::FederatedService>(
+      std::move(pipe), 0u /* version */));
+  federated_service_.set_disconnect_handler(base::BindOnce(
+      &ServiceConnectionImpl::OnMojoDisconnect, base::Unretained(this)));
+
+  // Send the file descriptor for the other end of |platform_channel| to the
+  // Federated service daemon over D-Bus.
+  FederatedClient::Get()->BootstrapMojoConnection(
+      platform_channel.TakeRemoteEndpoint().TakePlatformHandle().TakeFD(),
+      base::BindOnce(&ServiceConnectionImpl::OnBootstrapMojoConnectionResponse,
+                     base::Unretained(this)));
+}
+
+void ServiceConnectionImpl::OnMojoDisconnect() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  // Connection errors are not expected so log a warning.
+  LOG(WARNING) << "Federated Service Mojo connection closed";
+  federated_service_.reset();
+}
+
+void ServiceConnectionImpl::OnBootstrapMojoConnectionResponse(
+    const bool success) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (!success) {
+    LOG(WARNING) << "BootstrapMojoConnection D-Bus call failed";
+    federated_service_.reset();
+  }
+}
+
+}  // namespace
+
+ServiceConnection* ServiceConnection::GetInstance() {
+  if (g_fake_service_connection_for_testing) {
+    return g_fake_service_connection_for_testing;
+  }
+  static base::NoDestructor<ServiceConnectionImpl> service_connection;
+  return service_connection.get();
+}
+
+ScopedFakeServiceConnectionForTest::ScopedFakeServiceConnectionForTest(
+    ServiceConnection* fake_service_connection) {
+  DCHECK(!g_fake_service_connection_for_testing);
+  g_fake_service_connection_for_testing = fake_service_connection;
+}
+
+ScopedFakeServiceConnectionForTest::~ScopedFakeServiceConnectionForTest() {
+  g_fake_service_connection_for_testing = nullptr;
+}
+
+}  // namespace federated
+}  // namespace chromeos
diff --git a/chromeos/services/federated/public/cpp/service_connection.h b/chromeos/services/federated/public/cpp/service_connection.h
new file mode 100644
index 0000000..4f3c1b0b
--- /dev/null
+++ b/chromeos/services/federated/public/cpp/service_connection.h
@@ -0,0 +1,59 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMEOS_SERVICES_FEDERATED_PUBLIC_CPP_SERVICE_CONNECTION_H_
+#define CHROMEOS_SERVICES_FEDERATED_PUBLIC_CPP_SERVICE_CONNECTION_H_
+
+#include "chromeos/services/federated/public/mojom/federated_service.mojom.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+
+namespace chromeos {
+namespace federated {
+
+// Encapsulates a connection to the Chrome OS Federated Service daemon via its
+// Mojo interface. Usage:
+//  mojo::Remote<FederatedService> federated_service;
+//  chromeos::federated::ServiceConnection::GetInstance()->BindReceiver(
+//        federated_service.BindNewPipeAndPassReceiver());
+//  if (federated_service) {
+//    chromeos::federated::mojom::ExamplePtr example = ...;
+//    const std::string client_name = ...;
+//    federated_service->ReportExample(client_name, std::move(example));
+//  } else {
+//    // error handler
+//  }
+//
+// Sequencing: Must be used on a single sequence (may be created on another).
+class ServiceConnection {
+ public:
+  static ServiceConnection* GetInstance();
+
+  // Binds the receiver to the implementation in the Federated Service daemon.
+  virtual void BindReceiver(
+      mojo::PendingReceiver<mojom::FederatedService> receiver) = 0;
+
+ protected:
+  ServiceConnection() = default;
+  virtual ~ServiceConnection() = default;
+};
+
+// Helper class that sets a global fake service_connection pointer and
+// automatically clean up when it goes out of the scope.
+// Used in unit_test only to inject fake to ServiceConnection::GetInstance().
+class ScopedFakeServiceConnectionForTest {
+ public:
+  explicit ScopedFakeServiceConnectionForTest(
+      ServiceConnection* fake_service_connection);
+  ScopedFakeServiceConnectionForTest(
+      const ScopedFakeServiceConnectionForTest&) = delete;
+  ScopedFakeServiceConnectionForTest& operator=(
+      const ScopedFakeServiceConnectionForTest&) = delete;
+
+  ~ScopedFakeServiceConnectionForTest();
+};
+
+}  // namespace federated
+}  // namespace chromeos
+
+#endif  // CHROMEOS_SERVICES_FEDERATED_PUBLIC_CPP_SERVICE_CONNECTION_H_
diff --git a/chromeos/services/federated/public/cpp/service_connection_unittest.cc b/chromeos/services/federated/public/cpp/service_connection_unittest.cc
new file mode 100644
index 0000000..432466e
--- /dev/null
+++ b/chromeos/services/federated/public/cpp/service_connection_unittest.cc
@@ -0,0 +1,86 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromeos/services/federated/public/cpp/service_connection.h"
+
+#include <vector>
+
+#include "base/macros.h"
+#include "base/run_loop.h"
+#include "base/test/task_environment.h"
+#include "base/threading/thread.h"
+#include "chromeos/dbus/federated/federated_client.h"
+#include "chromeos/services/federated/public/cpp/fake_service_connection.h"
+#include "chromeos/services/federated/public/cpp/federated_example_util.h"
+#include "chromeos/services/federated/public/mojom/example.mojom.h"
+#include "mojo/core/embedder/scoped_ipc_support.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace chromeos {
+namespace federated {
+namespace {
+
+using chromeos::federated::mojom::Example;
+using chromeos::federated::mojom::ExamplePtr;
+using chromeos::federated::mojom::Features;
+
+// Create an ExamplePtr for testing. It must contain real value to avoid
+// VALIDATION_ERROR_UNEXPECTED_NULL_POINTER failure in dry run.
+ExamplePtr CreateExample() {
+  ExamplePtr example = Example::New();
+  example->features = Features::New();
+  auto& feature_map = example->features->feature;
+  feature_map["int_feature1"] = CreateInt64List({1, 2, 3, 4, 5});
+  feature_map["int_feature2"] = CreateInt64List({10, 20, 30, 40, 50});
+
+  return example;
+}
+
+class FederatedServiceConnectionTest : public testing::Test {
+ public:
+  FederatedServiceConnectionTest() = default;
+  FederatedServiceConnectionTest(const FederatedServiceConnectionTest&) =
+      delete;
+  FederatedServiceConnectionTest& operator=(
+      const FederatedServiceConnectionTest&) = delete;
+
+  // testing::Test:
+  void SetUp() override { FederatedClient::InitializeFake(); }
+
+  void TearDown() override { FederatedClient::Shutdown(); }
+
+ private:
+  base::test::TaskEnvironment task_environment_;
+  mojo::core::ScopedIPCSupport ipc_support_{
+      task_environment_.GetMainThreadTaskRunner(),
+      mojo::core::ScopedIPCSupport::ShutdownPolicy::CLEAN};
+};
+
+// Tests that BindReceiver runs OK (no crash) in a basic Mojo environment.
+TEST_F(FederatedServiceConnectionTest, ReportExample) {
+  mojo::Remote<mojom::FederatedService> federated_service;
+  chromeos::federated::ServiceConnection::GetInstance()->BindReceiver(
+      federated_service.BindNewPipeAndPassReceiver());
+}
+
+// Tests that FakeServiceConnection can handle BindReceiver and the bound
+// receiver can call ReportExample successfully.
+TEST_F(FederatedServiceConnectionTest, FakeServiceConnection) {
+  FakeServiceConnectionImpl fake_service_connection;
+  ScopedFakeServiceConnectionForTest scoped_fake_for_test(
+      &fake_service_connection);
+
+  mojo::Remote<mojom::FederatedService> federated_service;
+  chromeos::federated::ServiceConnection::GetInstance()->BindReceiver(
+      federated_service.BindNewPipeAndPassReceiver());
+  EXPECT_TRUE(federated_service.is_bound());
+  EXPECT_TRUE(federated_service.is_connected());
+
+  federated_service->ReportExample("test_client", CreateExample());
+  base::RunLoop().RunUntilIdle();
+}
+
+}  // namespace
+}  // namespace federated
+}  // namespace chromeos
diff --git a/chromeos/services/machine_learning/public/mojom/soda.mojom b/chromeos/services/machine_learning/public/mojom/soda.mojom
index c134078..f501923 100644
--- a/chromeos/services/machine_learning/public/mojom/soda.mojom
+++ b/chromeos/services/machine_learning/public/mojom/soda.mojom
@@ -13,11 +13,22 @@
 // clients (Chromium, other downstream repos) later.
 // Use //chromeos/services/machine_learning/public/mojom/roll_mojom.sh to help
 // replicate Chrome OS-side changes over to Chromium.
-
+// Versions list:
+// Version 0: Initial
+// Version 1: Include HypothesisPart Info in Final result.
+// Version 2: Include enable formatting in request config.
 module chromeos.machine_learning.mojom;
 
 import "mojo/public/mojom/base/time.mojom";
 
+// Augments a bool to include an 'unknown' value.
+[Stable, Extensible]
+enum OptionalBool {
+  [Default] kUnknown = 0,
+  kFalse,
+  kTrue,
+};
+
 // The configuration used to load Soda recognizer.
 [Stable]
 struct SodaConfig {
@@ -31,6 +42,9 @@
   string library_dlc_path;
   // Path to already-installed SODA language pack to use.
   string language_dlc_path;
+  // Whether to enable automated punctuation. Defaults to true as this
+  // is the default in the underlying protocol buffer.
+  [MinVersion=2] OptionalBool enable_formatting = kTrue;
 };
 
 // From the endpointer, What kind of endpointer event to record.
diff --git a/chromeos/services/network_config/cros_network_config.cc b/chromeos/services/network_config/cros_network_config.cc
index c9bc907..9ae3bb3 100644
--- a/chromeos/services/network_config/cros_network_config.cc
+++ b/chromeos/services/network_config/cros_network_config.cc
@@ -22,6 +22,7 @@
 #include "chromeos/network/managed_network_configuration_handler.h"
 #include "chromeos/network/network_connection_handler.h"
 #include "chromeos/network/network_device_handler.h"
+#include "chromeos/network/network_event_log.h"
 #include "chromeos/network/network_handler.h"
 #include "chromeos/network/network_metadata_store.h"
 #include "chromeos/network/network_name_util.h"
@@ -432,36 +433,14 @@
   return sim_info_mojos;
 }
 
-bool IsCellularConnecting(NetworkStateHandler* network_state_handler) {
-  NetworkStateHandler::NetworkStateList cellular_networks;
-  network_state_handler->GetVisibleNetworkListByType(
-      NetworkTypePattern::Cellular(), &cellular_networks);
-  auto iter = std::find_if(cellular_networks.begin(), cellular_networks.end(),
-                           [](const NetworkState* network_state) {
-                             return network_state->IsConnectingState();
-                           });
-  return iter != cellular_networks.end();
-}
-
-mojom::InhibitReason GetInhibitReason(
-    NetworkStateHandler* network_state_handler,
-    CellularInhibitor* cellular_inhibitor) {
+mojom::InhibitReason GetInhibitReason(CellularInhibitor* cellular_inhibitor) {
   if (!cellular_inhibitor)
     return mojom::InhibitReason::kNotInhibited;
 
   absl::optional<CellularInhibitor::InhibitReason> inhibit_reason =
       cellular_inhibitor->GetInhibitReason();
-  if (!inhibit_reason) {
-    // For devices with EUICC, the UI should be inhibited when a cellular
-    // network connection is in progress to prevent additional requests. This is
-    // due to complexity in switching slots.
-    if (!chromeos::HermesManagerClient::Get()->GetAvailableEuiccs().empty() &&
-        IsCellularConnecting(network_state_handler)) {
-      return mojom::InhibitReason::kConnectingToProfile;
-    }
-
+  if (!inhibit_reason)
     return mojom::InhibitReason::kNotInhibited;
-  }
 
   switch (*inhibit_reason) {
     case CellularInhibitor::InhibitReason::kInstallingProfile:
@@ -479,7 +458,6 @@
 
 mojom::DeviceStatePropertiesPtr DeviceStateToMojo(
     const DeviceState* device,
-    NetworkStateHandler* network_state_handler,
     CellularInhibitor* cellular_inhibitor,
     mojom::DeviceStateType technology_state) {
   mojom::NetworkType type = ShillTypeToMojo(device->type());
@@ -525,8 +503,7 @@
   }
   if (type == mojom::NetworkType::kCellular) {
     result->sim_infos = CellularSIMInfosToMojo(device);
-    result->inhibit_reason =
-        GetInhibitReason(network_state_handler, cellular_inhibitor);
+    result->inhibit_reason = GetInhibitReason(cellular_inhibitor);
   }
   return result;
 }
@@ -1981,8 +1958,8 @@
       NET_LOG(ERROR) << "Device state unavailable: " << device->name();
       continue;
     }
-    mojom::DeviceStatePropertiesPtr mojo_device = DeviceStateToMojo(
-        device, network_state_handler_, cellular_inhibitor_, technology_state);
+    mojom::DeviceStatePropertiesPtr mojo_device =
+        DeviceStateToMojo(device, cellular_inhibitor_, technology_state);
     if (mojo_device)
       result.emplace_back(std::move(mojo_device));
   }
@@ -2369,6 +2346,9 @@
     std::move(callback).Run(false);
     return;
   }
+
+  NET_LOG(USER) << __func__ << " " << type << ":" << enabled;
+
   // Set the technology enabled state and return true. The call to Shill does
   // not have a 'success' callback (and errors are already logged).
   network_state_handler_->SetTechnologyEnabled(
diff --git a/chromeos/services/network_config/cros_network_config_unittest.cc b/chromeos/services/network_config/cros_network_config_unittest.cc
index 6b3046d..acc1de0b 100644
--- a/chromeos/services/network_config/cros_network_config_unittest.cc
+++ b/chromeos/services/network_config/cros_network_config_unittest.cc
@@ -1344,39 +1344,6 @@
   EXPECT_EQ(mojom::InhibitReason::kInstallingProfile, cellular->inhibit_reason);
 }
 
-TEST_F(CrosNetworkConfigTest, CellularInhibitState_Connecting) {
-  const char kTestEuiccPath[] = "euicc_path";
-  mojom::DeviceStatePropertiesPtr cellular =
-      GetDeviceStateFromList(mojom::NetworkType::kCellular);
-  ASSERT_TRUE(cellular);
-  EXPECT_EQ(mojom::DeviceStateType::kEnabled, cellular->device_state);
-  EXPECT_EQ(mojom::InhibitReason::kNotInhibited, cellular->inhibit_reason);
-
-  // Set connect requested on cellular network.
-  NetworkStateHandler* network_state_handler =
-      NetworkHandler::Get()->network_state_handler();
-  const NetworkState* network_state =
-      network_state_handler->GetNetworkStateFromGuid("cellular_guid");
-  network_state_handler->SetNetworkConnectRequested(network_state->path(),
-                                                    true);
-
-  // Verify the inhibit state is not set when connecting if there are no EUICC.
-  cellular = GetDeviceStateFromList(mojom::NetworkType::kCellular);
-  ASSERT_TRUE(cellular);
-  EXPECT_EQ(mojom::DeviceStateType::kEnabled, cellular->device_state);
-  EXPECT_EQ(mojom::InhibitReason::kNotInhibited, cellular->inhibit_reason);
-
-  // Verify the adding EUICC sets the inhibit reason correctly.
-  helper()->hermes_manager_test()->AddEuicc(dbus::ObjectPath(kTestEuiccPath),
-                                            "eid", /*is_active=*/true,
-                                            /*physical_slot=*/0);
-  cellular = GetDeviceStateFromList(mojom::NetworkType::kCellular);
-  ASSERT_TRUE(cellular);
-  EXPECT_EQ(mojom::DeviceStateType::kEnabled, cellular->device_state);
-  EXPECT_EQ(mojom::InhibitReason::kConnectingToProfile,
-            cellular->inhibit_reason);
-}
-
 TEST_F(CrosNetworkConfigTest, SetCellularSimState) {
   // Assert initial state.
   mojom::DeviceStatePropertiesPtr cellular =
diff --git a/components/arc/net/arc_net_host_impl.cc b/components/arc/net/arc_net_host_impl.cc
index d71698f..d91861b 100644
--- a/components/arc/net/arc_net_host_impl.cc
+++ b/components/arc/net/arc_net_host_impl.cc
@@ -18,6 +18,7 @@
 #include "chromeos/network/managed_network_configuration_handler.h"
 #include "chromeos/network/network_configuration_handler.h"
 #include "chromeos/network/network_connection_handler.h"
+#include "chromeos/network/network_event_log.h"
 #include "chromeos/network/network_handler.h"
 #include "chromeos/network/network_state.h"
 #include "chromeos/network/network_state_handler.h"
@@ -646,6 +647,7 @@
     return;
   }
 
+  NET_LOG(USER) << __func__ << ":" << is_enabled;
   GetStateHandler()->SetTechnologyEnabled(
       chromeos::NetworkTypePattern::WiFi(), is_enabled,
       chromeos::network_handler::ErrorCallback());
diff --git a/components/autofill_payments_strings.grdp b/components/autofill_payments_strings.grdp
index 8c72cf4..1684adc 100644
--- a/components/autofill_payments_strings.grdp
+++ b/components/autofill_payments_strings.grdp
@@ -93,6 +93,9 @@
     <message name="IDS_AUTOFILL_SAVE_CARD_PROMPT_CONFIRM" desc="Text to show for the Autofill upload save credit card prompt accept button when more information (e.g., CVC) was needed in order to save the card and was entered." formatter_data="android_java">
       Confirm
     </message>
+    <message name="IDS_AUTOFILL_MOBILE_SAVE_CARD_TO_CLOUD_CONFIRMATION_DIALOG_TITLE" desc="This message appears as the title of a confirmation dialog in which the user can view/edit the information that will be saved to Google Payments, and only appears after the user has expressed interest in uploading the card.">
+      Save to Google Account
+    </message>
   </if>
   <if expr="is_ios">
     <then>
diff --git a/components/autofill_payments_strings_grdp/IDS_AUTOFILL_MOBILE_SAVE_CARD_TO_CLOUD_CONFIRMATION_DIALOG_TITLE.png.sha1 b/components/autofill_payments_strings_grdp/IDS_AUTOFILL_MOBILE_SAVE_CARD_TO_CLOUD_CONFIRMATION_DIALOG_TITLE.png.sha1
new file mode 100644
index 0000000..76813da
--- /dev/null
+++ b/components/autofill_payments_strings_grdp/IDS_AUTOFILL_MOBILE_SAVE_CARD_TO_CLOUD_CONFIRMATION_DIALOG_TITLE.png.sha1
@@ -0,0 +1 @@
+37c8c340a5db76c7b4183c3bf42f84a3b948074a
\ No newline at end of file
diff --git a/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/displaystyle/ViewResizer.java b/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/displaystyle/ViewResizer.java
index 67c5ab9b..7fa6d82 100644
--- a/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/displaystyle/ViewResizer.java
+++ b/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/displaystyle/ViewResizer.java
@@ -21,9 +21,9 @@
     private int mDefaultPaddingPixels;
     /** The minimum wide display value used for the lateral padding. */
     private int mMinWidePaddingPixels;
-    private final View mView;
+    protected final View mView;
     private final DisplayStyleObserverAdapter mDisplayStyleObserver;
-    private final UiConfig mUiConfig;
+    protected final UiConfig mUiConfig;
 
     @HorizontalDisplayStyle
     private int mCurrentDisplayStyle;
diff --git a/components/chromeos_camera/fake_mjpeg_decode_accelerator.cc b/components/chromeos_camera/fake_mjpeg_decode_accelerator.cc
index 9445e15..19b47a37 100644
--- a/components/chromeos_camera/fake_mjpeg_decode_accelerator.cc
+++ b/components/chromeos_camera/fake_mjpeg_decode_accelerator.cc
@@ -10,6 +10,7 @@
 #include "base/logging.h"
 #include "base/single_thread_task_runner.h"
 #include "base/threading/thread_task_runner_handle.h"
+#include "media/base/bind_to_current_loop.h"
 #include "media/base/unaligned_shared_memory.h"
 #include "media/base/video_frame.h"
 #include "media/base/video_types.h"
@@ -26,18 +27,32 @@
   DCHECK(client_task_runner_->BelongsToCurrentThread());
 }
 
-bool FakeMjpegDecodeAccelerator::Initialize(
-    MjpegDecodeAccelerator::Client* client) {
+void FakeMjpegDecodeAccelerator::InitializeOnTaskRunner(
+    MjpegDecodeAccelerator::Client* client,
+    InitCB init_cb) {
   DCHECK(client_task_runner_->BelongsToCurrentThread());
   client_ = client;
 
   if (!decoder_thread_.Start()) {
     DLOG(ERROR) << "Failed to start decoding thread.";
-    return false;
+    std::move(init_cb).Run(false);
+    return;
   }
-  decoder_task_runner_ = decoder_thread_.task_runner();
 
-  return true;
+  decoder_task_runner_ = decoder_thread_.task_runner();
+  std::move(init_cb).Run(true);
+}
+
+void FakeMjpegDecodeAccelerator::InitializeAsync(
+    MjpegDecodeAccelerator::Client* client,
+    InitCB init_cb) {
+  DCHECK(client_task_runner_->BelongsToCurrentThread());
+
+  client_task_runner_->PostTask(
+      FROM_HERE,
+      base::BindOnce(&FakeMjpegDecodeAccelerator::InitializeOnTaskRunner,
+                     weak_factory_.GetWeakPtr(), client,
+                     media::BindToCurrentLoop(std::move(init_cb))));
 }
 
 void FakeMjpegDecodeAccelerator::Decode(
diff --git a/components/chromeos_camera/fake_mjpeg_decode_accelerator.h b/components/chromeos_camera/fake_mjpeg_decode_accelerator.h
index ea51eb55..c1a71c7 100644
--- a/components/chromeos_camera/fake_mjpeg_decode_accelerator.h
+++ b/components/chromeos_camera/fake_mjpeg_decode_accelerator.h
@@ -31,7 +31,8 @@
   ~FakeMjpegDecodeAccelerator() override;
 
   // MjpegDecodeAccelerator implementation.
-  bool Initialize(MjpegDecodeAccelerator::Client* client) override;
+  void InitializeAsync(chromeos_camera::MjpegDecodeAccelerator::Client* client,
+                       InitCB init_cb) override;
   void Decode(media::BitstreamBuffer bitstream_buffer,
               scoped_refptr<media::VideoFrame> video_frame) override;
   void Decode(int32_t task_id,
@@ -50,6 +51,10 @@
   void NotifyErrorOnClientThread(int32_t task_id, Error error);
   void OnDecodeDoneOnClientThread(int32_t task_id);
 
+  void InitializeOnTaskRunner(
+      chromeos_camera::MjpegDecodeAccelerator::Client* client,
+      InitCB init_cb);
+
   // Task runner for calls to |client_|.
   const scoped_refptr<base::SingleThreadTaskRunner> client_task_runner_;
 
diff --git a/components/chromeos_camera/mjpeg_decode_accelerator.h b/components/chromeos_camera/mjpeg_decode_accelerator.h
index 58890e8..aee32513 100644
--- a/components/chromeos_camera/mjpeg_decode_accelerator.h
+++ b/components/chromeos_camera/mjpeg_decode_accelerator.h
@@ -91,16 +91,14 @@
   virtual ~MjpegDecodeAccelerator() = 0;
 
   // Initializes the MJPEG decoder. Should be called once per decoder
-  // construction. This call is synchronous and returns true iff initialization
-  // is successful.
-  // Parameters:
+  // construction. This call is asynchronous and executes |init_cb| upon
+  // completion. Parameters:
   //  |client| is the Client interface for decode callback. The provided
   //  pointer must be valid until destructor is called.
-  virtual bool Initialize(Client* client) = 0;
-
-  // TODO(c.padhi): Remove the sync version and rename this to Initialize.
-  // Async version of above Initialize(..) function. Executes the |init_cb|
-  // upon completion.
+  //  |init_cb| is the MJPEG decoder initialization status report callback.
+  //
+  // |init_cb| is called on the same thread as InitializeAsync() and can
+  // potentially be called even after the MjpegDecodeAccelerator destructor.
   virtual void InitializeAsync(Client* client, InitCB init_cb) {}
 
   // Decodes the given bitstream buffer that contains one JPEG frame. It
diff --git a/components/chromeos_camera/mjpeg_decode_accelerator_unittest.cc b/components/chromeos_camera/mjpeg_decode_accelerator_unittest.cc
index b550641..2a7f60d 100644
--- a/components/chromeos_camera/mjpeg_decode_accelerator_unittest.cc
+++ b/components/chromeos_camera/mjpeg_decode_accelerator_unittest.cc
@@ -533,6 +533,7 @@
   FRIEND_TEST_ALL_PREFIXES(JpegClientTest, GetMeanAbsoluteDifference);
 
   void SetState(ClientState new_state);
+  void OnInitialize(bool initialize_result);
 
   // Save a video frame that contains a decoded JPEG. The output is a PNG file.
   // The suffix will be added before the .png extension.
@@ -582,6 +583,8 @@
   std::vector<base::TimeDelta> decode_times_;
   std::vector<base::TimeDelta> decode_map_times_;
 
+  base::WeakPtrFactory<JpegClient> weak_factory_{this};
+
   DISALLOW_COPY_AND_ASSIGN(JpegClient);
 };
 
@@ -621,12 +624,18 @@
     return;
   }
 
-  if (!decoder_->Initialize(this)) {
-    LOG(ERROR) << "MjpegDecodeAccelerator::Initialize() failed";
-    SetState(CS_ERROR);
+  decoder_->InitializeAsync(this, base::BindOnce(&JpegClient::OnInitialize,
+                                                 weak_factory_.GetWeakPtr()));
+}
+
+void JpegClient::OnInitialize(bool initialize_result) {
+  if (initialize_result) {
+    SetState(CS_INITIALIZED);
     return;
   }
-  SetState(CS_INITIALIZED);
+
+  LOG(ERROR) << "MjpegDecodeAccelerator::InitializeAsync() failed";
+  SetState(CS_ERROR);
 }
 
 void JpegClient::VideoFrameReady(int32_t task_id) {
diff --git a/components/chromeos_camera/mojo_mjpeg_decode_accelerator.cc b/components/chromeos_camera/mojo_mjpeg_decode_accelerator.cc
index bb62c48..d2c959d 100644
--- a/components/chromeos_camera/mojo_mjpeg_decode_accelerator.cc
+++ b/components/chromeos_camera/mojo_mjpeg_decode_accelerator.cc
@@ -26,12 +26,6 @@
   DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
 }
 
-bool MojoMjpegDecodeAccelerator::Initialize(
-    MjpegDecodeAccelerator::Client* /*client*/) {
-  NOTIMPLEMENTED();
-  return false;
-}
-
 void MojoMjpegDecodeAccelerator::InitializeAsync(Client* client,
                                                  InitCB init_cb) {
   DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
diff --git a/components/chromeos_camera/mojo_mjpeg_decode_accelerator.h b/components/chromeos_camera/mojo_mjpeg_decode_accelerator.h
index 0dfb85ee..d2e16ca8 100644
--- a/components/chromeos_camera/mojo_mjpeg_decode_accelerator.h
+++ b/components/chromeos_camera/mojo_mjpeg_decode_accelerator.h
@@ -33,7 +33,6 @@
   // MjpegDecodeAccelerator implementation.
   // |client| is called on the IO thread, but is never called into after the
   // MojoMjpegDecodeAccelerator is destroyed.
-  bool Initialize(Client* client) override;
   void InitializeAsync(Client* client, InitCB init_cb) override;
   void Decode(media::BitstreamBuffer bitstream_buffer,
               scoped_refptr<media::VideoFrame> video_frame) override;
diff --git a/components/chromeos_camera/mojo_mjpeg_decode_accelerator_service.cc b/components/chromeos_camera/mojo_mjpeg_decode_accelerator_service.cc
index 9366ab0..592683b 100644
--- a/components/chromeos_camera/mojo_mjpeg_decode_accelerator_service.cc
+++ b/components/chromeos_camera/mojo_mjpeg_decode_accelerator_service.cc
@@ -71,11 +71,11 @@
 }
 
 MojoMjpegDecodeAcceleratorService::MojoMjpegDecodeAcceleratorService()
-    : accelerator_factory_functions_(
-          GpuMjpegDecodeAcceleratorFactory::GetAcceleratorFactories()) {}
+    : accelerator_initialized_(false), weak_this_factory_(this) {}
 
 MojoMjpegDecodeAcceleratorService::~MojoMjpegDecodeAcceleratorService() {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  accelerator_.reset();
 }
 
 void MojoMjpegDecodeAcceleratorService::VideoFrameReady(
@@ -93,6 +93,32 @@
   NotifyDecodeStatus(bitstream_buffer_id, error);
 }
 
+void MojoMjpegDecodeAcceleratorService::InitializeInternal(
+    std::vector<GpuMjpegDecodeAcceleratorFactory::CreateAcceleratorCB>
+        remaining_accelerator_factory_functions,
+    InitializeCallback init_cb) {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  if (remaining_accelerator_factory_functions.empty()) {
+    DLOG(ERROR) << "All JPEG accelerators failed to initialize";
+    std::move(init_cb).Run(false);
+    return;
+  }
+  accelerator_ = std::move(remaining_accelerator_factory_functions.front())
+                     .Run(base::ThreadTaskRunnerHandle::Get());
+  remaining_accelerator_factory_functions.erase(
+      remaining_accelerator_factory_functions.begin());
+  if (!accelerator_) {
+    OnInitialize(std::move(remaining_accelerator_factory_functions),
+                 std::move(init_cb), /*last_initialize_result=*/false);
+    return;
+  }
+  accelerator_->InitializeAsync(
+      this, base::BindOnce(&MojoMjpegDecodeAcceleratorService::OnInitialize,
+                           weak_this_factory_.GetWeakPtr(),
+                           std::move(remaining_accelerator_factory_functions),
+                           std::move(init_cb)));
+}
+
 void MojoMjpegDecodeAcceleratorService::Initialize(
     InitializeCallback callback) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
@@ -100,24 +126,33 @@
   // When adding non-chromeos platforms, VideoCaptureGpuJpegDecoder::Initialize
   // needs to be updated.
 
-  std::unique_ptr<::chromeos_camera::MjpegDecodeAccelerator> accelerator;
-  for (auto& create_jda_function : accelerator_factory_functions_) {
-    std::unique_ptr<::chromeos_camera::MjpegDecodeAccelerator> tmp_accelerator =
-        std::move(create_jda_function).Run(base::ThreadTaskRunnerHandle::Get());
-    if (tmp_accelerator && tmp_accelerator->Initialize(this)) {
-      accelerator = std::move(tmp_accelerator);
-      break;
-    }
-  }
+  InitializeInternal(
+      GpuMjpegDecodeAcceleratorFactory::GetAcceleratorFactories(),
+      std::move(callback));
+}
 
-  if (!accelerator) {
-    DLOG(ERROR) << "JPEG accelerator initialization failed";
-    std::move(callback).Run(false);
+void MojoMjpegDecodeAcceleratorService::OnInitialize(
+    std::vector<GpuMjpegDecodeAcceleratorFactory::CreateAcceleratorCB>
+        remaining_accelerator_factory_functions,
+    InitializeCallback init_cb,
+    bool last_initialize_result) {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+
+  if (last_initialize_result) {
+    accelerator_initialized_ = true;
+    std::move(init_cb).Run(true);
     return;
   }
-
-  accelerator_ = std::move(accelerator);
-  std::move(callback).Run(true);
+  // Note that we can't call InitializeInternal() directly. The reason is that
+  // InitializeInternal() may destroy |accelerator_| which could cause a
+  // use-after-free if |accelerator_| needs to do more stuff after calling
+  // OnInitialize().
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE,
+      base::BindOnce(&MojoMjpegDecodeAcceleratorService::InitializeInternal,
+                     weak_this_factory_.GetWeakPtr(),
+                     std::move(remaining_accelerator_factory_functions),
+                     std::move(init_cb)));
 }
 
 void MojoMjpegDecodeAcceleratorService::Decode(
@@ -183,6 +218,12 @@
   frame->BackWithOwnedSharedMemory(std::move(output_region),
                                    std::move(mapping));
 
+  if (!accelerator_initialized_) {
+    NotifyDecodeStatus(
+        input_buffer.id(),
+        ::chromeos_camera::MjpegDecodeAccelerator::Error::PLATFORM_FAILURE);
+    return;
+  }
   DCHECK(accelerator_);
   accelerator_->Decode(std::move(input_buffer), frame);
 }
@@ -226,6 +267,12 @@
   DCHECK_EQ(mojo_cb_map_.count(task_id), 0u);
   mojo_cb_map_[task_id] = std::move(callback);
 
+  if (!accelerator_initialized_) {
+    NotifyDecodeStatus(
+        task_id,
+        ::chromeos_camera::MjpegDecodeAccelerator::Error::PLATFORM_FAILURE);
+    return;
+  }
   DCHECK(accelerator_);
   accelerator_->Decode(task_id, src_handle.TakeFD(),
                        base::strict_cast<size_t>(src_size),
diff --git a/components/chromeos_camera/mojo_mjpeg_decode_accelerator_service.h b/components/chromeos_camera/mojo_mjpeg_decode_accelerator_service.h
index add89c9..11f99699 100644
--- a/components/chromeos_camera/mojo_mjpeg_decode_accelerator_service.h
+++ b/components/chromeos_camera/mojo_mjpeg_decode_accelerator_service.h
@@ -65,20 +65,32 @@
                         DecodeWithDmaBufCallback callback) override;
   void Uninitialize() override;
 
+  void OnInitialize(
+      std::vector<GpuMjpegDecodeAcceleratorFactory::CreateAcceleratorCB>
+          remaining_accelerator_factory_functions,
+      InitializeCallback init_cb,
+      bool last_initialize_result);
+
+  void InitializeInternal(
+      std::vector<GpuMjpegDecodeAcceleratorFactory::CreateAcceleratorCB>
+          remaining_accelerator_factory_functions,
+      InitializeCallback init_cb);
+
   void NotifyDecodeStatus(
       int32_t bitstream_buffer_id,
       ::chromeos_camera::MjpegDecodeAccelerator::Error error);
 
-  std::vector<GpuMjpegDecodeAcceleratorFactory::CreateAcceleratorCB>
-      accelerator_factory_functions_;
-
   // A map from |task_id| to MojoCallback.
   MojoCallbackMap mojo_cb_map_;
 
+  bool accelerator_initialized_;
+
   std::unique_ptr<::chromeos_camera::MjpegDecodeAccelerator> accelerator_;
 
   THREAD_CHECKER(thread_checker_);
 
+  base::WeakPtrFactory<MojoMjpegDecodeAcceleratorService> weak_this_factory_;
+
   DISALLOW_COPY_AND_ASSIGN(MojoMjpegDecodeAcceleratorService);
 };
 
diff --git a/components/feed/core/v2/BUILD.gn b/components/feed/core/v2/BUILD.gn
index 1d36927..960c770 100644
--- a/components/feed/core/v2/BUILD.gn
+++ b/components/feed/core/v2/BUILD.gn
@@ -94,7 +94,7 @@
     "wire_response_translator.h",
   ]
   deps = [
-    ":common",
+    "public:common",
     "//components/feed:feature_list",
     "//components/feed/core/shared_prefs:feed_shared_prefs",
     "//components/history/core/browser",
@@ -150,11 +150,6 @@
   ]
 }
 
-source_set("common") {
-  sources = [ "common_enums.h" ]
-  deps = []
-}
-
 source_set("test_helpers") {
   testonly = true
   sources = [
@@ -201,10 +196,10 @@
 
   public_deps = [ ":test_helpers" ]
   deps = [
-    ":common",
     ":feed_core_base_unit_tests",
     ":feed_core_v2",
     ":unit_tests_bundle_data",
+    "public:common",
     "//base",
     "//base/test:test_support",
     "//build:chromeos_buildflags",
@@ -270,8 +265,8 @@
 if (is_android) {
   java_cpp_enum("feedv2_enums_java") {
     sources = [
-      "common_enums.h",
       "enums.h",
+      "public/common_enums.h",
       "public/types.h",
     ]
   }
diff --git a/components/feed/core/v2/metrics_reporter.cc b/components/feed/core/v2/metrics_reporter.cc
index 04da533f..1db3d822 100644
--- a/components/feed/core/v2/metrics_reporter.cc
+++ b/components/feed/core/v2/metrics_reporter.cc
@@ -11,8 +11,8 @@
 #include "base/metrics/user_metrics.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "base/time/time.h"
-#include "components/feed/core/v2/common_enums.h"
 #include "components/feed/core/v2/prefs.h"
+#include "components/feed/core/v2/public/common_enums.h"
 #include "components/feed/core/v2/public/feed_api.h"
 
 namespace feed {
diff --git a/components/feed/core/v2/metrics_reporter.h b/components/feed/core/v2/metrics_reporter.h
index b14839c..3582b309 100644
--- a/components/feed/core/v2/metrics_reporter.h
+++ b/components/feed/core/v2/metrics_reporter.h
@@ -10,8 +10,8 @@
 
 #include "base/memory/weak_ptr.h"
 #include "base/time/time.h"
-#include "components/feed/core/v2/common_enums.h"
 #include "components/feed/core/v2/enums.h"
+#include "components/feed/core/v2/public/common_enums.h"
 #include "components/feed/core/v2/public/stream_type.h"
 #include "components/feed/core/v2/public/web_feed_subscriptions.h"
 #include "components/feed/core/v2/types.h"
diff --git a/components/feed/core/v2/metrics_reporter_unittest.cc b/components/feed/core/v2/metrics_reporter_unittest.cc
index b3f2c4b3..bd25cd2 100644
--- a/components/feed/core/v2/metrics_reporter_unittest.cc
+++ b/components/feed/core/v2/metrics_reporter_unittest.cc
@@ -12,7 +12,7 @@
 #include "base/test/task_environment.h"
 #include "components/feed/core/common/pref_names.h"
 #include "components/feed/core/shared_prefs/pref_names.h"
-#include "components/feed/core/v2/common_enums.h"
+#include "components/feed/core/v2/public/common_enums.h"
 #include "components/feed/core/v2/public/feed_api.h"
 #include "components/prefs/testing_pref_service.h"
 #include "testing/gtest/include/gtest/gtest.h"
diff --git a/components/feed/core/v2/public/BUILD.gn b/components/feed/core/v2/public/BUILD.gn
new file mode 100644
index 0000000..e88d46a
--- /dev/null
+++ b/components/feed/core/v2/public/BUILD.gn
@@ -0,0 +1,5 @@
+# TODO(crbug.com/1213474): Move public targets here.
+
+source_set("common") {
+  sources = [ "common_enums.h" ]
+}
diff --git a/components/feed/core/v2/common_enums.h b/components/feed/core/v2/public/common_enums.h
similarity index 95%
rename from components/feed/core/v2/common_enums.h
rename to components/feed/core/v2/public/common_enums.h
index 9fa23ff..6db9875 100644
--- a/components/feed/core/v2/common_enums.h
+++ b/components/feed/core/v2/public/common_enums.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_FEED_CORE_V2_COMMON_ENUMS_H_
-#define COMPONENTS_FEED_CORE_V2_COMMON_ENUMS_H_
+#ifndef COMPONENTS_FEED_CORE_V2_PUBLIC_COMMON_ENUMS_H_
+#define COMPONENTS_FEED_CORE_V2_PUBLIC_COMMON_ENUMS_H_
 
 // Unlike most code from feed/core, these enums are used by both iOS and
 // Android.
@@ -96,4 +96,4 @@
 
 }  // namespace feed
 
-#endif  // COMPONENTS_FEED_CORE_V2_COMMON_ENUMS_H_
+#endif  // COMPONENTS_FEED_CORE_V2_PUBLIC_COMMON_ENUMS_H_
diff --git a/components/feed/core/v2/public/feed_api.h b/components/feed/core/v2/public/feed_api.h
index 981df1c..4f26768 100644
--- a/components/feed/core/v2/public/feed_api.h
+++ b/components/feed/core/v2/public/feed_api.h
@@ -12,7 +12,7 @@
 #include "base/observer_list_types.h"
 #include "base/strings/string_piece_forward.h"
 #include "base/time/time.h"
-#include "components/feed/core/v2/common_enums.h"
+#include "components/feed/core/v2/public/common_enums.h"
 #include "components/feed/core/v2/public/refresh_task_scheduler.h"
 #include "components/feed/core/v2/public/stream_type.h"
 #include "components/feed/core/v2/public/types.h"
diff --git a/components/feed/core/v2/public/ios/BUILD.gn b/components/feed/core/v2/public/ios/BUILD.gn
new file mode 100644
index 0000000..f261eef
--- /dev/null
+++ b/components/feed/core/v2/public/ios/BUILD.gn
@@ -0,0 +1,19 @@
+source_set("feed_ios_public") {
+  sources = [
+    "notice_card_tracker.h",
+    "pref_names.cc",
+    "pref_names.h",
+    "prefs.cc",
+    "prefs.h",
+  ]
+  public_deps = [
+    "//components/feed/core/common:feed_core_common",
+    "//components/feed/core/shared_prefs:feed_shared_prefs",
+  ]
+  deps = [
+    "//base",
+    "//components/feed:feature_list",
+    "//components/feed/core/v2:feed_core_base",
+    "//components/prefs",
+  ]
+}
diff --git a/components/feed/core/v2/public/ios/README.md b/components/feed/core/v2/public/ios/README.md
new file mode 100644
index 0000000..bf9101b6
--- /dev/null
+++ b/components/feed/core/v2/public/ios/README.md
@@ -0,0 +1,2 @@
+APIs provided specifically for the Feed on iOS. Modifying these APIs may break
+the internal iOS build.
diff --git a/components/feed/core/v2/public/ios/notice_card_tracker.h b/components/feed/core/v2/public/ios/notice_card_tracker.h
new file mode 100644
index 0000000..f7c6122
--- /dev/null
+++ b/components/feed/core/v2/public/ios/notice_card_tracker.h
@@ -0,0 +1,15 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_FEED_CORE_V2_PUBLIC_IOS_NOTICE_CARD_TRACKER_H_
+#define COMPONENTS_FEED_CORE_V2_PUBLIC_IOS_NOTICE_CARD_TRACKER_H_
+
+#include "components/feed/core/v2/notice_card_tracker.h"
+
+namespace ios_feed {
+// TODO(crbug.com/1213474): Fork this class.
+using feed::NoticeCardTracker;
+}  // namespace ios_feed
+
+#endif  // COMPONENTS_FEED_CORE_V2_PUBLIC_IOS_NOTICE_CARD_TRACKER_H_
diff --git a/components/feed/core/v2/public/ios/pref_names.cc b/components/feed/core/v2/public/ios/pref_names.cc
new file mode 100644
index 0000000..2e86c72
--- /dev/null
+++ b/components/feed/core/v2/public/ios/pref_names.cc
@@ -0,0 +1,17 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/feed/core/v2/public/ios/pref_names.h"
+
+#include "components/feed/core/common/pref_names.h"
+#include "components/feed/core/shared_prefs/pref_names.h"
+
+namespace ios_feed {
+
+void RegisterProfilePrefs(PrefRegistrySimple* registry) {
+  feed::RegisterProfilePrefs(registry);
+  feed::prefs::RegisterFeedSharedProfilePrefs(registry);
+}
+
+}  // namespace ios_feed
diff --git a/components/feed/core/v2/public/ios/pref_names.h b/components/feed/core/v2/public/ios/pref_names.h
new file mode 100644
index 0000000..1eecbad
--- /dev/null
+++ b/components/feed/core/v2/public/ios/pref_names.h
@@ -0,0 +1,18 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_FEED_CORE_V2_PUBLIC_IOS_PREF_NAMES_H_
+#define COMPONENTS_FEED_CORE_V2_PUBLIC_IOS_PREF_NAMES_H_
+
+// TODO(crbug.com/1213474): Include only prefs needed for iOS.
+#include "components/feed/core/common/pref_names.h"
+#include "components/feed/core/shared_prefs/pref_names.h"
+
+class PrefRegistrySimple;
+
+namespace ios_feed {
+void RegisterProfilePrefs(PrefRegistrySimple* registry);
+}
+
+#endif  // COMPONENTS_FEED_CORE_V2_PUBLIC_IOS_PREF_NAMES_H_
diff --git a/components/feed/core/v2/public/ios/prefs.cc b/components/feed/core/v2/public/ios/prefs.cc
new file mode 100644
index 0000000..b4030535
--- /dev/null
+++ b/components/feed/core/v2/public/ios/prefs.cc
@@ -0,0 +1,34 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/feed/core/v2/public/ios/prefs.h"
+
+#include "components/feed/core/v2/public/ios/pref_names.h"
+#include "components/prefs/pref_service.h"
+
+namespace ios_feed {
+namespace prefs {
+
+void SetLastFetchHadNoticeCard(PrefService& pref_service, bool value) {
+  pref_service.SetBoolean(feed::prefs::kLastFetchHadNoticeCard, value);
+}
+
+bool GetLastFetchHadNoticeCard(const PrefService& pref_service) {
+  return pref_service.GetBoolean(feed::prefs::kLastFetchHadNoticeCard);
+}
+
+void SetHasReachedClickAndViewActionsUploadConditions(PrefService& pref_service,
+                                                      bool value) {
+  pref_service.SetBoolean(
+      feed::prefs::kHasReachedClickAndViewActionsUploadConditions, value);
+}
+
+bool GetHasReachedClickAndViewActionsUploadConditions(
+    const PrefService& pref_service) {
+  return pref_service.GetBoolean(
+      feed::prefs::kHasReachedClickAndViewActionsUploadConditions);
+}
+
+}  // namespace prefs
+}  // namespace ios_feed
diff --git a/components/feed/core/v2/public/ios/prefs.h b/components/feed/core/v2/public/ios/prefs.h
new file mode 100644
index 0000000..8f10abac
--- /dev/null
+++ b/components/feed/core/v2/public/ios/prefs.h
@@ -0,0 +1,23 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_FEED_CORE_V2_PUBLIC_IOS_PREFS_H_
+#define COMPONENTS_FEED_CORE_V2_PUBLIC_IOS_PREFS_H_
+
+class PrefService;
+
+namespace ios_feed {
+namespace prefs {
+
+void SetLastFetchHadNoticeCard(PrefService& pref_service, bool value);
+bool GetLastFetchHadNoticeCard(const PrefService& pref_service);
+void SetHasReachedClickAndViewActionsUploadConditions(PrefService& pref_service,
+                                                      bool value);
+bool GetHasReachedClickAndViewActionsUploadConditions(
+    const PrefService& pref_service);
+
+}  // namespace prefs
+}  // namespace ios_feed
+
+#endif  // COMPONENTS_FEED_CORE_V2_PUBLIC_IOS_PREFS_H_
diff --git a/components/omnibox/browser/android/java/src/org/chromium/components/omnibox/AutocompleteResult.java b/components/omnibox/browser/android/java/src/org/chromium/components/omnibox/AutocompleteResult.java
index aa45fdd..e5cdd9f0 100644
--- a/components/omnibox/browser/android/java/src/org/chromium/components/omnibox/AutocompleteResult.java
+++ b/components/omnibox/browser/android/java/src/org/chromium/components/omnibox/AutocompleteResult.java
@@ -78,7 +78,7 @@
         // Note that the mNativeResult might change at any point during the lifecycle of this object
         // to reflect relocation or destruction of the native object, so we cache this information
         // separately.
-        mIsFromCachedResult = nativeResult != 0;
+        mIsFromCachedResult = nativeResult == 0;
         mNativeAutocompleteResult = nativeResult;
         mSuggestions = suggestions != null ? suggestions : new ArrayList<>();
         mGroupsDetails = groupsDetails != null ? groupsDetails : new SparseArray<>();
diff --git a/components/omnibox/browser/clipboard_provider.cc b/components/omnibox/browser/clipboard_provider.cc
index c889cd38..c38879ed 100644
--- a/components/omnibox/browser/clipboard_provider.cc
+++ b/components/omnibox/browser/clipboard_provider.cc
@@ -680,8 +680,7 @@
       search_args, url_service->search_terms_data()));
 
   match->destination_url = result;
-  match->contents.assign(l10n_util::GetStringFUTF16(
-      IDS_COPIED_TEXT_FROM_CLIPBOARD, AutocompleteMatch::SanitizeString(text)));
+  match->contents.assign(AutocompleteMatch::SanitizeString(text));
   if (!match->contents.empty())
     match->contents_class.push_back({0, ACMatchClassification::NONE});
 
diff --git a/components/omnibox/browser/clipboard_provider_unittest.cc b/components/omnibox/browser/clipboard_provider_unittest.cc
index 58eb0b2..ab59596 100644
--- a/components/omnibox/browser/clipboard_provider_unittest.cc
+++ b/components/omnibox/browser/clipboard_provider_unittest.cc
@@ -37,7 +37,6 @@
 const char kCurrentURL[] = "http://example.com/current";
 const char kClipboardURL[] = "http://example.com/clipboard";
 const char16_t kClipboardText[] = u"Search for me";
-const char16_t kClipboardTitleText[] = u"\"Search for me\"";
 
 class CreateMatchWithContentCallbackWaiter {
  public:
@@ -188,7 +187,7 @@
   SetClipboardText(kClipboardText);
   provider_->Start(CreateAutocompleteInput(OmniboxFocusType::ON_FOCUS), false);
   ASSERT_GE(provider_->matches().size(), 1U);
-  EXPECT_EQ(kClipboardTitleText, provider_->matches().back().contents);
+  EXPECT_EQ(kClipboardText, provider_->matches().back().contents);
   EXPECT_EQ(kClipboardText, provider_->matches().back().fill_into_edit);
   EXPECT_EQ(AutocompleteMatchType::CLIPBOARD_TEXT,
             provider_->matches().back().type);
@@ -305,7 +304,7 @@
   CreateMatchWithContentCallbackWaiter waiter(provider_, &match);
   waiter.WaitForMatchUpdated();
 
-  EXPECT_EQ(kClipboardTitleText, match.contents);
+  EXPECT_EQ(kClipboardText, match.contents);
   EXPECT_EQ(kClipboardText, match.fill_into_edit);
   EXPECT_EQ(AutocompleteMatchType::CLIPBOARD_TEXT, match.type);
 }
diff --git a/components/omnibox_strings.grdp b/components/omnibox_strings.grdp
index 8cfc5dc..ef78aca 100644
--- a/components/omnibox_strings.grdp
+++ b/components/omnibox_strings.grdp
@@ -29,9 +29,6 @@
       Image you copied
     </message>
   </if>
-  <message name="IDS_COPIED_TEXT_FROM_CLIPBOARD" desc="The actual text the user copied, surrounded by quotation marks.">
-    &quot;<ph name="TEXT">$1<ex>search string</ex></ph>&quot;
-  </message>
   <message name="IDS_SECURE_CONNECTION_EV" desc="Short text shown in the location bar when the connection is secure with an EV cert.">
     <ph name="ORGANIZATION">$1<ex>Paypal Inc.</ex></ph> [<ph name="COUNTRY">$2<ex>US</ex></ph>]
   </message>
diff --git a/components/omnibox_strings_grdp/IDS_COPIED_TEXT_FROM_CLIPBOARD.png.sha1 b/components/omnibox_strings_grdp/IDS_COPIED_TEXT_FROM_CLIPBOARD.png.sha1
deleted file mode 100644
index 25160b9b..0000000
--- a/components/omnibox_strings_grdp/IDS_COPIED_TEXT_FROM_CLIPBOARD.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-b015bfd93d7d6b6c87e140f9e31796dad1d20a3c
\ No newline at end of file
diff --git a/components/ownership/owner_settings_service.cc b/components/ownership/owner_settings_service.cc
index 90be5e8..ccffed65 100644
--- a/components/ownership/owner_settings_service.cc
+++ b/components/ownership/owner_settings_service.cc
@@ -140,6 +140,13 @@
   return Set(setting, in_value);
 }
 
+void OwnerSettingsService::RunPendingIsOwnerCallbacksForTesting(bool is_owner) {
+  std::vector<IsOwnerCallback> is_owner_callbacks;
+  is_owner_callbacks.swap(pending_is_owner_callbacks_);
+  for (auto& callback : is_owner_callbacks)
+    std::move(callback).Run(is_owner);
+}
+
 bool OwnerSettingsService::SetString(const std::string& setting,
                                      const std::string& value) {
   DCHECK(thread_checker_.CalledOnValidThread());
diff --git a/components/ownership/owner_settings_service.h b/components/ownership/owner_settings_service.h
index b573f987..57b4913 100644
--- a/components/ownership/owner_settings_service.h
+++ b/components/ownership/owner_settings_service.h
@@ -119,6 +119,10 @@
   bool SetDouble(const std::string& setting, double value);
   bool SetString(const std::string& setting, const std::string& value);
 
+  // Run callbacks in test setting. Mocks ownership when full device setup is
+  // not needed.
+  void RunPendingIsOwnerCallbacksForTesting(bool is_owner);
+
  protected:
   void ReloadKeypair();
 
diff --git a/components/page_load_metrics/browser/observers/use_counter/ukm_features.cc b/components/page_load_metrics/browser/observers/use_counter/ukm_features.cc
index 62967f68..449c44c 100644
--- a/components/page_load_metrics/browser/observers/use_counter/ukm_features.cc
+++ b/components/page_load_metrics/browser/observers/use_counter/ukm_features.cc
@@ -209,6 +209,7 @@
           WebFeature::kSerialPortOpen,
           WebFeature::kHidRequestDevice,
           WebFeature::kHidDeviceOpen,
+          WebFeature::kCrossOriginWasmModuleSharing,
       }));
   return *opt_in_features;
 }
diff --git a/components/resources/BUILD.gn b/components/resources/BUILD.gn
index fb82ba643..9b87aa1 100644
--- a/components/resources/BUILD.gn
+++ b/components/resources/BUILD.gn
@@ -3,7 +3,6 @@
 # found in the LICENSE file.
 
 import("//build/config/android/config.gni")
-import("//build/config/python.gni")
 import("//components/safe_browsing/buildflags.gni")
 import("//printing/buildflags/buildflags.gni")
 import("//tools/grit/grit_rule.gni")
@@ -84,8 +83,7 @@
   output_dir = "$root_gen_dir/components"
 }
 
-# TODO(crbug.com/1112471): Get this to run cleanly under Python 3.
-python2_action("about_credits") {
+action("about_credits") {
   script = "//tools/licenses.py"
   depfile = "$target_gen_dir/$target_name.d"
 
diff --git a/components/safe_browsing/content/password_protection/password_protection_request_content.cc b/components/safe_browsing/content/password_protection/password_protection_request_content.cc
index 7434396d..4e3eeb1a 100644
--- a/components/safe_browsing/content/password_protection/password_protection_request_content.cc
+++ b/components/safe_browsing/content/password_protection/password_protection_request_content.cc
@@ -55,6 +55,24 @@
 }
 #endif  // BUILDFLAG(SAFE_BROWSING_AVAILABLE)
 
+int GetMinWidthForVisualFeatures() {
+  if (base::FeatureList::IsEnabled(kVisualFeaturesSizes)) {
+    return base::GetFieldTrialParamByFeatureAsInt(
+        kVisualFeaturesSizes, "min_width", kMinWidthForVisualFeatures);
+  }
+
+  return kMinWidthForVisualFeatures;
+}
+
+int GetMinHeightForVisualFeatures() {
+  if (base::FeatureList::IsEnabled(kVisualFeaturesSizes)) {
+    return base::GetFieldTrialParamByFeatureAsInt(
+        kVisualFeaturesSizes, "min_height", kMinHeightForVisualFeatures);
+  }
+
+  return kMinHeightForVisualFeatures;
+}
+
 }  // namespace
 
 PasswordProtectionRequestContent::PasswordProtectionRequestContent(
@@ -262,8 +280,8 @@
       trigger_type() == LoginReputationClientRequest::UNFAMILIAR_LOGIN_PAGE &&
       password_protection_service()->IsExtendedReporting() &&
       !password_protection_service()->IsIncognito() &&
-      request_proto_->content_area_width() >= kMinWidthForVisualFeatures &&
-      request_proto_->content_area_height() >= kMinHeightForVisualFeatures;
+      request_proto_->content_area_width() >= GetMinWidthForVisualFeatures() &&
+      request_proto_->content_area_height() >= GetMinHeightForVisualFeatures();
 #if !defined(OS_ANDROID)
   can_collect_visual_features &=
       zoom::ZoomController::GetZoomLevelForWebContents(web_contents_) <=
diff --git a/components/safe_browsing/content/renderer/phishing_classifier/flatbuffer_scorer.cc b/components/safe_browsing/content/renderer/phishing_classifier/flatbuffer_scorer.cc
index dce3b8f..85d1292 100644
--- a/components/safe_browsing/content/renderer/phishing_classifier/flatbuffer_scorer.cc
+++ b/components/safe_browsing/content/renderer/phishing_classifier/flatbuffer_scorer.cc
@@ -85,6 +85,11 @@
                      hash->data()->size());
 }
 
+void RecordScorerCreationStatus(ScorerCreationStatus status) {
+  UMA_HISTOGRAM_ENUMERATION("SBClientPhishing.FlatBufferScorer.CreationStatus",
+                            status, SCORER_STATUS_MAX);
+}
+
 }  // namespace
 
 FlatBufferModelScorer::FlatBufferModelScorer() = default;
diff --git a/components/safe_browsing/content/renderer/phishing_classifier/protobuf_scorer.cc b/components/safe_browsing/content/renderer/phishing_classifier/protobuf_scorer.cc
index e5553d6..3e68aa9 100644
--- a/components/safe_browsing/content/renderer/phishing_classifier/protobuf_scorer.cc
+++ b/components/safe_browsing/content/renderer/phishing_classifier/protobuf_scorer.cc
@@ -69,6 +69,12 @@
 
   return request;
 }
+
+void RecordScorerCreationStatus(ScorerCreationStatus status) {
+  UMA_HISTOGRAM_ENUMERATION("SBClientPhishing.ProtobufScorer.CreationStatus",
+                            status, SCORER_STATUS_MAX);
+}
+
 }  // namespace
 
 ProtobufModelScorer::ProtobufModelScorer() = default;
diff --git a/components/safe_browsing/content/renderer/phishing_classifier/scorer.h b/components/safe_browsing/content/renderer/phishing_classifier/scorer.h
index 7f8cf0f..3aea6442 100644
--- a/components/safe_browsing/content/renderer/phishing_classifier/scorer.h
+++ b/components/safe_browsing/content/renderer/phishing_classifier/scorer.h
@@ -126,11 +126,6 @@
   Scorer(const Scorer&) = delete;
   Scorer& operator=(const Scorer&) = delete;
 
-  static void RecordScorerCreationStatus(ScorerCreationStatus status) {
-    UMA_HISTOGRAM_ENUMERATION("SBClientPhishing.ScorerCreationStatus", status,
-                              SCORER_STATUS_MAX);
-  }
-
  protected:
   // Helper function which converts log odds to a probability in the range
   // [0.0,1.0].
diff --git a/components/safe_browsing/core/common/visual_utils.cc b/components/safe_browsing/core/common/visual_utils.cc
index 6a708ee..547043ef 100644
--- a/components/safe_browsing/core/common/visual_utils.cc
+++ b/components/safe_browsing/core/common/visual_utils.cc
@@ -8,8 +8,10 @@
 #include <vector>
 
 #include "base/check_op.h"
+#include "base/feature_list.h"
 #include "base/numerics/checked_math.h"
 #include "base/trace_event/trace_event.h"
+#include "components/safe_browsing/core/features.h"
 #include "components/safe_browsing/core/proto/client_model.pb.h"
 #include "components/safe_browsing/core/proto/csd.pb.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
@@ -130,6 +132,24 @@
   }
 }
 
+int GetPHashDownsampleWidth() {
+  if (base::FeatureList::IsEnabled(kVisualFeaturesSizes)) {
+    return base::GetFieldTrialParamByFeatureAsInt(
+        kVisualFeaturesSizes, "phash_width", kPHashDownsampleWidth);
+  }
+
+  return kPHashDownsampleWidth;
+}
+
+int GetPHashDownsampleHeight() {
+  if (base::FeatureList::IsEnabled(kVisualFeaturesSizes)) {
+    return base::GetFieldTrialParamByFeatureAsInt(
+        kVisualFeaturesSizes, "phash_height", kPHashDownsampleHeight);
+  }
+
+  return kPHashDownsampleHeight;
+}
+
 }  // namespace
 
 // A QuantizedColor takes the highest 3 bits of R, G, and B, and concatenates
@@ -240,9 +260,9 @@
   // average to be consistent with the backend.
   // TODO(drubery): Investigate whether this is necessary for performance or
   // not.
-  SkImageInfo downsampled_info =
-      SkImageInfo::MakeN32(kPHashDownsampleWidth, kPHashDownsampleHeight,
-                           SkAlphaType::kUnpremul_SkAlphaType, rec2020);
+  SkImageInfo downsampled_info = SkImageInfo::MakeN32(
+      GetPHashDownsampleWidth(), GetPHashDownsampleHeight(),
+      SkAlphaType::kUnpremul_SkAlphaType, rec2020);
   SkBitmap downsampled;
   if (!downsampled.tryAllocPixels(downsampled_info))
     return false;
diff --git a/components/safe_browsing/core/features.cc b/components/safe_browsing/core/features.cc
index be2f5bc..b43c72b 100644
--- a/components/safe_browsing/core/features.cc
+++ b/components/safe_browsing/core/features.cc
@@ -141,6 +141,9 @@
     "VisualFeaturesInPasswordProtectionAndroid",
     base::FEATURE_ENABLED_BY_DEFAULT};
 
+const base::Feature kVisualFeaturesSizes{"VisualFeaturesSizes",
+                                         base::FEATURE_DISABLED_BY_DEFAULT};
+
 namespace {
 // List of Safe Browsing features. Boolean value for each list member should be
 // set to true if the experiment state should be listed on
diff --git a/components/safe_browsing/core/features.h b/components/safe_browsing/core/features.h
index 814c572..1e3e252 100644
--- a/components/safe_browsing/core/features.h
+++ b/components/safe_browsing/core/features.h
@@ -143,6 +143,11 @@
 // Android.
 extern const base::Feature kVisualFeaturesInPasswordProtectionAndroid;
 
+// Controls the behavior of visual features in CSD pings. This feature is
+// checked for the final size of the visual features and the minimum size of
+// the screen.
+extern const base::Feature kVisualFeaturesSizes;
+
 // Controls whether the delayed warning experiment is enabled.
 extern const base::Feature kDelayedWarnings;
 // True if mouse clicks should undelay the warnings immediately when delayed
diff --git a/components/subresource_filter/content/browser/content_subresource_filter_throttle_manager_unittest.cc b/components/subresource_filter/content/browser/content_subresource_filter_throttle_manager_unittest.cc
index 63099e27..b8709d17 100644
--- a/components/subresource_filter/content/browser/content_subresource_filter_throttle_manager_unittest.cc
+++ b/components/subresource_filter/content/browser/content_subresource_filter_throttle_manager_unittest.cc
@@ -194,13 +194,16 @@
 
     NavigateAndCommit(GURL("https://example.first"));
 
-    // Initialize the ruleset dealer.
+    // Initialize the ruleset dealer. Allowlisted URLs must also match a
+    // disallowed rule in order to work correctly.
     std::vector<proto::UrlRule> rules;
     rules.push_back(testing::CreateAllowlistRuleForDocument(
         "allowlist.com", proto::ACTIVATION_TYPE_DOCUMENT,
         {"page-with-activation.com"}));
+    rules.push_back(testing::CreateRuleForDocument(
+        "allowlist.com", proto::ACTIVATION_TYPE_DOCUMENT,
+        {"page-with-activation.com"}));
 
-    // Allowlist rules must prefix a disallowed rule in order to work correctly.
     rules.push_back(testing::CreateAllowlistSuffixRule("not_disallowed.html"));
     rules.push_back(testing::CreateSuffixRule("disallowed.html"));
     ASSERT_NO_FATAL_FAILURE(test_ruleset_creator_.CreateRulesetWithRules(
diff --git a/components/subresource_filter/core/common/test_ruleset_utils.cc b/components/subresource_filter/core/common/test_ruleset_utils.cc
index 4771f494..b6e3bc7 100644
--- a/components/subresource_filter/core/common/test_ruleset_utils.cc
+++ b/components/subresource_filter/core/common/test_ruleset_utils.cc
@@ -13,62 +13,72 @@
 
 namespace proto = url_pattern_index::proto;
 
-proto::UrlRule CreateSubstringRule(base::StringPiece substring) {
+namespace {
+
+proto::UrlRule CreateRuleImpl(base::StringPiece substring,
+                              bool is_allowlist_rule,
+                              bool is_suffix_rule) {
   proto::UrlRule rule;
 
-  rule.set_semantics(proto::RULE_SEMANTICS_BLOCKLIST);
+  rule.set_semantics(is_allowlist_rule ? proto::RULE_SEMANTICS_ALLOWLIST
+                                       : proto::RULE_SEMANTICS_BLOCKLIST);
   rule.set_source_type(proto::SOURCE_TYPE_ANY);
   rule.set_element_types(proto::ELEMENT_TYPE_ALL);
   rule.set_url_pattern_type(proto::URL_PATTERN_TYPE_SUBSTRING);
   rule.set_anchor_left(proto::ANCHOR_TYPE_NONE);
-  rule.set_anchor_right(proto::ANCHOR_TYPE_NONE);
+  rule.set_anchor_right(is_suffix_rule ? proto::ANCHOR_TYPE_BOUNDARY
+                                       : proto::ANCHOR_TYPE_NONE);
   rule.set_url_pattern(std::string(substring));
 
   return rule;
 }
 
-proto::UrlRule CreateSuffixRule(base::StringPiece suffix) {
-  proto::UrlRule rule;
-  rule.set_semantics(proto::RULE_SEMANTICS_BLOCKLIST);
-  rule.set_source_type(proto::SOURCE_TYPE_ANY);
-  rule.set_element_types(proto::ELEMENT_TYPE_ALL);
-  rule.set_url_pattern_type(proto::URL_PATTERN_TYPE_SUBSTRING);
-  rule.set_anchor_left(proto::ANCHOR_TYPE_NONE);
-  rule.set_anchor_right(proto::ANCHOR_TYPE_BOUNDARY);
-  rule.set_url_pattern(std::string(suffix));
+proto::UrlRule CreateRuleForDocumentImpl(base::StringPiece substring,
+                                         int32_t activation_types,
+                                         std::vector<std::string> domains,
+                                         bool is_allowlist_rule,
+                                         bool is_suffix_rule) {
+  proto::UrlRule rule =
+      CreateRuleImpl(substring, is_allowlist_rule, is_suffix_rule);
+  rule.set_activation_types(activation_types);
+  for (std::string& domain : domains) {
+    rule.add_domains()->set_domain(std::move(domain));
+  }
   return rule;
 }
 
+}  // namespace
+
+proto::UrlRule CreateSubstringRule(base::StringPiece substring) {
+  return CreateRuleImpl(substring, /*is_allowlist_rule=*/false,
+                        /*is_suffix_rule=*/false);
+}
+
+proto::UrlRule CreateSuffixRule(base::StringPiece suffix) {
+  return CreateRuleImpl(suffix, /*is_allowlist_rule=*/false,
+                        /*is_suffix_rule=*/true);
+}
+
 proto::UrlRule CreateAllowlistSuffixRule(base::StringPiece suffix) {
-  proto::UrlRule rule;
-  rule.set_semantics(proto::RULE_SEMANTICS_ALLOWLIST);
-  rule.set_source_type(proto::SOURCE_TYPE_ANY);
-  rule.set_element_types(proto::ELEMENT_TYPE_ALL);
-  rule.set_url_pattern_type(proto::URL_PATTERN_TYPE_SUBSTRING);
-  rule.set_anchor_left(proto::ANCHOR_TYPE_NONE);
-  rule.set_anchor_right(proto::ANCHOR_TYPE_BOUNDARY);
-  rule.set_url_pattern(std::string(suffix));
-  return rule;
+  return CreateRuleImpl(suffix, /*is_allowlist_rule=*/true,
+                        /*is_suffix_rule=*/true);
+}
+
+proto::UrlRule CreateRuleForDocument(base::StringPiece pattern,
+                                     int32_t activation_types,
+                                     std::vector<std::string> domains) {
+  return CreateRuleForDocumentImpl(pattern, activation_types, domains,
+                                   /*is_allowlist_rule=*/false,
+                                   /*is_suffix_rule=*/false);
 }
 
 proto::UrlRule CreateAllowlistRuleForDocument(
     base::StringPiece pattern,
     int32_t activation_types,
     std::vector<std::string> domains) {
-  proto::UrlRule rule;
-  rule.set_semantics(proto::RULE_SEMANTICS_ALLOWLIST);
-  rule.set_source_type(proto::SOURCE_TYPE_ANY);
-  rule.set_activation_types(activation_types);
-
-  for (std::string& domain : domains) {
-    rule.add_domains()->set_domain(std::move(domain));
-  }
-
-  rule.set_url_pattern_type(proto::URL_PATTERN_TYPE_SUBSTRING);
-  rule.set_anchor_left(proto::ANCHOR_TYPE_NONE);
-  rule.set_anchor_right(proto::ANCHOR_TYPE_NONE);
-  rule.set_url_pattern(std::string(pattern));
-  return rule;
+  return CreateRuleForDocumentImpl(pattern, activation_types, domains,
+                                   /*is_allowlist_rule=*/true,
+                                   /*is_suffix_rule=*/false);
 }
 
 }  // namespace testing
diff --git a/components/subresource_filter/core/common/test_ruleset_utils.h b/components/subresource_filter/core/common/test_ruleset_utils.h
index 2fbfc96..61af431 100644
--- a/components/subresource_filter/core/common/test_ruleset_utils.h
+++ b/components/subresource_filter/core/common/test_ruleset_utils.h
@@ -24,16 +24,28 @@
 // that the resource URL ends with |suffix|.
 url_pattern_index::proto::UrlRule CreateSuffixRule(base::StringPiece suffix);
 
-// Creates a white URL rule which targets subresources of any type such that
-// the resource URL ends with |suffix|.
+// Creates an allowlisted URL rule which targets subresources of any type such
+// that the resource URL ends with `suffix`. Note that a URL must match both an
+// allowlist rule and a blocklist rule to be correctly considered allowlisted.
 url_pattern_index::proto::UrlRule CreateAllowlistSuffixRule(
     base::StringPiece suffix);
 
-// Same as CreateUrlRule(pattern, proto::URL_PATTERN_TYPE_WILDCARDED), but the
-// rule applies to the specified |activation_types|, and to no element types.
-// Additionally, it is restricted to a set of |domains| (if provided).
+// Creates a blocklisted URL rule which targets subresources of the specified
+// `activation_types` and a URL containing the given `substring`. Additionally,
+// it is restricted to a set of `domains`, if provided.
+url_pattern_index::proto::UrlRule CreateRuleForDocument(
+    base::StringPiece substring,
+    int32_t activation_types =
+        url_pattern_index::proto::ACTIVATION_TYPE_DOCUMENT,
+    std::vector<std::string> domains = std::vector<std::string>());
+
+// Creates an allowlisted URL rule which targets subresources of the specified
+// `activation_types` and a URL containing the given `substring`. Additionally,
+// it is restricted to a set of `domains`, if provided. Note that a URL must
+// match both an allowlist rule and a blocklist rule to be correctly considered
+// allowlisted.
 url_pattern_index::proto::UrlRule CreateAllowlistRuleForDocument(
-    base::StringPiece pattern,
+    base::StringPiece substring,
     int32_t activation_types =
         url_pattern_index::proto::ACTIVATION_TYPE_DOCUMENT,
     std::vector<std::string> domains = std::vector<std::string>());
diff --git a/content/browser/conversions/conversion_host_unittest.cc b/content/browser/conversions/conversion_host_unittest.cc
index 619a735..849b0532 100644
--- a/content/browser/conversions/conversion_host_unittest.cc
+++ b/content/browser/conversions/conversion_host_unittest.cc
@@ -11,7 +11,6 @@
 #include "content/browser/conversions/conversion_test_utils.h"
 #include "content/browser/web_contents/web_contents_impl.h"
 #include "content/public/common/content_client.h"
-#include "content/public/common/content_features.h"
 #include "content/public/test/test_renderer_host.h"
 #include "content/test/fake_mojo_message_dispatch_context.h"
 #include "content/test/navigation_simulator_impl.h"
diff --git a/content/browser/conversions/conversion_network_sender_impl_unittest.cc b/content/browser/conversions/conversion_network_sender_impl_unittest.cc
index 6e73fc9..4b715e5e 100644
--- a/content/browser/conversions/conversion_network_sender_impl_unittest.cc
+++ b/content/browser/conversions/conversion_network_sender_impl_unittest.cc
@@ -20,7 +20,6 @@
 #include "content/browser/storage_partition_impl.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/storage_partition.h"
-#include "content/public/common/content_features.h"
 #include "content/public/test/browser_task_environment.h"
 #include "content/public/test/test_browser_context.h"
 #include "net/base/load_flags.h"
diff --git a/content/browser/conversions/conversion_registration_browsertest.cc b/content/browser/conversions/conversion_registration_browsertest.cc
index 2f83daf..ebdf33a6 100644
--- a/content/browser/conversions/conversion_registration_browsertest.cc
+++ b/content/browser/conversions/conversion_registration_browsertest.cc
@@ -6,11 +6,9 @@
 #include <memory>
 
 #include "base/bind.h"
-#include "base/test/scoped_feature_list.h"
 #include "content/browser/conversions/conversion_host.h"
 #include "content/browser/conversions/conversion_manager_impl.h"
 #include "content/browser/web_contents/web_contents_impl.h"
-#include "content/public/common/content_features.h"
 #include "content/public/common/content_switches.h"
 #include "content/public/test/browser_test.h"
 #include "content/public/test/browser_test_utils.h"
@@ -88,7 +86,6 @@
  public:
   ConversionDisabledBrowserTest() {
     ConversionManagerImpl::RunInMemoryForTesting();
-    feature_list_.InitAndEnableFeature(features::kConversionMeasurement);
   }
 
   void SetUpOnMainThread() override {
@@ -112,9 +109,6 @@
 
   net::EmbeddedTestServer* https_server() { return https_server_.get(); }
 
- protected:
-  base::test::ScopedFeatureList feature_list_;
-
  private:
   std::unique_ptr<net::EmbeddedTestServer> https_server_;
 };
diff --git a/content/browser/conversions/conversions_browsertest.cc b/content/browser/conversions/conversions_browsertest.cc
index 492cbd9c..c673348 100644
--- a/content/browser/conversions/conversions_browsertest.cc
+++ b/content/browser/conversions/conversions_browsertest.cc
@@ -7,12 +7,10 @@
 #include "base/command_line.h"
 #include "base/sequenced_task_runner.h"
 #include "base/strings/strcat.h"
-#include "base/test/scoped_feature_list.h"
 #include "base/threading/sequenced_task_runner_handle.h"
 #include "content/browser/conversions/conversion_manager_impl.h"
 #include "content/browser/conversions/conversion_test_utils.h"
 #include "content/public/common/content_client.h"
-#include "content/public/common/content_features.h"
 #include "content/public/common/content_switches.h"
 #include "content/public/test/browser_test.h"
 #include "content/public/test/browser_test_utils.h"
@@ -91,7 +89,6 @@
 class ConversionsBrowserTest : public ContentBrowserTest {
  public:
   ConversionsBrowserTest() {
-    feature_list_.InitAndEnableFeature(features::kConversionMeasurement);
     ConversionManagerImpl::RunInMemoryForTesting();
   }
 
@@ -122,7 +119,6 @@
   ConversionDisallowingContentBrowserClient disallowed_browser_client_;
 
  private:
-  base::test::ScopedFeatureList feature_list_;
   std::unique_ptr<net::EmbeddedTestServer> https_server_;
 };
 
diff --git a/content/browser/conversions/conversions_origin_trial_browsertest.cc b/content/browser/conversions/conversions_origin_trial_browsertest.cc
index 96c90f2..875410e1 100644
--- a/content/browser/conversions/conversions_origin_trial_browsertest.cc
+++ b/content/browser/conversions/conversions_origin_trial_browsertest.cc
@@ -16,7 +16,6 @@
 #include "content/public/browser/storage_partition.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/browser/web_contents_observer.h"
-#include "content/public/common/content_features.h"
 #include "content/public/common/content_switches.h"
 #include "content/public/test/browser_test.h"
 #include "content/public/test/browser_test_utils.h"
@@ -27,6 +26,7 @@
 #include "content/shell/browser/shell.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/common/features.h"
 #include "url/gurl.h"
 
 namespace content {
@@ -35,9 +35,9 @@
 constexpr char kBaseDataDir[] = "content/test/data/conversions/";
 }
 
-class ConversionsOriginTrialBrowserTestBase : public ContentBrowserTest {
+class ConversionsOriginTrialBrowserTest : public ContentBrowserTest {
  public:
-  ConversionsOriginTrialBrowserTestBase() = default;
+  ConversionsOriginTrialBrowserTest() = default;
 
   void SetUpOnMainThread() override {
     ContentBrowserTest::SetUpOnMainThread();
@@ -64,17 +64,6 @@
   std::unique_ptr<URLLoaderInterceptor> url_loader_interceptor_;
 };
 
-class ConversionsOriginTrialBrowserTest
-    : public ConversionsOriginTrialBrowserTestBase {
- public:
-  ConversionsOriginTrialBrowserTest() {
-    feature_list_.InitAndEnableFeature(features::kConversionMeasurement);
-  }
-
- private:
-  base::test::ScopedFeatureList feature_list_;
-};
-
 IN_PROC_BROWSER_TEST_F(ConversionsOriginTrialBrowserTest,
                        OriginTrialEnabled_FeatureDetected) {
   EXPECT_TRUE(NavigateToURL(
@@ -132,10 +121,11 @@
 // UrlLoadInterceptor cannot properly redirect the conversion pings.
 
 class ConversionsOriginTrialNoBrowserFeatureBrowserTest
-    : public ConversionsOriginTrialBrowserTestBase {
+    : public ConversionsOriginTrialBrowserTest {
  public:
   ConversionsOriginTrialNoBrowserFeatureBrowserTest() {
-    feature_list_.InitAndDisableFeature(features::kConversionMeasurement);
+    feature_list_.InitAndDisableFeature(
+        blink::features::kConversionMeasurement);
   }
 
  private:
diff --git a/content/browser/conversions/impression_declaration_browsertest.cc b/content/browser/conversions/impression_declaration_browsertest.cc
index 2bdc45d..c1555e9 100644
--- a/content/browser/conversions/impression_declaration_browsertest.cc
+++ b/content/browser/conversions/impression_declaration_browsertest.cc
@@ -8,14 +8,12 @@
 #include "base/bind.h"
 #include "base/run_loop.h"
 #include "base/test/metrics/histogram_tester.h"
-#include "base/test/scoped_feature_list.h"
 #include "base/time/time.h"
 #include "build/build_config.h"
 #include "content/browser/conversions/conversion_host.h"
 #include "content/browser/conversions/conversion_manager_impl.h"
 #include "content/browser/web_contents/web_contents_impl.h"
 #include "content/public/browser/navigation_handle.h"
-#include "content/public/common/content_features.h"
 #include "content/public/common/content_switches.h"
 #include "content/public/test/browser_test.h"
 #include "content/public/test/browser_test_utils.h"
@@ -140,7 +138,6 @@
 class ImpressionDisabledBrowserTest : public ContentBrowserTest {
  public:
   ImpressionDisabledBrowserTest() {
-    feature_list_.InitAndEnableFeature(features::kConversionMeasurement);
     ConversionManagerImpl::RunInMemoryForTesting();
   }
 
@@ -168,7 +165,6 @@
   net::EmbeddedTestServer* https_server() { return https_server_.get(); }
 
  private:
-  base::test::ScopedFeatureList feature_list_;
   std::unique_ptr<net::EmbeddedTestServer> https_server_;
 };
 
diff --git a/content/browser/devtools/devtools_conversion_browsertest.cc b/content/browser/devtools/devtools_conversion_browsertest.cc
index bb5c5cb..a13d9fcc 100644
--- a/content/browser/devtools/devtools_conversion_browsertest.cc
+++ b/content/browser/devtools/devtools_conversion_browsertest.cc
@@ -4,10 +4,8 @@
 
 #include <iostream>
 
-#include "base/test/scoped_feature_list.h"
 #include "content/browser/conversions/conversion_manager_impl.h"
 #include "content/browser/devtools/protocol/devtools_protocol_test_support.h"
-#include "content/public/common/content_features.h"
 #include "content/public/common/content_switches.h"
 #include "content/public/test/browser_test.h"
 #include "content/public/test/content_browser_test_utils.h"
@@ -24,7 +22,6 @@
  public:
   DevToolsConversionBrowserTest() {
     ConversionManagerImpl::RunInMemoryForTesting();
-    feature_list_.InitAndEnableFeature(features::kConversionMeasurement);
   }
 
   void SetUpCommandLine(base::CommandLine* command_line) override {
@@ -53,9 +50,6 @@
   WebContents* web_contents() { return shell()->web_contents(); }
   net::EmbeddedTestServer* https_server() { return https_server_.get(); }
 
- protected:
-  base::test::ScopedFeatureList feature_list_;
-
  private:
   std::unique_ptr<net::EmbeddedTestServer> https_server_;
 };
diff --git a/content/browser/devtools/render_frame_devtools_agent_host.cc b/content/browser/devtools/render_frame_devtools_agent_host.cc
index 0e32577..0ee5e24 100644
--- a/content/browser/devtools/render_frame_devtools_agent_host.cc
+++ b/content/browser/devtools/render_frame_devtools_agent_host.cc
@@ -435,6 +435,9 @@
 void RenderFrameDevToolsAgentHost::DidFinishNavigation(
     NavigationHandle* navigation_handle) {
   NavigationRequest* request = NavigationRequest::From(navigation_handle);
+  // If we opt for retaning self within the conditional block below, do so
+  // till the end of the function, as we require |this| after the conditional.
+  scoped_refptr<RenderFrameDevToolsAgentHost> protect;
   if (request->frame_tree_node() == frame_tree_node_) {
     navigation_requests_.erase(request);
     if (request->HasCommitted())
@@ -444,7 +447,7 @@
       UpdateRawHeadersAccess(frame_tree_node_->current_frame_host());
     }
     // UpdateFrameHost may destruct |this|.
-    scoped_refptr<RenderFrameDevToolsAgentHost> protect(this);
+    protect = this;
     UpdateFrameHost(frame_tree_node_->current_frame_host());
 
     if (navigation_requests_.empty()) {
diff --git a/content/browser/media/session/media_session_controllers_manager_unittest.cc b/content/browser/media/session/media_session_controllers_manager_unittest.cc
index 34b5b06..075e2c63 100644
--- a/content/browser/media/session/media_session_controllers_manager_unittest.cc
+++ b/content/browser/media/session/media_session_controllers_manager_unittest.cc
@@ -52,8 +52,6 @@
   static const int kIsAudioFocusEnabled = 1;
 
   void SetUp() override {
-    RenderViewHostImplTestHarness::SetUp();
-
     std::vector<base::Feature> enabled_features;
     std::vector<base::Feature> disabled_features;
 
@@ -79,6 +77,8 @@
 
     scoped_feature_list_.InitWithFeatures(enabled_features, disabled_features);
 
+    RenderViewHostImplTestHarness::SetUp();
+
     GlobalFrameRoutingId frame_routing_id =
         contents()->GetMainFrame()->GetGlobalFrameRoutingId();
     media_player_id_ = MediaPlayerId(frame_routing_id, 1);
diff --git a/content/browser/renderer_host/render_process_host_impl.cc b/content/browser/renderer_host/render_process_host_impl.cc
index 2821b23..17a6fd15 100644
--- a/content/browser/renderer_host/render_process_host_impl.cc
+++ b/content/browser/renderer_host/render_process_host_impl.cc
@@ -603,7 +603,7 @@
     // got too many processes. See also ShouldTryToUseExistingProcessHost in
     // this file.
     if (RenderProcessHost::run_renderer_in_process() ||
-        GetAllHosts().size() >=
+        RenderProcessHostImpl::GetProcessCountForLimit() >=
             RenderProcessHostImpl::GetMaxRendererProcessCount())
       return;
 
@@ -703,7 +703,7 @@
       // If the spare shouldn't be kept around, then discard it as soon as we
       // find that the current spare was mismatched.
       CleanupSpareRenderProcessHost();
-    } else if (GetAllHosts().size() >=
+    } else if (RenderProcessHostImpl::GetProcessCountForLimit() >=
                RenderProcessHostImpl::GetMaxRendererProcessCount()) {
       // Drop the spare if we are at a process limit and the spare wasn't taken.
       // This helps avoid process reuse.
@@ -4256,6 +4256,17 @@
 }
 
 // static
+size_t RenderProcessHostImpl::GetProcessCountForLimit() {
+  // Let the embedder specify a number of processes to ignore when checking
+  // against the process limit, to avoid forcing normal pages to reuse processes
+  // too soon.
+  size_t process_count_to_ignore =
+      GetContentClient()->browser()->GetProcessCountToIgnoreForLimit();
+  CHECK_LE(process_count_to_ignore, GetAllHosts().size());
+  return GetAllHosts().size() - process_count_to_ignore;
+}
+
+// static
 bool RenderProcessHost::ShouldTryToUseExistingProcessHost(
     BrowserContext* browser_context,
     const GURL& url) {
@@ -4267,11 +4278,11 @@
   //       a renderer process for a browser context that has no existing
   //       renderers. This is OK in moderation, since the
   //       GetMaxRendererProcessCount() is conservative.
-  if (GetAllHosts().size() >= GetMaxRendererProcessCount()) {
+  size_t process_count = RenderProcessHostImpl::GetProcessCountForLimit();
+  if (process_count >= GetMaxRendererProcessCount()) {
     MAYBEVLOG(4) << __func__
-                 << ": GetAllHosts().size() >= GetMaxRendererProcessCount() ("
-                 << GetAllHosts().size()
-                 << " >= " << GetMaxRendererProcessCount()
+                 << ": process_count >= GetMaxRendererProcessCount() ("
+                 << process_count << " >= " << GetMaxRendererProcessCount()
                  << ") - will try to reuse an existing process";
     return true;
   }
diff --git a/content/browser/renderer_host/render_process_host_impl.h b/content/browser/renderer_host/render_process_host_impl.h
index a7108cc3..59ee682 100644
--- a/content/browser/renderer_host/render_process_host_impl.h
+++ b/content/browser/renderer_host/render_process_host_impl.h
@@ -335,6 +335,11 @@
   // Implementation of FilterURL below that can be shared with the mock class.
   static void FilterURL(RenderProcessHost* rph, bool empty_allowed, GURL* url);
 
+  // Returns the current process count for comparisons against
+  // GetMaxRendererProcessCount, taking into account any processes the embedder
+  // wants to ignore via ContentBrowserClient::GetProcessCountToIgnoreForLimit.
+  static size_t GetProcessCountForLimit();
+
   // Returns true if |host| is suitable for rendering a page in the given
   // |isolation_context|, where the page would utilize |site_info.site_url()| as
   // its SiteInstance site URL, and its process would be locked to
diff --git a/content/browser/speech/speech_recognition_engine.cc b/content/browser/speech/speech_recognition_engine.cc
index 92b878f1..9bae55c 100644
--- a/content/browser/speech/speech_recognition_engine.cc
+++ b/content/browser/speech/speech_recognition_engine.cc
@@ -10,6 +10,7 @@
 
 #include "base/big_endian.h"
 #include "base/bind.h"
+#include "base/metrics/histogram_functions.h"
 #include "base/rand_util.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_util.h"
@@ -18,6 +19,7 @@
 #include "content/browser/speech/audio_buffer.h"
 #include "content/public/browser/google_streaming_api.pb.h"
 #include "google_apis/google_api_keys.h"
+#include "media/base/audio_timestamp_helper.h"
 #include "mojo/public/c/system/types.h"
 #include "mojo/public/cpp/bindings/receiver_set.h"
 #include "net/base/escape.h"
@@ -35,6 +37,8 @@
 const char kDownstreamUrl[] = "/down?";
 const char kUpstreamUrl[] = "/up?";
 
+constexpr char kWebSpeechAudioDuration[] = "Accessibility.WebSpeech.Duration";
+
 // Used to override |kWebServiceBaseUrl| when non-null, only set in tests.
 const char* web_service_base_url_for_tests = nullptr;
 
@@ -115,11 +119,15 @@
 }
 
 void SpeechRecognitionEngine::StartRecognition() {
+  upstream_audio_duration_ = base::TimeDelta();
   FSMEventArgs event_args(EVENT_START_RECOGNITION);
   DispatchEvent(event_args);
 }
 
 void SpeechRecognitionEngine::EndRecognition() {
+  base::UmaHistogramLongTimes100(kWebSpeechAudioDuration,
+                                 upstream_audio_duration_);
+
   FSMEventArgs event_args(EVENT_END_RECOGNITION);
   DispatchEvent(event_args);
 }
@@ -495,6 +503,10 @@
   DCHECK(event_args.audio_data.get());
   const AudioChunk& audio = *(event_args.audio_data.get());
 
+  base::TimeDelta duration = media::AudioTimestampHelper::FramesToTime(
+      audio.NumSamples(), config_.audio_sample_rate);
+  upstream_audio_duration_ += duration;
+
   DCHECK_EQ(audio.bytes_per_sample(), config_.audio_num_bits_per_sample / 8);
   encoder_->Encode(audio);
   scoped_refptr<AudioChunk> encoded_data(encoder_->GetEncodedDataAndClear());
diff --git a/content/browser/speech/speech_recognition_engine.h b/content/browser/speech/speech_recognition_engine.h
index e9f2f8b..87131194 100644
--- a/content/browser/speech/speech_recognition_engine.h
+++ b/content/browser/speech/speech_recognition_engine.h
@@ -27,6 +27,10 @@
 #include "third_party/blink/public/mojom/speech/speech_recognition_grammar.mojom.h"
 #include "third_party/blink/public/mojom/speech/speech_recognition_result.mojom.h"
 
+namespace base {
+class TimeDelta;
+}
+
 namespace network {
 class SharedURLLoaderFactory;
 }
@@ -210,6 +214,9 @@
   // upload formats, and uses the appropriate one.
   void UploadAudioChunk(const std::string& data, FrameType type, bool is_final);
 
+  // The total audio duration of the upstream request.
+  base::TimeDelta upstream_audio_duration_;
+
   Config config_;
   std::unique_ptr<speech::UpstreamLoader> upstream_loader_;
   std::unique_ptr<speech::DownstreamLoader> downstream_loader_;
diff --git a/content/browser/storage_partition_impl.cc b/content/browser/storage_partition_impl.cc
index 143f571..fed97ca 100644
--- a/content/browser/storage_partition_impl.cc
+++ b/content/browser/storage_partition_impl.cc
@@ -1296,7 +1296,7 @@
 
   // The Conversion Measurement API is not available in Incognito mode.
   if (!is_in_memory_ &&
-      base::FeatureList::IsEnabled(features::kConversionMeasurement)) {
+      base::FeatureList::IsEnabled(blink::features::kConversionMeasurement)) {
     conversion_manager_ = std::make_unique<ConversionManagerImpl>(
         this, path, special_storage_policy_);
   }
diff --git a/content/browser/storage_partition_impl_unittest.cc b/content/browser/storage_partition_impl_unittest.cc
index b0dcc28..ab16fbf 100644
--- a/content/browser/storage_partition_impl_unittest.cc
+++ b/content/browser/storage_partition_impl_unittest.cc
@@ -820,11 +820,10 @@
   StoragePartitionImplTest()
       : task_environment_(content::BrowserTaskEnvironment::IO_MAINLOOP),
         browser_context_(new TestBrowserContext()) {
-    // Configures the Conversion API to run in memory to speed up it's
+    // Configures the Conversion API to run in memory to speed up its
     // initialization and avoid timeouts. See https://crbug.com/1080764.
     ConversionManagerImpl::RunInMemoryForTesting();
-    feature_list_.InitWithFeatures({features::kConversionMeasurement,
-                                    blink::features::kFledgeInterestGroups},
+    feature_list_.InitWithFeatures({blink::features::kFledgeInterestGroups},
                                    {});
   }
 
diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc
index df03885..3ce2828 100644
--- a/content/browser/web_contents/web_contents_impl.cc
+++ b/content/browser/web_contents/web_contents_impl.cc
@@ -926,7 +926,7 @@
 
   // ConversionHost takes a weak ref on |this|, so it must be created outside of
   // the initializer list.
-  if (base::FeatureList::IsEnabled(features::kConversionMeasurement)) {
+  if (base::FeatureList::IsEnabled(blink::features::kConversionMeasurement)) {
     ConversionHost::CreateForWebContents(this);
   }
 }
diff --git a/content/child/runtime_features.cc b/content/child/runtime_features.cc
index bb7c815..04cdd66e 100644
--- a/content/child/runtime_features.cc
+++ b/content/child/runtime_features.cc
@@ -227,8 +227,6 @@
     {wf::EnableClickPointerEvent, features::kClickPointerEvent},
     {wf::EnableCompositeBGColorAnimation, features::kCompositeBGColorAnimation},
     {wf::EnableConsolidatedMovementXY, features::kConsolidatedMovementXY},
-    {wf::EnableConversionMeasurementInfraSupport,
-     features::kConversionMeasurement},
     {wf::EnableCookiesWithoutSameSiteMustBeSecure,
      net::features::kCookiesWithoutSameSiteMustBeSecure},
     {wf::EnableCooperativeScheduling, features::kCooperativeScheduling},
diff --git a/content/public/browser/content_browser_client.cc b/content/public/browser/content_browser_client.cc
index ffb6f3a..53de48c 100644
--- a/content/public/browser/content_browser_client.cc
+++ b/content/public/browser/content_browser_client.cc
@@ -241,6 +241,10 @@
   return true;
 }
 
+size_t ContentBrowserClient::GetProcessCountToIgnoreForLimit() {
+  return 0;
+}
+
 bool ContentBrowserClient::ShouldTryToUseExistingProcessHost(
     BrowserContext* browser_context,
     const GURL& url) {
diff --git a/content/public/browser/content_browser_client.h b/content/public/browser/content_browser_client.h
index f83b53c..4b1479f6 100644
--- a/content/public/browser/content_browser_client.h
+++ b/content/public/browser/content_browser_client.h
@@ -527,6 +527,13 @@
   // given |process_host|.
   virtual bool MayReuseHost(RenderProcessHost* process_host);
 
+  // Returns a number of processes to ignore when deciding whether to reuse
+  // processes when over the process limit. This is useful for embedders that
+  // may want to partly delay when normal pages start reusing processes (e.g.,
+  // if another process type has a large number of processes). Defaults to 0.
+  // Must be less than or equal to the total number of RenderProcessHosts.
+  virtual size_t GetProcessCountToIgnoreForLimit();
+
   // Returns whether a new process should be created or an existing one should
   // be reused based on the URL we want to load. This should return false,
   // unless there is a good reason otherwise.
diff --git a/content/public/common/content_features.cc b/content/public/common/content_features.cc
index 75cc0124b..c0ac9feb 100644
--- a/content/public/common/content_features.cc
+++ b/content/public/common/content_features.cc
@@ -183,10 +183,6 @@
 const base::Feature kConsolidatedMovementXY{"ConsolidatedMovementXY",
                                             base::FEATURE_ENABLED_BY_DEFAULT};
 
-// Controls whether the Conversion Measurement API infrastructure is enabled.
-const base::Feature kConversionMeasurement{"ConversionMeasurement",
-                                           base::FEATURE_ENABLED_BY_DEFAULT};
-
 // Enables Blink cooperative scheduling.
 const base::Feature kCooperativeScheduling{"CooperativeScheduling",
                                            base::FEATURE_DISABLED_BY_DEFAULT};
diff --git a/content/public/common/content_features.h b/content/public/common/content_features.h
index b71e4cd..fca527ac 100644
--- a/content/public/common/content_features.h
+++ b/content/public/common/content_features.h
@@ -47,7 +47,6 @@
 CONTENT_EXPORT extern const base::Feature kCompositeBGColorAnimation;
 CONTENT_EXPORT extern const base::Feature kCodeCacheDeletionWithoutFilter;
 CONTENT_EXPORT extern const base::Feature kConsolidatedMovementXY;
-CONTENT_EXPORT extern const base::Feature kConversionMeasurement;
 CONTENT_EXPORT extern const base::Feature kCooperativeScheduling;
 CONTENT_EXPORT extern const base::Feature kCrashReporting;
 CONTENT_EXPORT extern const base::Feature kCriticalClientHint;
diff --git a/content/test/gpu/gpu_tests/test_expectations/pixel_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/pixel_expectations.txt
index 74bc9fcc..36b23e4a 100644
--- a/content/test/gpu/gpu_tests/test_expectations/pixel_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/pixel_expectations.txt
@@ -397,6 +397,11 @@
 crbug.com/1213657 [ linux skia-renderer-vulkan ] Pixel_WebGPUImport2DCanvas [ Skip ]
 crbug.com/1213657 [ linux skia-renderer-vulkan ] Pixel_WebGPUImportUnaccelerated2DCanvas [ Skip ]
 
+# WebGPU experimentalImportTexture pixel tests fail on Win10+SkiaRenderer+Dawn.
+crbug.com/1213920 [ win10 skia-renderer-dawn ] Pixel_WebGPUImportWebGLCanvas [ Failure ]
+crbug.com/1213920 [ win10 skia-renderer-dawn ] Pixel_WebGPUImport2DCanvas [ Failure ]
+crbug.com/1213920 [ win10 skia-renderer-dawn ] Pixel_WebGPUImportUnaccelerated2DCanvas [ Failure ]
+
 # WebGPU is only supported on Win10, Mac, and Linux+SkiaRenderer+Vulkan.
 crbug.com/976495 [ linux skia-renderer-gl ] Pixel_WebGPUImportWebGLCanvas [ Skip ]
 crbug.com/976495 [ linux skia-renderer-gl ] Pixel_WebGPUImport2DCanvas [ Skip ]
diff --git a/device/fido/cable/v2_registration.cc b/device/fido/cable/v2_registration.cc
index 34c5a1f..44201f9 100644
--- a/device/fido/cable/v2_registration.cc
+++ b/device/fido/cable/v2_registration.cc
@@ -116,7 +116,9 @@
       return;
     }
 
-    event.value()->contact_id = contact_id();
+    if (type_ == Type::LINKING) {
+      event.value()->contact_id = contact_id();
+    }
     event_callback_.Run(std::move(*event));
   }
 
@@ -296,6 +298,9 @@
   }
 
   if (CBS_len(&cbs) > 0) {
+    if (e->source == Type::SYNC) {
+      return nullptr;
+    }
     e->contact_id.emplace(CBS_data(&cbs), CBS_data(&cbs) + CBS_len(&cbs));
   }
 
diff --git a/extensions/browser/api/networking_private/networking_private_chromeos.cc b/extensions/browser/api/networking_private/networking_private_chromeos.cc
index c2b8e8d..6de14f9 100644
--- a/extensions/browser/api/networking_private/networking_private_chromeos.cc
+++ b/extensions/browser/api/networking_private/networking_private_chromeos.cc
@@ -758,6 +758,7 @@
   NetworkTypePattern pattern =
       chromeos::onc::NetworkTypePatternFromOncType(type);
 
+  NET_LOG(USER) << __func__ << ":" << type;
   GetStateHandler()->SetTechnologyEnabled(
       pattern, true, chromeos::network_handler::ErrorCallback());
 
@@ -768,6 +769,7 @@
   NetworkTypePattern pattern =
       chromeos::onc::NetworkTypePatternFromOncType(type);
 
+  NET_LOG(USER) << __func__ << ":" << type;
   GetStateHandler()->SetTechnologyEnabled(
       pattern, false, chromeos::network_handler::ErrorCallback());
 
diff --git a/extensions/browser/api/socket/socket.h b/extensions/browser/api/socket/socket.h
index 086f2ff..da6e240 100644
--- a/extensions/browser/api/socket/socket.h
+++ b/extensions/browser/api/socket/socket.h
@@ -64,6 +64,9 @@
 // we need to manage it in the context of an extension.
 class Socket : public ApiResource {
  public:
+  static const content::BrowserThread::ID kThreadId =
+      content::BrowserThread::UI;
+
   enum SocketType { TYPE_TCP, TYPE_UDP, TYPE_TLS };
 
   ~Socket() override;
@@ -80,9 +83,7 @@
   void set_hostname(const std::string& hostname) { hostname_ = hostname; }
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
-  void set_firewall_hole(
-      std::unique_ptr<AppFirewallHole, content::BrowserThread::DeleteOnUIThread>
-          firewall_hole) {
+  void set_firewall_hole(std::unique_ptr<AppFirewallHole> firewall_hole) {
     firewall_hole_ = std::move(firewall_hole);
   }
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
@@ -175,8 +176,7 @@
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
   // Represents a hole punched in the system firewall for this socket.
-  std::unique_ptr<AppFirewallHole, content::BrowserThread::DeleteOnUIThread>
-      firewall_hole_;
+  std::unique_ptr<AppFirewallHole> firewall_hole_;
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 };
 
diff --git a/extensions/browser/api/socket/socket_api.cc b/extensions/browser/api/socket/socket_api.cc
index 0bcb92e..7ad313fc 100644
--- a/extensions/browser/api/socket/socket_api.cc
+++ b/extensions/browser/api/socket/socket_api.cc
@@ -79,12 +79,52 @@
 
 SocketAsyncApiFunction::~SocketAsyncApiFunction() {}
 
-bool SocketAsyncApiFunction::PrePrepare() {
+ExtensionFunction::ResponseAction SocketAsyncApiFunction::Run() {
   manager_ = CreateSocketResourceManager();
-  return manager_->SetBrowserContext(browser_context());
+  manager_->SetBrowserContext(browser_context());
+  if (!PrePrepare() || !Prepare()) {
+    DCHECK(!results_);
+    DCHECK(!error_.empty());
+    return RespondNow(Error(error_));
+  }
+  AsyncWorkStart();
+  return did_respond() ? AlreadyResponded() : RespondLater();
 }
 
-bool SocketAsyncApiFunction::Respond() { return error_.empty(); }
+bool SocketAsyncApiFunction::PrePrepare() {
+  return true;
+}
+
+bool SocketAsyncApiFunction::Prepare() {
+  return true;
+}
+
+void SocketAsyncApiFunction::Work() {}
+
+void SocketAsyncApiFunction::AsyncWorkStart() {
+  Work();
+  AsyncWorkCompleted();
+}
+
+ExtensionFunction::ResponseValue SocketAsyncApiFunction::GetResponseValue() {
+  ResponseValue response;
+  if (error_.empty()) {
+    response = ArgumentList(std::move(results_));
+  } else {
+    response = results_ ? ErrorWithArguments(std::move(results_), error_)
+                        : Error(error_);
+  }
+  return response;
+}
+
+void SocketAsyncApiFunction::AsyncWorkCompleted() {
+  Respond(GetResponseValue());
+}
+
+void SocketAsyncApiFunction::SetResult(std::unique_ptr<base::Value> result) {
+  results_ = std::make_unique<base::ListValue>();
+  results_->Append(std::move(result));
+}
 
 std::unique_ptr<SocketResourceManagerInterface>
 SocketAsyncApiFunction::CreateSocketResourceManager() {
@@ -92,6 +132,12 @@
       new SocketResourceManager<Socket>());
 }
 
+// static
+bool SocketAsyncApiFunction::ValidationFailure(
+    SocketAsyncApiFunction* function) {
+  return false;
+}
+
 int SocketAsyncApiFunction::AddSocket(Socket* socket) {
   return manager_->Add(socket);
 }
@@ -116,6 +162,7 @@
 void SocketAsyncApiFunction::OpenFirewallHole(const std::string& address,
                                               int socket_id,
                                               Socket* socket) {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
 #if BUILDFLAG(IS_CHROMEOS_ASH)
   if (!net::HostStringIsLocalhost(address)) {
     net::IPEndPoint local_address;
@@ -131,57 +178,32 @@
                                          ? AppFirewallHole::PortType::TCP
                                          : AppFirewallHole::PortType::UDP;
 
-    content::GetUIThreadTaskRunner({})->PostTask(
-        FROM_HERE,
-        base::BindOnce(&SocketAsyncApiFunction::OpenFirewallHoleOnUIThread,
-                       this, type, local_address.port(), socket_id));
-    return;
+    AppFirewallHoleManager* manager =
+        AppFirewallHoleManager::Get(browser_context());
+    std::unique_ptr<AppFirewallHole> hole(
+        manager->Open(type, local_address.port(), extension_id()).release());
+
+    if (!hole) {
+      error_ = kFirewallFailure;
+      SetResult(std::make_unique<base::Value>(-1));
+      AsyncWorkCompleted();
+      return;
+    }
+
+    Socket* socket = GetSocket(socket_id);
+    if (!socket) {
+      error_ = kSocketNotFoundError;
+      SetResult(std::make_unique<base::Value>(-1));
+      AsyncWorkCompleted();
+      return;
+    }
+
+    socket->set_firewall_hole(std::move(hole));
   }
 #endif
   AsyncWorkCompleted();
 }
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-
-void SocketAsyncApiFunction::OpenFirewallHoleOnUIThread(
-    AppFirewallHole::PortType type,
-    uint16_t port,
-    int socket_id) {
-  DCHECK_CURRENTLY_ON(BrowserThread::UI);
-  AppFirewallHoleManager* manager =
-      AppFirewallHoleManager::Get(browser_context());
-  std::unique_ptr<AppFirewallHole, BrowserThread::DeleteOnUIThread> hole(
-      manager->Open(type, port, extension_id()).release());
-  content::GetIOThreadTaskRunner({})->PostTask(
-      FROM_HERE, base::BindOnce(&SocketAsyncApiFunction::OnFirewallHoleOpened,
-                                this, socket_id, std::move(hole)));
-}
-
-void SocketAsyncApiFunction::OnFirewallHoleOpened(
-    int socket_id,
-    std::unique_ptr<AppFirewallHole, BrowserThread::DeleteOnUIThread> hole) {
-  DCHECK_CURRENTLY_ON(BrowserThread::IO);
-  if (!hole) {
-    error_ = kFirewallFailure;
-    SetResult(std::make_unique<base::Value>(-1));
-    AsyncWorkCompleted();
-    return;
-  }
-
-  Socket* socket = GetSocket(socket_id);
-  if (!socket) {
-    error_ = kSocketNotFoundError;
-    SetResult(std::make_unique<base::Value>(-1));
-    AsyncWorkCompleted();
-    return;
-  }
-
-  socket->set_firewall_hole(std::move(hole));
-  AsyncWorkCompleted();
-}
-
-#endif  // IS_CHROMEOS_ASH
-
 SocketExtensionWithDnsLookupFunction::SocketExtensionWithDnsLookupFunction() =
     default;
 
@@ -244,8 +266,6 @@
   params_ = api::socket::Create::Params::Create(*args_);
   EXTENSION_FUNCTION_VALIDATE(params_.get());
 
-  browser_context_ = browser_context();
-
   switch (params_->type) {
     case extensions::api::socket::SOCKET_TYPE_TCP:
       socket_type_ = kSocketTypeTCP;
@@ -256,7 +276,8 @@
       mojo::PendingRemote<network::mojom::UDPSocketListener> listener_remote;
       socket_listener_receiver_ =
           listener_remote.InitWithNewPipeAndPassReceiver();
-      browser_context_->GetDefaultStoragePartition()
+      browser_context()
+          ->GetDefaultStoragePartition()
           ->GetNetworkContext()
           ->CreateUDPSocket(socket_.InitWithNewPipeAndPassReceiver(),
                             std::move(listener_remote));
@@ -273,10 +294,7 @@
 void SocketCreateFunction::Work() {
   Socket* socket = nullptr;
   if (socket_type_ == kSocketTypeTCP) {
-    // TODO(crbug.com/1191472): |browser_context_| is unsafe to access when
-    // DestroyProfileOnBrowserClose is enabled, since it could've been deleted
-    // by now. Fix this by creating the TCPSocket on the UI thread instead.
-    socket = new TCPSocket(browser_context_, extension_->id());
+    socket = new TCPSocket(browser_context(), extension_->id());
   } else if (socket_type_ == kSocketTypeUDP) {
     socket =
         new UDPSocket(std::move(socket_), std::move(socket_listener_receiver_),
@@ -1121,7 +1139,7 @@
 // Override the regular implementation, which would call AsyncWorkCompleted
 // immediately after Work().
 void SocketSecureFunction::AsyncWorkStart() {
-  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
   Socket* socket = GetSocket(params_->socket_id);
   if (!socket) {
diff --git a/extensions/browser/api/socket/socket_api.h b/extensions/browser/api/socket/socket_api.h
index 4af9e81..60d0d3b 100644
--- a/extensions/browser/api/socket/socket_api.h
+++ b/extensions/browser/api/socket/socket_api.h
@@ -113,16 +113,38 @@
   ApiResourceManager<T>* manager_;
 };
 
-class SocketAsyncApiFunction : public AsyncApiFunction {
+// TODO(crbug.com/1200440): Stop using an AsyncApiFunction-like API to avoid
+// confusion. Use plain ExtensionFunction::Run() and re-write each function so
+// they don't split Prepare/Work/AsyncWorkStart.
+class SocketAsyncApiFunction : public ExtensionFunction {
  public:
   SocketAsyncApiFunction();
 
  protected:
   ~SocketAsyncApiFunction() override;
 
-  // AsyncApiFunction:
-  bool PrePrepare() override;
-  bool Respond() override;
+  // ExtensionFunction:
+  ResponseAction Run() override;
+
+  // These 3 override-able function are run in sequence. Return false to abort
+  // with an error.
+  virtual bool PrePrepare();
+  virtual bool Prepare();
+  virtual void AsyncWorkStart();
+
+  // The default AsyncWorkStart() calls Work() followed by AsyncWorkCompleted().
+  virtual void Work();
+
+  // Notify that the ExtensionFunction is done running. Subclasses only need to
+  // call this if they override AsyncWorkStart().
+  void AsyncWorkCompleted();
+
+  // Sets a single Value as the results of the function.
+  void SetResult(std::unique_ptr<base::Value> result);
+
+  // ValidationFailure override to match RunAsync(). This lets us use the
+  // EXTENSION_FUNCTION_VALIDATE() macro.
+  static bool ValidationFailure(SocketAsyncApiFunction* function);
 
   virtual std::unique_ptr<SocketResourceManagerInterface>
   CreateSocketResourceManager();
@@ -138,16 +160,11 @@
                         int socket_id,
                         Socket* socket);
 
+  std::string error_;
+  std::unique_ptr<base::ListValue> results_;
+
  private:
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-  void OpenFirewallHoleOnUIThread(AppFirewallHole::PortType type,
-                                  uint16_t port,
-                                  int socket_id);
-  void OnFirewallHoleOpened(
-      int socket_id,
-      std::unique_ptr<AppFirewallHole, content::BrowserThread::DeleteOnUIThread>
-          hole);
-#endif  // IS_CHROMEOS_ASH
+  ResponseValue GetResponseValue();
 
   std::unique_ptr<SocketResourceManagerInterface> manager_;
 };
@@ -206,8 +223,6 @@
 
   std::unique_ptr<api::socket::Create::Params> params_;
   SocketType socket_type_;
-
-  content::BrowserContext* browser_context_ = nullptr;
 };
 
 class SocketDestroyFunction : public SocketAsyncApiFunction {
diff --git a/extensions/browser/api/sockets_tcp/sockets_tcp_api.cc b/extensions/browser/api/sockets_tcp/sockets_tcp_api.cc
index 64fdb9f7..498fc22 100644
--- a/extensions/browser/api/sockets_tcp/sockets_tcp_api.cc
+++ b/extensions/browser/api/sockets_tcp/sockets_tcp_api.cc
@@ -124,17 +124,13 @@
 
 bool SocketsTcpCreateFunction::Prepare() {
   params_ = sockets_tcp::Create::Params::Create(*args_);
-  browser_context_ = browser_context();
   EXTENSION_FUNCTION_VALIDATE(params_.get());
   return true;
 }
 
 void SocketsTcpCreateFunction::Work() {
-  // TODO(crbug.com/1191472): |browser_context_| is unsafe to access when
-  // DestroyProfileOnBrowserClose is enabled, since it could've been deleted by
-  // now. Fix this by creating the TCPSocket on the UI thread instead.
   ResumableTCPSocket* socket =
-      new ResumableTCPSocket(browser_context_, extension_->id());
+      new ResumableTCPSocket(browser_context(), extension_->id());
 
   sockets_tcp::SocketProperties* properties = params_->properties.get();
   if (properties) {
@@ -502,7 +498,7 @@
 // Override the regular implementation, which would call AsyncWorkCompleted
 // immediately after Work().
 void SocketsTcpSecureFunction::AsyncWorkStart() {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 
   ResumableTCPSocket* socket = GetTcpSocket(params_->socket_id);
   if (!socket) {
diff --git a/extensions/browser/api/sockets_tcp/sockets_tcp_api.h b/extensions/browser/api/sockets_tcp/sockets_tcp_api.h
index a1cfaa6c..cec877f 100644
--- a/extensions/browser/api/sockets_tcp/sockets_tcp_api.h
+++ b/extensions/browser/api/sockets_tcp/sockets_tcp_api.h
@@ -62,8 +62,6 @@
  private:
   FRIEND_TEST_ALL_PREFIXES(SocketsTcpUnitTest, Create);
   std::unique_ptr<sockets_tcp::Create::Params> params_;
-
-  content::BrowserContext* browser_context_ = nullptr;
 };
 
 class SocketsTcpUpdateFunction : public TCPSocketAsyncApiFunction {
diff --git a/extensions/browser/api/sockets_tcp/sockets_tcp_api_unittest.cc b/extensions/browser/api/sockets_tcp/sockets_tcp_api_unittest.cc
index 4d861ee0..df57170 100644
--- a/extensions/browser/api/sockets_tcp/sockets_tcp_api_unittest.cc
+++ b/extensions/browser/api/sockets_tcp/sockets_tcp_api_unittest.cc
@@ -39,7 +39,6 @@
 TEST_F(SocketsTcpUnitTest, Create) {
   // Create SocketCreateFunction and put it on BrowserThread
   SocketsTcpCreateFunction* function = new SocketsTcpCreateFunction();
-  function->set_work_task_runner(base::SequencedTaskRunnerHandle::Get());
 
   // Run tests
   std::unique_ptr<base::DictionaryValue> result(RunFunctionAndReturnDictionary(
diff --git a/extensions/browser/api/sockets_tcp_server/sockets_tcp_server_api.cc b/extensions/browser/api/sockets_tcp_server/sockets_tcp_server_api.cc
index d4c1b05..953e35ee 100644
--- a/extensions/browser/api/sockets_tcp_server/sockets_tcp_server_api.cc
+++ b/extensions/browser/api/sockets_tcp_server/sockets_tcp_server_api.cc
@@ -84,17 +84,13 @@
 
 bool SocketsTcpServerCreateFunction::Prepare() {
   params_ = sockets_tcp_server::Create::Params::Create(*args_);
-  browser_context_ = browser_context();
   EXTENSION_FUNCTION_VALIDATE(params_.get());
   return true;
 }
 
 void SocketsTcpServerCreateFunction::Work() {
-  // TODO(crbug.com/1191472): |browser_context_| is unsafe to access when
-  // DestroyProfileOnBrowserClose is enabled, since it could've been deleted by
-  // now. Fix this by creating the TCPSocket on the UI thread instead.
   auto* socket =
-      new ResumableTCPServerSocket(browser_context_, extension_->id());
+      new ResumableTCPServerSocket(browser_context(), extension_->id());
 
   sockets_tcp_server::SocketProperties* properties = params_->properties.get();
   if (properties) {
diff --git a/extensions/browser/api/sockets_tcp_server/sockets_tcp_server_api.h b/extensions/browser/api/sockets_tcp_server/sockets_tcp_server_api.h
index 1bab117..96ed487 100644
--- a/extensions/browser/api/sockets_tcp_server/sockets_tcp_server_api.h
+++ b/extensions/browser/api/sockets_tcp_server/sockets_tcp_server_api.h
@@ -46,8 +46,6 @@
  private:
   FRIEND_TEST_ALL_PREFIXES(SocketsTcpServerUnitTest, Create);
   std::unique_ptr<sockets_tcp_server::Create::Params> params_;
-
-  content::BrowserContext* browser_context_ = nullptr;
 };
 
 class SocketsTcpServerUpdateFunction : public TCPServerSocketAsyncApiFunction {
diff --git a/extensions/browser/api/sockets_udp/sockets_udp_api_unittest.cc b/extensions/browser/api/sockets_udp/sockets_udp_api_unittest.cc
index 2f2cac7..ce6d8c6 100644
--- a/extensions/browser/api/sockets_udp/sockets_udp_api_unittest.cc
+++ b/extensions/browser/api/sockets_udp/sockets_udp_api_unittest.cc
@@ -38,7 +38,6 @@
 TEST_F(SocketsUdpUnitTest, Create) {
   // Create SocketCreateFunction and put it on BrowserThread
   SocketsUdpCreateFunction* function = new SocketsUdpCreateFunction();
-  function->set_work_task_runner(base::SequencedTaskRunnerHandle::Get());
 
   // Run tests
   std::unique_ptr<base::DictionaryValue> result(RunFunctionAndReturnDictionary(
diff --git a/extensions/browser/user_script_manager.cc b/extensions/browser/user_script_manager.cc
index cb3c05b..e415c03d 100644
--- a/extensions/browser/user_script_manager.cc
+++ b/extensions/browser/user_script_manager.cc
@@ -4,7 +4,9 @@
 
 #include "extensions/browser/user_script_manager.h"
 
+#include "base/containers/contains.h"
 #include "content/public/browser/browser_context.h"
+#include "extensions/browser/extension_registry.h"
 #include "extensions/browser/extension_util.h"
 #include "extensions/browser/extensions_browser_client.h"
 #include "extensions/browser/user_script_loader.h"
@@ -33,10 +35,15 @@
 
 ExtensionUserScriptLoader* UserScriptManager::GetUserScriptLoaderForExtension(
     const ExtensionId& extension_id) {
-  auto it = extension_script_loaders_.find(extension_id);
-  DCHECK(it != extension_script_loaders_.end());
+  const Extension* extension = ExtensionRegistry::Get(browser_context_)
+                                   ->enabled_extensions()
+                                   .GetByID(extension_id);
+  DCHECK(extension);
 
-  return it->second.get();
+  auto it = extension_script_loaders_.find(extension->id());
+  return (it == extension_script_loaders_.end())
+             ? CreateExtensionUserScriptLoader(extension)
+             : it->second.get();
 }
 
 WebUIUserScriptLoader* UserScriptManager::GetUserScriptLoaderForWebUI(
@@ -50,12 +57,7 @@
     content::BrowserContext* browser_context,
     const Extension* extension) {
   ExtensionUserScriptLoader* loader =
-      extension_script_loaders_
-          .emplace(extension->id(),
-                   std::make_unique<ExtensionUserScriptLoader>(
-                       browser_context_, *extension,
-                       true /* listen_for_extension_system_loaded */))
-          .first->second.get();
+      GetUserScriptLoaderForExtension(extension->id());
 
   std::unique_ptr<UserScriptList> scripts =
       GetManifestScriptsMetadata(extension);
@@ -114,8 +116,24 @@
   return script_vector;
 }
 
+ExtensionUserScriptLoader* UserScriptManager::CreateExtensionUserScriptLoader(
+    const Extension* extension) {
+  DCHECK(!base::Contains(extension_script_loaders_, extension->id()));
+  // Inserts a new ExtensionUserScriptLoader and returns a ptr to it.
+  ExtensionUserScriptLoader* loader =
+      extension_script_loaders_
+          .emplace(extension->id(),
+                   std::make_unique<ExtensionUserScriptLoader>(
+                       browser_context_, *extension,
+                       true /* listen_for_extension_system_loaded */))
+          .first->second.get();
+
+  return loader;
+}
+
 WebUIUserScriptLoader* UserScriptManager::CreateWebUIUserScriptLoader(
     const GURL& url) {
+  DCHECK(!base::Contains(webui_script_loaders_, url));
   // Inserts a new WebUIUserScriptLoader and returns a ptr to it.
   WebUIUserScriptLoader* loader =
       webui_script_loaders_
diff --git a/extensions/browser/user_script_manager.h b/extensions/browser/user_script_manager.h
index a28d077..f873763 100644
--- a/extensions/browser/user_script_manager.h
+++ b/extensions/browser/user_script_manager.h
@@ -66,6 +66,10 @@
   std::unique_ptr<UserScriptList> GetManifestScriptsMetadata(
       const Extension* extension);
 
+  // Creates a ExtensionUserScriptLoader object.
+  ExtensionUserScriptLoader* CreateExtensionUserScriptLoader(
+      const Extension* extension);
+
   // Creates a WebUIUserScriptLoader object.
   WebUIUserScriptLoader* CreateWebUIUserScriptLoader(const GURL& url);
 
diff --git a/extensions/common/extension_features.cc b/extensions/common/extension_features.cc
index 42b4360..425d9f5 100644
--- a/extensions/common/extension_features.cc
+++ b/extensions/common/extension_features.cc
@@ -83,4 +83,9 @@
 const base::Feature kReportKeepaliveUkm{"ReportKeepaliveUkm",
                                         base::FEATURE_ENABLED_BY_DEFAULT};
 
+// Controls whether every extension will require a locked process, preventing
+// process sharing between extensions. See https://crbug.com/1209417.
+const base::Feature kStrictExtensionIsolation{
+    "StrictExtensionIsolation", base::FEATURE_DISABLED_BY_DEFAULT};
+
 }  // namespace extensions_features
diff --git a/extensions/common/extension_features.h b/extensions/common/extension_features.h
index 3d4c1ea..a51676c 100644
--- a/extensions/common/extension_features.h
+++ b/extensions/common/extension_features.h
@@ -35,6 +35,8 @@
 
 extern const base::Feature kReportKeepaliveUkm;
 
+extern const base::Feature kStrictExtensionIsolation;
+
 }  // namespace extensions_features
 
 #endif  // EXTENSIONS_COMMON_EXTENSION_FEATURES_H_
diff --git a/infra/config/generated/commit-queue.cfg b/infra/config/generated/commit-queue.cfg
index 07c6a99..86060518f 100644
--- a/infra/config/generated/commit-queue.cfg
+++ b/infra/config/generated/commit-queue.cfg
@@ -666,6 +666,10 @@
         includable_only: true
       }
       builders {
+        name: "chromium/try/fuchsia-fyi-arm64-femu"
+        includable_only: true
+      }
+      builders {
         name: "chromium/try/fuchsia-fyi-arm64-rel"
         includable_only: true
       }
diff --git a/infra/config/generated/cr-buildbucket.cfg b/infra/config/generated/cr-buildbucket.cfg
index 806c2c7..70b4caf 100644
--- a/infra/config/generated/cr-buildbucket.cfg
+++ b/infra/config/generated/cr-buildbucket.cfg
@@ -24484,6 +24484,72 @@
       }
     }
     builders {
+      name: "fuchsia-fyi-arm64-femu"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-18.04"
+      dimensions: "pool:luci.chromium.ci"
+      dimensions: "ssd:0"
+      recipe {
+        name: "chromium"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\",\"use_luci_auth\":true}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "$recipe_engine/isolated:{\"server\":\"https://isolateserver.appspot.com\"}"
+        properties_j: "$recipe_engine/resultdb/test_presentation:{\"column_keys\":[],\"grouping_keys\":[\"status\",\"v.test_suite\"]}"
+        properties_j: "builder_group:\"chromium.fyi\""
+      }
+      execution_timeout_secs: 36000
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+      experiments {
+        key: "chromium.resultdb.result_sink"
+        value: 100
+      }
+      experiments {
+        key: "chromium.resultdb.result_sink.gtests_local"
+        value: 100
+      }
+      experiments {
+        key: "chromium.resultdb.result_sink.junit_tests"
+        value: 100
+      }
+      experiments {
+        key: "luci.buildbucket.use_bbagent"
+        value: 20
+      }
+      experiments {
+        key: "luci.use_realms"
+        value: 100
+      }
+      resultdb {
+        enable: true
+        bq_exports {
+          project: "luci-resultdb"
+          dataset: "chromium"
+          table: "ci_test_results"
+          test_results {}
+        }
+        bq_exports {
+          project: "luci-resultdb"
+          dataset: "chromium"
+          table: "gpu_ci_test_results"
+          test_results {
+            predicate {
+              test_id_regexp: "ninja://(chrome/test:|content/test:fuchsia_)telemetry_gpu_integration_test/.+"
+            }
+          }
+        }
+        history_options {
+          use_invocation_timestamp: true
+        }
+      }
+    }
+    builders {
       name: "fuchsia-fyi-arm64-rel"
       swarming_host: "chromium-swarm.appspot.com"
       swarming_tags: "vpython:native-python-wrapper"
@@ -43191,6 +43257,75 @@
       }
     }
     builders {
+      name: "fuchsia-fyi-arm64-femu"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-18.04"
+      dimensions: "pool:luci.chromium.try"
+      dimensions: "ssd:0"
+      recipe {
+        name: "chromium_trybot"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\",\"use_luci_auth\":true}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "$recipe_engine/isolated:{\"server\":\"https://isolateserver.appspot.com\"}"
+        properties_j: "$recipe_engine/resultdb/test_presentation:{\"column_keys\":[],\"grouping_keys\":[\"status\",\"v.test_suite\"]}"
+        properties_j: "builder_group:\"tryserver.chromium.linux\""
+      }
+      execution_timeout_secs: 14400
+      expiration_secs: 7200
+      grace_period {
+        seconds: 120
+      }
+      caches {
+        name: "win_toolchain"
+        path: "win_toolchain"
+      }
+      build_numbers: YES
+      service_account: "chromium-try-builder@chops-service-accounts.iam.gserviceaccount.com"
+      task_template_canary_percentage {
+        value: 5
+      }
+      experiments {
+        key: "chromium.resultdb.result_sink"
+        value: 100
+      }
+      experiments {
+        key: "chromium.resultdb.result_sink.junit_tests"
+        value: 100
+      }
+      experiments {
+        key: "luci.use_realms"
+        value: 100
+      }
+      resultdb {
+        enable: true
+        bq_exports {
+          project: "luci-resultdb"
+          dataset: "chromium"
+          table: "try_test_results"
+          test_results {}
+        }
+        bq_exports {
+          project: "luci-resultdb"
+          dataset: "chromium"
+          table: "gpu_try_test_results"
+          test_results {
+            predicate {
+              test_id_regexp: "ninja://(chrome/test:|content/test:fuchsia_)telemetry_gpu_integration_test/.+"
+            }
+          }
+        }
+        history_options {
+          use_invocation_timestamp: true
+        }
+      }
+    }
+    builders {
       name: "fuchsia-fyi-arm64-rel"
       swarming_host: "chromium-swarm.appspot.com"
       swarming_tags: "vpython:native-python-wrapper"
diff --git a/infra/config/generated/luci-milo.cfg b/infra/config/generated/luci-milo.cfg
index c8cfc402..5bec738 100644
--- a/infra/config/generated/luci-milo.cfg
+++ b/infra/config/generated/luci-milo.cfg
@@ -6118,6 +6118,11 @@
     short_name: "dbg"
   }
   builders {
+    name: "buildbucket/luci.chromium.ci/fuchsia-fyi-arm64-femu"
+    category: "fuchsia|a64"
+    short_name: "femu"
+  }
+  builders {
     name: "buildbucket/luci.chromium.ci/fuchsia-fyi-arm64-rel"
     category: "fuchsia|a64"
     short_name: "rel"
@@ -13444,6 +13449,9 @@
     name: "buildbucket/luci.chromium.try/fuchsia-fyi-arm64-dbg"
   }
   builders {
+    name: "buildbucket/luci.chromium.try/fuchsia-fyi-arm64-femu"
+  }
+  builders {
     name: "buildbucket/luci.chromium.try/fuchsia-fyi-arm64-rel"
   }
   builders {
@@ -14646,6 +14654,9 @@
     name: "buildbucket/luci.chromium.try/fuchsia-fyi-arm64-dbg"
   }
   builders {
+    name: "buildbucket/luci.chromium.try/fuchsia-fyi-arm64-femu"
+  }
+  builders {
     name: "buildbucket/luci.chromium.try/fuchsia-fyi-arm64-rel"
   }
   builders {
diff --git a/infra/config/generated/luci-notify.cfg b/infra/config/generated/luci-notify.cfg
index 03fde7ae..f0f5c083 100644
--- a/infra/config/generated/luci-notify.cfg
+++ b/infra/config/generated/luci-notify.cfg
@@ -2732,6 +2732,20 @@
   }
   builders {
     bucket: "ci"
+    name: "fuchsia-fyi-arm64-femu"
+    repository: "https://chromium.googlesource.com/chromium/src"
+  }
+}
+notifiers {
+  notifications {
+    on_change: true
+    email {
+      recipients: "cr-fuchsia+bot@chromium.org"
+      recipients: "chrome-fuchsia-gardener@grotations.appspotmail.com"
+    }
+  }
+  builders {
+    bucket: "ci"
     name: "fuchsia-fyi-arm64-rel"
     repository: "https://chromium.googlesource.com/chromium/src"
   }
diff --git a/infra/config/generated/luci-scheduler.cfg b/infra/config/generated/luci-scheduler.cfg
index 65e9c8aa..a5ac84e 100644
--- a/infra/config/generated/luci-scheduler.cfg
+++ b/infra/config/generated/luci-scheduler.cfg
@@ -5050,6 +5050,16 @@
   }
 }
 job {
+  id: "fuchsia-fyi-arm64-femu"
+  realm: "ci"
+  acl_sets: "ci"
+  buildbucket {
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci"
+    builder: "fuchsia-fyi-arm64-femu"
+  }
+}
+job {
   id: "fuchsia-fyi-arm64-rel"
   realm: "ci"
   acl_sets: "ci"
@@ -7102,6 +7112,7 @@
   triggers: "fuchsia-angle-builder"
   triggers: "fuchsia-arm64-cast"
   triggers: "fuchsia-fyi-arm64-dbg"
+  triggers: "fuchsia-fyi-arm64-femu"
   triggers: "fuchsia-fyi-arm64-rel"
   triggers: "fuchsia-fyi-x64-dbg"
   triggers: "fuchsia-fyi-x64-rel"
diff --git a/infra/config/subprojects/chromium/ci.star b/infra/config/subprojects/chromium/ci.star
index 0e134ed0..8f29d1d 100644
--- a/infra/config/subprojects/chromium/ci.star
+++ b/infra/config/subprojects/chromium/ci.star
@@ -3107,6 +3107,18 @@
 )
 
 ci.fyi_builder(
+    name = "fuchsia-fyi-arm64-femu",
+    console_view_entry = [
+        consoles.console_view_entry(
+            category = "fuchsia|a64",
+            short_name = "femu",
+        ),
+    ],
+    notifies = ["cr-fuchsia"],
+    os = os.LINUX_BIONIC_REMOVE,
+)
+
+ci.fyi_builder(
     name = "fuchsia-fyi-arm64-rel",
     console_view_entry = [
         consoles.console_view_entry(
diff --git a/infra/config/subprojects/chromium/try.star b/infra/config/subprojects/chromium/try.star
index 79ff4860..18ac6c9da 100644
--- a/infra/config/subprojects/chromium/try.star
+++ b/infra/config/subprojects/chromium/try.star
@@ -1099,6 +1099,10 @@
 )
 
 try_.chromium_linux_builder(
+    name = "fuchsia-fyi-arm64-femu",
+)
+
+try_.chromium_linux_builder(
     name = "fuchsia-fyi-arm64-rel",
 )
 
diff --git a/ios/chrome/browser/DEPS b/ios/chrome/browser/DEPS
index 2b900979..7b7a431 100644
--- a/ios/chrome/browser/DEPS
+++ b/ios/chrome/browser/DEPS
@@ -25,7 +25,8 @@
   "+components/favicon/ios",
   "+components/favicon_base",
   "+components/feature_engagement",
-  "+components/feed",
+  "+components/feed/core/v2/public",
+  "+components/feed/feed_feature_list.h",
   "+components/flags_ui",
   "+components/gcm_driver",
   "+components/google/core",
@@ -53,7 +54,6 @@
   "+components/network_session_configurator",
   "+components/network_time",
   "+components/ntp_snippets",
-  "+components/feed/core/shared_prefs",
   "+components/ntp_tiles",
   "+components/omnibox/browser",
   "+components/omnibox/common",
diff --git a/ios/chrome/browser/infobars/OWNERS b/ios/chrome/browser/infobars/OWNERS
index 23b84a8..db73a55 100644
--- a/ios/chrome/browser/infobars/OWNERS
+++ b/ios/chrome/browser/infobars/OWNERS
@@ -1,2 +1,3 @@
+thegreenfrog@chromium.org
 rohitrao@chromium.org
 sczs@chromium.org
diff --git a/ios/chrome/browser/infobars/infobar_metrics_recorder.mm b/ios/chrome/browser/infobars/infobar_metrics_recorder.mm
index 35853312..37e4d31 100644
--- a/ios/chrome/browser/infobars/infobar_metrics_recorder.mm
+++ b/ios/chrome/browser/infobars/infobar_metrics_recorder.mm
@@ -123,6 +123,8 @@
       UMA_HISTOGRAM_ENUMERATION(kInfobarTranslateBannerEventHistogram, event);
       break;
     case InfobarType::kInfobarTypeSaveAutofillAddressProfile:
+      // TODO(crbug.com/1195978): Add metrics.
+    case InfobarType::kInfobarTypeAddToReadingList:
       // TODO(crbug.com/1167062): Add metrics.
       break;
   }
@@ -151,6 +153,7 @@
                                 dismissType);
       break;
     case InfobarType::kInfobarTypeSaveAutofillAddressProfile:
+    case InfobarType::kInfobarTypeAddToReadingList:
       // TODO(crbug.com/1167062): Add metrics.
       break;
   }
@@ -180,6 +183,7 @@
       UMA_HISTOGRAM_ENUMERATION(kInfobarTranslateModalEventHistogram, event);
       break;
     case InfobarType::kInfobarTypeSaveAutofillAddressProfile:
+    case InfobarType::kInfobarTypeAddToReadingList:
       // TODO(crbug.com/1167062): Add metrics.
       break;
   }
@@ -205,6 +209,7 @@
       UMA_HISTOGRAM_ENUMERATION(kInfobarTranslateBadgeTappedHistogram, state);
       break;
     case InfobarType::kInfobarTypeSaveAutofillAddressProfile:
+    case InfobarType::kInfobarTypeAddToReadingList:
       // TODO(crbug.com/1167062): Add metrics.
       break;
   }
diff --git a/ios/chrome/browser/infobars/infobar_type.h b/ios/chrome/browser/infobars/infobar_type.h
index 98ade2c..9352e05 100644
--- a/ios/chrome/browser/infobars/infobar_type.h
+++ b/ios/chrome/browser/infobars/infobar_type.h
@@ -20,6 +20,8 @@
   kInfobarTypeTranslate = 4,
   // Message Infobar for Saving an address profile.
   kInfobarTypeSaveAutofillAddressProfile = 5,
+  // Message Infobar for Adding to Reading List.
+  kInfobarTypeAddToReadingList = 6,
 };
 
 // Message "Confirm Infobars" types, these are the generic kInfobarTypeConfirm
diff --git a/ios/chrome/browser/overlays/public/infobar_banner/BUILD.gn b/ios/chrome/browser/overlays/public/infobar_banner/BUILD.gn
index 9fe9e111..ed15cdd 100644
--- a/ios/chrome/browser/overlays/public/infobar_banner/BUILD.gn
+++ b/ios/chrome/browser/overlays/public/infobar_banner/BUILD.gn
@@ -4,6 +4,8 @@
 
 source_set("infobar_banner") {
   sources = [
+    "add_to_reading_list_infobar_banner_overlay_request_config.h",
+    "add_to_reading_list_infobar_banner_overlay_request_config.mm",
     "confirm_infobar_banner_overlay_request_config.h",
     "confirm_infobar_banner_overlay_request_config.mm",
     "infobar_banner_overlay_responses.cc",
@@ -38,6 +40,7 @@
     "//ios/chrome/browser/passwords:infobar_delegates",
     "//ios/chrome/browser/ui/authentication",
     "//ios/chrome/browser/ui/infobars:infobars_ui",
+    "//ios/chrome/browser/ui/reading_list:infobar",
     "//ui/base",
   ]
 }
diff --git a/ios/chrome/browser/overlays/public/infobar_banner/add_to_reading_list_infobar_banner_overlay_request_config.h b/ios/chrome/browser/overlays/public/infobar_banner/add_to_reading_list_infobar_banner_overlay_request_config.h
new file mode 100644
index 0000000..3682e39
--- /dev/null
+++ b/ios/chrome/browser/overlays/public/infobar_banner/add_to_reading_list_infobar_banner_overlay_request_config.h
@@ -0,0 +1,50 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IOS_CHROME_BROWSER_OVERLAYS_PUBLIC_INFOBAR_BANNER_ADD_TO_READING_LIST_INFOBAR_BANNER_OVERLAY_REQUEST_CONFIG_H_
+#define IOS_CHROME_BROWSER_OVERLAYS_PUBLIC_INFOBAR_BANNER_ADD_TO_READING_LIST_INFOBAR_BANNER_OVERLAY_REQUEST_CONFIG_H_
+
+#include "ios/chrome/browser/overlays/public/overlay_request_config.h"
+#include "ios/chrome/browser/overlays/public/overlay_user_data.h"
+
+namespace infobars {
+class InfoBar;
+}
+
+namespace reading_list_infobar_overlay {
+
+// Configuration object for OverlayRequests for the banner UI for an Infobar
+// with a IOSAddToReadingListInfobarDelegate.
+class ReadingListBannerRequestConfig
+    : public OverlayRequestConfig<ReadingListBannerRequestConfig> {
+ public:
+  ~ReadingListBannerRequestConfig() override;
+
+  // The title text.
+  NSString* title_text() const { return title_text_; }
+
+  // The message text.
+  NSString* message_text() const { return message_text_; }
+
+  // The button text.
+  NSString* button_text() const { return button_text_; }
+
+ private:
+  OVERLAY_USER_DATA_SETUP(ReadingListBannerRequestConfig);
+  explicit ReadingListBannerRequestConfig(infobars::InfoBar* infobar);
+
+  // OverlayUserData:
+  void CreateAuxiliaryData(base::SupportsUserData* user_data) override;
+
+  NSString* title_text_;
+  NSString* message_text_;
+  NSString* button_text_;
+
+  // The InfoBar causing this banner.
+  infobars::InfoBar* infobar_ = nullptr;
+};
+
+}  // namespace reading_list_infobar_overlay
+
+#endif  // IOS_CHROME_BROWSER_OVERLAYS_PUBLIC_INFOBAR_BANNER_ADD_TO_READING_LIST_INFOBAR_BANNER_OVERLAY_REQUEST_CONFIG_H_
diff --git a/ios/chrome/browser/overlays/public/infobar_banner/add_to_reading_list_infobar_banner_overlay_request_config.mm b/ios/chrome/browser/overlays/public/infobar_banner/add_to_reading_list_infobar_banner_overlay_request_config.mm
new file mode 100644
index 0000000..36bdaf73
--- /dev/null
+++ b/ios/chrome/browser/overlays/public/infobar_banner/add_to_reading_list_infobar_banner_overlay_request_config.mm
@@ -0,0 +1,45 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "ios/chrome/browser/overlays/public/infobar_banner/add_to_reading_list_infobar_banner_overlay_request_config.h"
+
+#include "components/infobars/core/infobar.h"
+#include "ios/chrome/browser/infobars/infobar_ios.h"
+#import "ios/chrome/browser/infobars/overlays/infobar_overlay_type.h"
+#import "ios/chrome/browser/overlays/public/common/infobars/infobar_overlay_request_config.h"
+#import "ios/chrome/browser/ui/reading_list/ios_add_to_reading_list_infobar_delegate.h"
+#include "url/gurl.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+using infobars::InfoBar;
+
+namespace reading_list_infobar_overlay {
+
+OVERLAY_USER_DATA_SETUP_IMPL(ReadingListBannerRequestConfig);
+
+ReadingListBannerRequestConfig::ReadingListBannerRequestConfig(InfoBar* infobar)
+    : infobar_(infobar) {
+  DCHECK(infobar_);
+  IOSAddToReadingListInfobarDelegate* delegate =
+      static_cast<IOSAddToReadingListInfobarDelegate*>(infobar_->delegate());
+  int time_to_read = delegate->time_to_read();
+  // TODO(crbug.com/1195978): Use localized strings.
+  message_text_ = [NSString stringWithFormat:@"%i minute read", time_to_read];
+  title_text_ = [NSString stringWithFormat:@"Add to Reading List for Later?"];
+  button_text_ = @"Add";
+}
+
+ReadingListBannerRequestConfig::~ReadingListBannerRequestConfig() = default;
+
+void ReadingListBannerRequestConfig::CreateAuxiliaryData(
+    base::SupportsUserData* user_data) {
+  InfobarOverlayRequestConfig::CreateForUserData(
+      user_data, static_cast<InfoBarIOS*>(infobar_),
+      InfobarOverlayType::kBanner, false);
+}
+
+}  // namespace reading_list_infobar_overlay
diff --git a/ios/chrome/browser/prefs/BUILD.gn b/ios/chrome/browser/prefs/BUILD.gn
index ccd71e1d..c7e17c2 100644
--- a/ios/chrome/browser/prefs/BUILD.gn
+++ b/ios/chrome/browser/prefs/BUILD.gn
@@ -37,8 +37,7 @@
     "//components/content_settings/core/browser",
     "//components/dom_distiller/core",
     "//components/enterprise",
-    "//components/feed/core/common:feed_core_common",
-    "//components/feed/core/shared_prefs:feed_shared_prefs",
+    "//components/feed/core/v2/public/ios:feed_ios_public",
     "//components/flags_ui",
     "//components/gcm_driver",
     "//components/handoff",
diff --git a/ios/chrome/browser/prefs/browser_prefs.mm b/ios/chrome/browser/prefs/browser_prefs.mm
index f22d914..ae6e4b43 100644
--- a/ios/chrome/browser/prefs/browser_prefs.mm
+++ b/ios/chrome/browser/prefs/browser_prefs.mm
@@ -10,8 +10,7 @@
 #include "components/content_settings/core/browser/host_content_settings_map.h"
 #include "components/dom_distiller/core/distilled_page_prefs.h"
 #include "components/enterprise/browser/reporting/common_pref_names.h"
-#include "components/feed/core/common/pref_names.h"
-#include "components/feed/core/shared_prefs/pref_names.h"
+#include "components/feed/core/v2/public/ios/pref_names.h"
 #include "components/flags_ui/pref_service_flags_storage.h"
 #import "components/handoff/handoff_manager.h"
 #include "components/history/core/common/pref_names.h"
@@ -177,8 +176,7 @@
 void RegisterBrowserStatePrefs(user_prefs::PrefRegistrySyncable* registry) {
   autofill::prefs::RegisterProfilePrefs(registry);
   dom_distiller::DistilledPagePrefs::RegisterProfilePrefs(registry);
-  feed::prefs::RegisterFeedSharedProfilePrefs(registry);
-  feed::RegisterProfilePrefs(registry);
+  ios_feed::RegisterProfilePrefs(registry);
   FirstRun::RegisterProfilePrefs(registry);
   FontSizeTabHelper::RegisterBrowserStatePrefs(registry);
   HostContentSettingsMap::RegisterProfilePrefs(registry);
diff --git a/ios/chrome/browser/ui/content_suggestions/BUILD.gn b/ios/chrome/browser/ui/content_suggestions/BUILD.gn
index d8c79c9..11e88ed 100644
--- a/ios/chrome/browser/ui/content_suggestions/BUILD.gn
+++ b/ios/chrome/browser/ui/content_suggestions/BUILD.gn
@@ -40,6 +40,7 @@
     "//components/favicon/ios",
     "//components/feature_engagement/public",
     "//components/feed/core/shared_prefs:feed_shared_prefs",
+    "//components/feed/core/v2/public/ios:feed_ios_public",
     "//components/ntp_snippets",
     "//components/ntp_tiles",
     "//components/pref_registry",
@@ -207,7 +208,7 @@
   deps = [
     ":feature_flags",
     "//base",
-    "//components/feed/core/v2:common",
+    "//components/feed/core/v2/public:common",
   ]
   configs += [ "//build/config/compiler:enable_arc" ]
 }
@@ -323,6 +324,7 @@
     "//base",
     "//base/test:test_support",
     "//components/feed/core/shared_prefs:feed_shared_prefs",
+    "//components/feed/core/v2/public/ios:feed_ios_public",
     "//components/strings",
     "//ios/chrome/app/strings",
     "//ios/chrome/browser:pref_names",
diff --git a/ios/chrome/browser/ui/content_suggestions/content_suggestions_coordinator.mm b/ios/chrome/browser/ui/content_suggestions/content_suggestions_coordinator.mm
index 7083bb1..29a3b63f 100644
--- a/ios/chrome/browser/ui/content_suggestions/content_suggestions_coordinator.mm
+++ b/ios/chrome/browser/ui/content_suggestions/content_suggestions_coordinator.mm
@@ -12,7 +12,7 @@
 #include "base/strings/sys_string_conversions.h"
 #import "components/feature_engagement/public/event_constants.h"
 #import "components/feature_engagement/public/tracker.h"
-#include "components/feed/core/shared_prefs/pref_names.h"
+#include "components/feed/core/v2/public/ios/pref_names.h"
 #include "components/ntp_snippets/content_suggestions_service.h"
 #include "components/ntp_snippets/pref_names.h"
 #include "components/ntp_snippets/remote/remote_suggestions_scheduler.h"
diff --git a/ios/chrome/browser/ui/content_suggestions/discover_feed_metrics_recorder.mm b/ios/chrome/browser/ui/content_suggestions/discover_feed_metrics_recorder.mm
index bbb10925..a712cc8 100644
--- a/ios/chrome/browser/ui/content_suggestions/discover_feed_metrics_recorder.mm
+++ b/ios/chrome/browser/ui/content_suggestions/discover_feed_metrics_recorder.mm
@@ -9,7 +9,7 @@
 #import "base/metrics/histogram_macros.h"
 #import "base/metrics/user_metrics.h"
 #import "base/metrics/user_metrics_action.h"
-#import "components/feed/core/v2/common_enums.h"
+#import "components/feed/core/v2/public/common_enums.h"
 #import "ios/chrome/browser/ui/content_suggestions/content_suggestions_feature.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
diff --git a/ios/chrome/browser/ui/content_suggestions/ntp_home_egtest.mm b/ios/chrome/browser/ui/content_suggestions/ntp_home_egtest.mm
index 0c7457e..c583ae0 100644
--- a/ios/chrome/browser/ui/content_suggestions/ntp_home_egtest.mm
+++ b/ios/chrome/browser/ui/content_suggestions/ntp_home_egtest.mm
@@ -6,7 +6,7 @@
 #include "base/ios/ios_util.h"
 #include "base/mac/foundation_util.h"
 #include "base/strings/sys_string_conversions.h"
-#include "components/feed/core/shared_prefs/pref_names.h"
+#include "components/feed/core/v2/public/ios/pref_names.h"
 #include "components/strings/grit/components_strings.h"
 #include "ios/chrome/browser/chrome_switches.h"
 #import "ios/chrome/browser/pref_names.h"
diff --git a/ios/chrome/browser/ui/ntp/BUILD.gn b/ios/chrome/browser/ui/ntp/BUILD.gn
index 20b77988..8b16a2d 100644
--- a/ios/chrome/browser/ui/ntp/BUILD.gn
+++ b/ios/chrome/browser/ui/ntp/BUILD.gn
@@ -37,7 +37,7 @@
     ":feature_flags",
     ":ntp",
     ":ntp_internal",
-    "//components/feed/core/shared_prefs:feed_shared_prefs",
+    "//components/feed/core/v2/public/ios:feed_ios_public",
     "//components/pref_registry",
     "//components/prefs",
     "//components/prefs/ios",
diff --git a/ios/chrome/browser/ui/ntp/new_tab_page_coordinator.mm b/ios/chrome/browser/ui/ntp/new_tab_page_coordinator.mm
index 48d586d..a7673f9 100644
--- a/ios/chrome/browser/ui/ntp/new_tab_page_coordinator.mm
+++ b/ios/chrome/browser/ui/ntp/new_tab_page_coordinator.mm
@@ -7,7 +7,7 @@
 #include "base/metrics/histogram_functions.h"
 #include "base/metrics/user_metrics.h"
 #include "base/metrics/user_metrics_action.h"
-#include "components/feed/core/shared_prefs/pref_names.h"
+#include "components/feed/core/v2/public/ios/pref_names.h"
 #import "components/pref_registry/pref_registry_syncable.h"
 #import "components/prefs/ios/pref_observer_bridge.h"
 #import "components/prefs/pref_change_registrar.h"
diff --git a/ios/chrome/browser/ui/omnibox/omnibox_egtest.mm b/ios/chrome/browser/ui/omnibox/omnibox_egtest.mm
index 38b27ee..083a762 100644
--- a/ios/chrome/browser/ui/omnibox/omnibox_egtest.mm
+++ b/ios/chrome/browser/ui/omnibox/omnibox_egtest.mm
@@ -738,9 +738,7 @@
   // Returns the popup row containing the |url| as suggestion.
   id<GREYMatcher> textYouCopiedMatch =
       grey_allOf(grey_kindOfClassName(@"OmniboxPopupRowCell"),
-                 grey_descendant(grey_accessibilityLabel(
-                     [NSString stringWithFormat:@"\"%@\"", copiedText])),
-                 nil);
+                 grey_descendant(grey_accessibilityLabel(copiedText)), nil);
   [[EarlGrey selectElementWithMatcher:textYouCopiedMatch]
       assertWithMatcher:grey_notNil()];
 }
diff --git a/ios/chrome/browser/ui/overlays/infobar_banner/BUILD.gn b/ios/chrome/browser/ui/overlays/infobar_banner/BUILD.gn
index f03ffccd..12bba23 100644
--- a/ios/chrome/browser/ui/overlays/infobar_banner/BUILD.gn
+++ b/ios/chrome/browser/ui/overlays/infobar_banner/BUILD.gn
@@ -35,6 +35,7 @@
     "//ios/chrome/browser/ui/overlays/infobar_banner/autofill_address_profile",
     "//ios/chrome/browser/ui/overlays/infobar_banner/confirm",
     "//ios/chrome/browser/ui/overlays/infobar_banner/passwords",
+    "//ios/chrome/browser/ui/overlays/infobar_banner/reading_list",
     "//ios/chrome/browser/ui/overlays/infobar_banner/save_card",
     "//ios/chrome/browser/ui/overlays/infobar_banner/translate:mediators",
     "//ios/chrome/browser/ui/util",
diff --git a/ios/chrome/browser/ui/overlays/infobar_banner/infobar_banner_overlay_coordinator.mm b/ios/chrome/browser/ui/overlays/infobar_banner/infobar_banner_overlay_coordinator.mm
index 2732a3d..ae964bd8 100644
--- a/ios/chrome/browser/ui/overlays/infobar_banner/infobar_banner_overlay_coordinator.mm
+++ b/ios/chrome/browser/ui/overlays/infobar_banner/infobar_banner_overlay_coordinator.mm
@@ -21,6 +21,7 @@
 #import "ios/chrome/browser/ui/overlays/infobar_banner/infobar_banner_overlay_mediator.h"
 #import "ios/chrome/browser/ui/overlays/infobar_banner/passwords/save_password_infobar_banner_overlay_mediator.h"
 #import "ios/chrome/browser/ui/overlays/infobar_banner/passwords/update_password_infobar_banner_overlay_mediator.h"
+#import "ios/chrome/browser/ui/overlays/infobar_banner/reading_list/reading_list_infobar_banner_overlay_mediator.h"
 #import "ios/chrome/browser/ui/overlays/infobar_banner/save_card/save_card_infobar_banner_overlay_mediator.h"
 #import "ios/chrome/browser/ui/overlays/infobar_banner/translate/translate_infobar_banner_overlay_mediator.h"
 #import "ios/chrome/browser/ui/overlays/overlay_request_coordinator+subclassing.h"
@@ -54,6 +55,7 @@
     [TranslateInfobarBannerOverlayMediator class],
     [SaveCardInfobarBannerOverlayMediator class],
     [SaveAddressProfileInfobarBannerOverlayMediator class],
+    [AddToReadingListInfobarBannerOverlayMediator class],
   ];
 }
 
diff --git a/ios/chrome/browser/ui/overlays/infobar_banner/reading_list/BUILD.gn b/ios/chrome/browser/ui/overlays/infobar_banner/reading_list/BUILD.gn
new file mode 100644
index 0000000..ab13a22
--- /dev/null
+++ b/ios/chrome/browser/ui/overlays/infobar_banner/reading_list/BUILD.gn
@@ -0,0 +1,20 @@
+# Copyright 2021 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+source_set("reading_list") {
+  sources = [
+    "reading_list_infobar_banner_overlay_mediator.h",
+    "reading_list_infobar_banner_overlay_mediator.mm",
+  ]
+
+  configs += [ "//build/config/compiler:enable_arc" ]
+
+  deps = [
+    "//ios/chrome/browser/overlays",
+    "//ios/chrome/browser/overlays/public/infobar_banner",
+    "//ios/chrome/browser/ui/infobars/banners",
+    "//ios/chrome/browser/ui/overlays:coordinators",
+    "//ios/chrome/browser/ui/overlays/infobar_banner:mediators",
+  ]
+}
diff --git a/ios/chrome/browser/ui/overlays/infobar_banner/reading_list/reading_list_infobar_banner_overlay_mediator.h b/ios/chrome/browser/ui/overlays/infobar_banner/reading_list/reading_list_infobar_banner_overlay_mediator.h
new file mode 100644
index 0000000..c7ef25bd
--- /dev/null
+++ b/ios/chrome/browser/ui/overlays/infobar_banner/reading_list/reading_list_infobar_banner_overlay_mediator.h
@@ -0,0 +1,15 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IOS_CHROME_BROWSER_UI_OVERLAYS_INFOBAR_BANNER_READING_LIST_READING_LIST_INFOBAR_BANNER_OVERLAY_MEDIATOR_H_
+#define IOS_CHROME_BROWSER_UI_OVERLAYS_INFOBAR_BANNER_READING_LIST_READING_LIST_INFOBAR_BANNER_OVERLAY_MEDIATOR_H_
+
+#import "ios/chrome/browser/ui/overlays/infobar_banner/infobar_banner_overlay_mediator.h"
+
+// Mediator that configures an infobar banner for a add to reading list infobar.
+@interface AddToReadingListInfobarBannerOverlayMediator
+    : InfobarBannerOverlayMediator
+@end
+
+#endif  // IOS_CHROME_BROWSER_UI_OVERLAYS_INFOBAR_BANNER_READING_LIST_READING_LIST_INFOBAR_BANNER_OVERLAY_MEDIATOR_H_
diff --git a/ios/chrome/browser/ui/overlays/infobar_banner/reading_list/reading_list_infobar_banner_overlay_mediator.mm b/ios/chrome/browser/ui/overlays/infobar_banner/reading_list/reading_list_infobar_banner_overlay_mediator.mm
new file mode 100644
index 0000000..e25a911
--- /dev/null
+++ b/ios/chrome/browser/ui/overlays/infobar_banner/reading_list/reading_list_infobar_banner_overlay_mediator.mm
@@ -0,0 +1,53 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "ios/chrome/browser/ui/overlays/infobar_banner/reading_list/reading_list_infobar_banner_overlay_mediator.h"
+
+#import "ios/chrome/browser/overlays/public/infobar_banner/add_to_reading_list_infobar_banner_overlay_request_config.h"
+#import "ios/chrome/browser/ui/infobars/banners/infobar_banner_consumer.h"
+#import "ios/chrome/browser/ui/overlays/infobar_banner/infobar_banner_overlay_mediator+consumer_support.h"
+#import "ios/chrome/browser/ui/overlays/overlay_request_mediator+subclassing.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+using reading_list_infobar_overlay::ReadingListBannerRequestConfig;
+
+@interface AddToReadingListInfobarBannerOverlayMediator ()
+// The add to reading list banner config from the request.
+@property(nonatomic, readonly) ReadingListBannerRequestConfig* config;
+@end
+
+@implementation AddToReadingListInfobarBannerOverlayMediator
+
+#pragma mark - Accessors
+
+- (ReadingListBannerRequestConfig*)config {
+  return self.request
+             ? self.request->GetConfig<ReadingListBannerRequestConfig>()
+             : nullptr;
+}
+
+#pragma mark - OverlayRequestMediator
+
++ (const OverlayRequestSupport*)requestSupport {
+  return ReadingListBannerRequestConfig::RequestSupport();
+}
+
+@end
+
+@implementation AddToReadingListInfobarBannerOverlayMediator (ConsumerSupport)
+
+- (void)configureConsumer {
+  ReadingListBannerRequestConfig* config = self.config;
+  if (!self.consumer || !config)
+    return;
+
+  [self.consumer setTitleText:config->title_text()];
+  [self.consumer setSubtitleText:config->message_text()];
+  [self.consumer setButtonText:config->button_text()];
+}
+
+@end
diff --git a/media/audio/audio_debug_recording_manager.h b/media/audio/audio_debug_recording_manager.h
index 19af6454..cef0a5e 100644
--- a/media/audio/audio_debug_recording_manager.h
+++ b/media/audio/audio_debug_recording_manager.h
@@ -7,7 +7,6 @@
 
 #include <map>
 #include <memory>
-#include <string>
 #include <utility>
 
 #include "base/callback.h"
diff --git a/media/audio/audio_output_delegate.h b/media/audio/audio_output_delegate.h
index ab14a28c..7cb5e00 100644
--- a/media/audio/audio_output_delegate.h
+++ b/media/audio/audio_output_delegate.h
@@ -6,7 +6,6 @@
 #define MEDIA_AUDIO_AUDIO_OUTPUT_DELEGATE_H_
 
 #include <memory>
-#include <string>
 
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
diff --git a/media/base/android/android_util.h b/media/base/android/android_util.h
index 39026177..b70b6495 100644
--- a/media/base/android/android_util.h
+++ b/media/base/android/android_util.h
@@ -6,7 +6,6 @@
 #define MEDIA_BASE_ANDROID_ANDROID_UTIL_H_
 
 #include <memory>
-#include <string>
 
 #include "base/android/scoped_java_ref.h"
 
diff --git a/media/base/audio_renderer_mixer.h b/media/base/audio_renderer_mixer.h
index 3891986..f8293130 100644
--- a/media/base/audio_renderer_mixer.h
+++ b/media/base/audio_renderer_mixer.h
@@ -9,7 +9,6 @@
 
 #include <map>
 #include <memory>
-#include <string>
 
 #include "base/containers/flat_map.h"
 #include "base/containers/flat_set.h"
diff --git a/media/base/media_client.h b/media/base/media_client.h
index db1d309..8931490 100644
--- a/media/base/media_client.h
+++ b/media/base/media_client.h
@@ -6,7 +6,6 @@
 #define MEDIA_BASE_MEDIA_CLIENT_H_
 
 #include <memory>
-#include <string>
 #include <vector>
 
 #include "media/base/audio_codecs.h"
diff --git a/media/base/media_tracks.h b/media/base/media_tracks.h
index 900c3b1..36fbd2b 100644
--- a/media/base/media_tracks.h
+++ b/media/base/media_tracks.h
@@ -7,7 +7,6 @@
 
 #include <map>
 #include <memory>
-#include <string>
 #include <vector>
 
 #include "base/macros.h"
diff --git a/media/base/scoped_async_trace.h b/media/base/scoped_async_trace.h
index b82df2e..e448689 100644
--- a/media/base/scoped_async_trace.h
+++ b/media/base/scoped_async_trace.h
@@ -6,7 +6,6 @@
 #define MEDIA_BASE_SCOPED_ASYNC_TRACE_H_
 
 #include <memory>
-#include <string>
 
 #include "base/macros.h"
 #include "media/base/media_export.h"
diff --git a/media/base/stream_parser.h b/media/base/stream_parser.h
index 43d788e..a7de9bc 100644
--- a/media/base/stream_parser.h
+++ b/media/base/stream_parser.h
@@ -10,7 +10,6 @@
 
 #include <map>
 #include <memory>
-#include <string>
 #include <vector>
 
 #include "base/callback_forward.h"
diff --git a/media/base/video_frame_layout.h b/media/base/video_frame_layout.h
index 73b106e..b1f40ea 100644
--- a/media/base/video_frame_layout.h
+++ b/media/base/video_frame_layout.h
@@ -9,7 +9,6 @@
 #include <stdint.h>
 
 #include <ostream>
-#include <string>
 #include <utility>
 #include <vector>
 
diff --git a/media/blink/learning_experiment_helper.h b/media/blink/learning_experiment_helper.h
index 907bacf..4b0e14f 100644
--- a/media/blink/learning_experiment_helper.h
+++ b/media/blink/learning_experiment_helper.h
@@ -6,7 +6,6 @@
 #define MEDIA_BLINK_LEARNING_EXPERIMENT_HELPER_H_
 
 #include <memory>
-#include <string>
 
 #include "base/macros.h"
 #include "media/blink/media_blink_export.h"
diff --git a/media/blink/multibuffer_data_source.h b/media/blink/multibuffer_data_source.h
index 37be059f..1ee69446 100644
--- a/media/blink/multibuffer_data_source.h
+++ b/media/blink/multibuffer_data_source.h
@@ -8,7 +8,6 @@
 #include <stdint.h>
 
 #include <memory>
-#include <string>
 #include <vector>
 
 #include "base/callback.h"
diff --git a/media/blink/watch_time_reporter_unittest.cc b/media/blink/watch_time_reporter_unittest.cc
index 7111207..2bf7871 100644
--- a/media/blink/watch_time_reporter_unittest.cc
+++ b/media/blink/watch_time_reporter_unittest.cc
@@ -276,6 +276,8 @@
     void SetContainerName(
         container_names::MediaContainerName container_name) override {}
     void SetRendererType(RendererType renderer_type) override {}
+    void SetKeySystem(const std::string& key_system) override {}
+    void SetIsHardwareSecure() override {}
     void SetHasPlayed() override {}
     void SetHaveEnough() override {}
     void SetHasAudio(AudioCodec audio_codec) override {}
diff --git a/media/blink/webmediaplayer_impl.cc b/media/blink/webmediaplayer_impl.cc
index 1427d22..09f81a8 100644
--- a/media/blink/webmediaplayer_impl.cc
+++ b/media/blink/webmediaplayer_impl.cc
@@ -1551,6 +1551,10 @@
   cdm_config_ = web_cdm->GetCdmConfig();
   key_system_ = web_cdm->GetKeySystem();
   DCHECK(!key_system_.empty());
+
+  media_metrics_provider_->SetKeySystem(key_system_);
+  if (cdm_config_->use_hw_secure_codecs)
+    media_metrics_provider_->SetIsHardwareSecure();
   CreateVideoDecodeStatsReporter();
 
   CdmContext* cdm_context = cdm_context_ref->GetCdmContext();
diff --git a/media/blink/webmediasource_impl.h b/media/blink/webmediasource_impl.h
index e9c4a35..94fdb6e 100644
--- a/media/blink/webmediasource_impl.h
+++ b/media/blink/webmediasource_impl.h
@@ -5,7 +5,6 @@
 #ifndef MEDIA_BLINK_WEBMEDIASOURCE_IMPL_H_
 #define MEDIA_BLINK_WEBMEDIASOURCE_IMPL_H_
 
-#include <string>
 #include <vector>
 
 #include "base/macros.h"
diff --git a/media/capture/video/android/photo_capabilities.h b/media/capture/video/android/photo_capabilities.h
index 49e7115..d2c47f1 100644
--- a/media/capture/video/android/photo_capabilities.h
+++ b/media/capture/video/android/photo_capabilities.h
@@ -6,7 +6,6 @@
 #define MEDIA_CAPTURE_VIDEO_ANDROID_PHOTO_CAPABILITIES_H_
 
 #include <jni.h>
-#include <string>
 #include <vector>
 
 #include "base/android/scoped_java_ref.h"
diff --git a/media/capture/video/fake_video_capture_device.h b/media/capture/video/fake_video_capture_device.h
index 3f1c237..a393717 100644
--- a/media/capture/video/fake_video_capture_device.h
+++ b/media/capture/video/fake_video_capture_device.h
@@ -8,7 +8,6 @@
 #include <stdint.h>
 
 #include <memory>
-#include <string>
 #include <vector>
 
 #include "base/threading/thread_checker.h"
diff --git a/media/capture/video/file_video_capture_device.h b/media/capture/video/file_video_capture_device.h
index a08187a..7862676 100644
--- a/media/capture/video/file_video_capture_device.h
+++ b/media/capture/video/file_video_capture_device.h
@@ -8,7 +8,6 @@
 #include <stdint.h>
 
 #include <memory>
-#include <string>
 
 #include "base/containers/queue.h"
 #include "base/files/file.h"
diff --git a/media/capture/video/linux/video_capture_device_linux.h b/media/capture/video/linux/video_capture_device_linux.h
index 073b511..88e9263 100644
--- a/media/capture/video/linux/video_capture_device_linux.h
+++ b/media/capture/video/linux/video_capture_device_linux.h
@@ -13,7 +13,6 @@
 #include <stdint.h>
 
 #include <memory>
-#include <string>
 #include <vector>
 
 #include "base/files/file_util.h"
diff --git a/media/cast/net/cast_transport_defines.h b/media/cast/net/cast_transport_defines.h
index 7c11de2d..3eac16ea 100644
--- a/media/cast/net/cast_transport_defines.h
+++ b/media/cast/net/cast_transport_defines.h
@@ -10,7 +10,6 @@
 
 #include <map>
 #include <set>
-#include <string>
 #include <vector>
 
 #include "base/macros.h"
diff --git a/media/filters/dav1d_video_decoder.h b/media/filters/dav1d_video_decoder.h
index 938ec76..3cdc5184 100644
--- a/media/filters/dav1d_video_decoder.h
+++ b/media/filters/dav1d_video_decoder.h
@@ -5,8 +5,6 @@
 #ifndef MEDIA_FILTERS_DAV1D_VIDEO_DECODER_H_
 #define MEDIA_FILTERS_DAV1D_VIDEO_DECODER_H_
 
-#include <string>
-
 #include "base/callback_forward.h"
 #include "base/macros.h"
 #include "base/memory/ref_counted_memory.h"
diff --git a/media/filters/gav1_video_decoder.h b/media/filters/gav1_video_decoder.h
index 3f815625..b9ba0eb 100644
--- a/media/filters/gav1_video_decoder.h
+++ b/media/filters/gav1_video_decoder.h
@@ -7,7 +7,6 @@
 
 #include <memory>
 #include <queue>
-#include <string>
 #include <vector>
 
 #include "base/containers/queue.h"
diff --git a/media/gpu/android/codec_wrapper.h b/media/gpu/android/codec_wrapper.h
index 53ec972..5a12ed3 100644
--- a/media/gpu/android/codec_wrapper.h
+++ b/media/gpu/android/codec_wrapper.h
@@ -9,7 +9,6 @@
 #include <stdint.h>
 
 #include <memory>
-#include <string>
 #include <vector>
 
 #include "base/memory/ref_counted.h"
diff --git a/media/gpu/chromeos/gpu_buffer_layout.h b/media/gpu/chromeos/gpu_buffer_layout.h
index 833c182..3e415a04 100644
--- a/media/gpu/chromeos/gpu_buffer_layout.h
+++ b/media/gpu/chromeos/gpu_buffer_layout.h
@@ -6,7 +6,6 @@
 #define MEDIA_GPU_CHROMEOS_GPU_BUFFER_LAYOUT_H_
 
 #include <ostream>
-#include <string>
 #include <vector>
 
 #include "media/base/color_plane_layout.h"
diff --git a/media/gpu/test/image_processor/image_processor_client.h b/media/gpu/test/image_processor/image_processor_client.h
index 61b50d7..0e09b0e5 100644
--- a/media/gpu/test/image_processor/image_processor_client.h
+++ b/media/gpu/test/image_processor/image_processor_client.h
@@ -6,7 +6,6 @@
 #define MEDIA_GPU_TEST_IMAGE_PROCESSOR_IMAGE_PROCESSOR_CLIENT_H_
 
 #include <memory>
-#include <string>
 #include <vector>
 
 #include "base/atomicops.h"
diff --git a/media/gpu/test/video_player/test_vda_video_decoder.h b/media/gpu/test/video_player/test_vda_video_decoder.h
index a556a52..2d0de12 100644
--- a/media/gpu/test/video_player/test_vda_video_decoder.h
+++ b/media/gpu/test/video_player/test_vda_video_decoder.h
@@ -8,7 +8,6 @@
 #include <stdint.h>
 #include <map>
 #include <memory>
-#include <string>
 
 #include "base/containers/mru_cache.h"
 #include "base/macros.h"
diff --git a/media/gpu/v4l2/v4l2_mjpeg_decode_accelerator.cc b/media/gpu/v4l2/v4l2_mjpeg_decode_accelerator.cc
index b129903..7e14f49 100644
--- a/media/gpu/v4l2/v4l2_mjpeg_decode_accelerator.cc
+++ b/media/gpu/v4l2/v4l2_mjpeg_decode_accelerator.cc
@@ -21,6 +21,7 @@
 #include "base/memory/page_size.h"
 #include "base/numerics/safe_conversions.h"
 #include "base/threading/thread_task_runner_handle.h"
+#include "media/base/bind_to_current_loop.h"
 #include "media/base/bitstream_buffer.h"
 #include "media/base/unaligned_shared_memory.h"
 #include "media/base/video_frame.h"
@@ -313,13 +314,14 @@
                                 weak_ptr_, task_id, error));
 }
 
-bool V4L2MjpegDecodeAccelerator::Initialize(
-    chromeos_camera::MjpegDecodeAccelerator::Client* client) {
+void V4L2MjpegDecodeAccelerator::InitializeOnTaskRunner(
+    chromeos_camera::MjpegDecodeAccelerator::Client* client,
+    chromeos_camera::MjpegDecodeAccelerator::InitCB init_cb) {
   DCHECK(child_task_runner_->BelongsToCurrentThread());
-
   if (!device_->Open(V4L2Device::Type::kJpegDecoder, V4L2_PIX_FMT_JPEG)) {
     VLOGF(1) << "Failed to open device";
-    return false;
+    std::move(init_cb).Run(false);
+    return;
   }
 
   // Capabilities check.
@@ -328,12 +330,14 @@
   memset(&caps, 0, sizeof(caps));
   if (device_->Ioctl(VIDIOC_QUERYCAP, &caps) != 0) {
     VPLOGF(1) << "ioctl() failed: VIDIOC_QUERYCAP";
-    return false;
+    std::move(init_cb).Run(false);
+    return;
   }
   if ((caps.capabilities & kCapsRequired) != kCapsRequired) {
     VLOGF(1) << "VIDIOC_QUERYCAP, caps check failed: 0x" << std::hex
              << caps.capabilities;
-    return false;
+    std::move(init_cb).Run(false);
+    return;
   }
 
   // Subscribe to the source change event.
@@ -342,12 +346,14 @@
   sub.type = V4L2_EVENT_SOURCE_CHANGE;
   if (device_->Ioctl(VIDIOC_SUBSCRIBE_EVENT, &sub) != 0) {
     VPLOGF(1) << "ioctl() failed: VIDIOC_SUBSCRIBE_EVENT";
-    return false;
+    std::move(init_cb).Run(false);
+    return;
   }
 
   if (!decoder_thread_.Start()) {
     VLOGF(1) << "decoder thread failed to start";
-    return false;
+    std::move(init_cb).Run(false);
+    return;
   }
   client_ = client;
   decoder_task_runner_ = decoder_thread_.task_runner();
@@ -357,7 +363,21 @@
                                 base::Unretained(this)));
 
   VLOGF(2) << "V4L2MjpegDecodeAccelerator initialized.";
-  return true;
+  std::move(init_cb).Run(true);
+}
+
+void V4L2MjpegDecodeAccelerator::InitializeAsync(
+    chromeos_camera::MjpegDecodeAccelerator::Client* client,
+    chromeos_camera::MjpegDecodeAccelerator::InitCB init_cb) {
+  DCHECK(child_task_runner_->BelongsToCurrentThread());
+
+  // To guarantee that the caller receives an asynchronous call after the
+  // return path, we are making use of InitializeOnTaskRunner.
+  child_task_runner_->PostTask(
+      FROM_HERE,
+      base::BindOnce(&V4L2MjpegDecodeAccelerator::InitializeOnTaskRunner,
+                     weak_factory_.GetWeakPtr(), client,
+                     BindToCurrentLoop(std::move(init_cb))));
 }
 
 void V4L2MjpegDecodeAccelerator::Decode(BitstreamBuffer bitstream_buffer,
diff --git a/media/gpu/v4l2/v4l2_mjpeg_decode_accelerator.h b/media/gpu/v4l2/v4l2_mjpeg_decode_accelerator.h
index 76ee0afd9..a8bf30e8 100644
--- a/media/gpu/v4l2/v4l2_mjpeg_decode_accelerator.h
+++ b/media/gpu/v4l2/v4l2_mjpeg_decode_accelerator.h
@@ -42,8 +42,9 @@
   ~V4L2MjpegDecodeAccelerator() override;
 
   // MjpegDecodeAccelerator implementation.
-  bool Initialize(
-      chromeos_camera::MjpegDecodeAccelerator::Client* client) override;
+  void InitializeAsync(
+      chromeos_camera::MjpegDecodeAccelerator::Client* client,
+      chromeos_camera::MjpegDecodeAccelerator::InitCB init_cb) override;
   void Decode(BitstreamBuffer bitstream_buffer,
               scoped_refptr<VideoFrame> video_frame) override;
   void Decode(int32_t task_id,
@@ -75,6 +76,10 @@
   void DestroyInputBuffers();
   void DestroyOutputBuffers();
 
+  void InitializeOnTaskRunner(
+      chromeos_camera::MjpegDecodeAccelerator::Client* client,
+      InitCB init_cb);
+
   // Convert |output_buffer| to |dst_frame|. The function supports the following
   // formats:
   //   - All formats that libyuv::ConvertToI420 can handle.
diff --git a/media/gpu/v4l2/v4l2_video_decoder.h b/media/gpu/v4l2/v4l2_video_decoder.h
index 9d96b5b..8f28cab 100644
--- a/media/gpu/v4l2/v4l2_video_decoder.h
+++ b/media/gpu/v4l2/v4l2_video_decoder.h
@@ -9,7 +9,6 @@
 
 #include <map>
 #include <memory>
-#include <string>
 #include <utility>
 #include <vector>
 
diff --git a/media/gpu/vaapi/vaapi_mjpeg_decode_accelerator.cc b/media/gpu/vaapi/vaapi_mjpeg_decode_accelerator.cc
index 3b0d6580..f436784 100644
--- a/media/gpu/vaapi/vaapi_mjpeg_decode_accelerator.cc
+++ b/media/gpu/vaapi/vaapi_mjpeg_decode_accelerator.cc
@@ -25,6 +25,7 @@
 #include "base/trace_event/trace_event.h"
 #include "gpu/ipc/common/gpu_memory_buffer_impl.h"
 #include "gpu/ipc/common/gpu_memory_buffer_support.h"
+#include "media/base/bind_to_current_loop.h"
 #include "media/base/bitstream_buffer.h"
 #include "media/base/format_utils.h"
 #include "media/base/unaligned_shared_memory.h"
@@ -118,25 +119,40 @@
       decoder_thread_("VaapiMjpegDecoderThread"),
       weak_this_factory_(this) {}
 
+// Destroy |decoder_| and |vpp_vaapi_wrapper_| on |decoder_thread_|.
+void VaapiMjpegDecodeAccelerator::CleanUpOnDecoderThread() {
+  DCHECK(decoder_task_runner_->BelongsToCurrentThread());
+  DCHECK(vpp_vaapi_wrapper_->HasOneRef());
+  vpp_vaapi_wrapper_.reset();
+  decoder_.reset();
+}
+
 VaapiMjpegDecodeAccelerator::~VaapiMjpegDecodeAccelerator() {
   DCHECK(task_runner_->BelongsToCurrentThread());
   VLOGF(2) << "Destroying VaapiMjpegDecodeAccelerator";
-
   weak_this_factory_.InvalidateWeakPtrs();
+
+  if (decoder_task_runner_) {
+    // base::Unretained() is fine here because we control |decoder_task_runner_|
+    // lifetime.
+    decoder_task_runner_->PostTask(
+        FROM_HERE,
+        base::BindOnce(&VaapiMjpegDecodeAccelerator::CleanUpOnDecoderThread,
+                       base::Unretained(this)));
+  }
   decoder_thread_.Stop();
 }
 
-bool VaapiMjpegDecodeAccelerator::Initialize(
-    chromeos_camera::MjpegDecodeAccelerator::Client* client) {
-  VLOGF(2);
-  DCHECK(task_runner_->BelongsToCurrentThread());
+void VaapiMjpegDecodeAccelerator::InitializeOnDecoderTaskRunner(
+    chromeos_camera::MjpegDecodeAccelerator::InitCB init_cb) {
+  DCHECK(decoder_task_runner_->BelongsToCurrentThread());
 
-  client_ = client;
-
-  if (!decoder_.Initialize(base::BindRepeating(
+  decoder_ = std::make_unique<media::VaapiJpegDecoder>();
+  if (!decoder_->Initialize(base::BindRepeating(
           &ReportVaapiErrorToUMA,
           "Media.VaapiMjpegDecodeAccelerator.VAAPIError"))) {
-    return false;
+    VLOGF(1) << "Failed initializing |decoder_|";
+    std::move(init_cb).Run(false);
   }
 
   vpp_vaapi_wrapper_ = VaapiWrapper::Create(
@@ -146,24 +162,53 @@
                           "Media.VaapiMjpegDecodeAccelerator.Vpp.VAAPIError"));
   if (!vpp_vaapi_wrapper_) {
     VLOGF(1) << "Failed initializing VAAPI for VPP";
-    return false;
+    std::move(init_cb).Run(false);
   }
 
   // Size is irrelevant for a VPP context.
   if (!vpp_vaapi_wrapper_->CreateContext(gfx::Size())) {
     VLOGF(1) << "Failed to create context for VPP";
-    return false;
+    std::move(init_cb).Run(false);
   }
 
-  gpu_memory_buffer_support_ = std::make_unique<gpu::GpuMemoryBufferSupport>();
+  std::move(init_cb).Run(true);
+}
+
+void VaapiMjpegDecodeAccelerator::InitializeOnTaskRunner(
+    chromeos_camera::MjpegDecodeAccelerator::Client* client,
+    chromeos_camera::MjpegDecodeAccelerator::InitCB init_cb) {
+  DCHECK(task_runner_->BelongsToCurrentThread());
+  client_ = client;
 
   if (!decoder_thread_.Start()) {
     VLOGF(1) << "Failed to start decoding thread.";
-    return false;
+    std::move(init_cb).Run(false);
   }
   decoder_task_runner_ = decoder_thread_.task_runner();
+  gpu_memory_buffer_support_ = std::make_unique<gpu::GpuMemoryBufferSupport>();
 
-  return true;
+  // base::Unretained() is fine here because we control |decoder_task_runner_|
+  // lifetime.
+  decoder_task_runner_->PostTask(
+      FROM_HERE,
+      base::BindOnce(
+          &VaapiMjpegDecodeAccelerator::InitializeOnDecoderTaskRunner,
+          base::Unretained(this), std::move(init_cb)));
+}
+
+void VaapiMjpegDecodeAccelerator::InitializeAsync(
+    chromeos_camera::MjpegDecodeAccelerator::Client* client,
+    chromeos_camera::MjpegDecodeAccelerator::InitCB init_cb) {
+  VLOGF(2);
+  DCHECK(task_runner_->BelongsToCurrentThread());
+
+  // To guarantee that the caller receives an asynchronous call after the
+  // return path, we are making use of InitializeOnTaskRunner.
+  task_runner_->PostTask(
+      FROM_HERE,
+      base::BindOnce(&VaapiMjpegDecodeAccelerator::InitializeOnTaskRunner,
+                     weak_this_factory_.GetWeakPtr(), client,
+                     BindToCurrentLoop(std::move(init_cb))));
 }
 
 bool VaapiMjpegDecodeAccelerator::OutputPictureLibYuvOnTaskRunner(
@@ -182,7 +227,7 @@
   DCHECK_EQ(video_frame->visible_rect().size(), video_frame->coded_size());
   DCHECK_EQ(0, video_frame->visible_rect().x());
   DCHECK_EQ(0, video_frame->visible_rect().y());
-  DCHECK(decoder_.GetScopedVASurface());
+  DCHECK(decoder_->GetScopedVASurface());
   const gfx::Size visible_size(base::strict_cast<int>(image->width),
                                base::strict_cast<int>(image->height));
   if (visible_size != video_frame->visible_rect().size()) {
@@ -448,12 +493,12 @@
   // TODO(andrescj): validate that the video frame's visible size is the same as
   // the parsed JPEG's visible size when it is returned from Decode(), and
   // remove the size checks in OutputPicture*().
-  VaapiImageDecodeStatus status = decoder_.Decode(src_image);
+  VaapiImageDecodeStatus status = decoder_->Decode(src_image);
   if (status != VaapiImageDecodeStatus::kSuccess) {
     NotifyError(task_id, VaapiJpegDecodeStatusToError(status));
     return;
   }
-  const ScopedVASurface* surface = decoder_.GetScopedVASurface();
+  const ScopedVASurface* surface = decoder_->GetScopedVASurface();
   DCHECK(surface);
   DCHECK(surface->IsValid());
 
@@ -492,7 +537,7 @@
   // 2. VPP doesn't support the format conversion. This is intended for AMD
   //    VAAPI driver whose VPP only supports converting decoded 4:2:0 JPEGs.
   std::unique_ptr<ScopedVAImage> image =
-      decoder_.GetImage(*video_frame_va_fourcc, &status);
+      decoder_->GetImage(*video_frame_va_fourcc, &status);
   if (status != VaapiImageDecodeStatus::kSuccess) {
     NotifyError(task_id, VaapiJpegDecodeStatusToError(status));
     return;
diff --git a/media/gpu/vaapi/vaapi_mjpeg_decode_accelerator.h b/media/gpu/vaapi/vaapi_mjpeg_decode_accelerator.h
index 419ddd4..7c41829 100644
--- a/media/gpu/vaapi/vaapi_mjpeg_decode_accelerator.h
+++ b/media/gpu/vaapi/vaapi_mjpeg_decode_accelerator.h
@@ -50,8 +50,9 @@
   ~VaapiMjpegDecodeAccelerator() override;
 
   // chromeos_camera::MjpegDecodeAccelerator implementation.
-  bool Initialize(
-      chromeos_camera::MjpegDecodeAccelerator::Client* client) override;
+  void InitializeAsync(
+      chromeos_camera::MjpegDecodeAccelerator::Client* client,
+      chromeos_camera::MjpegDecodeAccelerator::InitCB init_cb) override;
   void Decode(BitstreamBuffer bitstream_buffer,
               scoped_refptr<VideoFrame> video_frame) override;
   void Decode(int32_t task_id,
@@ -100,6 +101,14 @@
                                        int32_t input_buffer_id,
                                        scoped_refptr<VideoFrame> video_frame);
 
+  void InitializeOnDecoderTaskRunner(InitCB init_cb);
+
+  void InitializeOnTaskRunner(
+      chromeos_camera::MjpegDecodeAccelerator::Client* client,
+      InitCB init_cb);
+
+  void CleanUpOnDecoderThread();
+
   // ChildThread's task runner.
   const scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
 
@@ -109,7 +118,7 @@
   // The client of this class.
   chromeos_camera::MjpegDecodeAccelerator::Client* client_;
 
-  VaapiJpegDecoder decoder_;
+  std::unique_ptr<media::VaapiJpegDecoder> decoder_;
 
   // VaapiWrapper for VPP context. This is used to convert decoded data into
   // client buffer.
diff --git a/media/gpu/vaapi/vaapi_video_decoder.h b/media/gpu/vaapi/vaapi_video_decoder.h
index 8aabb95..837ac11 100644
--- a/media/gpu/vaapi/vaapi_video_decoder.h
+++ b/media/gpu/vaapi/vaapi_video_decoder.h
@@ -10,7 +10,6 @@
 
 #include <map>
 #include <memory>
-#include <string>
 #include <utility>
 
 #include "base/containers/mru_cache.h"
diff --git a/media/gpu/windows/d3d11_video_decoder.h b/media/gpu/windows/d3d11_video_decoder.h
index a539820..60fa5198 100644
--- a/media/gpu/windows/d3d11_video_decoder.h
+++ b/media/gpu/windows/d3d11_video_decoder.h
@@ -6,7 +6,6 @@
 #define MEDIA_GPU_WINDOWS_D3D11_VIDEO_DECODER_H_
 
 #include <d3d11.h>
-#include <string>
 #include <vector>
 
 #include "base/memory/ptr_util.h"
diff --git a/media/gpu/windows/d3d11_video_decoder_impl.h b/media/gpu/windows/d3d11_video_decoder_impl.h
index 4a7f1449..004295b5 100644
--- a/media/gpu/windows/d3d11_video_decoder_impl.h
+++ b/media/gpu/windows/d3d11_video_decoder_impl.h
@@ -10,7 +10,6 @@
 
 #include <list>
 #include <memory>
-#include <string>
 #include <tuple>
 
 #include "base/callback.h"
diff --git a/media/midi/midi_manager_winrt.h b/media/midi/midi_manager_winrt.h
index 0d9ac02..121ff6f 100644
--- a/media/midi/midi_manager_winrt.h
+++ b/media/midi/midi_manager_winrt.h
@@ -6,7 +6,6 @@
 #define MEDIA_MIDI_MIDI_MANAGER_WINRT_H_
 
 #include <memory>
-#include <string>
 
 #include "base/thread_annotations.h"
 #include "media/midi/midi_manager.h"
diff --git a/media/mojo/mojom/media_metrics_provider.mojom b/media/mojo/mojom/media_metrics_provider.mojom
index b4cd252f..da74931d 100644
--- a/media/mojo/mojom/media_metrics_provider.mojom
+++ b/media/mojo/mojom/media_metrics_provider.mojom
@@ -55,6 +55,17 @@
   SetTimeToFirstFrame(mojo_base.mojom.TimeDelta elapsed);
   SetTimeToPlayReady(mojo_base.mojom.TimeDelta elapsed);
 
+  // Sets the RendererType used in the playback.
+  SetRendererType(RendererType renderer_type);
+
+  // Sets the EME key system used for an EME playback. For clear playback, or if
+  // the `key_system` is not recognized, value 0 (kUnknownKeySystemForUkm) will
+  // be reported.
+  SetKeySystem(string key_system);
+
+  // Called when the EME playback uses a hardware secure pipeline.
+  SetIsHardwareSecure();
+
   // For src= playbacks, this is the container (".mp4", ".webm", etc).
   SetContainerName(MediaContainerName container_name);
 
@@ -78,7 +89,6 @@
        pending_receiver<PlaybackEventsRecorder> receiver);
 
   // Can be called multiple times to set properties about a playback.
-  SetRendererType(RendererType renderer_type);
   SetHasAudio(AudioCodec codec);
   SetHasVideo(VideoCodec codec);
   SetVideoPipelineInfo(VideoDecoderInfo info);
diff --git a/media/mojo/services/media_metrics_provider.cc b/media/mojo/services/media_metrics_provider.cc
index df63b74..758349a 100644
--- a/media/mojo/services/media_metrics_provider.cc
+++ b/media/mojo/services/media_metrics_provider.cc
@@ -12,6 +12,7 @@
 #include "base/metrics/histogram_macros.h"
 #include "build/build_config.h"
 #include "build/chromecast_buildflags.h"
+#include "media/base/key_systems.h"
 #include "media/learning/mojo/mojo_learning_task_controller_service.h"
 #include "media/mojo/services/video_decode_stats_recorder.h"
 #include "media/mojo/services/watch_time_recorder.h"
@@ -73,6 +74,8 @@
   builder.SetIsEME(uma_info_.is_eme);
   builder.SetIsMSE(is_mse_);
   builder.SetRendererType(static_cast<int>(renderer_type_));
+  builder.SetKeySystem(GetKeySystemIntForUKM(key_system_));
+  builder.SetIsHardwareSecure(is_hardware_secure_);
   builder.SetFinalPipelineStatus(uma_info_.last_pipeline_status);
   if (!is_mse_) {
     builder.SetURLScheme(static_cast<int64_t>(url_scheme_));
@@ -266,6 +269,14 @@
   renderer_type_ = renderer_type;
 }
 
+void MediaMetricsProvider::SetKeySystem(const std::string& key_system) {
+  key_system_ = key_system;
+}
+
+void MediaMetricsProvider::SetIsHardwareSecure() {
+  is_hardware_secure_ = true;
+}
+
 void MediaMetricsProvider::AcquireWatchTimeRecorder(
     mojom::PlaybackPropertiesPtr properties,
     mojo::PendingReceiver<mojom::WatchTimeRecorder> receiver) {
diff --git a/media/mojo/services/media_metrics_provider.h b/media/mojo/services/media_metrics_provider.h
index 07736259..dc0bb7aa 100644
--- a/media/mojo/services/media_metrics_provider.h
+++ b/media/mojo/services/media_metrics_provider.h
@@ -110,6 +110,8 @@
   void SetContainerName(
       container_names::MediaContainerName container_name) override;
   void SetRendererType(RendererType renderer_type) override;
+  void SetKeySystem(const std::string& key_system) override;
+  void SetIsHardwareSecure() override;
   void SetHasAudio(AudioCodec audio_codec) override;
   void SetHasPlayed() override;
   void SetHasVideo(VideoCodec video_codec) override;
@@ -158,6 +160,8 @@
   mojom::MediaURLScheme url_scheme_;
   mojom::MediaStreamType media_stream_type_;
   RendererType renderer_type_ = RendererType::kDefault;
+  std::string key_system_;
+  bool is_hardware_secure_ = false;
 
   base::TimeDelta time_to_metadata_ = kNoTimestamp;
   base::TimeDelta time_to_first_frame_ = kNoTimestamp;
diff --git a/media/mojo/services/media_metrics_provider_unittest.cc b/media/mojo/services/media_metrics_provider_unittest.cc
index 972f79d..29aef85 100644
--- a/media/mojo/services/media_metrics_provider_unittest.cc
+++ b/media/mojo/services/media_metrics_provider_unittest.cc
@@ -101,6 +101,8 @@
       EXPECT_HAS_UKM(UkmEntry::kPlayerIDName);
       EXPECT_UKM(UkmEntry::kIsTopFrameName, true);
       EXPECT_UKM(UkmEntry::kIsEMEName, false);
+      EXPECT_UKM(UkmEntry::kKeySystemName, 0);
+      EXPECT_UKM(UkmEntry::kIsHardwareSecureName, false);
       EXPECT_UKM(UkmEntry::kIsMSEName, true);
       EXPECT_UKM(UkmEntry::kFinalPipelineStatusName, PIPELINE_OK);
 
@@ -118,6 +120,7 @@
 
   // Now try one with different values and optional parameters set.
   const std::string kTestOrigin2 = "https://test2.google.com/";
+  const std::string kClearKeyKeySystem = "org.w3.clearkey";
   const base::TimeDelta kMetadataTime = base::TimeDelta::FromSeconds(1);
   const base::TimeDelta kFirstFrameTime = base::TimeDelta::FromSeconds(2);
   const base::TimeDelta kPlayReadyTime = base::TimeDelta::FromSeconds(3);
@@ -125,6 +128,8 @@
   ResetMetricRecorders();
   Initialize(false, false, false, kTestOrigin2, mojom::MediaURLScheme::kHttps);
   provider_->SetIsEME();
+  provider_->SetKeySystem(kClearKeyKeySystem);
+  provider_->SetIsHardwareSecure();
   provider_->SetTimeToMetadata(kMetadataTime);
   provider_->SetTimeToFirstFrame(kFirstFrameTime);
   provider_->SetTimeToPlayReady(kPlayReadyTime);
@@ -142,6 +147,8 @@
       EXPECT_HAS_UKM(UkmEntry::kPlayerIDName);
       EXPECT_UKM(UkmEntry::kIsTopFrameName, false);
       EXPECT_UKM(UkmEntry::kIsEMEName, true);
+      EXPECT_UKM(UkmEntry::kKeySystemName, 1);
+      EXPECT_UKM(UkmEntry::kIsHardwareSecureName, true);
       EXPECT_UKM(UkmEntry::kIsMSEName, false);
       EXPECT_UKM(UkmEntry::kURLSchemeName,
                  static_cast<int64_t>(mojom::MediaURLScheme::kHttps));
@@ -228,7 +235,7 @@
   histogram_tester.ExpectBucketCount("Media.HasEverPlayed", true, 0);
 }
 
-TEST_F(MediaMetricsProviderTest, TestPipelineUMANoAudioEMEHW) {
+TEST_F(MediaMetricsProviderTest, TestPipelineUMANoAudioWithEme) {
   base::HistogramTester histogram_tester;
   Initialize(false, false, false, kTestOrigin, mojom::MediaURLScheme::kHttps);
   provider_->SetIsEME();
diff --git a/media/mojo/services/mojo_audio_output_stream.h b/media/mojo/services/mojo_audio_output_stream.h
index bbb56fce..8a85269 100644
--- a/media/mojo/services/mojo_audio_output_stream.h
+++ b/media/mojo/services/mojo_audio_output_stream.h
@@ -6,7 +6,6 @@
 #define MEDIA_MOJO_SERVICES_MOJO_AUDIO_OUTPUT_STREAM_H_
 
 #include <memory>
-#include <string>
 
 #include "base/sequence_checker.h"
 #include "media/audio/audio_output_delegate.h"
diff --git a/media/mojo/services/mojo_video_decoder_service.cc b/media/mojo/services/mojo_video_decoder_service.cc
index 80bc77c..bc67d12 100644
--- a/media/mojo/services/mojo_video_decoder_service.cc
+++ b/media/mojo/services/mojo_video_decoder_service.cc
@@ -59,6 +59,13 @@
       elapsed);
 }
 
+base::debug::CrashKeyString* GetNumVideoDecodersCrashKeyString() {
+  static base::debug::CrashKeyString* codec_count_crash_key =
+      base::debug::AllocateCrashKeyString("num-video-decoders",
+                                          base::debug::CrashKeySize::Size32);
+  return codec_count_crash_key;
+}
+
 }  // namespace
 
 class VideoFrameHandleReleaserImpl final
@@ -126,8 +133,12 @@
   if (reset_cb_)
     OnDecoderReset();
 
-  if (is_active_instance_)
+  if (is_active_instance_) {
     g_num_active_mvd_instances--;
+    base::debug::SetCrashKeyString(
+        GetNumVideoDecodersCrashKeyString(),
+        base::NumberToString(g_num_active_mvd_instances));
+  }
 
   // Destruct the VideoDecoder here so its destruction duration is included by
   // the histogram timer below.
@@ -244,6 +255,17 @@
     return;
   }
 
+  auto gfx_cs = config.color_space_info().ToGfxColorSpace();
+  codec_string_ = base::StringPrintf(
+      "name=%s:codec=%s:profile=%d:size=%s:cs=[%d,%d,%d,%d]:hdrm=%d",
+      GetDecoderName(decoder_->GetDecoderType()).c_str(),
+      GetCodecName(config.codec()).c_str(), config.profile(),
+      config.coded_size().ToString().c_str(),
+      static_cast<int>(gfx_cs.GetPrimaryID()),
+      static_cast<int>(gfx_cs.GetTransferID()),
+      static_cast<int>(gfx_cs.GetMatrixID()),
+      static_cast<int>(gfx_cs.GetRangeID()), config.hdr_metadata().has_value());
+
   using Self = MojoVideoDecoderService;
   decoder_->Initialize(
       config, low_delay, cdm_context,
@@ -277,6 +299,14 @@
     g_num_active_mvd_instances++;
     UMA_HISTOGRAM_EXACT_LINEAR("Media.MojoVideoDecoder.ActiveInstances",
                                g_num_active_mvd_instances, 64);
+    base::debug::SetCrashKeyString(
+        GetNumVideoDecodersCrashKeyString(),
+        base::NumberToString(g_num_active_mvd_instances));
+
+    // This will be overwritten as subsequent decoders are created.
+    static auto* last_codec_crash_key = base::debug::AllocateCrashKeyString(
+        "last-video-decoder", base::debug::CrashKeySize::Size256);
+    base::debug::SetCrashKeyString(last_codec_crash_key, codec_string_);
   }
 
   mojo_decoder_buffer_reader_->ReadDecoderBuffer(
diff --git a/media/mojo/services/mojo_video_decoder_service.h b/media/mojo/services/mojo_video_decoder_service.h
index 8fbb7f6..df1c13b 100644
--- a/media/mojo/services/mojo_video_decoder_service.h
+++ b/media/mojo/services/mojo_video_decoder_service.h
@@ -93,6 +93,9 @@
   // Whether this instance is active (Decode() was called at least once).
   bool is_active_instance_ = false;
 
+  // Codec information stored via crash key.
+  std::string codec_string_;
+
   // Decoder factory.
   MojoMediaClient* mojo_media_client_;
 
diff --git a/media/mojo/services/video_decode_perf_history.h b/media/mojo/services/video_decode_perf_history.h
index 4c50c74..30e3823a 100644
--- a/media/mojo/services/video_decode_perf_history.h
+++ b/media/mojo/services/video_decode_perf_history.h
@@ -8,7 +8,6 @@
 #include <stdint.h>
 #include <memory>
 #include <queue>
-#include <string>
 
 #include "base/callback.h"
 #include "base/metrics/field_trial_params.h"
diff --git a/media/mojo/services/watch_time_recorder.h b/media/mojo/services/watch_time_recorder.h
index 77e768f..669781c 100644
--- a/media/mojo/services/watch_time_recorder.h
+++ b/media/mojo/services/watch_time_recorder.h
@@ -6,7 +6,6 @@
 #define MEDIA_MOJO_SERVICES_WATCH_TIME_RECORDER_H_
 
 #include <stdint.h>
-#include <string>
 
 #include "base/compiler_specific.h"
 #include "base/containers/flat_map.h"
diff --git a/media/remoting/proto_utils.h b/media/remoting/proto_utils.h
index f0bba1c..0265bef 100644
--- a/media/remoting/proto_utils.h
+++ b/media/remoting/proto_utils.h
@@ -6,7 +6,6 @@
 #define MEDIA_REMOTING_PROTO_UTILS_H_
 
 #include <cstdint>
-#include <string>
 #include <vector>
 
 #include "base/macros.h"
diff --git a/media/remoting/rpc_broker.h b/media/remoting/rpc_broker.h
index 6830228..b0d8227 100644
--- a/media/remoting/rpc_broker.h
+++ b/media/remoting/rpc_broker.h
@@ -7,7 +7,6 @@
 
 #include <map>
 #include <memory>
-#include <string>
 #include <vector>
 
 #include "base/callback.h"
diff --git a/media/renderers/win/media_foundation_renderer.cc b/media/renderers/win/media_foundation_renderer.cc
index d58dfb7..f37f9d8b 100644
--- a/media/renderers/win/media_foundation_renderer.cc
+++ b/media/renderers/win/media_foundation_renderer.cc
@@ -11,6 +11,7 @@
 
 #include "base/callback_helpers.h"
 #include "base/guid.h"
+#include "base/process/process_handle.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/win/scoped_bstr.h"
@@ -421,7 +422,19 @@
   HANDLE surface_handle = INVALID_HANDLE_VALUE;
   HRESULT hr = GetDCompSurfaceInternal(&surface_handle);
   DVLOG_IF(1, FAILED(hr)) << "Failed to get DComp surface: " << PrintHr(hr);
-  std::move(callback).Run(std::move(surface_handle));
+
+  // Only need read & execute access right for the handle to be duplicated
+  // without breaking in sandbox_win.cc!CheckDuplicateHandle().
+  const base::ProcessHandle process = ::GetCurrentProcess();
+  HANDLE duplicated_handle = INVALID_HANDLE_VALUE;
+  const BOOL result = ::DuplicateHandle(
+      process, surface_handle, process, &duplicated_handle,
+      GENERIC_READ | GENERIC_EXECUTE, false, DUPLICATE_CLOSE_SOURCE);
+  if (!result) {
+    DLOG(ERROR) << "Duplicate surface_handle failed: " << ::GetLastError();
+  }
+
+  std::move(callback).Run(std::move(duplicated_handle));
 }
 
 // TODO(crbug.com/1070030): Investigate if we need to add
diff --git a/net/android/network_change_notifier_delegate_android.h b/net/android/network_change_notifier_delegate_android.h
index 905d2e6d..a703ee6c 100644
--- a/net/android/network_change_notifier_delegate_android.h
+++ b/net/android/network_change_notifier_delegate_android.h
@@ -6,7 +6,6 @@
 #define NET_ANDROID_NETWORK_CHANGE_NOTIFIER_DELEGATE_ANDROID_H_
 
 #include <map>
-#include <string>
 
 #include "base/android/jni_android.h"
 #include "base/memory/ref_counted.h"
diff --git a/net/base/interval.h b/net/base/interval.h
index 24f3853..8ac3b5e 100644
--- a/net/base/interval.h
+++ b/net/base/interval.h
@@ -65,7 +65,6 @@
 #include <algorithm>
 #include <functional>
 #include <ostream>
-#include <string>
 #include <utility>
 #include <vector>
 
diff --git a/net/cert/internal/path_builder.h b/net/cert/internal/path_builder.h
index e7ef60d..0a358ced 100644
--- a/net/cert/internal/path_builder.h
+++ b/net/cert/internal/path_builder.h
@@ -6,7 +6,6 @@
 #define NET_CERT_INTERNAL_PATH_BUILDER_H_
 
 #include <memory>
-#include <string>
 #include <vector>
 
 #include "base/supports_user_data.h"
diff --git a/net/cookies/cookie_store_test_callbacks.h b/net/cookies/cookie_store_test_callbacks.h
index b3195c20..71597d99 100644
--- a/net/cookies/cookie_store_test_callbacks.h
+++ b/net/cookies/cookie_store_test_callbacks.h
@@ -5,7 +5,6 @@
 #ifndef NET_COOKIES_COOKIE_STORE_TEST_CALLBACKS_H_
 #define NET_COOKIES_COOKIE_STORE_TEST_CALLBACKS_H_
 
-#include <string>
 #include <vector>
 
 #include "base/bind.h"
diff --git a/net/disk_cache/simple/simple_index_file.h b/net/disk_cache/simple/simple_index_file.h
index 0e768f98..b397469 100644
--- a/net/disk_cache/simple/simple_index_file.h
+++ b/net/disk_cache/simple/simple_index_file.h
@@ -8,7 +8,6 @@
 #include <stdint.h>
 
 #include <memory>
-#include <string>
 #include <vector>
 
 #include "base/files/file_path.h"
@@ -205,7 +204,6 @@
   DISALLOW_COPY_AND_ASSIGN(SimpleIndexFile);
 };
 
-
 }  // namespace disk_cache
 
 #endif  // NET_DISK_CACHE_SIMPLE_SIMPLE_INDEX_FILE_H_
diff --git a/net/http/http_network_layer.h b/net/http/http_network_layer.h
index 51223d9..49ec2247 100644
--- a/net/http/http_network_layer.h
+++ b/net/http/http_network_layer.h
@@ -6,7 +6,6 @@
 #define NET_HTTP_HTTP_NETWORK_LAYER_H_
 
 #include <memory>
-#include <string>
 
 #include "base/compiler_specific.h"
 #include "base/macros.h"
diff --git a/net/log/test_net_log.h b/net/log/test_net_log.h
index 2aebd279..f59e23a 100644
--- a/net/log/test_net_log.h
+++ b/net/log/test_net_log.h
@@ -7,7 +7,6 @@
 
 #include <stddef.h>
 
-#include <string>
 #include <vector>
 
 #include "base/callback.h"
diff --git a/net/network_error_logging/mock_persistent_nel_store.h b/net/network_error_logging/mock_persistent_nel_store.h
index a5a646da..15af5fa 100644
--- a/net/network_error_logging/mock_persistent_nel_store.h
+++ b/net/network_error_logging/mock_persistent_nel_store.h
@@ -5,7 +5,6 @@
 #ifndef NET_NETWORK_ERROR_LOGGING_MOCK_PERSISTENT_NEL_STORE_H_
 #define NET_NETWORK_ERROR_LOGGING_MOCK_PERSISTENT_NEL_STORE_H_
 
-#include <string>
 #include <vector>
 
 #include "base/callback.h"
diff --git a/net/nqe/network_quality_estimator.h b/net/nqe/network_quality_estimator.h
index 4c8d636b..dd9111a9 100644
--- a/net/nqe/network_quality_estimator.h
+++ b/net/nqe/network_quality_estimator.h
@@ -9,7 +9,6 @@
 
 #include <map>
 #include <memory>
-#include <string>
 #include <vector>
 
 #include "base/compiler_specific.h"
diff --git a/net/ntlm/ntlm_buffer_reader.h b/net/ntlm/ntlm_buffer_reader.h
index d014934..d6cb7d7 100644
--- a/net/ntlm/ntlm_buffer_reader.h
+++ b/net/ntlm/ntlm_buffer_reader.h
@@ -8,7 +8,6 @@
 #include <stddef.h>
 #include <stdint.h>
 
-#include <string>
 #include <vector>
 
 #include "base/containers/span.h"
diff --git a/net/proxy_resolution/proxy_resolver.h b/net/proxy_resolution/proxy_resolver.h
index ed59ffcc..7b54e57 100644
--- a/net/proxy_resolution/proxy_resolver.h
+++ b/net/proxy_resolution/proxy_resolver.h
@@ -5,8 +5,6 @@
 #ifndef NET_PROXY_RESOLUTION_PROXY_RESOLVER_H_
 #define NET_PROXY_RESOLUTION_PROXY_RESOLVER_H_
 
-#include <string>
-
 #include "base/callback_forward.h"
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
diff --git a/net/proxy_resolution/win/proxy_config_service_win.h b/net/proxy_resolution/win/proxy_config_service_win.h
index b9ed016..886ebfc 100644
--- a/net/proxy_resolution/win/proxy_config_service_win.h
+++ b/net/proxy_resolution/win/proxy_config_service_win.h
@@ -9,7 +9,6 @@
 #include <winhttp.h>
 
 #include <memory>
-#include <string>
 #include <vector>
 
 #include "base/compiler_specific.h"
diff --git a/net/quic/mock_crypto_client_stream.h b/net/quic/mock_crypto_client_stream.h
index 9cb0cfd..68e5ad9 100644
--- a/net/quic/mock_crypto_client_stream.h
+++ b/net/quic/mock_crypto_client_stream.h
@@ -5,8 +5,6 @@
 #ifndef NET_QUIC_MOCK_CRYPTO_CLIENT_STREAM_H_
 #define NET_QUIC_MOCK_CRYPTO_CLIENT_STREAM_H_
 
-#include <string>
-
 #include "base/macros.h"
 #include "net/quic/crypto/proof_verifier_chromium.h"
 #include "net/third_party/quiche/src/quic/core/crypto/crypto_handshake.h"
diff --git a/net/quic/mock_crypto_client_stream_factory.h b/net/quic/mock_crypto_client_stream_factory.h
index b5e89f0..b0f8193e6 100644
--- a/net/quic/mock_crypto_client_stream_factory.h
+++ b/net/quic/mock_crypto_client_stream_factory.h
@@ -6,7 +6,6 @@
 #define NET_QUIC_MOCK_CRYPTO_CLIENT_STREAM_FACTORY_H_
 
 #include <memory>
-#include <string>
 
 #include "base/containers/queue.h"
 #include "base/macros.h"
diff --git a/net/quic/quic_client_session_cache.h b/net/quic/quic_client_session_cache.h
index cf1a90f8..d566fca 100644
--- a/net/quic/quic_client_session_cache.h
+++ b/net/quic/quic_client_session_cache.h
@@ -9,7 +9,6 @@
 #include <time.h>
 
 #include <memory>
-#include <string>
 
 #include "base/bind.h"
 #include "base/containers/mru_cache.h"
diff --git a/net/quic/quic_connection_logger.h b/net/quic/quic_connection_logger.h
index 8234979..6e41cb2 100644
--- a/net/quic/quic_connection_logger.h
+++ b/net/quic/quic_connection_logger.h
@@ -8,7 +8,6 @@
 #include <stddef.h>
 
 #include <bitset>
-#include <string>
 
 #include "base/macros.h"
 #include "net/base/ip_endpoint.h"
diff --git a/net/quic/quic_crypto_client_stream_factory.h b/net/quic/quic_crypto_client_stream_factory.h
index a010b7f..33ddde2 100644
--- a/net/quic/quic_crypto_client_stream_factory.h
+++ b/net/quic/quic_crypto_client_stream_factory.h
@@ -6,7 +6,6 @@
 #define NET_QUIC_QUIC_CRYPTO_CLIENT_STREAM_FACTORY_H_
 
 #include <memory>
-#include <string>
 
 #include "net/base/net_export.h"
 #include "net/third_party/quiche/src/quic/core/quic_server_id.h"
diff --git a/net/reporting/mock_persistent_reporting_store.h b/net/reporting/mock_persistent_reporting_store.h
index 04f1d89..b53e397 100644
--- a/net/reporting/mock_persistent_reporting_store.h
+++ b/net/reporting/mock_persistent_reporting_store.h
@@ -5,7 +5,6 @@
 #ifndef NET_REPORTING_MOCK_PERSISTENT_REPORTING_STORE_H_
 #define NET_REPORTING_MOCK_PERSISTENT_REPORTING_STORE_H_
 
-#include <string>
 #include <vector>
 
 #include "base/callback.h"
diff --git a/net/reporting/reporting_endpoint_manager.h b/net/reporting/reporting_endpoint_manager.h
index a760849bd..5b14a597 100644
--- a/net/reporting/reporting_endpoint_manager.h
+++ b/net/reporting/reporting_endpoint_manager.h
@@ -6,7 +6,6 @@
 #define NET_REPORTING_REPORTING_ENDPOINT_MANAGER_H_
 
 #include <memory>
-#include <string>
 
 #include "base/macros.h"
 #include "net/base/net_export.h"
diff --git a/net/socket/client_socket_handle.h b/net/socket/client_socket_handle.h
index 1a8f4c5..2e41eba 100644
--- a/net/socket/client_socket_handle.h
+++ b/net/socket/client_socket_handle.h
@@ -6,7 +6,6 @@
 #define NET_SOCKET_CLIENT_SOCKET_HANDLE_H_
 
 #include <memory>
-#include <string>
 #include <utility>
 
 #include "base/bind.h"
diff --git a/net/socket/fuzzed_server_socket.h b/net/socket/fuzzed_server_socket.h
index 03c64672..7d8dc80 100644
--- a/net/socket/fuzzed_server_socket.h
+++ b/net/socket/fuzzed_server_socket.h
@@ -8,7 +8,6 @@
 #include <stdint.h>
 
 #include <memory>
-#include <string>
 
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
diff --git a/net/socket/socks_connect_job.h b/net/socket/socks_connect_job.h
index 7ce44f52..21db95cf 100644
--- a/net/socket/socks_connect_job.h
+++ b/net/socket/socks_connect_job.h
@@ -6,7 +6,6 @@
 #define NET_SOCKET_SOCKS_CONNECT_JOB_H_
 
 #include <memory>
-#include <string>
 
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
diff --git a/net/socket/transport_connect_job.h b/net/socket/transport_connect_job.h
index 5b394234..6fbf0b9a3 100644
--- a/net/socket/transport_connect_job.h
+++ b/net/socket/transport_connect_job.h
@@ -6,7 +6,6 @@
 #define NET_SOCKET_TRANSPORT_CONNECT_JOB_H_
 
 #include <memory>
-#include <string>
 
 #include "base/callback.h"
 #include "base/macros.h"
diff --git a/net/socket/websocket_transport_connect_job.h b/net/socket/websocket_transport_connect_job.h
index aa7939e..b4aae073 100644
--- a/net/socket/websocket_transport_connect_job.h
+++ b/net/socket/websocket_transport_connect_job.h
@@ -7,7 +7,6 @@
 
 #include <memory>
 #include <set>
-#include <string>
 
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
diff --git a/net/ssl/ssl_client_auth_cache.h b/net/ssl/ssl_client_auth_cache.h
index a380ea7..facbc5f9 100644
--- a/net/ssl/ssl_client_auth_cache.h
+++ b/net/ssl/ssl_client_auth_cache.h
@@ -6,7 +6,6 @@
 #define NET_SSL_SSL_CLIENT_AUTH_CACHE_H_
 
 #include <map>
-#include <string>
 #include <utility>
 
 #include "base/compiler_specific.h"
diff --git a/net/test/spawned_test_server/local_test_server.h b/net/test/spawned_test_server/local_test_server.h
index a9dc1af..74622a8 100644
--- a/net/test/spawned_test_server/local_test_server.h
+++ b/net/test/spawned_test_server/local_test_server.h
@@ -5,7 +5,6 @@
 #ifndef NET_TEST_SPAWNED_TEST_SERVER_LOCAL_TEST_SERVER_H_
 #define NET_TEST_SPAWNED_TEST_SERVER_LOCAL_TEST_SERVER_H_
 
-#include <string>
 #include <vector>
 
 #include "base/files/file_util.h"
diff --git a/net/tools/cert_verify_tool/verify_using_path_builder.h b/net/tools/cert_verify_tool/verify_using_path_builder.h
index 31f0145..1e8e844 100644
--- a/net/tools/cert_verify_tool/verify_using_path_builder.h
+++ b/net/tools/cert_verify_tool/verify_using_path_builder.h
@@ -5,7 +5,6 @@
 #ifndef NET_TOOLS_CERT_VERIFY_TOOL_VERIFY_USING_PATH_BUILDER_H_
 #define NET_TOOLS_CERT_VERIFY_TOOL_VERIFY_USING_PATH_BUILDER_H_
 
-#include <string>
 #include <vector>
 
 #include "base/memory/ref_counted.h"
diff --git a/net/tools/huffman_trie/trie/trie_writer.h b/net/tools/huffman_trie/trie/trie_writer.h
index 3c824e7..9fdc1ee 100644
--- a/net/tools/huffman_trie/trie/trie_writer.h
+++ b/net/tools/huffman_trie/trie/trie_writer.h
@@ -5,7 +5,6 @@
 #ifndef NET_TOOLS_HUFFMAN_TRIE_TRIE_TRIE_WRITER_H_
 #define NET_TOOLS_HUFFMAN_TRIE_TRIE_TRIE_WRITER_H_
 
-#include <string>
 #include <vector>
 
 #include "net/tools/huffman_trie/bit_writer.h"
diff --git a/net/tools/quic/quic_client_message_loop_network_helper.h b/net/tools/quic/quic_client_message_loop_network_helper.h
index 82aeedda..a0ba9ad 100644
--- a/net/tools/quic/quic_client_message_loop_network_helper.h
+++ b/net/tools/quic/quic_client_message_loop_network_helper.h
@@ -11,7 +11,6 @@
 #include <stddef.h>
 
 #include <memory>
-#include <string>
 
 #include "base/command_line.h"
 #include "base/macros.h"
diff --git a/net/tools/quic/quic_simple_client.h b/net/tools/quic/quic_simple_client.h
index 3ac2841..5e9d4d1f 100644
--- a/net/tools/quic/quic_simple_client.h
+++ b/net/tools/quic/quic_simple_client.h
@@ -11,7 +11,6 @@
 #include <stddef.h>
 
 #include <memory>
-#include <string>
 
 #include "base/command_line.h"
 #include "base/macros.h"
diff --git a/net/url_request/report_sender.h b/net/url_request/report_sender.h
index 7e2f57c..2400db5 100644
--- a/net/url_request/report_sender.h
+++ b/net/url_request/report_sender.h
@@ -7,7 +7,6 @@
 
 #include <map>
 #include <memory>
-#include <string>
 
 #include "base/callback.h"
 #include "base/macros.h"
diff --git a/remoting/protocol/webrtc_video_track_source.cc b/remoting/protocol/webrtc_video_track_source.cc
index 071543f..588bb62 100644
--- a/remoting/protocol/webrtc_video_track_source.cc
+++ b/remoting/protocol/webrtc_video_track_source.cc
@@ -4,7 +4,11 @@
 
 #include "remoting/protocol/webrtc_video_track_source.h"
 
+#include <utility>
+
+#include "base/logging.h"
 #include "base/threading/sequenced_task_runner_handle.h"
+#include "remoting/protocol/webrtc_video_frame_adapter.h"
 
 namespace remoting {
 namespace protocol {
@@ -40,11 +44,26 @@
 void WebrtcVideoTrackSource::AddOrUpdateSink(
     rtc::VideoSinkInterface<webrtc::VideoFrame>* sink,
     const rtc::VideoSinkWants& wants) {
+  DCHECK(sink);
+  if (sink_ && (sink != sink_)) {
+    // The same sink can be added more than once, but there should only be 1
+    // in total.
+    LOG(WARNING) << "More than one sink added, only the latest will be used.";
+  }
+  sink_ = sink;
   main_task_runner_->PostTask(FROM_HERE, add_sink_callback_);
 }
 
 void WebrtcVideoTrackSource::RemoveSink(
-    rtc::VideoSinkInterface<webrtc::VideoFrame>* sink) {}
+    rtc::VideoSinkInterface<webrtc::VideoFrame>* sink) {
+  DCHECK(sink);
+  if (sink != sink_) {
+    // This might happen if more than one sink was added.
+    LOG(WARNING) << "RemoveSink() called with unexpected sink.";
+    return;
+  }
+  sink_ = nullptr;
+}
 
 bool WebrtcVideoTrackSource::SupportsEncodedOutput() const {
   return false;
@@ -58,5 +77,18 @@
 void WebrtcVideoTrackSource::RemoveEncodedSink(
     rtc::VideoSinkInterface<webrtc::RecordableEncodedFrame>* sink) {}
 
+void WebrtcVideoTrackSource::SendCapturedFrame(
+    std::unique_ptr<webrtc::DesktopFrame> desktop_frame,
+    std::unique_ptr<WebrtcVideoEncoder::FrameStats> frame_stats) {
+  if (!sink_) {
+    LOG(WARNING) << "No sink registered, dropping frame.";
+    return;
+  }
+
+  webrtc::VideoFrame video_frame = WebrtcVideoFrameAdapter::CreateVideoFrame(
+      std::move(desktop_frame), std::move(frame_stats));
+  sink_->OnFrame(video_frame);
+}
+
 }  // namespace protocol
 }  // namespace remoting
diff --git a/remoting/protocol/webrtc_video_track_source.h b/remoting/protocol/webrtc_video_track_source.h
index 11b248f5..e54e44b 100644
--- a/remoting/protocol/webrtc_video_track_source.h
+++ b/remoting/protocol/webrtc_video_track_source.h
@@ -7,8 +7,12 @@
 
 #include "base/callback.h"
 #include "base/sequenced_task_runner.h"
+#include "remoting/codec/webrtc_video_encoder.h"
 #include "third_party/webrtc/api/media_stream_interface.h"
 #include "third_party/webrtc/api/notifier.h"
+#include "third_party/webrtc/modules/desktop_capture/desktop_frame.h"
+
+#include <memory>
 
 namespace remoting {
 namespace protocol {
@@ -39,7 +43,15 @@
   void RemoveEncodedSink(
       rtc::VideoSinkInterface<webrtc::RecordableEncodedFrame>* sink) override;
 
+  // Sends a captured frame to the sink if one was added. The |frame_stats|
+  // will be associated with the frame and will be attached to the output
+  // EncodedFrame.
+  void SendCapturedFrame(
+      std::unique_ptr<webrtc::DesktopFrame> desktop_frame,
+      std::unique_ptr<WebrtcVideoEncoder::FrameStats> frame_stats);
+
  private:
+  rtc::VideoSinkInterface<webrtc::VideoFrame>* sink_ = nullptr;
   base::RepeatingClosure add_sink_callback_;
   scoped_refptr<base::SequencedTaskRunner> main_task_runner_;
 };
diff --git a/remoting/protocol/webrtc_video_track_source_unittest.cc b/remoting/protocol/webrtc_video_track_source_unittest.cc
index 122fd329..70dd343 100644
--- a/remoting/protocol/webrtc_video_track_source_unittest.cc
+++ b/remoting/protocol/webrtc_video_track_source_unittest.cc
@@ -4,14 +4,37 @@
 
 #include "remoting/protocol/webrtc_video_track_source.h"
 
+#include <memory>
+#include <utility>
+
+#include "base/callback_helpers.h"
 #include "base/test/bind.h"
 #include "base/test/task_environment.h"
+#include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/webrtc/modules/desktop_capture/desktop_frame.h"
+#include "third_party/webrtc/modules/desktop_capture/desktop_geometry.h"
 #include "third_party/webrtc/rtc_base/ref_counted_object.h"
 
+using testing::Property;
+using webrtc::BasicDesktopFrame;
+using webrtc::DesktopSize;
+using webrtc::VideoFrame;
+
 namespace remoting {
 namespace protocol {
 
+namespace {
+
+class MockVideoSink : public rtc::VideoSinkInterface<VideoFrame> {
+ public:
+  ~MockVideoSink() override = default;
+
+  MOCK_METHOD(void, OnFrame, (const VideoFrame& frame), (override));
+};
+
+}  // namespace
+
 class WebrtcVideoTrackSourceTest : public testing::Test {
  public:
   WebrtcVideoTrackSourceTest() = default;
@@ -19,13 +42,27 @@
  protected:
   base::test::SingleThreadTaskEnvironment task_environment_{
       base::test::TaskEnvironment::TimeSource::MOCK_TIME};
+
+  MockVideoSink video_sink_;
 };
 
 TEST_F(WebrtcVideoTrackSourceTest, AddSinkTriggersCallback) {
   rtc::scoped_refptr<webrtc::VideoTrackSourceInterface> source =
       new rtc::RefCountedObject<WebrtcVideoTrackSource>(
           base::MakeExpectedRunAtLeastOnceClosure(FROM_HERE));
-  source->AddOrUpdateSink(nullptr, rtc::VideoSinkWants());
+  source->AddOrUpdateSink(&video_sink_, rtc::VideoSinkWants());
+
+  task_environment_.FastForwardUntilNoTasksRemain();
+}
+
+TEST_F(WebrtcVideoTrackSourceTest, CapturedFrameSentToAddedSink) {
+  auto frame = std::make_unique<BasicDesktopFrame>(DesktopSize(123, 234));
+  EXPECT_CALL(video_sink_, OnFrame(Property(&VideoFrame::width, 123)));
+
+  rtc::scoped_refptr<WebrtcVideoTrackSource> source =
+      new rtc::RefCountedObject<WebrtcVideoTrackSource>(base::DoNothing());
+  source->AddOrUpdateSink(&video_sink_, rtc::VideoSinkWants());
+  source->SendCapturedFrame(std::move(frame), nullptr);
 
   task_environment_.FastForwardUntilNoTasksRemain();
 }
diff --git a/testing/buildbot/chromium.android.fyi.json b/testing/buildbot/chromium.android.fyi.json
index e37de78..aa69a5f 100644
--- a/testing/buildbot/chromium.android.fyi.json
+++ b/testing/buildbot/chromium.android.fyi.json
@@ -4689,7 +4689,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M91",
-              "revision": "version:91.0.4472.82"
+              "revision": "version:91.0.4472.83"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -4768,7 +4768,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M92",
-              "revision": "version:92.0.4515.32"
+              "revision": "version:92.0.4515.36"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -4926,7 +4926,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M91",
-              "revision": "version:91.0.4472.82"
+              "revision": "version:91.0.4472.83"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -5005,7 +5005,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M92",
-              "revision": "version:92.0.4515.32"
+              "revision": "version:92.0.4515.36"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
diff --git a/testing/buildbot/chromium.android.json b/testing/buildbot/chromium.android.json
index 6394061..1faf0ae 100644
--- a/testing/buildbot/chromium.android.json
+++ b/testing/buildbot/chromium.android.json
@@ -53852,7 +53852,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M91",
-              "revision": "version:91.0.4472.82"
+              "revision": "version:91.0.4472.83"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -53932,7 +53932,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M92",
-              "revision": "version:92.0.4515.32"
+              "revision": "version:92.0.4515.36"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -54092,7 +54092,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M91",
-              "revision": "version:91.0.4472.82"
+              "revision": "version:91.0.4472.83"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -54172,7 +54172,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M92",
-              "revision": "version:92.0.4515.32"
+              "revision": "version:92.0.4515.36"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -54397,7 +54397,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M91",
-              "revision": "version:91.0.4472.82"
+              "revision": "version:91.0.4472.83"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -54476,7 +54476,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M92",
-              "revision": "version:92.0.4515.32"
+              "revision": "version:92.0.4515.36"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -54634,7 +54634,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M91",
-              "revision": "version:91.0.4472.82"
+              "revision": "version:91.0.4472.83"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -54713,7 +54713,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M92",
-              "revision": "version:92.0.4515.32"
+              "revision": "version:92.0.4515.36"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -54938,7 +54938,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M91",
-              "revision": "version:91.0.4472.82"
+              "revision": "version:91.0.4472.83"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -55017,7 +55017,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M92",
-              "revision": "version:92.0.4515.32"
+              "revision": "version:92.0.4515.36"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -55175,7 +55175,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M91",
-              "revision": "version:91.0.4472.82"
+              "revision": "version:91.0.4472.83"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -55254,7 +55254,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M92",
-              "revision": "version:92.0.4515.32"
+              "revision": "version:92.0.4515.36"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
diff --git a/testing/buildbot/chromium.fyi.json b/testing/buildbot/chromium.fyi.json
index 51c1735..49709b1 100644
--- a/testing/buildbot/chromium.fyi.json
+++ b/testing/buildbot/chromium.fyi.json
@@ -19626,6 +19626,1289 @@
       }
     ]
   },
+  "fuchsia-fyi-arm64-femu": {
+    "additional_compile_targets": [
+      "all"
+    ],
+    "gtest_tests": [
+      {
+        "args": [
+          "--device=aemu"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-20.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "absl_hardening_tests",
+        "test_id_prefix": "ninja://third_party/abseil-cpp:absl_hardening_tests/"
+      },
+      {
+        "args": [
+          "--device=aemu",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-20.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "accessibility_unittests",
+        "test_id_prefix": "ninja://ui/accessibility:accessibility_unittests/"
+      },
+      {
+        "args": [
+          "--device=aemu",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-20.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "aura_unittests",
+        "test_id_prefix": "ninja://ui/aura:aura_unittests/"
+      },
+      {
+        "args": [
+          "--device=aemu",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-20.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "base_unittests",
+        "test_id_prefix": "ninja://base:base_unittests/"
+      },
+      {
+        "args": [
+          "--device=aemu",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-20.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "base_util_unittests",
+        "test_id_prefix": "ninja://base/util:base_util_unittests/"
+      },
+      {
+        "args": [
+          "--device=aemu",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-20.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "blink_common_unittests",
+        "test_id_prefix": "ninja://third_party/blink/common:blink_common_unittests/"
+      },
+      {
+        "args": [
+          "--device=aemu",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-20.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "blink_heap_unittests",
+        "test_id_prefix": "ninja://third_party/blink/renderer/platform/heap:blink_heap_unittests/"
+      },
+      {
+        "args": [
+          "--device=aemu",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-20.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "blink_platform_unittests",
+        "test_id_prefix": "ninja://third_party/blink/renderer/platform:blink_platform_unittests/"
+      },
+      {
+        "args": [
+          "--test-launcher-filter-file=../../testing/buildbot/filters/fuchsia.blink_unittests.filter",
+          "--device=aemu",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-20.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "blink_unittests",
+        "test_id_prefix": "ninja://third_party/blink/renderer/controller:blink_unittests/"
+      },
+      {
+        "args": [
+          "--device=aemu",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-20.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "boringssl_crypto_tests",
+        "test_id_prefix": "ninja://third_party/boringssl:boringssl_crypto_tests/"
+      },
+      {
+        "args": [
+          "--device=aemu",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-20.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "boringssl_ssl_tests",
+        "test_id_prefix": "ninja://third_party/boringssl:boringssl_ssl_tests/"
+      },
+      {
+        "args": [
+          "--device=aemu",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-20.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "capture_unittests",
+        "test_id_prefix": "ninja://media/capture:capture_unittests/"
+      },
+      {
+        "args": [
+          "--device=aemu",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-20.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "cast_runner_browsertests",
+        "test_id_prefix": "ninja://fuchsia/runners:cast_runner_browsertests/"
+      },
+      {
+        "args": [
+          "--device=aemu",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-20.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "cast_runner_integration_tests",
+        "test_id_prefix": "ninja://fuchsia/runners:cast_runner_integration_tests/"
+      },
+      {
+        "args": [
+          "--device=aemu",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-20.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "cast_runner_unittests",
+        "test_id_prefix": "ninja://fuchsia/runners:cast_runner_unittests/"
+      },
+      {
+        "args": [
+          "--device=aemu",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-20.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 2
+        },
+        "test": "cc_unittests",
+        "test_id_prefix": "ninja://cc:cc_unittests/"
+      },
+      {
+        "args": [
+          "--device=aemu",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-20.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "compositor_unittests",
+        "test_id_prefix": "ninja://ui/compositor:compositor_unittests/"
+      },
+      {
+        "args": [
+          "--test-launcher-filter-file=../../testing/buildbot/filters/fuchsia.content_unittests.filter",
+          "--device=aemu",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-20.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "content_unittests",
+        "test_id_prefix": "ninja://content/test:content_unittests/"
+      },
+      {
+        "args": [
+          "--device=aemu",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-20.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "cr_fuchsia_base_unittests",
+        "test_id_prefix": "ninja://fuchsia/base:cr_fuchsia_base_unittests/"
+      },
+      {
+        "args": [
+          "--device=aemu",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-20.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "cronet_tests",
+        "test_id_prefix": "ninja://components/cronet:cronet_tests/"
+      },
+      {
+        "args": [
+          "--device=aemu",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-20.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "cronet_unittests",
+        "test_id_prefix": "ninja://components/cronet:cronet_unittests/"
+      },
+      {
+        "args": [
+          "--device=aemu",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-20.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "crypto_unittests",
+        "test_id_prefix": "ninja://crypto:crypto_unittests/"
+      },
+      {
+        "args": [
+          "--device=aemu",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-20.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "display_unittests",
+        "test_id_prefix": "ninja://ui/display:display_unittests/"
+      },
+      {
+        "args": [
+          "--device=aemu",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-20.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "events_unittests",
+        "test_id_prefix": "ninja://ui/events:events_unittests/"
+      },
+      {
+        "args": [
+          "--device=aemu",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-20.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "gfx_unittests",
+        "test_id_prefix": "ninja://ui/gfx:gfx_unittests/"
+      },
+      {
+        "args": [
+          "--device=aemu",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-20.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "gin_unittests",
+        "test_id_prefix": "ninja://gin:gin_unittests/"
+      },
+      {
+        "args": [
+          "--device=aemu",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-20.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "google_apis_unittests",
+        "test_id_prefix": "ninja://google_apis:google_apis_unittests/"
+      },
+      {
+        "args": [
+          "--device=aemu"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-20.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "gpu_unittests",
+        "test_id_prefix": "ninja://gpu:gpu_unittests/"
+      },
+      {
+        "args": [
+          "--test-launcher-filter-file=../../testing/buildbot/filters/fuchsia.headless_browsertests.filter",
+          "--device=aemu",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-20.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "headless_browsertests",
+        "test_id_prefix": "ninja://headless:headless_browsertests/"
+      },
+      {
+        "args": [
+          "--device=aemu",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-20.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "headless_unittests",
+        "test_id_prefix": "ninja://headless:headless_unittests/"
+      },
+      {
+        "args": [
+          "--device=aemu",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-20.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "http_service_tests",
+        "test_id_prefix": "ninja://fuchsia/http:http_service_tests/"
+      },
+      {
+        "args": [
+          "--device=aemu",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-20.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "ipc_tests",
+        "test_id_prefix": "ninja://ipc:ipc_tests/"
+      },
+      {
+        "args": [
+          "--device=aemu",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-20.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "latency_unittests",
+        "test_id_prefix": "ninja://ui/latency:latency_unittests/"
+      },
+      {
+        "args": [
+          "--device=aemu",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-20.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "media_blink_unittests",
+        "test_id_prefix": "ninja://media/blink:media_blink_unittests/"
+      },
+      {
+        "args": [
+          "--device=aemu",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-20.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "media_unittests",
+        "test_id_prefix": "ninja://media:media_unittests/"
+      },
+      {
+        "args": [
+          "--device=aemu",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-20.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "mojo_unittests",
+        "test_id_prefix": "ninja://mojo:mojo_unittests/"
+      },
+      {
+        "args": [
+          "--device=aemu",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-20.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "native_theme_unittests",
+        "test_id_prefix": "ninja://ui/native_theme:native_theme_unittests/"
+      },
+      {
+        "args": [
+          "--test-launcher-filter-file=../../testing/buildbot/filters/fuchsia.net_unittests.filter",
+          "--device=aemu",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-20.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 4
+        },
+        "test": "net_unittests",
+        "test_id_prefix": "ninja://net:net_unittests/"
+      },
+      {
+        "args": [
+          "--device=aemu",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-20.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "perfetto_unittests",
+        "test_id_prefix": "ninja://third_party/perfetto:perfetto_unittests/"
+      },
+      {
+        "args": [
+          "--device=aemu",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-20.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "service_manager_unittests",
+        "test_id_prefix": "ninja://services/service_manager/tests:service_manager_unittests/"
+      },
+      {
+        "args": [
+          "--test-launcher-filter-file=../../testing/buildbot/filters/fuchsia.services_unittests.filter",
+          "--device=aemu",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-20.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "services_unittests",
+        "test_id_prefix": "ninja://services:services_unittests/"
+      },
+      {
+        "args": [
+          "--device=aemu",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-20.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "shell_dialogs_unittests",
+        "test_id_prefix": "ninja://ui/shell_dialogs:shell_dialogs_unittests/"
+      },
+      {
+        "args": [
+          "--device=aemu",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-20.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "skia_unittests",
+        "test_id_prefix": "ninja://skia:skia_unittests/"
+      },
+      {
+        "args": [
+          "--device=aemu",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-20.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "snapshot_unittests",
+        "test_id_prefix": "ninja://ui/snapshot:snapshot_unittests/"
+      },
+      {
+        "args": [
+          "--device=aemu",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-20.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "sql_unittests",
+        "test_id_prefix": "ninja://sql:sql_unittests/"
+      },
+      {
+        "args": [
+          "--test-launcher-filter-file=../../testing/buildbot/filters/fuchsia.storage_unittests.filter",
+          "--device=aemu",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-20.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "storage_unittests",
+        "test_id_prefix": "ninja://storage:storage_unittests/"
+      },
+      {
+        "args": [
+          "--test-launcher-filter-file=../../testing/buildbot/filters/fuchsia.ui_base_unittests.filter",
+          "--device=aemu",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-20.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "ui_base_unittests",
+        "test_id_prefix": "ninja://ui/base:ui_base_unittests/"
+      },
+      {
+        "args": [
+          "--device=aemu",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-20.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "url_unittests",
+        "test_id_prefix": "ninja://url:url_unittests/"
+      },
+      {
+        "args": [
+          "--test-launcher-filter-file=../../testing/buildbot/filters/fuchsia.viz_unittests.filter",
+          "--device=aemu",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-20.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "viz_unittests",
+        "test_id_prefix": "ninja://components/viz:viz_unittests/"
+      },
+      {
+        "args": [
+          "--device=aemu",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-20.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "web_engine_browsertests",
+        "test_id_prefix": "ninja://fuchsia/engine:web_engine_browsertests/"
+      },
+      {
+        "args": [
+          "--child-arg=--vmodule=test_navigation_listener=1",
+          "--device=aemu"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-20.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "web_engine_integration_tests",
+        "test_id_prefix": "ninja://fuchsia/engine:web_engine_integration_tests/"
+      },
+      {
+        "args": [
+          "--device=aemu",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-20.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "web_engine_unittests",
+        "test_id_prefix": "ninja://fuchsia/engine:web_engine_unittests/"
+      },
+      {
+        "args": [
+          "--device=aemu"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-20.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "web_runner_integration_tests",
+        "test_id_prefix": "ninja://fuchsia/runners:web_runner_integration_tests/"
+      },
+      {
+        "args": [
+          "--device=aemu",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-20.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "wtf_unittests",
+        "test_id_prefix": "ninja://third_party/blink/renderer/platform/wtf:wtf_unittests/"
+      }
+    ],
+    "isolated_scripts": [
+      {
+        "args": [
+          "bin/run_angle_unittests",
+          "--device=aemu",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "isolate_name": "angle_unittests",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "angle_unittests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-20.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://third_party/angle/src/tests:angle_unittests/"
+      }
+    ]
+  },
   "fuchsia-fyi-arm64-rel": {
     "additional_compile_targets": [
       "all"
diff --git a/testing/buildbot/mixins.pyl b/testing/buildbot/mixins.pyl
index 613e265..fd2932c 100644
--- a/testing/buildbot/mixins.pyl
+++ b/testing/buildbot/mixins.pyl
@@ -391,6 +391,13 @@
       ],
     },
   },
+  'fuchsia-femu': {
+    '$mixin_append': {
+      'args': [
+        '--device=aemu',
+      ],
+    },
+  },
   'fuchsia_runner_logs': {
     '$mixin_append': {
       'args': [
@@ -546,6 +553,13 @@
       },
     },
   },
+  'linux-focal': {
+    'swarming': {
+      'dimensions': {
+        'os': 'Ubuntu-20.04',
+      },
+    },
+  },
   'linux-trusty': {
     'swarming': {
       'dimensions': {
diff --git a/testing/buildbot/test_suite_exceptions.pyl b/testing/buildbot/test_suite_exceptions.pyl
index b584340..aefd12f 100644
--- a/testing/buildbot/test_suite_exceptions.pyl
+++ b/testing/buildbot/test_suite_exceptions.pyl
@@ -1094,6 +1094,7 @@
       'Fuchsia x64', # https://crbug.com/961457
       'fuchsia-code-coverage',  # https://crbug.com/961457
       'fuchsia-fyi-arm64-dbg',  # https://crbug.com/961457
+      'fuchsia-fyi-arm64-femu',  # https://crbug.com/961457
       'fuchsia-fyi-arm64-rel',  # https://crbug.com/961457
       'fuchsia-fyi-x64-dbg',  # https://crbug.com/961457
       'fuchsia-fyi-x64-rel',  # https://crbug.com/961457
diff --git a/testing/buildbot/variants.pyl b/testing/buildbot/variants.pyl
index dd4f394..5811895 100644
--- a/testing/buildbot/variants.pyl
+++ b/testing/buildbot/variants.pyl
@@ -424,7 +424,7 @@
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M92',
-          'revision': 'version:92.0.4515.32',
+          'revision': 'version:92.0.4515.36',
         }
       ],
     },
@@ -448,7 +448,7 @@
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M91',
-          'revision': 'version:91.0.4472.82',
+          'revision': 'version:91.0.4472.83',
         }
       ],
     },
@@ -496,7 +496,7 @@
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M92',
-          'revision': 'version:92.0.4515.32',
+          'revision': 'version:92.0.4515.36',
         }
       ],
     },
@@ -520,7 +520,7 @@
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M91',
-          'revision': 'version:91.0.4472.82',
+          'revision': 'version:91.0.4472.83',
         }
       ],
     },
@@ -568,7 +568,7 @@
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M92',
-          'revision': 'version:92.0.4515.32',
+          'revision': 'version:92.0.4515.36',
         }
       ],
     },
@@ -592,7 +592,7 @@
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M91',
-          'revision': 'version:91.0.4472.82',
+          'revision': 'version:91.0.4472.83',
         }
       ],
     },
diff --git a/testing/buildbot/waterfalls.pyl b/testing/buildbot/waterfalls.pyl
index 993e4d8..01b2e3d 100644
--- a/testing/buildbot/waterfalls.pyl
+++ b/testing/buildbot/waterfalls.pyl
@@ -2795,6 +2795,21 @@
           'linux-xenial',
         ],
       },
+      'fuchsia-fyi-arm64-femu': {
+        'additional_compile_targets': [
+          'all',
+        ],
+        'test_suites': {
+          'gtest_tests': 'fuchsia_gtests',
+          'isolated_scripts': 'gpu_angle_fuchsia_unittests_isolated_scripts',
+        },
+        'mixins': [
+          'arm64',
+          'docker',
+          'fuchsia-femu',
+          'linux-focal',
+        ],
+      },
       'fuchsia-fyi-arm64-rel': {
         'additional_compile_targets': [
           'all',
diff --git a/third_party/blink/common/features.cc b/third_party/blink/common/features.cc
index c3eb4d77..a4b956eb 100644
--- a/third_party/blink/common/features.cc
+++ b/third_party/blink/common/features.cc
@@ -26,6 +26,10 @@
 const base::Feature kCSSContainerQueries{"CSSContainerQueries",
                                          base::FEATURE_DISABLED_BY_DEFAULT};
 
+// Controls whether the Conversion Measurement API infrastructure is enabled.
+const base::Feature kConversionMeasurement{"ConversionMeasurement",
+                                           base::FEATURE_ENABLED_BY_DEFAULT};
+
 const base::Feature kGMSCoreEmoji{"GMSCoreEmoji",
                                   base::FEATURE_DISABLED_BY_DEFAULT};
 
diff --git a/third_party/blink/public/common/features.h b/third_party/blink/public/common/features.h
index f4f0925..4f97c5709 100644
--- a/third_party/blink/public/common/features.h
+++ b/third_party/blink/public/common/features.h
@@ -20,6 +20,7 @@
     kBlockingDownloadsInAdFrameWithoutUserActivation;
 BLINK_COMMON_EXPORT extern const base::Feature kCOLRV1Fonts;
 BLINK_COMMON_EXPORT extern const base::Feature kCSSContainerQueries;
+BLINK_COMMON_EXPORT extern const base::Feature kConversionMeasurement;
 BLINK_COMMON_EXPORT extern const base::Feature kGMSCoreEmoji;
 BLINK_COMMON_EXPORT extern const base::Feature
     kHandwritingRecognitionWebPlatformApi;
diff --git a/third_party/blink/public/platform/web_runtime_features.h b/third_party/blink/public/platform/web_runtime_features.h
index 0aa93ca..accd722 100644
--- a/third_party/blink/public/platform/web_runtime_features.h
+++ b/third_party/blink/public/platform/web_runtime_features.h
@@ -236,8 +236,6 @@
       bool);
   BLINK_PLATFORM_EXPORT static void EnableContentIndex(bool);
   BLINK_PLATFORM_EXPORT static void EnableRestrictGamepadAccess(bool);
-  BLINK_PLATFORM_EXPORT static void EnableConversionMeasurementInfraSupport(
-      bool);
 
   BLINK_PLATFORM_EXPORT static void EnableTargetBlankImpliesNoOpener(bool);
 
diff --git a/third_party/blink/renderer/bindings/BUILD.gn b/third_party/blink/renderer/bindings/BUILD.gn
index 538cfc1..f133084 100644
--- a/third_party/blink/renderer/bindings/BUILD.gn
+++ b/third_party/blink/renderer/bindings/BUILD.gn
@@ -171,9 +171,6 @@
 
 template("generate_bindings") {
   action_with_pydeps(target_name) {
-    # TODO(crbug.com/1194277): Investigate non-determinism in Py3 builds.
-    run_under_python2 = true
-
     script = "${bindings_scripts_dir}/generate_bindings.py"
 
     if (defined(invoker.visibility)) {
diff --git a/third_party/blink/renderer/bindings/scripts/bind_gen/interface.py b/third_party/blink/renderer/bindings/scripts/bind_gen/interface.py
index ddf6e21..621e3080 100644
--- a/third_party/blink/renderer/bindings/scripts/bind_gen/interface.py
+++ b/third_party/blink/renderer/bindings/scripts/bind_gen/interface.py
@@ -2232,9 +2232,9 @@
         arg_list.append(
             ArgumentInfo(v8_type, v8_arg_name, blink_arg_name, symbol_node))
 
-    arg_decls = (["v8::Local<v8::Object> v8_arg0_receiver"] +
-                 map(lambda arg: "{} {}".format(arg.v8_type, arg.v8_arg_name),
-                     arg_list) +
+    arg_decls = (["v8::Local<v8::Object> v8_arg0_receiver"] + list(
+        map(lambda arg: "{} {}".format(arg.v8_type, arg.v8_arg_name),
+            arg_list)) +
                  ["v8::FastApiCallbackOptions& v8_arg_callback_options"])
     return_type = ("void" if cg_context.operation.return_type.is_void else
                    blink_type_info(cg_context.operation.return_type).value_t)
diff --git a/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm.cc b/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm.cc
index 335d2875..d1328c8 100644
--- a/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm.cc
+++ b/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm.cc
@@ -548,10 +548,13 @@
     NGLogicalLineItems* line_box) {
   DCHECK(item_result->layout_result);
 
-  // The input |position| is the line-left edge of the margin box.
-  // Adjust it to the border box by adding the line-left margin.
-  // const ComputedStyle& style = *item.Style();
-  // position += item_result->margins.LineLeft(style.Direction());
+  // Reset the ellipsizing state. Atomic inline is monolithic.
+  LayoutObject* layout_object = item.GetLayoutObject();
+  DCHECK(layout_object);
+  DCHECK(layout_object->IsAtomicInlineLevel());
+  DCHECK_EQ(To<LayoutBox>(layout_object)->GetNGPaginationBreakability(),
+            LayoutBox::kForbidBreaks);
+  layout_object->SetIsTruncated(false);
 
   item_result->has_edge = true;
   NGInlineBoxState* box =
diff --git a/third_party/blink/renderer/core/layout/ng/inline/ng_line_truncator.cc b/third_party/blink/renderer/core/layout/ng/inline/ng_line_truncator.cc
index 6cfdc37d..a7260a4 100644
--- a/third_party/blink/renderer/core/layout/ng/inline/ng_line_truncator.cc
+++ b/third_party/blink/renderer/core/layout/ng/inline/ng_line_truncator.cc
@@ -410,7 +410,12 @@
     if (fragment.HasOutOfFlowPositionedDescendants())
       return;
 
-    child->layout_result = fragment.CloneAsHiddenForPaint();
+    // Truncate this object. Atomic inline is monolithic.
+    DCHECK(fragment.IsMonolithic());
+    LayoutObject* layout_object = fragment.GetMutableLayoutObject();
+    DCHECK(layout_object);
+    DCHECK(layout_object->IsAtomicInlineLevel());
+    layout_object->SetIsTruncated(true);
     return;
   }
 
diff --git a/third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.cc b/third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.cc
index ac8bf4ea..60b95d2 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.cc
@@ -512,19 +512,6 @@
               : nullptr),
       table_cell_column_index(other.table_cell_column_index) {}
 
-scoped_refptr<const NGLayoutResult>
-NGPhysicalBoxFragment::CloneAsHiddenForPaint() const {
-  const ComputedStyle& style = Style();
-  NGBoxFragmentBuilder builder(GetMutableLayoutObject(), &style,
-                               style.GetWritingDirection());
-  builder.SetBoxType(BoxType());
-  NGFragmentGeometry initial_fragment_geometry{
-      Size().ConvertToLogical(style.GetWritingMode())};
-  builder.SetInitialFragmentGeometry(initial_fragment_geometry);
-  builder.SetIsHiddenForPaint(true);
-  return builder.ToBoxFragment();
-}
-
 const LayoutBox* NGPhysicalBoxFragment::OwnerLayoutBox() const {
   const LayoutBox* owner_box =
       DynamicTo<LayoutBox>(GetSelfOrContainerLayoutObject());
diff --git a/third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h b/third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h
index a9bb22f..c5e4b18 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h
+++ b/third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h
@@ -58,8 +58,6 @@
                         const PhysicalRect& layout_overflow,
                         bool recalculate_layout_overflow);
 
-  scoped_refptr<const NGLayoutResult> CloneAsHiddenForPaint() const;
-
   ~NGPhysicalBoxFragment() {
     ink_overflow_.Reset(InkOverflowType());
     if (const_has_fragment_items_)
diff --git a/third_party/blink/renderer/core/layout/ng/ng_physical_fragment.h b/third_party/blink/renderer/core/layout/ng/ng_physical_fragment.h
index 37c61b9c..fbb88ad 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_physical_fragment.h
+++ b/third_party/blink/renderer/core/layout/ng/ng_physical_fragment.h
@@ -343,7 +343,9 @@
 
   // This fragment is hidden for paint purpose, but exists for querying layout
   // information. Used for `text-overflow: ellipsis`.
-  bool IsHiddenForPaint() const { return is_hidden_for_paint_; }
+  bool IsHiddenForPaint() const {
+    return is_hidden_for_paint_ || layout_object_->IsTruncated();
+  }
 
   // Return true if this fragment is monolithic, as far as block fragmentation
   // is concerned.
diff --git a/third_party/blink/renderer/core/origin_trials/origin_trial_context.cc b/third_party/blink/renderer/core/origin_trials/origin_trial_context.cc
index 44185845..d1f3ee0 100644
--- a/third_party/blink/renderer/core/origin_trials/origin_trial_context.cc
+++ b/third_party/blink/renderer/core/origin_trials/origin_trial_context.cc
@@ -460,6 +460,10 @@
       !base::FeatureList::IsEnabled(features::kSpeculationRulesPrefetchProxy)) {
     return false;
   }
+  if (trial_name == "ConversionMeasurement" &&
+      !base::FeatureList::IsEnabled(features::kConversionMeasurement)) {
+    return false;
+  }
   return true;
 }
 
diff --git a/third_party/blink/renderer/modules/media_capabilities/media_capabilities_test.cc b/third_party/blink/renderer/modules/media_capabilities/media_capabilities_test.cc
index c196239..04e80a8 100644
--- a/third_party/blink/renderer/modules/media_capabilities/media_capabilities_test.cc
+++ b/third_party/blink/renderer/modules/media_capabilities/media_capabilities_test.cc
@@ -185,6 +185,8 @@
       media::mojom::blink::MediaContainerName container_name) override {}
   void SetRendererType(
       media::mojom::blink::RendererType renderer_type) override {}
+  void SetKeySystem(const String& key_system) override {}
+  void SetIsHardwareSecure() override {}
   void SetHasPlayed() override {}
   void SetHaveEnough() override {}
   void SetHasAudio(media::mojom::AudioCodec audio_codec) override {}
diff --git a/third_party/blink/renderer/platform/exported/web_runtime_features.cc b/third_party/blink/renderer/platform/exported/web_runtime_features.cc
index 7a4bfa26..1870532e 100644
--- a/third_party/blink/renderer/platform/exported/web_runtime_features.cc
+++ b/third_party/blink/renderer/platform/exported/web_runtime_features.cc
@@ -662,10 +662,6 @@
   RuntimeEnabledFeatures::SetRestrictGamepadAccessEnabled(enable);
 }
 
-void WebRuntimeFeatures::EnableConversionMeasurementInfraSupport(bool enable) {
-  RuntimeEnabledFeatures::SetConversionMeasurementInfraSupportEnabled(enable);
-}
-
 void WebRuntimeFeatures::EnableParseUrlProtocolHandler(bool enable) {
   RuntimeEnabledFeatures::SetParseUrlProtocolHandlerEnabled(enable);
 }
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index 17fbb1a..ea4e277a 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -424,12 +424,6 @@
       origin_trial_feature_name: "ConversionMeasurement",
       origin_trial_allows_third_party: true,
       status: "experimental",
-      depends_on: ["ConversionMeasurementInfraSupport"],
-    },
-    {
-      // Feature to explicitly disable/enable the Conversion
-      // Measurement API when there is not browser process side infra.
-      name: "ConversionMeasurementInfraSupport",
     },
     {
       name: "CookiesWithoutSameSiteMustBeSecure",
diff --git a/third_party/blink/renderer/platform/wtf/text/atomic_string.h b/third_party/blink/renderer/platform/wtf/text/atomic_string.h
index a533ffaa..b36b2e90 100644
--- a/third_party/blink/renderer/platform/wtf/text/atomic_string.h
+++ b/third_party/blink/renderer/platform/wtf/text/atomic_string.h
@@ -274,7 +274,6 @@
 }
 
 // Define external global variables for the commonly used atomic strings.
-// These are only usable from the main thread.
 WTF_EXPORT extern const AtomicString& g_null_atom;
 WTF_EXPORT extern const AtomicString& g_empty_atom;
 WTF_EXPORT extern const AtomicString& g_star_atom;
diff --git a/third_party/blink/renderer/platform/wtf/text/string_statics.cc b/third_party/blink/renderer/platform/wtf/text/string_statics.cc
index aeb0d1d2..3c90f13d 100644
--- a/third_party/blink/renderer/platform/wtf/text/string_statics.cc
+++ b/third_party/blink/renderer/platform/wtf/text/string_statics.cc
@@ -83,7 +83,8 @@
       String(StringImpl::empty16_bit_);
 
   // FIXME: These should be allocated at compile time.
-  new (NotNullTag::kNotNull, (void*)&g_star_atom) AtomicString("*");
+  new (NotNullTag::kNotNull, (void*)&g_star_atom)
+      AtomicString(AddStaticASCIILiteral("*"));
   new (NotNullTag::kNotNull, (void*)&g_xml_atom)
       AtomicString(AddStaticASCIILiteral("xml"));
   new (NotNullTag::kNotNull, (void*)&g_xmlns_atom)
diff --git a/third_party/blink/web_tests/external/wpt/resource-timing/buffer-full-add-after-full-event.html b/third_party/blink/web_tests/external/wpt/resource-timing/buffer-full-add-after-full-event.html
index 84d257e..43dc3d8 100644
--- a/third_party/blink/web_tests/external/wpt/resource-timing/buffer-full-add-after-full-event.html
+++ b/third_party/blink/web_tests/external/wpt/resource-timing/buffer-full-add-after-full-event.html
@@ -2,84 +2,26 @@
 <html>
 <head>
 <meta charset="utf-8">
-<link rel="help" href="https://w3c.github.io/resource-timing/#dom-performance-setresourcetimingbuffersize">
+<link rel="author" title="Google" href="http://www.google.com/" />
+<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#dom-performance-setresourcetimingbuffersize">
 <title>This test validates that setResourceTimingBufferFull behaves appropriately when set to the current buffer level.</title>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
+<script src="resources/resource-loaders.js"></script>
 <script src="resources/buffer-full-utilities.js"></script>
 </head>
 <body>
 <script>
-let eventFired = false;
-let loadRandomResource = () => {
-    return fetch(window.location.href + "?" + Math.random()).then(r => r.text());
-}
-
-setup(() => {
-    // Get the browser into a consistent state.
-    clearBufferAndSetSize(100);
-});
-
-let loadResourcesToFillFutureBuffer = () => {
+promise_test(async t => {
+    await forceBufferFullEvent();
+    performance.clearResourceTimings();
     return new Promise(resolve => {
-        // Gather up 3 Resource Entries to kick off the rest of test behavior.
-        let resources = 0;
-        let observer = new PerformanceObserver(function(list) {
-            resources += list.getEntriesByType("resource").length;
-            if (resources !== 3)
-                return;
-            observer.disconnect();
-            resolve();
-        });
-        observer.observe({entryTypes: ["resource"]});
-        for (let i = 0; i < 3; ++i)
-            loadRandomResource();
+      new PerformanceObserver(t.step_func(() => {
+        assert_equals(performance.getEntriesByType('resource').length, 1,
+            'The entry should be available in the performance timeline!');
+        resolve();
+      })).observe({type: 'resource'});
+      load.script(scriptResources[2]);
     });
-};
-
-let setBufferFullEventAndBufferSize = () => {
-    performance.setResourceTimingBufferSize(3);
-    performance.onresourcetimingbufferfull = function() {
-        eventFired = true;
-        performance.clearResourceTimings();
-    };
-};
-
-let clearAndAddAnotherEntryToBuffer = () => {
-    return new Promise(resolve => {
-        performance.clearResourceTimings();
-        loadRandomResource().then(resolve);
-    });
-};
-
-let testThatEntryWasAdded = () => {
-    let tries = 1;
-    let maxTries = 5;
-    return waitUntilConditionIsMet( function() {
-        if (performance.getEntriesByType("resource").length) {
-            return true;
-        } else {
-            if (tries < maxTries) {
-                tries++;
-                return false;
-            } else {
-                return true;
-            }
-        }
-    }).then( () => {
-        assert_true((performance.getEntriesByType("resource").length) === 1);
-    });
-};
-
-promise_test(async () => {
-    await loadResourcesToFillFutureBuffer();
-    setBufferFullEventAndBufferSize();
-    // Overflow the buffer.
-    await loadRandomResource();
-    await waitForEventToFire();
-    await clearAndAddAnotherEntryToBuffer();
-    // Since we have no strict guarantees when an entry will be added to the
-    // buffer, waiting till next task to try to avoid flakiness.
-    await testThatEntryWasAdded();
 }, "Test that entry was added to the buffer after a buffer full event");
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/resource-timing/buffer-full-add-entries-during-callback-that-drop.html b/third_party/blink/web_tests/external/wpt/resource-timing/buffer-full-add-entries-during-callback-that-drop.html
index d61d2af0..b00185c5 100644
--- a/third_party/blink/web_tests/external/wpt/resource-timing/buffer-full-add-entries-during-callback-that-drop.html
+++ b/third_party/blink/web_tests/external/wpt/resource-timing/buffer-full-add-entries-during-callback-that-drop.html
@@ -3,48 +3,26 @@
 <head onload>
 <meta charset="utf-8" />
 <title>This test validates that synchronously adding entries in onresourcetimingbufferfull callback results in these entries being properly handled.</title>
-<link rel="help" href="http://www.w3.org/TR/resource-timing/#performanceresourcetiming"/>
+<link rel="author" title="Google" href="http://www.google.com/" />
+<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#dom-performance-onresourcetimingbufferfull"/>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
+<script src="resources/resource-loaders.js"></script>
 <script src="resources/buffer-full-utilities.js"></script>
 </head>
 <body>
 <script>
-const resource_timing_buffer_size = 1;
-
-setup(() => {
-    // Get the browser into a consistent state.
-    clearBufferAndSetSize(resource_timing_buffer_size);
-});
-
-let overflowTheBufferAndWaitForEvent = () => {
-    return new Promise(resolve => {
-        var add_entry = () => {
-            performance.setResourceTimingBufferSize(resource_timing_buffer_size + 1);
-            // The sync entry is added to the secondary buffer, so will be the last one there and eventually dropped.
-            xhrScript("resources/empty.js?xhr");
-            resolve();
-        }
-        performance.addEventListener('resourcetimingbufferfull', add_entry);
-        // This resource overflows the entry buffer, and goes into the secondary buffer.
-        appendScript('resources/empty_script.js');
-    });
-};
-
-let testThatBufferContainsTheRightResources = () => {
-    let entries = performance.getEntriesByType('resource');
-    assert_equals(entries.length, 2,
-                  'Both entries should be stored in resource timing buffer since its increases size once it overflows.');
-    assert_true(entries[0].name.includes('empty.js'), "empty.js is in the entries buffer");
-    assert_true(entries[1].name.includes('empty_script.js'), "empty_script.js is in the entries buffer");
-};
-
 promise_test(async () => {
-    await fillUpTheBufferWithSingleResource("resources/empty.js");
-    await overflowTheBufferAndWaitForEvent();
-    // TODO(yoav): Figure out why this task is needed
-    await waitForNextTask();
-    testThatBufferContainsTheRightResources();
+    await fillUpTheBufferWithSingleResource();
+    performance.addEventListener('resourcetimingbufferfull', () => {
+        performance.setResourceTimingBufferSize(2);
+        // The sync entry is added to the secondary buffer, so will be the last one there and eventually dropped.
+        load.xhr_sync(scriptResources[2]);
+    });
+    // This resource overflows the entry buffer, and goes into the secondary buffer.
+    load.script(scriptResources[1]);
+    await bufferFullFirePromise;
+    checkEntries(2);
 }, "Test that entries synchronously added to the buffer during the callback are dropped");
 </script>
 </body>
diff --git a/third_party/blink/web_tests/external/wpt/resource-timing/buffer-full-add-entries-during-callback.html b/third_party/blink/web_tests/external/wpt/resource-timing/buffer-full-add-entries-during-callback.html
index b37c47b..d5883d3 100644
--- a/third_party/blink/web_tests/external/wpt/resource-timing/buffer-full-add-entries-during-callback.html
+++ b/third_party/blink/web_tests/external/wpt/resource-timing/buffer-full-add-entries-during-callback.html
@@ -3,47 +3,25 @@
 <head onload>
 <meta charset="utf-8" />
 <title>This test validates that synchronously adding entries in onresourcetimingbufferfull callback results in these entries being properly handled.</title>
-<link rel="help" href="http://www.w3.org/TR/resource-timing/#performanceresourcetiming"/>
+<link rel="author" title="Google" href="http://www.google.com/" />
+<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#dom-performance-onresourcetimingbufferfull"/>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
+<script src="resources/resource-loaders.js"></script>
 <script src="resources/buffer-full-utilities.js"></script>
 </head>
 <body>
 <script>
-const resource_timing_buffer_size = 1;
-
-setup(() => {
-    // Get the browser into a consistent state.
-    clearBufferAndSetSize(resource_timing_buffer_size);
-});
-
-let overflowTheBufferAndWaitForEvent = () => {
-    return new Promise(resolve => {
-        var add_entry = () => {
-            performance.setResourceTimingBufferSize(resource_timing_buffer_size + 2);
-            xhrScript("resources/empty.js?xhr");
-            resolve();
-        }
-        performance.addEventListener('resourcetimingbufferfull', add_entry);
-        // This resource overflows the entry buffer, and goes into the secondary buffer.
-        appendScript('resources/empty_script.js');
-    });
-};
-
-let testThatBufferContainsTheRightResources = () => {
-    let entries = performance.getEntriesByType('resource');
-    assert_equals(entries.length, 3,
-                  'All entries should be stored in resource timing buffer since its increases size once it overflows.');
-    assert_true(entries[0].name.includes('empty.js'), "empty.js is in the entries buffer");
-    assert_true(entries[1].name.includes('empty_script.js'), "empty_script.js is in the entries buffer");
-    assert_true(entries[2].name.includes('empty.js?xhr'), "empty.js?xhr is in the entries buffer");
-};
-
 promise_test(async () => {
-    await fillUpTheBufferWithSingleResource("resources/empty.js");
-    await overflowTheBufferAndWaitForEvent();
-    await waitForNextTask();
-    testThatBufferContainsTheRightResources();
+    await fillUpTheBufferWithSingleResource();
+    performance.addEventListener('resourcetimingbufferfull', () => {
+        performance.setResourceTimingBufferSize(3);
+        load.xhr_sync(scriptResources[2]);
+    });
+    // This resource overflows the entry buffer, and goes into the secondary buffer.
+    load.script(scriptResources[1]);
+    await bufferFullFirePromise;
+    checkEntries(3);
 }, "Test that entries synchronously added to the buffer during the callback don't get dropped if the buffer is increased");
 </script>
 </body>
diff --git a/third_party/blink/web_tests/external/wpt/resource-timing/buffer-full-add-then-clear.html b/third_party/blink/web_tests/external/wpt/resource-timing/buffer-full-add-then-clear.html
index 710852c..5617c30 100644
--- a/third_party/blink/web_tests/external/wpt/resource-timing/buffer-full-add-then-clear.html
+++ b/third_party/blink/web_tests/external/wpt/resource-timing/buffer-full-add-then-clear.html
@@ -3,46 +3,28 @@
 <head onload>
 <meta charset="utf-8" />
 <title>This test validates that synchronously adding entries in onresourcetimingbufferfull callback results in these entries being properly handled.</title>
-<link rel="help" href="http://www.w3.org/TR/resource-timing/#performanceresourcetiming"/>
+<link rel="author" title="Google" href="http://www.google.com/" />
+<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#dom-performance-onresourcetimingbufferfull"/>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
+<script src="resources/resource-loaders.js"></script>
 <script src="resources/buffer-full-utilities.js"></script>
 </head>
 <body>
 <script>
-const resource_timing_buffer_size = 1;
-
-setup(() => {
-    // Get the browser into a consistent state.
-    clearBufferAndSetSize(resource_timing_buffer_size);
-    performance.addEventListener('resourcetimingbufferfull', () => { assert_unreached("resourcetimingbufferfull should not fire")});
-});
-
-let overflowTheBuffer = () => {
+promise_test(async t => {
+    addAssertUnreachedBufferFull(t);
+    await fillUpTheBufferWithSingleResource('resources/empty.js?willbelost');
     // These resources overflow the entry buffer, and go into the secondary buffer.
-    xhrScript('resources/empty.js?xhr2');
-    xhrScript('resources/empty.js?xhr3');
+    load.xhr_sync(scriptResources[0]);
+    load.xhr_sync(scriptResources[1]);
     performance.clearResourceTimings();
     performance.setResourceTimingBufferSize(3);
-    xhrScript('resources/empty.js?xhr4');
-    window.entriesAfterAddition = performance.getEntriesByType('resource');
-};
-
-let testThatBufferContainsTheRightResources = () => {
-    let entries = performance.getEntriesByType('resource');
-    assert_equals(entries.length, 3,
-                  'the last 3 resources should be in the buffer, since the first one was cleared');
-    assert_true(entries[0].name.includes('empty.js?xhr2'), "empty.js?xhr2 is in the entries buffer");
-    assert_true(entries[1].name.includes('empty.js?xhr3'), "empty.js?xhr3 is in the entries buffer");
-    assert_true(entries[2].name.includes('empty.js?xhr4'), "empty.js?xhr4 is in the entries buffer");
-    assert_equals(entriesAfterAddition.length, 0, "No entries should have been added to the primary buffer before the task to 'fire a buffer full event'.");
-};
-
-promise_test(async () => {
-    await fillUpTheBufferWithSingleResource("resources/empty.js");
-    overflowTheBuffer();
+    load.xhr_sync(scriptResources[2]);
+    const entriesAfterAddition = performance.getEntriesByType('resource');
     await waitForNextTask();
-    testThatBufferContainsTheRightResources();
+    checkEntries(3);
+    assert_equals(entriesAfterAddition.length, 0, "No entries should have been added to the primary buffer before the task to 'fire a buffer full event'.");
 }, "Test that if the buffer is cleared after entries were added to the secondary buffer, those entries make it into the primary one");
 </script>
 </body>
diff --git a/third_party/blink/web_tests/external/wpt/resource-timing/buffer-full-decrease-buffer-during-callback.html b/third_party/blink/web_tests/external/wpt/resource-timing/buffer-full-decrease-buffer-during-callback.html
index e6de33d..3091fcf 100644
--- a/third_party/blink/web_tests/external/wpt/resource-timing/buffer-full-decrease-buffer-during-callback.html
+++ b/third_party/blink/web_tests/external/wpt/resource-timing/buffer-full-decrease-buffer-during-callback.html
@@ -3,46 +3,23 @@
 <head onload>
 <meta charset="utf-8" />
 <title>This test validates that decreasing the buffer size in onresourcetimingbufferfull callback does not result in extra entries being dropped.</title>
-<link rel="help" href="http://www.w3.org/TR/resource-timing/#performanceresourcetiming"/>
+<link rel="author" title="Google" href="http://www.google.com/" />
+<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#dom-performance-onresourcetimingbufferfull"/>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
+<script src="resources/resource-loaders.js"></script>
 <script src="resources/buffer-full-utilities.js"></script>
 </head>
 <body>
 <script>
-const resource_timing_buffer_size = 2;
-let eventFired = false;
-setup(() => {
-    // Get the browser into a consistent state.
-    clearBufferAndSetSize(resource_timing_buffer_size);
-    let resize = () => {
-        performance.setResourceTimingBufferSize(resource_timing_buffer_size - 1);
-        eventFired = true;
-    }
-    performance.addEventListener('resourcetimingbufferfull', resize);
-});
-
-let overflowTheBuffer = () => {
-    return new Promise(resolve => {
-        // This resource overflows the entry buffer, and goes into the secondary buffer.
-        // Since the buffer size doesn't increase, it will eventually be dropped.
-        appendScript('resources/empty_script.js', resolve);
-    });
-};
-
-let testThatBufferContainsTheRightResources = () => {
-    let entries = performance.getEntriesByType('resource');
-    assert_equals(entries.length, 2,
-                  'Both entries should be stored in resource timing buffer since it decreased its limit only after it overflowed.');
-    assert_true(entries[0].name.includes('empty.js'), "empty.js is in the entries buffer");
-    assert_true(entries[1].name.includes('empty.js?second'), "empty.js?second is in the entries buffer");
-};
-
 promise_test(async () => {
-    await fillUpTheBufferWithTwoResources('resources/empty.js');
-    await overflowTheBuffer();
-    await waitForEventToFire();
-    testThatBufferContainsTheRightResources();
+    performance.addEventListener('resourcetimingbufferfull', () => {
+        performance.setResourceTimingBufferSize(1);
+    });
+    await fillUpTheBufferWithTwoResources();
+    load.script(scriptResources[2]);
+    await bufferFullFirePromise;
+    checkEntries(2);
 }, "Test that decreasing the buffer limit during the callback does not drop entries");
 </script>
 </body>
diff --git a/third_party/blink/web_tests/external/wpt/resource-timing/buffer-full-increase-buffer-during-callback.html b/third_party/blink/web_tests/external/wpt/resource-timing/buffer-full-increase-buffer-during-callback.html
index b46d2d65..dd12dd7 100644
--- a/third_party/blink/web_tests/external/wpt/resource-timing/buffer-full-increase-buffer-during-callback.html
+++ b/third_party/blink/web_tests/external/wpt/resource-timing/buffer-full-increase-buffer-during-callback.html
@@ -3,46 +3,23 @@
 <head onload>
 <meta charset="utf-8" />
 <title>This test validates increasing the buffer size in onresourcetimingbufferfull callback of resource timing.</title>
-<link rel="help" href="http://www.w3.org/TR/resource-timing/#performanceresourcetiming"/>
+<link rel="author" title="Google" href="http://www.google.com/" />
+<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#dom-performance-onresourcetimingbufferfull"/>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
+<script src="resources/resource-loaders.js"></script>
 <script src="resources/buffer-full-utilities.js"></script>
 </head>
 <body>
 <script>
-const resource_timing_buffer_size = 1;
-let eventFired = false;
-
-setup(() => {
-    // Get the browser into a consistent state.
-    clearBufferAndSetSize(resource_timing_buffer_size);
-    var increase = function() {
-        performance.setResourceTimingBufferSize(resource_timing_buffer_size * 2);
-        eventFired = true;
-    }
-    performance.addEventListener('resourcetimingbufferfull', increase);
-});
-
-let overflowTheBuffer = () => {
-    return new Promise(resolve => {
-        // This resource overflows the entry buffer, and goes into the secondary buffer.
-        appendScript('resources/empty_script.js', resolve);
-    });
-};
-
-let testThatBufferContainsTheRightResources = () => {
-    let entries = performance.getEntriesByType('resource');
-    assert_equals(entries.length, 2,
-                  'Both entries should be stored in resource timing buffer since its increases size once it overflows.');
-    assert_true(entries[0].name.includes('empty.js'), "empty.js is in the entries buffer");
-    assert_true(entries[1].name.includes('empty_script.js'), "empty_script.js is in the entries buffer");
-};
-
 promise_test(async () => {
-    await fillUpTheBufferWithSingleResource("resources/empty.js");
-    await overflowTheBuffer();
-    await waitForEventToFire();
-    testThatBufferContainsTheRightResources();
+    await fillUpTheBufferWithSingleResource();
+    performance.addEventListener('resourcetimingbufferfull', () => {
+        performance.setResourceTimingBufferSize(2);
+    });
+    await load.script(scriptResources[1]);
+    await bufferFullFirePromise;
+    checkEntries(2);
 }, "Test that increasing the buffer during the callback is enough for entries not to be dropped");
 </script>
 </body>
diff --git a/third_party/blink/web_tests/external/wpt/resource-timing/buffer-full-inspect-buffer-during-callback.html b/third_party/blink/web_tests/external/wpt/resource-timing/buffer-full-inspect-buffer-during-callback.html
index d46d469..d5cc8e6 100644
--- a/third_party/blink/web_tests/external/wpt/resource-timing/buffer-full-inspect-buffer-during-callback.html
+++ b/third_party/blink/web_tests/external/wpt/resource-timing/buffer-full-inspect-buffer-during-callback.html
@@ -3,53 +3,27 @@
 <head onload>
 <meta charset="utf-8" />
 <title>This test validates the buffer doesn't contain more entries than it should inside onresourcetimingbufferfull callback.</title>
-<link rel="help" href="http://www.w3.org/TR/resource-timing/#performanceresourcetiming"/>
+<link rel="author" title="Google" href="http://www.google.com/" />
+<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#dom-performance-setresourcetimingbuffersize"/>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
+<script src="resources/resource-loaders.js"></script>
 <script src="resources/buffer-full-utilities.js"></script>
 </head>
 <body>
 <script>
-let resource_timing_buffer_size = 2;
-let eventFired = false;
-
-setup(() => {
-    clearBufferAndSetSize(resource_timing_buffer_size);
-    var resize = function() {
-        assert_equals(performance.getEntriesByType("resource").length, resource_timing_buffer_size, "resource timing buffer in resourcetimingbufferfull is the size of the limit");
-        ++resource_timing_buffer_size;
-        performance.setResourceTimingBufferSize(resource_timing_buffer_size);
-        xhrScript("resources/empty.js?xhr");
-        assert_equals(performance.getEntriesByType("resource").length, resource_timing_buffer_size - 1, "A sync request was not added to the primary buffer just yet, because it is full");
-        ++resource_timing_buffer_size;
-        performance.setResourceTimingBufferSize(resource_timing_buffer_size);
-        eventFired = true;
-    }
-    performance.addEventListener('resourcetimingbufferfull', resize);
-});
-
-let overflowTheBuffer = () => {
-    return new Promise(resolve => {
-        // This resource overflows the entry buffer, and goes into the secondary buffer.
-        appendScript('resources/empty_script.js', resolve);
-    });
-};
-
-let testThatBufferContainsTheRightResources = () => {
-    let entries = performance.getEntriesByType('resource');
-    assert_equals(entries.length, resource_timing_buffer_size,
-                  'All 4 entries should be stored in resource timing buffer.');
-    assert_true(entries[0].name.includes('empty.js'), "empty.js is in the entries buffer");
-    assert_true(entries[1].name.includes('empty.js?second'), "empty.js?second is in the entries buffer");
-    assert_true(entries[2].name.includes('empty_script.js'), "empty_script.js is in the entries buffer");
-    assert_true(entries[3].name.includes('empty.js?xhr'), "empty.js?xhr is in the entries buffer");
-};
-
-promise_test(async () => {
-    await fillUpTheBufferWithTwoResources('resources/empty.js');
-    await overflowTheBuffer();
-    await waitForEventToFire();
-    testThatBufferContainsTheRightResources();
+promise_test(async t => {
+    performance.addEventListener('resourcetimingbufferfull', t.step_func(() => {
+        assert_equals(performance.getEntriesByType("resource").length, 1,
+            "resource timing buffer in resourcetimingbufferfull is the size of the limit");
+        load.xhr_sync(scriptResources[2]);
+        performance.setResourceTimingBufferSize(3);
+        assert_equals(performance.getEntriesByType("resource").length, 1,
+            "A sync request must not be added to the primary buffer just yet, because it is full");
+    }));
+    await forceBufferFullEvent();
+    await waitForNextTask();
+    checkEntries(3);
 }, "Test that entries in the secondary buffer are not exposed during the callback and before they are copied to the primary buffer");
 </script>
 </body>
diff --git a/third_party/blink/web_tests/external/wpt/resource-timing/buffer-full-set-to-current-buffer.html b/third_party/blink/web_tests/external/wpt/resource-timing/buffer-full-set-to-current-buffer.html
index 1e5486ec..dc527b9a 100644
--- a/third_party/blink/web_tests/external/wpt/resource-timing/buffer-full-set-to-current-buffer.html
+++ b/third_party/blink/web_tests/external/wpt/resource-timing/buffer-full-set-to-current-buffer.html
@@ -2,81 +2,33 @@
 <html>
 <head>
 <meta charset="utf-8">
-<link rel="help" href="https://w3c.github.io/resource-timing/#dom-performance-setresourcetimingbuffersize">
 <title>This test validates that setResourceTimingBufferFull behaves appropriately when set to the current buffer level.</title>
+<link rel="author" title="Google" href="http://www.google.com/" />
+<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#dom-performance-onresourcetimingbufferfull">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
+<script src="resources/resource-loaders.js"></script>
 <script src="resources/buffer-full-utilities.js"></script>
 </head>
 <body>
 <script>
-let eventFired = false;
-
-let loadRandomResource = () => {
-    return fetch(window.location.href + "?" + Math.random());
-};
-
-setup(() => {
-    // Get the browser into a consistent state.
-    clearBufferAndSetSize(100);
-    window.result = "";
-});
-
-let fillUpTheBuffer = () => {
-    return new Promise(resolve => {
-        // Gather up 3 Resource Entries to kick off the rest of test behavior.
-        let resources = 0;
-        let observer = new PerformanceObserver(list => {
-            resources += list.getEntriesByType("resource").length;
-            if (resources !== 3)
-                return;
-            observer.disconnect();
-            resolve();
-        });
-        observer.observe({entryTypes: ["resource"]});
-        for (let i = 0; i < 3; ++i)
-            loadRandomResource();
-    });
-};
-
-let setBufferSize = () => {
-    performance.onresourcetimingbufferfull = () => {
-        eventFired = true;
-        window.result += "Event Fired with "  + performance.getEntriesByType("resource").length + " entries. ";
-        performance.clearResourceTimings();
-    };
-    window.result += "before setLimit(3). ";
-    performance.setResourceTimingBufferSize(3);
-    window.result += "after setLimit(3). ";
-};
-
-let overflowTheBuffer = () => {
-    return new Promise(resolve => {
-        loadRandomResource().then(() => {
-            window.result += "after loading 4th resource. ";
-            resolve();
-        });
-    });
-};
-
-let checkResult = () => {
-    return new Promise((resolve, reject) => {
-        if (window.result != "before setLimit(3). after setLimit(3). after loading 4th resource. Event Fired with 3 entries. ") {
-            reject("Non matching value: " + window.result);
-        }
-        let entries = performance.getEntriesByType("resource");
-        if (entries.length != 1) {
-            reject("Number of entries in resource timing buffer is unexpected: " + entries.length);
-        }
-        resolve();
-    });
-};
-
 promise_test(async () => {
-    await fillUpTheBuffer();
-    setBufferSize();
-    await overflowTheBuffer();
-    await waitForEventToFire();
-    await checkResult();
-}, "Test that entries added and event firing happened in the right sequence");
+    let result = '';
+    performance.addEventListener('resourcetimingbufferfull', () => {
+        result += 'Event Fired with '  +
+            performance.getEntriesByType('resource').length + ' entries.';
+        performance.clearResourceTimings();
+    });
+    result += 'Before adding entries. ';
+    await fillUpTheBufferWithTwoResources();
+    result += 'After adding entries. ';
+    load.script(scriptResources[2]);
+    await bufferFullFirePromise;
+    assert_equals(result, 'Before adding entries. After adding entries. Event Fired with 2 entries.');
+    const entries = performance.getEntriesByType('resource');
+    assert_equals(entries.length, 1,
+        'Number of entries in resource timing buffer is unexpected');
+    assert_true(entries[0].name.includes(scriptResources[2]),
+        'The entry must correspond to the last resource loaded.')
+}, "Test that adding entries and firing the buffer full event happen in the right order.");
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/resource-timing/buffer-full-store-and-clear-during-callback.html b/third_party/blink/web_tests/external/wpt/resource-timing/buffer-full-store-and-clear-during-callback.html
index f0791cba..3ea05772 100644
--- a/third_party/blink/web_tests/external/wpt/resource-timing/buffer-full-store-and-clear-during-callback.html
+++ b/third_party/blink/web_tests/external/wpt/resource-timing/buffer-full-store-and-clear-during-callback.html
@@ -4,52 +4,33 @@
 <meta charset="utf-8" />
 <title>This test validates the behavior of read and clear operation in onresourcetimingbufferfull callback of resource timing.</title>
 <link rel="author" title="Intel" href="http://www.intel.com/" />
-<link rel="help" href="http://www.w3.org/TR/resource-timing/#performanceresourcetiming"/>
+<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#dom-performance-onresourcetimingbufferfull"/>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
+<script src="resources/resource-loaders.js"></script>
 <script src="resources/buffer-full-utilities.js"></script>
 </head>
 <body>
 <script>
-const resource_timing_buffer_size = 1;
-let global_buffer = [];
-let eventFired = false;
-
-setup(() => {
-    clearBufferAndSetSize(resource_timing_buffer_size);
-    let store_and_clear = function() {
-        const entryList = performance.getEntriesByType('resource');
-        entryList.forEach(function (entry) {
-            global_buffer.push(entry);
-        });
-        performance.clearResourceTimings();
-        eventFired = true;
-    }
-    performance.addEventListener('resourcetimingbufferfull', store_and_clear);
-});
-
-let overflowTheBuffer = () => {
-    return new Promise(resolve => {
-        // This resource overflows the entry buffer, and goes into the secondary buffer.
-        appendScript('resources/empty_script.js', resolve);
-    });
-};
-
-let testThatBufferContainsTheRightResources = () => {
-    let entries = performance.getEntriesByType('resource');
-    assert_equals(entries.length, 1,
-                  "Only the last entry should be stored in resource timing buffer since it's cleared once it overflows.");
-    assert_equals(global_buffer.length, 1, '1 resource timing entry should be moved to global buffer.');
-    assert_true(global_buffer[0].name.includes('empty.js'), "empty.js is in the global buffer");
-    assert_true(entries[0].name.includes('empty_script.js'), "empty_script.js is in the entries buffer");
-};
-
 promise_test(async () => {
-    await fillUpTheBufferWithSingleResource("resources/empty.js");
-    await overflowTheBuffer();
-    await waitForEventToFire();
-    testThatBufferContainsTheRightResources();
-}, "Test that entries overflowing the buffer trigger the buffer full event, can be stored, and find themselves in the primary buffer after it's cleared.");
+    await fillUpTheBufferWithSingleResource();
+    const entryBuffer = [];
+    performance.addEventListener('resourcetimingbufferfull', () => {
+      entryBuffer.push(...performance.getEntriesByType('resource'));
+      performance.clearResourceTimings();
+    });
+    load.script(scriptResources[1]);
+    await bufferFullFirePromise;
+    const entries = performance.getEntriesByType('resource');
+    assert_equals(entries.length, 1,
+        "Only the last entry should be stored in resource timing buffer since it's cleared once it overflows.");
+    assert_true(entries[0].name.includes(scriptResources[1]),
+        scriptResources[1]  + " is in the entries buffer");
+    assert_equals(entryBuffer.length, 1,
+        '1 resource timing entry should be moved to entryBuffer.');
+    assert_true(entryBuffer[0].name.includes(scriptResources[0]),
+        scriptResources[0] + ' is in the entryBuffer');
+}, "Test that entries overflowing the buffer trigger the buffer full event, can be stored, and make their way to the primary buffer after it's cleared in the buffer full event.");
 </script>
 </body>
 </html>
diff --git a/third_party/blink/web_tests/external/wpt/resource-timing/buffer-full-then-increased.html b/third_party/blink/web_tests/external/wpt/resource-timing/buffer-full-then-increased.html
index 2265077..de517bf4 100644
--- a/third_party/blink/web_tests/external/wpt/resource-timing/buffer-full-then-increased.html
+++ b/third_party/blink/web_tests/external/wpt/resource-timing/buffer-full-then-increased.html
@@ -3,41 +3,25 @@
 <head onload>
 <meta charset="utf-8" />
 <title>This test validates that synchronously adding entries in onresourcetimingbufferfull callback results in these entries being properly handled.</title>
-<link rel="help" href="http://www.w3.org/TR/resource-timing/#performanceresourcetiming"/>
+<link rel="author" title="Google" href="http://www.google.com/" />
+<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#dom-performance-onresourcetimingbufferfull"/>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
+<script src="resources/resource-loaders.js"></script>
 <script src="resources/buffer-full-utilities.js"></script>
 </head>
 <body>
 <script>
-const resource_timing_buffer_size = 1;
-
-setup(() => {
-    clearBufferAndSetSize(resource_timing_buffer_size);
-    performance.addEventListener('resourcetimingbufferfull', () => { assert_unreached("resourcetimingbufferfull should not fire"); });
-});
-
-let overflowTheBuffer = () => {
+promise_test(async t => {
+    addAssertUnreachedBufferFull(t);
+    await fillUpTheBufferWithSingleResource();
     // These resources overflow the entry buffer, and go into the secondary buffer.
-    xhrScript('resources/empty.js?xhr2');
-    xhrScript('resources/empty.js?xhr3');
+    load.xhr_sync(scriptResources[1]);
+    load.xhr_sync(scriptResources[2]);
+    // Immediately increase the size: the bufferfull event should not be fired.
     performance.setResourceTimingBufferSize(3);
-};
-
-let testThatBufferContainsTheRightResources = () => {
-        let entries = performance.getEntriesByType('resource');
-        assert_equals(entries.length, 3,
-                      'All resources should be in the buffer, since its size was increased');
-        assert_true(entries[0].name.includes('empty.js'), "empty.js?xhr2 is in the entries buffer");
-        assert_true(entries[1].name.includes('empty.js?xhr2'), "empty.js?xhr3 is in the entries buffer");
-        assert_true(entries[2].name.includes('empty.js?xhr3'), "empty.js?xhr3 is in the entries buffer");
-};
-
-promise_test(async () => {
-    await fillUpTheBufferWithSingleResource("resources/empty.js");
-    overflowTheBuffer();
     await waitForNextTask();
-    testThatBufferContainsTheRightResources();
+    checkEntries(3);
 }, "Test that overflowing the buffer and immediately increasing its limit does not trigger the resourcetimingbufferfull event");
 </script>
 </body>
diff --git a/third_party/blink/web_tests/external/wpt/resource-timing/buffer-full-when-populate-entries.html b/third_party/blink/web_tests/external/wpt/resource-timing/buffer-full-when-populate-entries.html
index 00d2ae0..f4b1a2e 100644
--- a/third_party/blink/web_tests/external/wpt/resource-timing/buffer-full-when-populate-entries.html
+++ b/third_party/blink/web_tests/external/wpt/resource-timing/buffer-full-when-populate-entries.html
@@ -2,44 +2,28 @@
 <html>
 <head>
 <meta charset="utf-8" />
-<link rel="author" title="Intel" href="http://www.intel.com/" />
-<link rel="help" href="http://www.w3.org/TR/resource-timing/#performanceresourcetiming"/>
 <title>This test validates the functionality of onresourcetimingbufferfull in resource timing.</title>
+<link rel="author" title="Intel" href="http://www.intel.com/" />
+<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#dom-performance-onresourcetimingbufferfull"/>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
+<script src="resources/resource-loaders.js"></script>
 <script src="resources/buffer-full-utilities.js"></script>
 </head>
 <body>
 <script>
-const resource_timing_buffer_size = 2;
-let bufferFullCount = 0;
-let eventFired = false;
-setup(() => {
-    clearBufferAndSetSize(resource_timing_buffer_size);
+promise_test(async () => {
+    let bufferFullCount = 0;
     performance.addEventListener('resourcetimingbufferfull', e => {
         assert_equals(e.bubbles, false, "Event bubbles attribute is false");
         bufferFullCount++;
-        eventFired = true;
     });
-});
-
-let overflowTheBuffer = () => {
-    return new Promise(resolve => {
-        // This resource overflows the entry buffer, and goes into the secondary buffer.
-        appendScript('resources/empty_script.js', resolve);
-    });
-};
-
-let testThatBufferContainsTheRightResources = () => {
-    assert_equals(performance.getEntriesByType('resource').length, resource_timing_buffer_size, 'There should only be |bufferSize| resource entries.');
+    await fillUpTheBufferWithTwoResources();
+    // Overflow the buffer
+    await load.script(scriptResources[2]);
+    await waitForNextTask();
+    checkEntries(2);
     assert_equals(bufferFullCount, 1, 'onresourcetimingbufferfull should have been invoked once.');
-};
-
-promise_test(async () => {
-    await fillUpTheBufferWithTwoResources('resources/empty.js');
-    await overflowTheBuffer();
-    await waitForEventToFire();
-    testThatBufferContainsTheRightResources();
 }, "Test that a buffer full event does not bubble and that resourcetimingbufferfull is called only once per overflow");
 </script>
 </body>
diff --git a/third_party/blink/web_tests/external/wpt/resource-timing/resources/buffer-full-utilities.js b/third_party/blink/web_tests/external/wpt/resource-timing/resources/buffer-full-utilities.js
index e0a4f0b1..6cb1753 100644
--- a/third_party/blink/web_tests/external/wpt/resource-timing/resources/buffer-full-utilities.js
+++ b/third_party/blink/web_tests/external/wpt/resource-timing/resources/buffer-full-utilities.js
@@ -1,71 +1,75 @@
-let appendScript = (src, resolve) => {
-    const script = document.createElement('script');
-    script.type = 'text/javascript';
-    script.src = src;
-    script.onload = resolve;
-    document.body.appendChild(script);
-}
+// This script relies on resources/resource-loaders.js. Include it before in order for the below
+// methods to work properly.
 
-let xhrScript = src => {
-    var xhr = new XMLHttpRequest();
-    xhr.open("GET", src, false);
-    xhr.send(null);
-}
+// The resources used to trigger new entries.
+const scriptResources = [
+    'resources/empty.js',
+    'resources/empty_script.js',
+    'resources/empty.js?id'
+];
 
-let waitForNextTask = () => {
+const waitForNextTask = () => {
     return new Promise(resolve => {
         step_timeout(resolve, 0);
     });
 };
 
-let waitUntilConditionIsMet = cond => {
-    return new Promise(resolve => {
-        let checkCondition = function() {
-            if (cond.apply(null)) {
-                resolve();
-            } else {
-                step_timeout(checkCondition.bind(null,cond), 0);
-            }
-        }
-        step_timeout(checkCondition.bind(null, cond), 0);
-    });
+const clearBufferAndSetSize = size => {
+  performance.clearResourceTimings();
+  performance.setResourceTimingBufferSize(size);
 }
 
-let waitForEventToFire = () => {
-    return new Promise(resolve => {
-        let waitForIt = function() {
-            if (eventFired) {
-                eventFired = false;
-                resolve();
-            } else {
-                step_timeout(waitForIt, 0);
-            }
-        }
-        step_timeout(waitForIt, 0);
-    });
+const forceBufferFullEvent = async () => {
+  clearBufferAndSetSize(1);
+  return new Promise(async resolve => {
+    performance.addEventListener('resourcetimingbufferfull', resolve);
+    // Load 2 resources to ensure onresourcetimingbufferfull is fired.
+    // Load them in order in order to get the entries in that order!
+    await load.script(scriptResources[0]);
+    await load.script(scriptResources[1]);
+  });
 };
 
-let clearBufferAndSetSize = size => {
-    performance.clearResourceTimings();
-    performance.setResourceTimingBufferSize(size);
+const fillUpTheBufferWithSingleResource = async (src = scriptResources[0]) => {
+  clearBufferAndSetSize(1);
+  await load.script(src);
+};
+
+const fillUpTheBufferWithTwoResources = async () => {
+    clearBufferAndSetSize(2);
+    // Load them in order in order to get the entries in that order!
+    await load.script(scriptResources[0]);
+    await load.script(scriptResources[1]);
+};
+
+const addAssertUnreachedBufferFull = t => {
+  performance.addEventListener('resourcetimingbufferfull', t.step_func(() => {
+    assert_unreached("resourcetimingbufferfull should not fire")
+  }));
+};
+
+const checkEntries = numEntries => {
+  const entries = performance.getEntriesByType('resource');
+  assert_equals(entries.length, numEntries,
+      'Number of entries does not match the expected value.');
+  assert_true(entries[0].name.includes(scriptResources[0]),
+      scriptResources[0] + " is in the entries buffer");
+  if (entries.length > 1) {
+    assert_true(entries[1].name.includes(scriptResources[1]),
+        scriptResources[1] + " is in the entries buffer");
+  }
+  if (entries.length > 2) {
+    assert_true(entries[2].name.includes(scriptResources[2]),
+        scriptResources[2] + " is in the entries buffer");
+  }
 }
 
-let fillUpTheBufferWithSingleResource = src => {
-    return new Promise(resolve => {
-        // This resource gets buffered in the resource timing entry buffer.
-        appendScript(src, resolve);
-    });
-};
-
-let loadResource = src => {
-    return new Promise(resolve => {
-        appendScript(src, resolve);
-    });
-};
-
-let fillUpTheBufferWithTwoResources = async src => {
-    // These resources get buffered in the resource timing entry buffer.
-    await loadResource(src);
-    await loadResource(src + '?second');
-};
-
+const bufferFullFirePromise = new Promise(resolve => {
+  performance.addEventListener('resourcetimingbufferfull', async () => {
+    // Wait for the next task just to ensure that all bufferfull events have fired, and to ensure
+    // that the secondary buffer is copied (as this is an event, there may be microtask checkpoints
+    // right after running an event handler).
+    await waitForNextTask();
+    resolve();
+  });
+});
diff --git a/third_party/blink/web_tests/external/wpt/webaudio/the-audio-api/the-audioparam-interface/audioparam-cancel-and-hold.html b/third_party/blink/web_tests/external/wpt/webaudio/the-audio-api/the-audioparam-interface/audioparam-cancel-and-hold.html
index 2b57b4d..0a8e7a7 100644
--- a/third_party/blink/web_tests/external/wpt/webaudio/the-audio-api/the-audioparam-interface/audioparam-cancel-and-hold.html
+++ b/third_party/blink/web_tests/external/wpt/webaudio/the-audio-api/the-audioparam-interface/audioparam-cancel-and-hold.html
@@ -59,7 +59,7 @@
           function(task, should) {
             cancelTest(should, linearRampTest('linearRampToValueAtTime'), {
               valueThreshold: 8.3998e-5,
-              curveThreshold: 0
+              curveThreshold: 5.9605e-5
             }).then(task.done.bind(task));
           });
 
@@ -247,7 +247,7 @@
                 summary: 'Initial setTargetAtTime',
               };
             }, {
-              valueThreshold: 1.2320e-6,
+              valueThreshold: 3.1210e-6,
               curveThreshold: 0
             }).then(task.done.bind(task));
           });
@@ -269,7 +269,7 @@
             cancelTest(
                 should,
                 linearRampTest('Post cancellation linearRampToValueAtTime'),
-                {valueThreshold: 8.3998e-5, curveThreshold: 0},
+                {valueThreshold: 8.3998e-5, curveThreshold: 5.9605e-8},
                 function(g, cancelTime, expectedConstant) {
                   // Schedule the linear ramp on g[0], and do the same for g[2],
                   // using the starting point given by expectedConstant.
@@ -298,7 +298,7 @@
             cancelTest(
                 should,
                 linearRampTest('Post cancel exponentialRampToValueAtTime'),
-                {valueThreshold: 8.3998e-5, curveThreshold: 0},
+                {valueThreshold: 8.3998e-5, curveThreshold: 5.9605e-8},
                 function(g, cancelTime, expectedConstant) {
                   // Schedule the exponential ramp on g[0], and do the same for
                   // g[2], using the starting point given by expectedConstant.
@@ -320,7 +320,7 @@
         // Then schedule a setValueCurve after the cancellation.
         cancelTest(
             should, linearRampTest('Post cancel setValueCurveAtTime'),
-            {valueThreshold: 8.3998e-5, curveThreshold: 0},
+            {valueThreshold: 8.3998e-5, curveThreshold: 5.9605e-8},
             function(g, cancelTime, expectedConstant) {
               // Schedule the exponential ramp on g[0], and do the same for
               // g[2], using the starting point given by expectedConstant.
@@ -345,7 +345,7 @@
         // Then schedule a setTarget after the cancellation.
         cancelTest(
             should, linearRampTest('Post cancel setTargetAtTime'),
-            {valueThreshold: 8.3998e-5, curveThreshold: 0},
+            {valueThreshold: 8.3998e-5, curveThreshold: 5.9605e-8},
             function(g, cancelTime, expectedConstant) {
               // Schedule the exponential ramp on g[0], and do the same for
               // g[2], using the starting point given by expectedConstant.
@@ -370,7 +370,7 @@
         // Then schedule a setTarget after the cancellation.
         cancelTest(
             should, linearRampTest('Post cancel setValueAtTime'),
-            {valueThreshold: 8.3998e-5, curveThreshold: 0},
+            {valueThreshold: 8.3998e-5, curveThreshold: 5.9605e-8},
             function(g, cancelTime, expectedConstant) {
               // Schedule the exponential ramp on g[0], and do the same for
               // g[2], using the starting point given by expectedConstant.
@@ -494,7 +494,7 @@
           cancelTest2(
             should,
             linearRampTest('1st linearRamp'),
-            {valueThreshold: 0, curveThreshold: 0},
+            {valueThreshold: 0, curveThreshold: 5.9605e-8},
             (g, cancelTime, expectedConstant, cancelTime2) => {
               // Ramp from first cancel time to the end will be cancelled at
               // second cancel time.
diff --git a/third_party/blink/web_tests/external/wpt/webaudio/the-audio-api/the-audioparam-interface/event-insertion.html b/third_party/blink/web_tests/external/wpt/webaudio/the-audio-api/the-audioparam-interface/event-insertion.html
index 2eef895..b846f98 100644
--- a/third_party/blink/web_tests/external/wpt/webaudio/the-audio-api/the-audioparam-interface/event-insertion.html
+++ b/third_party/blink/web_tests/external/wpt/webaudio/the-audio-api/the-audioparam-interface/event-insertion.html
@@ -206,7 +206,7 @@
                           `At time ${eventTime} (frame ${
                                                          eventFrame
                                                        }) and later`)
-                      .beCloseToArray(expected, {relativeThreshold: 1.7807e-7});
+                      .beCloseToArray(expected, {relativeThreshold: 2.6694e-7});
                 })
                 .then(() => task.done());
           });
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/page/reload-with-oopifs-crash-expected.txt b/third_party/blink/web_tests/http/tests/inspector-protocol/page/reload-with-oopifs-crash-expected.txt
new file mode 100644
index 0000000..596ea66
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/page/reload-with-oopifs-crash-expected.txt
@@ -0,0 +1,3 @@
+Tests that reload of an OOPIF page doesn't cause a crash
+PASSED: alive and kicking!
+
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/page/reload-with-oopifs-crash.js b/third_party/blink/web_tests/http/tests/inspector-protocol/page/reload-with-oopifs-crash.js
new file mode 100644
index 0000000..e9709d2
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/page/reload-with-oopifs-crash.js
@@ -0,0 +1,30 @@
+(async function(testRunner) {
+  const {session, dp} = await testRunner.startBlank(
+      `Tests that reload of an OOPIF page doesn't cause a crash`);
+
+  dp.Target.setAutoAttach({autoAttach: true, flatten: true, waitForDebuggerOnStart: false});
+  dp.Network.enable();
+
+  dp.Page.navigate({url: 'http://localhost:8000/inspector-protocol/resources/iframe-navigation.html'});
+
+  const oopifRequests = new Set();
+  dp.Network.onRequestWillBeSent(event => {
+    const params = event.params;
+    if (/oopif/.test(params.request.url))
+      oopifRequests.add(params.requestId);
+  });
+  dp.Network.onLoadingFinished(event => {
+    if (!oopifRequests.has(event.params.requestId))
+      return;
+    // Site isolation disabled, nothing to test here, bail out.
+    testRunner.log('PASSED: alive and kicking!');
+    testRunner.completeTest();
+  });
+  const attachedEvent = await dp.Target.onceAttachedToTarget();
+  session.evaluate(`location.reload()`);
+  await dp.Target.onceDetachedFromTarget();
+  await dp.Target.onceAttachedToTarget();
+
+  testRunner.log('PASSED: alive and kicking!');
+  testRunner.completeTest();
+})
\ No newline at end of file
diff --git a/third_party/blink/web_tests/webaudio/AudioParam/audioparam-setTargetAtTime-limit.html b/third_party/blink/web_tests/webaudio/AudioParam/audioparam-setTargetAtTime-limit.html
index bf36d71..23d2938ea 100644
--- a/third_party/blink/web_tests/webaudio/AudioParam/audioparam-setTargetAtTime-limit.html
+++ b/third_party/blink/web_tests/webaudio/AudioParam/audioparam-setTargetAtTime-limit.html
@@ -71,7 +71,7 @@
           timeConstant: timeConstant,
           eps: limitThreshold,
           // Experimentally determined
-          threshold: 4.7470e-8,
+          threshold: 1.2591e-7,
           tailThreshold: {absoluteThreshold: 2.3310e-5}
         }).then(() => task.done());
       });
@@ -104,7 +104,7 @@
                duration, 0, targetValue, context.sampleRate, timeConstant);
 
              should(actual, 'Output')
-              .beCloseToArray(expected, {absoluteThreshold: 9.8172e-7});
+              .beCloseToArray(expected, {absoluteThreshold: 5.0098e-6});
           })
           .then(() => task.done());
       });
diff --git a/tools/json_comment_eater/json_comment_eater.py b/tools/json_comment_eater/json_comment_eater.py
index 17a1525..8506772 100755
--- a/tools/json_comment_eater/json_comment_eater.py
+++ b/tools/json_comment_eater/json_comment_eater.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
diff --git a/tools/json_comment_eater/json_comment_eater_test.py b/tools/json_comment_eater/json_comment_eater_test.py
index ae03474..d053faf 100755
--- a/tools/json_comment_eater/json_comment_eater_test.py
+++ b/tools/json_comment_eater/json_comment_eater_test.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 # Copyright 2013 The Chromium Authors. All rights reserved.
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
diff --git a/tools/json_schema_compiler/PRESUBMIT.py b/tools/json_schema_compiler/PRESUBMIT.py
index 5c8a221a..ef37b32 100644
--- a/tools/json_schema_compiler/PRESUBMIT.py
+++ b/tools/json_schema_compiler/PRESUBMIT.py
@@ -9,6 +9,7 @@
 """
 
 FILE_PATTERN = [ r'.+_test.py$' ]
+USE_PYTHON3 = True
 
 def CheckChangeOnUpload(input_api, output_api):
   return input_api.canned_checks.RunUnitTestsInDirectory(
diff --git a/tools/json_schema_compiler/code_test.py b/tools/json_schema_compiler/code_test.py
index 6d54ecc..151a6ea1 100755
--- a/tools/json_schema_compiler/code_test.py
+++ b/tools/json_schema_compiler/code_test.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
@@ -10,7 +10,7 @@
   def testAppend(self):
     c = Code()
     c.Append('line')
-    self.assertEquals('line', c.Render())
+    self.assertEqual('line', c.Render())
 
   def testBlock(self):
     c = Code()
@@ -24,7 +24,7 @@
         .Append('inner')
       .Eblock('out')
     )
-    self.assertEquals(
+    self.assertEqual(
       'line\n'
       'sblock\n'
       '  inner\n'
@@ -59,9 +59,9 @@
     d = Code()
     a = Code()
     a.Concat(d)
-    self.assertEquals('', a.Render())
+    self.assertEqual('', a.Render())
     a.Concat(c)
-    self.assertEquals(
+    self.assertEqual(
       '1\n'
       '  2\n'
       '    2\n'
@@ -85,11 +85,11 @@
     c = Code()
     c.Append('%(var1)s %(var2)s %(var1)s')
     c.Substitute({'var1': 'one', 'var2': 'two'})
-    self.assertEquals('one two one', c.Render())
+    self.assertEqual('one two one', c.Render())
     c.Append('%(var1)s %(var2)s %(var3)s')
     c.Append('%(var2)s %(var1)s %(var3)s')
     c.Substitute({'var1': 'one', 'var2': 'two', 'var3': 'three'})
-    self.assertEquals(
+    self.assertEqual(
         'one two one\n'
         'one two three\n'
         'two one three',
@@ -121,7 +121,7 @@
         'that is, using a different word, length.')
     c = Code()
     c.Comment(long_comment)
-    self.assertEquals(
+    self.assertEqual(
         '// This comment is ninety one characters '
         'in longness, that is, using a different\n'
         '// word, length.',
@@ -131,7 +131,7 @@
     c.Comment(long_comment)
     c.Eblock('eblock')
     c.Comment(long_comment)
-    self.assertEquals(
+    self.assertEqual(
         'sblock\n'
         '  // This comment is ninety one characters '
         'in longness, that is, using a\n'
@@ -147,7 +147,7 @@
     c.Comment('xxx')
     c.Comment(long_word)
     c.Comment('xxx')
-    self.assertEquals(
+    self.assertEqual(
         '// xxx\n'
         '// ' + 'x' * 100 + '\n'
         '// xxx',
@@ -155,7 +155,7 @@
     c = Code(indent_size=2, comment_length=40)
     c.Comment('Pretend this is a Closure Compiler style comment, which should '
         'both wrap and indent', comment_prefix=' * ', wrap_indent=4)
-    self.assertEquals(
+    self.assertEqual(
         ' * Pretend this is a Closure Compiler\n'
         ' *     style comment, which should both\n'
         ' *     wrap and indent',
@@ -165,11 +165,11 @@
     c = Code()
     c.Comment('20% of 80%s')
     c.Substitute({})
-    self.assertEquals('// 20% of 80%s', c.Render())
+    self.assertEqual('// 20% of 80%s', c.Render())
     d = Code()
     d.Append('90')
     d.Concat(c)
-    self.assertEquals('90\n'
+    self.assertEqual('90\n'
         '// 20% of 80%s',
         d.Render())
 
@@ -202,14 +202,14 @@
     d = Code()
     d.Append('And this.')
     c.Concat(d, new_line=False)
-    self.assertEquals('This is a line.This too.And this.', c.Render())
+    self.assertEqual('This is a line.This too.And this.', c.Render())
     c = Code()
     c.Append('This is a')
     c.Comment(' spectacular 80-character line thingy ' +
                   'that fits wonderfully everywhere.',
               comment_prefix='',
               new_line=False)
-    self.assertEquals('This is a spectacular 80-character line thingy that ' +
+    self.assertEqual('This is a spectacular 80-character line thingy that ' +
                           'fits wonderfully everywhere.',
                       c.Render())
 
diff --git a/tools/json_schema_compiler/compiler.py b/tools/json_schema_compiler/compiler.py
index 216146b..6e69905 100755
--- a/tools/json_schema_compiler/compiler.py
+++ b/tools/json_schema_compiler/compiler.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
diff --git a/tools/json_schema_compiler/cpp_bundle_generator_test.py b/tools/json_schema_compiler/cpp_bundle_generator_test.py
index 172fecae6..69fa42cf 100755
--- a/tools/json_schema_compiler/cpp_bundle_generator_test.py
+++ b/tools/json_schema_compiler/cpp_bundle_generator_test.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 # Copyright 2016 The Chromium Authors. All rights reserved.
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
@@ -21,20 +21,20 @@
 
 def _getPlatformIfdefs(cpp_bundle_generator, model):
   return cpp_bundle_generator._GetPlatformIfdefs(
-      model.namespaces.values()[0].functions.values()[0])
+      list(list(model.namespaces.values())[0].functions.values())[0])
 
 class CppBundleGeneratorTest(unittest.TestCase):
   def testIfDefsForWinLinux(self):
     cpp_bundle_generator, model = _createCppBundleGenerator(
         'test/function_platform_win_linux.json')
-    self.assertEquals(
+    self.assertEqual(
         'defined(OS_WIN) || (defined(OS_LINUX) && !defined(OS_CHROMEOS))',
         _getPlatformIfdefs(cpp_bundle_generator, model))
 
   def testIfDefsForAll(self):
     cpp_bundle_generator, model = _createCppBundleGenerator(
         'test/function_platform_all.json')
-    self.assertEquals(
+    self.assertEqual(
         'defined(OS_WIN) || (defined(OS_LINUX) && !defined(OS_CHROMEOS)) || '
         '(defined(OS_CHROMEOS) && !BUILDFLAG(IS_CHROMEOS_LACROS))',
         _getPlatformIfdefs(cpp_bundle_generator, model))
@@ -42,7 +42,7 @@
   def testIfDefsForChromeOS(self):
     cpp_bundle_generator, model = _createCppBundleGenerator(
         'test/function_platform_chromeos.json')
-    self.assertEquals('(defined(OS_CHROMEOS) && '
+    self.assertEqual('(defined(OS_CHROMEOS) && '
                       '!BUILDFLAG(IS_CHROMEOS_LACROS))',
                       _getPlatformIfdefs(cpp_bundle_generator, model))
 
diff --git a/tools/json_schema_compiler/cpp_type_generator.py b/tools/json_schema_compiler/cpp_type_generator.py
index 4e394f1d..4cae3c11 100644
--- a/tools/json_schema_compiler/cpp_type_generator.py
+++ b/tools/json_schema_compiler/cpp_type_generator.py
@@ -32,7 +32,7 @@
     """
     self._default_namespace = default_namespace
     if self._default_namespace is None:
-      self._default_namespace = model.namespaces.values()[0]
+      self._default_namespace = list(model.namespaces.values())[0]
     self._namespace_resolver = namespace_resolver
 
   def GetEnumNoneValue(self, type_):
diff --git a/tools/json_schema_compiler/cpp_type_generator_test.py b/tools/json_schema_compiler/cpp_type_generator_test.py
index 3aaa575a..8ac7f7dc2 100755
--- a/tools/json_schema_compiler/cpp_type_generator_test.py
+++ b/tools/json_schema_compiler/cpp_type_generator_test.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
@@ -86,10 +86,10 @@
                    environment=CppNamespaceEnvironment('%(namespace)s'))
     manager = CppTypeGenerator(m, _FakeSchemaLoader(m))
 
-    self.assertEquals('', manager.GenerateIncludes().Render())
-    self.assertEquals('#include "path/to/tabs.h"',
+    self.assertEqual('', manager.GenerateIncludes().Render())
+    self.assertEqual('#include "path/to/tabs.h"',
                       manager.GenerateIncludes(include_soft=True).Render())
-    self.assertEquals(
+    self.assertEqual(
         'namespace tabs {\n'
         'struct Tab;\n'
         '}  // namespace tabs',
@@ -105,7 +105,7 @@
                    environment=CppNamespaceEnvironment(
                        'foo::bar::%(namespace)s'))
     manager = CppTypeGenerator(m, _FakeSchemaLoader(m))
-    self.assertEquals(
+    self.assertEqual(
         'namespace foo {\n'
         'namespace bar {\n'
         'namespace tabs {\n'
@@ -116,12 +116,12 @@
         manager.GenerateForwardDeclarations().Render())
     manager = CppTypeGenerator(self.models.get('permissions'),
                                _FakeSchemaLoader(m))
-    self.assertEquals('', manager.GenerateIncludes().Render())
-    self.assertEquals('', manager.GenerateIncludes().Render())
-    self.assertEquals('', manager.GenerateForwardDeclarations().Render())
+    self.assertEqual('', manager.GenerateIncludes().Render())
+    self.assertEqual('', manager.GenerateIncludes().Render())
+    self.assertEqual('', manager.GenerateForwardDeclarations().Render())
     manager = CppTypeGenerator(self.models.get('content_settings'),
                                _FakeSchemaLoader(m))
-    self.assertEquals('', manager.GenerateIncludes().Render())
+    self.assertEqual('', manager.GenerateIncludes().Render())
 
   def testGenerateIncludesAndForwardDeclarationsDependencies(self):
     m = model.Model()
@@ -134,54 +134,54 @@
     manager = CppTypeGenerator(m,
                                _FakeSchemaLoader(m),
                                default_namespace=dependency_tester)
-    self.assertEquals('#include "path/to/browser_action.h"\n'
+    self.assertEqual('#include "path/to/browser_action.h"\n'
                       '#include "path/to/font_settings.h"',
                       manager.GenerateIncludes().Render())
-    self.assertEquals('', manager.GenerateForwardDeclarations().Render())
+    self.assertEqual('', manager.GenerateForwardDeclarations().Render())
 
   def testGetCppTypeSimple(self):
     manager = CppTypeGenerator(self.models.get('tabs'), _FakeSchemaLoader(None))
-    self.assertEquals(
+    self.assertEqual(
         'int',
         manager.GetCppType(self.tabs.types['Tab'].properties['id'].type_))
-    self.assertEquals(
+    self.assertEqual(
         'std::string',
         manager.GetCppType(self.tabs.types['Tab'].properties['status'].type_))
-    self.assertEquals(
+    self.assertEqual(
         'bool',
         manager.GetCppType(self.tabs.types['Tab'].properties['selected'].type_))
 
   def testStringAsType(self):
     manager = CppTypeGenerator(self.models.get('font_settings'),
                                _FakeSchemaLoader(None))
-    self.assertEquals(
+    self.assertEqual(
         'std::string',
         manager.GetCppType(self.font_settings.types['FakeStringType']))
 
   def testArrayAsType(self):
     manager = CppTypeGenerator(self.models.get('browser_action'),
                                _FakeSchemaLoader(None))
-    self.assertEquals(
+    self.assertEqual(
         'std::vector<int>',
         manager.GetCppType(self.browser_action.types['ColorArray']))
 
   def testGetCppTypeArray(self):
     manager = CppTypeGenerator(self.models.get('windows'),
                                 _FakeSchemaLoader(None))
-    self.assertEquals(
+    self.assertEqual(
         'std::vector<Window>',
         manager.GetCppType(
             self.windows.functions['getAll'].returns_async.params[0].type_))
     manager = CppTypeGenerator(self.models.get('permissions'),
                                _FakeSchemaLoader(None))
-    self.assertEquals(
+    self.assertEqual(
         'std::vector<std::string>',
         manager.GetCppType(
             self.permissions.types['Permissions'].properties['origins'].type_))
 
     manager = CppTypeGenerator(self.models.get('objects_movable'),
                                _FakeSchemaLoader(None))
-    self.assertEquals(
+    self.assertEqual(
         'std::vector<MovablePod>',
         manager.GetCppType(
             self.objects_movable.types['MovableParent'].
@@ -189,7 +189,7 @@
 
   def testGetCppTypeLocalRef(self):
     manager = CppTypeGenerator(self.models.get('tabs'), _FakeSchemaLoader(None))
-    self.assertEquals(
+    self.assertEqual(
         'Tab',
         manager.GetCppType(
             self.tabs.functions['get'].returns_async.params[0].type_))
@@ -203,7 +203,7 @@
                    'path/to/tabs.json',
                    environment=CppNamespaceEnvironment('%(namespace)s'))
     manager = CppTypeGenerator(m, _FakeSchemaLoader(m))
-    self.assertEquals(
+    self.assertEqual(
         'std::vector<tabs::Tab>',
         manager.GetCppType(
             self.windows.types['Window'].properties['tabs'].type_))
@@ -211,15 +211,15 @@
   def testGetCppTypeWithPadForGeneric(self):
     manager = CppTypeGenerator(self.models.get('permissions'),
                                _FakeSchemaLoader(None))
-    self.assertEquals('std::vector<std::string>',
+    self.assertEqual('std::vector<std::string>',
         manager.GetCppType(
             self.permissions.types['Permissions'].properties['origins'].type_,
             is_in_container=False))
-    self.assertEquals('std::vector<std::string>',
+    self.assertEqual('std::vector<std::string>',
         manager.GetCppType(
             self.permissions.types['Permissions'].properties['origins'].type_,
             is_in_container=True))
-    self.assertEquals(
+    self.assertEqual(
         'bool',
         manager.GetCppType(self.permissions.functions['contains'].returns_async.
                            params[0].type_, is_in_container=True))
@@ -239,7 +239,7 @@
     manager = CppTypeGenerator(self.models.get('crossref_enums'),
                                _FakeSchemaLoader(m))
 
-    self.assertEquals('#include "path/to/simple_api.h"',
+    self.assertEqual('#include "path/to/simple_api.h"',
                       manager.GenerateIncludes().Render())
 
   def testHardIncludesForEnumArrays(self):
@@ -257,7 +257,7 @@
     manager = CppTypeGenerator(self.models.get('crossref_enums_array'),
                                _FakeSchemaLoader(m))
 
-    self.assertEquals('#include "path/to/simple_api.h"',
+    self.assertEqual('#include "path/to/simple_api.h"',
                       manager.GenerateIncludes().Render())
 
 if __name__ == '__main__':
diff --git a/tools/json_schema_compiler/cpp_util_test.py b/tools/json_schema_compiler/cpp_util_test.py
index eef4c554b..e913ecf5 100755
--- a/tools/json_schema_compiler/cpp_util_test.py
+++ b/tools/json_schema_compiler/cpp_util_test.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
@@ -10,44 +10,44 @@
 
 class CppUtilTest(unittest.TestCase):
   def testClassname(self):
-    self.assertEquals('Permissions', Classname('permissions'))
-    self.assertEquals('UpdateAllTheThings',
+    self.assertEqual('Permissions', Classname('permissions'))
+    self.assertEqual('UpdateAllTheThings',
         Classname('updateAllTheThings'))
-    self.assertEquals('Aa_Bb_Cc', Classname('aa.bb.cc'))
+    self.assertEqual('Aa_Bb_Cc', Classname('aa.bb.cc'))
 
   def testNamespaceDeclaration(self):
-    self.assertEquals('namespace foo {',
+    self.assertEqual('namespace foo {',
                       OpenNamespace('foo').Render())
-    self.assertEquals('}  // namespace foo',
+    self.assertEqual('}  // namespace foo',
                       CloseNamespace('foo').Render())
 
-    self.assertEquals(
+    self.assertEqual(
         'namespace extensions {\n'
         'namespace foo {',
         OpenNamespace('extensions::foo').Render())
-    self.assertEquals(
+    self.assertEqual(
         '}  // namespace foo\n'
         '}  // namespace extensions',
         CloseNamespace('extensions::foo').Render())
 
-    self.assertEquals(
+    self.assertEqual(
         'namespace extensions {\n'
         'namespace gen {\n'
         'namespace api {',
         OpenNamespace('extensions::gen::api').Render())
-    self.assertEquals(
+    self.assertEqual(
         '}  // namespace api\n'
         '}  // namespace gen\n'
         '}  // namespace extensions',
         CloseNamespace('extensions::gen::api').Render())
 
-    self.assertEquals(
+    self.assertEqual(
         'namespace extensions {\n'
         'namespace gen {\n'
         'namespace api {\n'
         'namespace foo {',
         OpenNamespace('extensions::gen::api::foo').Render())
-    self.assertEquals(
+    self.assertEqual(
         '}  // namespace foo\n'
         '}  // namespace api\n'
         '}  // namespace gen\n'
@@ -55,8 +55,8 @@
         CloseNamespace('extensions::gen::api::foo').Render())
 
   def testGenerateIfndefName(self):
-    self.assertEquals('FOO_BAR_BAZ_H__', GenerateIfndefName('foo\\bar\\baz.h'))
-    self.assertEquals('FOO_BAR_BAZ_H__', GenerateIfndefName('foo/bar/baz.h'))
+    self.assertEqual('FOO_BAR_BAZ_H__', GenerateIfndefName('foo\\bar\\baz.h'))
+    self.assertEqual('FOO_BAR_BAZ_H__', GenerateIfndefName('foo/bar/baz.h'))
 
 
 if __name__ == '__main__':
diff --git a/tools/json_schema_compiler/feature_compiler_test.py b/tools/json_schema_compiler/feature_compiler_test.py
index ff7231d0..40a7f851 100755
--- a/tools/json_schema_compiler/feature_compiler_test.py
+++ b/tools/json_schema_compiler/feature_compiler_test.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 # Copyright 2015 The Chromium Authors. All rights reserved.
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
diff --git a/tools/json_schema_compiler/features_compiler.py b/tools/json_schema_compiler/features_compiler.py
index 1b744e2..ef85940 100755
--- a/tools/json_schema_compiler/features_compiler.py
+++ b/tools/json_schema_compiler/features_compiler.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 # Copyright 2013 The Chromium Authors. All rights reserved.
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
diff --git a/tools/json_schema_compiler/idl_schema.py b/tools/json_schema_compiler/idl_schema.py
index 1ab54c8..029e83c 100755
--- a/tools/json_schema_compiler/idl_schema.py
+++ b/tools/json_schema_compiler/idl_schema.py
@@ -1,4 +1,4 @@
-#! /usr/bin/env python
+#!/usr/bin/env python3
 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
diff --git a/tools/json_schema_compiler/idl_schema_test.py b/tools/json_schema_compiler/idl_schema_test.py
index e98a20a..915d4629a 100755
--- a/tools/json_schema_compiler/idl_schema_test.py
+++ b/tools/json_schema_compiler/idl_schema_test.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
@@ -34,30 +34,30 @@
 class IdlSchemaTest(unittest.TestCase):
   def setUp(self):
     loaded = idl_schema.Load('test/idl_basics.idl')
-    self.assertEquals(1, len(loaded))
-    self.assertEquals('idl_basics', loaded[0]['namespace'])
+    self.assertEqual(1, len(loaded))
+    self.assertEqual('idl_basics', loaded[0]['namespace'])
     self.idl_basics = loaded[0]
     self.maxDiff = None
 
   def testSimpleCallbacks(self):
     schema = self.idl_basics
     expected = [{'type': 'function', 'name': 'cb', 'parameters':[]}]
-    self.assertEquals(expected, getParams(schema, 'function4'))
+    self.assertEqual(expected, getParams(schema, 'function4'))
 
     expected = [{'type': 'function', 'name': 'cb',
                  'parameters':[{'name': 'x', 'type': 'integer'}]}]
-    self.assertEquals(expected, getParams(schema, 'function5'))
+    self.assertEqual(expected, getParams(schema, 'function5'))
 
     expected = [{'type': 'function', 'name': 'cb',
                  'parameters':[{'name': 'arg', '$ref': 'MyType1'}]}]
-    self.assertEquals(expected, getParams(schema, 'function6'))
+    self.assertEqual(expected, getParams(schema, 'function6'))
 
   def testCallbackWithArrayArgument(self):
     schema = self.idl_basics
     expected = [{'type': 'function', 'name': 'cb',
                  'parameters':[{'name': 'arg', 'type': 'array',
                                 'items':{'$ref': 'MyType2'}}]}]
-    self.assertEquals(expected, getParams(schema, 'function12'))
+    self.assertEqual(expected, getParams(schema, 'function12'))
 
 
   def testArrayOfCallbacks(self):
@@ -65,10 +65,10 @@
     expected = [{'type': 'array', 'name': 'callbacks',
                  'items':{'type': 'function', 'name': 'MyCallback',
                           'parameters':[{'type': 'integer', 'name': 'x'}]}}]
-    self.assertEquals(expected, getParams(schema, 'whatever'))
+    self.assertEqual(expected, getParams(schema, 'whatever'))
 
   def testLegalValues(self):
-    self.assertEquals({
+    self.assertEqual({
         'x': {'name': 'x', 'type': 'integer', 'enum': [1,2],
               'description': 'This comment tests "double-quotes".',
               'jsexterns': None},
@@ -80,9 +80,9 @@
       getType(self.idl_basics, 'MyType1')['properties'])
 
   def testMemberOrdering(self):
-    self.assertEquals(
+    self.assertEqual(
         ['x', 'y', 'z', 'a', 'b', 'c'],
-        getType(self.idl_basics, 'MyType1')['properties'].keys())
+        list(getType(self.idl_basics, 'MyType1')['properties'].keys()))
 
   def testEnum(self):
     schema = self.idl_basics
@@ -90,36 +90,36 @@
                          {'name': 'name2'}],
                 'description': 'Enum description',
                 'type': 'string', 'id': 'EnumType'}
-    self.assertEquals(expected, getType(schema, expected['id']))
+    self.assertEqual(expected, getType(schema, expected['id']))
 
     expected = [{'name': 'type', '$ref': 'EnumType'},
                 {'type': 'function', 'name': 'cb',
                   'parameters':[{'name': 'type', '$ref': 'EnumType'}]}]
-    self.assertEquals(expected, getParams(schema, 'function13'))
+    self.assertEqual(expected, getParams(schema, 'function13'))
 
     expected = [{'items': {'$ref': 'EnumType'}, 'name': 'types',
                  'type': 'array'}]
-    self.assertEquals(expected, getParams(schema, 'function14'))
+    self.assertEqual(expected, getParams(schema, 'function14'))
 
   def testScopedArguments(self):
     schema = self.idl_basics
     expected = [{'name': 'value', '$ref': 'idl_other_namespace.SomeType'}]
-    self.assertEquals(expected, getParams(schema, 'function20'))
+    self.assertEqual(expected, getParams(schema, 'function20'))
 
     expected = [{'items': {'$ref': 'idl_other_namespace.SomeType'},
                  'name': 'values',
                  'type': 'array'}]
-    self.assertEquals(expected, getParams(schema, 'function21'))
+    self.assertEqual(expected, getParams(schema, 'function21'))
 
     expected = [{'name': 'value',
                  '$ref': 'idl_other_namespace.sub_namespace.AnotherType'}]
-    self.assertEquals(expected, getParams(schema, 'function22'))
+    self.assertEqual(expected, getParams(schema, 'function22'))
 
     expected = [{'items': {'$ref': 'idl_other_namespace.sub_namespace.'
                                    'AnotherType'},
                  'name': 'values',
                  'type': 'array'}]
-    self.assertEquals(expected, getParams(schema, 'function23'))
+    self.assertEqual(expected, getParams(schema, 'function23'))
 
   def testNoCompile(self):
     schema = self.idl_basics
@@ -150,114 +150,114 @@
         'id': 'EnumTypeWithNoDocValue',
         'description': ''
     }
-    self.assertEquals(expected, getType(schema, expected['id']))
+    self.assertEqual(expected, getType(schema, expected['id']))
 
   def testInternalNamespace(self):
     idl_basics  = self.idl_basics
-    self.assertEquals('idl_basics', idl_basics['namespace'])
+    self.assertEqual('idl_basics', idl_basics['namespace'])
     self.assertTrue(idl_basics['internal'])
     self.assertFalse(idl_basics['nodoc'])
 
   def testReturnTypes(self):
     schema = self.idl_basics
-    self.assertEquals({'name': 'function24', 'type': 'integer'},
+    self.assertEqual({'name': 'function24', 'type': 'integer'},
                       getReturns(schema, 'function24'))
-    self.assertEquals({'name': 'function25', '$ref': 'MyType1',
+    self.assertEqual({'name': 'function25', '$ref': 'MyType1',
                        'optional': True},
                       getReturns(schema, 'function25'))
-    self.assertEquals({'name': 'function26', 'type': 'array',
+    self.assertEqual({'name': 'function26', 'type': 'array',
                        'items': {'$ref': 'MyType1'}},
                       getReturns(schema, 'function26'))
-    self.assertEquals({'name': 'function27', '$ref': 'EnumType',
+    self.assertEqual({'name': 'function27', '$ref': 'EnumType',
                        'optional': True},
                       getReturns(schema, 'function27'))
-    self.assertEquals({'name': 'function28', 'type': 'array',
+    self.assertEqual({'name': 'function28', 'type': 'array',
                        'items': {'$ref': 'EnumType'}},
                       getReturns(schema, 'function28'))
-    self.assertEquals({'name': 'function29', '$ref':
+    self.assertEqual({'name': 'function29', '$ref':
                        'idl_other_namespace.SomeType',
                        'optional': True},
                       getReturns(schema, 'function29'))
-    self.assertEquals({'name': 'function30', 'type': 'array',
+    self.assertEqual({'name': 'function30', 'type': 'array',
                        'items': {'$ref': 'idl_other_namespace.SomeType'}},
                       getReturns(schema, 'function30'))
 
   def testChromeOSPlatformsNamespace(self):
     schema = idl_schema.Load('test/idl_namespace_chromeos.idl')[0]
-    self.assertEquals('idl_namespace_chromeos', schema['namespace'])
+    self.assertEqual('idl_namespace_chromeos', schema['namespace'])
     expected = ['chromeos']
-    self.assertEquals(expected, schema['platforms'])
+    self.assertEqual(expected, schema['platforms'])
 
   def testAllPlatformsNamespace(self):
     schema = idl_schema.Load('test/idl_namespace_all_platforms.idl')[0]
-    self.assertEquals('idl_namespace_all_platforms', schema['namespace'])
+    self.assertEqual('idl_namespace_all_platforms', schema['namespace'])
     expected = ['chromeos', 'linux', 'mac', 'win']
-    self.assertEquals(expected, schema['platforms'])
+    self.assertEqual(expected, schema['platforms'])
 
   def testNonSpecificPlatformsNamespace(self):
     schema = idl_schema.Load('test/idl_namespace_non_specific_platforms.idl')[0]
-    self.assertEquals('idl_namespace_non_specific_platforms',
+    self.assertEqual('idl_namespace_non_specific_platforms',
                       schema['namespace'])
     expected = None
-    self.assertEquals(expected, schema['platforms'])
+    self.assertEqual(expected, schema['platforms'])
 
   def testGenerateErrorMessages(self):
     schema = idl_schema.Load('test/idl_generate_error_messages.idl')[0]
-    self.assertEquals('idl_generate_error_messages', schema['namespace'])
+    self.assertEqual('idl_generate_error_messages', schema['namespace'])
     self.assertTrue(schema['compiler_options'].get('generate_error_messages',
                     False))
 
     schema = idl_schema.Load('test/idl_basics.idl')[0]
-    self.assertEquals('idl_basics', schema['namespace'])
+    self.assertEqual('idl_basics', schema['namespace'])
     self.assertFalse(schema['compiler_options'].get('generate_error_messages',
                      False))
 
   def testSpecificImplementNamespace(self):
     schema = idl_schema.Load('test/idl_namespace_specific_implement.idl')[0]
-    self.assertEquals('idl_namespace_specific_implement',
+    self.assertEqual('idl_namespace_specific_implement',
                       schema['namespace'])
     expected = 'idl_namespace_specific_implement.idl'
-    self.assertEquals(expected, schema['compiler_options']['implemented_in'])
+    self.assertEqual(expected, schema['compiler_options']['implemented_in'])
 
   def testSpecificImplementOnChromeOSNamespace(self):
     schema = idl_schema.Load(
         'test/idl_namespace_specific_implement_chromeos.idl')[0]
-    self.assertEquals('idl_namespace_specific_implement_chromeos',
+    self.assertEqual('idl_namespace_specific_implement_chromeos',
                       schema['namespace'])
     expected_implemented_path = 'idl_namespace_specific_implement_chromeos.idl'
     expected_platform = ['chromeos']
-    self.assertEquals(expected_implemented_path,
+    self.assertEqual(expected_implemented_path,
                       schema['compiler_options']['implemented_in'])
-    self.assertEquals(expected_platform, schema['platforms'])
+    self.assertEqual(expected_platform, schema['platforms'])
 
   def testCallbackComment(self):
     schema = self.idl_basics
-    self.assertEquals('A comment on a callback.',
+    self.assertEqual('A comment on a callback.',
                       getParams(schema, 'function16')[0]['description'])
-    self.assertEquals(
+    self.assertEqual(
         'A parameter.',
         getParams(schema, 'function16')[0]['parameters'][0]['description'])
-    self.assertEquals(
+    self.assertEqual(
         'Just a parameter comment, with no comment on the callback.',
         getParams(schema, 'function17')[0]['parameters'][0]['description'])
-    self.assertEquals(
+    self.assertEqual(
         'Override callback comment.',
         getParams(schema, 'function18')[0]['description'])
 
   def testFunctionComment(self):
     schema = self.idl_basics
     func = getFunction(schema, 'function3')
-    self.assertEquals(('This comment should appear in the documentation, '
+    self.assertEqual(('This comment should appear in the documentation, '
                        'despite occupying multiple lines.'),
                       func['description'])
-    self.assertEquals(
+    self.assertEqual(
         [{'description': ('So should this comment about the argument. '
                           '<em>HTML</em> is fine too.'),
           'name': 'arg',
           '$ref': 'MyType1'}],
         func['parameters'])
     func = getFunction(schema, 'function4')
-    self.assertEquals(
+    self.assertEqual(
         '<p>This tests if "double-quotes" are escaped correctly.</p>'
         '<p>It also tests a comment with two newlines.</p>',
         func['description'])
@@ -266,62 +266,62 @@
     schema = idl_schema.Load('test/idl_reserved_words.idl')[0]
 
     foo_type = getType(schema, 'Foo')
-    self.assertEquals([{'name': 'float'}, {'name': 'DOMString'}],
+    self.assertEqual([{'name': 'float'}, {'name': 'DOMString'}],
                       foo_type['enum'])
 
     enum_type = getType(schema, 'enum')
-    self.assertEquals([{'name': 'callback'}, {'name': 'namespace'}],
+    self.assertEqual([{'name': 'callback'}, {'name': 'namespace'}],
                       enum_type['enum'])
 
     dictionary = getType(schema, 'dictionary')
-    self.assertEquals('integer', dictionary['properties']['long']['type'])
+    self.assertEqual('integer', dictionary['properties']['long']['type'])
 
     mytype = getType(schema, 'MyType')
-    self.assertEquals('string', mytype['properties']['interface']['type'])
+    self.assertEqual('string', mytype['properties']['interface']['type'])
 
     params = getParams(schema, 'static')
-    self.assertEquals('Foo', params[0]['$ref'])
-    self.assertEquals('enum', params[1]['$ref'])
+    self.assertEqual('Foo', params[0]['$ref'])
+    self.assertEqual('enum', params[1]['$ref'])
 
   def testObjectTypes(self):
     schema = idl_schema.Load('test/idl_object_types.idl')[0]
 
     foo_type = getType(schema, 'FooType')
-    self.assertEquals('object', foo_type['type'])
-    self.assertEquals('integer', foo_type['properties']['x']['type'])
-    self.assertEquals('object', foo_type['properties']['y']['type'])
-    self.assertEquals(
+    self.assertEqual('object', foo_type['type'])
+    self.assertEqual('integer', foo_type['properties']['x']['type'])
+    self.assertEqual('object', foo_type['properties']['y']['type'])
+    self.assertEqual(
         'any',
         foo_type['properties']['y']['additionalProperties']['type'])
-    self.assertEquals('object', foo_type['properties']['z']['type'])
-    self.assertEquals(
+    self.assertEqual('object', foo_type['properties']['z']['type'])
+    self.assertEqual(
         'any',
         foo_type['properties']['z']['additionalProperties']['type'])
-    self.assertEquals('Window', foo_type['properties']['z']['isInstanceOf'])
+    self.assertEqual('Window', foo_type['properties']['z']['isInstanceOf'])
 
     bar_type = getType(schema, 'BarType')
-    self.assertEquals('object', bar_type['type'])
-    self.assertEquals('any', bar_type['properties']['x']['type'])
+    self.assertEqual('object', bar_type['type'])
+    self.assertEqual('any', bar_type['properties']['x']['type'])
 
   def testObjectTypesInFunctions(self):
     schema = idl_schema.Load('test/idl_object_types.idl')[0]
 
     params = getParams(schema, 'objectFunction1')
-    self.assertEquals('object', params[0]['type'])
-    self.assertEquals('any', params[0]['additionalProperties']['type'])
-    self.assertEquals('ImageData', params[0]['isInstanceOf'])
+    self.assertEqual('object', params[0]['type'])
+    self.assertEqual('any', params[0]['additionalProperties']['type'])
+    self.assertEqual('ImageData', params[0]['isInstanceOf'])
 
     params = getParams(schema, 'objectFunction2')
-    self.assertEquals('any', params[0]['type'])
+    self.assertEqual('any', params[0]['type'])
 
   def testObjectTypesWithOptionalFields(self):
     schema = idl_schema.Load('test/idl_object_types.idl')[0]
 
     baz_type = getType(schema, 'BazType')
-    self.assertEquals(True, baz_type['properties']['x']['optional'])
-    self.assertEquals('integer', baz_type['properties']['x']['type'])
-    self.assertEquals(True, baz_type['properties']['foo']['optional'])
-    self.assertEquals('FooType', baz_type['properties']['foo']['$ref'])
+    self.assertEqual(True, baz_type['properties']['x']['optional'])
+    self.assertEqual('integer', baz_type['properties']['x']['type'])
+    self.assertEqual(True, baz_type['properties']['foo']['optional'])
+    self.assertEqual('FooType', baz_type['properties']['foo']['$ref'])
 
   def testObjectTypesWithUnions(self):
     schema = idl_schema.Load('test/idl_object_types.idl')[0]
@@ -358,7 +358,7 @@
                  },
                }
 
-    self.assertEquals(expected, union_type)
+    self.assertEqual(expected, union_type)
 
   def testUnionsWithModifiers(self):
     schema = idl_schema.Load('test/idl_object_types.idl')[0]
@@ -379,7 +379,7 @@
                  }
                }
 
-    self.assertEquals(expected, union_type)
+    self.assertEqual(expected, union_type)
 
   def testSerializableFunctionType(self):
     schema = idl_schema.Load('test/idl_object_types.idl')[0]
@@ -396,7 +396,7 @@
                    }
                  }
                }
-    self.assertEquals(expected, object_type)
+    self.assertEqual(expected, object_type)
 
   def testUnionsWithFunctions(self):
     schema = idl_schema.Load('test/idl_function_types.idl')[0]
@@ -410,7 +410,7 @@
                  ]
                }]
 
-    self.assertEquals(expected, union_params)
+    self.assertEqual(expected, union_params)
 
   def testUnionsWithCallbacks(self):
     schema = idl_schema.Load('test/idl_function_types.idl')[0]
@@ -425,7 +425,7 @@
                    ]}
                  ]
                }]
-    self.assertEquals(expected, blah_params)
+    self.assertEqual(expected, blah_params)
 
     badabish_params = getParams(schema, 'badabish')
     expected = [{
@@ -437,7 +437,7 @@
                  }]
                }]
 
-    self.assertEquals(expected, badabish_params)
+    self.assertEqual(expected, badabish_params)
 
   def testFunctionWithPromise(self):
     schema = idl_schema.Load('test/idl_function_types.idl')[0]
@@ -452,7 +452,7 @@
         ('name', 'promise_supporting'),
         ('type', 'function')
     ])
-    self.assertEquals(expected, promise_function)
+    self.assertEqual(expected, promise_function)
 
   def testFunctionWithPromiseAndParams(self):
     schema = idl_schema.Load('test/idl_function_types.idl')[0]
@@ -475,11 +475,11 @@
         ('name', 'promise_supporting_with_params'),
         ('type', 'function')
     ])
-    self.assertEquals(expected, promise_function)
+    self.assertEqual(expected, promise_function)
 
   def testProperties(self):
     schema = idl_schema.Load('test/idl_properties.idl')[0]
-    self.assertEquals(OrderedDict([
+    self.assertEqual(OrderedDict([
       ('first', OrderedDict([
         ('description', 'Integer property.'),
         ('jsexterns', None),
@@ -507,7 +507,7 @@
 
   def testManifestKeys(self):
     schema = self.idl_basics
-    self.assertEquals(
+    self.assertEqual(
         OrderedDict([('key_str',
                       OrderedDict([('description', 'String manifest key.'),
                                    ('jsexterns', None), ('name', 'key_str'),
diff --git a/tools/json_schema_compiler/js_externs_generator_test.py b/tools/json_schema_compiler/js_externs_generator_test.py
index f7f46974..07d8600 100755
--- a/tools/json_schema_compiler/js_externs_generator_test.py
+++ b/tools/json_schema_compiler/js_externs_generator_test.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 # Copyright 2015 The Chromium Authors. All rights reserved.
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
diff --git a/tools/json_schema_compiler/js_interface_generator_test.py b/tools/json_schema_compiler/js_interface_generator_test.py
index a108bf5..6d3bddf 100755
--- a/tools/json_schema_compiler/js_interface_generator_test.py
+++ b/tools/json_schema_compiler/js_interface_generator_test.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 # Copyright 2015 The Chromium Authors. All rights reserved.
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
diff --git a/tools/json_schema_compiler/json_schema_test.py b/tools/json_schema_compiler/json_schema_test.py
index edbb06e5..5b24967 100755
--- a/tools/json_schema_compiler/json_schema_test.py
+++ b/tools/json_schema_compiler/json_schema_test.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
@@ -76,7 +76,7 @@
     ]
 
     schema = json_schema.CachedLoad('test/json_schema_test.json')
-    self.assertEquals(compiled, json_schema.DeleteNodes(schema, 'nocompile'))
+    self.assertEqual(compiled, json_schema.DeleteNodes(schema, 'nocompile'))
 
     def should_delete(value):
       return isinstance(value, dict) and not value.get('valid', True)
@@ -91,7 +91,7 @@
       {},
       {'valid': False}
     ]
-    self.assertEquals(
+    self.assertEqual(
         expected, json_schema.DeleteNodes(given, matcher=should_delete))
 
 
diff --git a/tools/json_schema_compiler/model_test.py b/tools/json_schema_compiler/model_test.py
index a560279e..3b5757e 100755
--- a/tools/json_schema_compiler/model_test.py
+++ b/tools/json_schema_compiler/model_test.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
@@ -70,47 +70,47 @@
         'function_platform_win_linux')
 
   def testNamespaces(self):
-    self.assertEquals(12, len(self.model.namespaces))
+    self.assertEqual(12, len(self.model.namespaces))
     self.assertTrue(self.permissions)
 
   def testHasFunctions(self):
-    self.assertEquals(["contains", "getAll", "remove", "request"],
+    self.assertEqual(["contains", "getAll", "remove", "request"],
         sorted(self.permissions.functions.keys()))
 
   def testHasTypes(self):
-    self.assertEquals(['Tab'], self.tabs.types.keys())
-    self.assertEquals(['Permissions'], self.permissions.types.keys())
-    self.assertEquals(['Window'], self.windows.types.keys())
+    self.assertEqual(['Tab'], list(self.tabs.types.keys()))
+    self.assertEqual(['Permissions'], list(self.permissions.types.keys()))
+    self.assertEqual(['Window'], list(self.windows.types.keys()))
 
   def testHasProperties(self):
-    self.assertEquals(["active", "favIconUrl", "highlighted", "id",
+    self.assertEqual(["active", "favIconUrl", "highlighted", "id",
         "incognito", "index", "pinned", "selected", "status", "title", "url",
         "windowId"],
         sorted(self.tabs.types['Tab'].properties.keys()))
 
   def testProperties(self):
     string_prop = self.tabs.types['Tab'].properties['status']
-    self.assertEquals(model.PropertyType.STRING,
+    self.assertEqual(model.PropertyType.STRING,
                       string_prop.type_.property_type)
     integer_prop = self.tabs.types['Tab'].properties['id']
-    self.assertEquals(model.PropertyType.INTEGER,
+    self.assertEqual(model.PropertyType.INTEGER,
                       integer_prop.type_.property_type)
     array_prop = self.windows.types['Window'].properties['tabs']
-    self.assertEquals(model.PropertyType.ARRAY,
+    self.assertEqual(model.PropertyType.ARRAY,
                       array_prop.type_.property_type)
-    self.assertEquals(model.PropertyType.REF,
+    self.assertEqual(model.PropertyType.REF,
                       array_prop.type_.item_type.property_type)
-    self.assertEquals('tabs.Tab', array_prop.type_.item_type.ref_type)
+    self.assertEqual('tabs.Tab', array_prop.type_.item_type.ref_type)
     object_prop = self.tabs.functions['query'].params[0]
-    self.assertEquals(model.PropertyType.OBJECT,
+    self.assertEqual(model.PropertyType.OBJECT,
                       object_prop.type_.property_type)
-    self.assertEquals(
+    self.assertEqual(
         ["active", "highlighted", "pinned", "status", "title", "url",
          "windowId", "windowType"],
         sorted(object_prop.type_.properties.keys()))
 
   def testChoices(self):
-    self.assertEquals(model.PropertyType.CHOICES,
+    self.assertEqual(model.PropertyType.CHOICES,
                       self.tabs.functions['move'].params[0].type_.property_type)
 
   def testPropertyNotImplemented(self):
@@ -135,7 +135,7 @@
   def testDescription(self):
     self.assertFalse(
         self.permissions.functions['contains'].params[0].description)
-    self.assertEquals(
+    self.assertEqual(
         'True if the extension has the specified permissions.', self.
         permissions.functions['contains'].returns_async.params[0].description)
 
@@ -153,7 +153,7 @@
 
   def testPropertyUnixName(self):
     param = self.tabs.functions['move'].params[0]
-    self.assertEquals('tab_ids', param.unix_name)
+    self.assertEqual('tab_ids', param.unix_name)
 
   def testUnixName(self):
     expectations = {
@@ -170,7 +170,7 @@
       'foo_Bar_Baz_box': 'foo_bar_baz_box',
       }
     for name in expectations:
-      self.assertEquals(expectations[name], model.UnixName(name))
+      self.assertEqual(expectations[name], model.UnixName(name))
 
   def testCamelName(self):
     expectations = {
@@ -185,7 +185,7 @@
       'bar_baz_': 'barBaz',
       }
     for testcase, expected in expectations.items():
-      self.assertEquals(expected, model.CamelName(testcase))
+      self.assertEqual(expected, model.CamelName(testcase))
 
   def testPlatforms(self):
     self.assertEqual([Platforms.CHROMEOS],
diff --git a/tools/json_schema_compiler/preview.py b/tools/json_schema_compiler/preview.py
index 208e7fa..1d6f43e 100755
--- a/tools/json_schema_compiler/preview.py
+++ b/tools/json_schema_compiler/preview.py
@@ -1,5 +1,4 @@
-#!/usr/bin/env python
-
+#!/usr/bin/env python3
 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
diff --git a/tools/json_schema_compiler/schema_util_test.py b/tools/json_schema_compiler/schema_util_test.py
index 154da0137..0cc024d 100755
--- a/tools/json_schema_compiler/schema_util_test.py
+++ b/tools/json_schema_compiler/schema_util_test.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
@@ -9,16 +9,16 @@
 
 class SchemaUtilTest(unittest.TestCase):
   def testStripNamespace(self):
-    self.assertEquals('Bar', StripNamespace('foo.Bar'))
-    self.assertEquals('Baz', StripNamespace('Baz'))
+    self.assertEqual('Bar', StripNamespace('foo.Bar'))
+    self.assertEqual('Baz', StripNamespace('Baz'))
 
   def testJsFunctionNameToClassName(self):
-    self.assertEquals('FooBar', JsFunctionNameToClassName('foo', 'bar'))
-    self.assertEquals('FooBar',
+    self.assertEqual('FooBar', JsFunctionNameToClassName('foo', 'bar'))
+    self.assertEqual('FooBar',
                       JsFunctionNameToClassName('experimental.foo', 'bar'))
-    self.assertEquals('FooBarBaz',
+    self.assertEqual('FooBarBaz',
                       JsFunctionNameToClassName('foo.bar', 'baz'))
-    self.assertEquals('FooBarBaz',
+    self.assertEqual('FooBarBaz',
                       JsFunctionNameToClassName('experimental.foo.bar', 'baz'))
 
 if __name__ == '__main__':
diff --git a/tools/json_to_struct/PRESUBMIT.py b/tools/json_to_struct/PRESUBMIT.py
index bb36f47..d77764f 100644
--- a/tools/json_to_struct/PRESUBMIT.py
+++ b/tools/json_to_struct/PRESUBMIT.py
@@ -8,6 +8,7 @@
 for more details about the presubmit API built into depot_tools.
 """
 
+USE_PYTHON3 = True
 WHITELIST = [ r'.+_test.py$' ]
 
 def CheckChangeOnUpload(input_api, output_api):
diff --git a/tools/json_to_struct/element_generator_test.py b/tools/json_to_struct/element_generator_test.py
index ac856c8..9a3b9e1b 100755
--- a/tools/json_to_struct/element_generator_test.py
+++ b/tools/json_to_struct/element_generator_test.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
diff --git a/tools/json_to_struct/json_to_struct.py b/tools/json_to_struct/json_to_struct.py
index c98d3cd..86d1772b 100755
--- a/tools/json_to_struct/json_to_struct.py
+++ b/tools/json_to_struct/json_to_struct.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 # Copyright 2012 The Chromium Authors. All rights reserved.
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
diff --git a/tools/json_to_struct/struct_generator_test.py b/tools/json_to_struct/struct_generator_test.py
index a6c1c46f..51bc609 100755
--- a/tools/json_to_struct/struct_generator_test.py
+++ b/tools/json_to_struct/struct_generator_test.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
diff --git a/tools/licenses.py b/tools/licenses.py
index 7c2e504..d6787f6 100755
--- a/tools/licenses.py
+++ b/tools/licenses.py
@@ -550,7 +550,7 @@
       third_party_dirs.add(dir)
       ProcessAdditionalReadmePathsJson(root, dir, third_party_dirs)
 
-  return third_party_dirs
+  return sorted(third_party_dirs)
 
 
 def FindThirdPartyDirsWithFiles(root):
@@ -601,7 +601,7 @@
       # Skip over files that are known not to be used on iOS.
       continue
     third_party_deps.add(third_party_path[:-1])
-  return third_party_deps
+  return sorted(third_party_deps)
 
 
 def FindThirdPartyDeps(gn_out_dir, gn_target, target_os):
diff --git a/tools/mb/mb_config.pyl b/tools/mb/mb_config.pyl
index a4c0da8..cd4bf1c 100644
--- a/tools/mb/mb_config.pyl
+++ b/tools/mb/mb_config.pyl
@@ -315,6 +315,7 @@
       'chromeos-amd64-generic-rel-dchecks': 'chromeos_amd64-generic_use_fake_dbus_clients_dchecks',
       'fuchsia-code-coverage': 'fuchsia_clang_code_coverage',
       'fuchsia-fyi-arm64-dbg': 'debug_bot_fuchsia_arm64',
+      'fuchsia-fyi-arm64-femu': 'release_bot_fuchsia_arm64',
       'fuchsia-fyi-arm64-rel': 'release_bot_fuchsia_arm64',
       'fuchsia-fyi-x64-dbg': 'debug_bot_fuchsia',
       'fuchsia-fyi-x64-rel': 'release_bot_fuchsia',
@@ -824,7 +825,7 @@
       'android_cronet': 'android_cronet_release_trybot_arm_no_neon',
       'android_mojo': 'android_release_trybot_arm64',
       'android_n5x_swarming_dbg': 'android_debug_trybot_arm64',
-      'android_optional_gpu_tests_rel': 'gpu_tests_android_release_trybot_arm64',
+      'android_optional_gpu_tests_rel': 'gpu_tests_android_release_trybot',
       'android_unswarmed_pixel_aosp': 'android_debug_trybot_arm64',
       'android-deterministic-dbg': 'android_debug_bot',
       'android-deterministic-rel': 'android_without_codecs_release_trybot',
@@ -965,6 +966,7 @@
       'fuchsia-compile-x64-dbg': 'debug_bot_fuchsia_compile_only',
       'fuchsia-deterministic-dbg': 'debug_bot_fuchsia',
       'fuchsia-fyi-arm64-dbg': 'debug_bot_fuchsia_arm64',
+      'fuchsia-fyi-arm64-femu': 'release_trybot_fuchsia_arm64',
       'fuchsia-fyi-arm64-rel': 'release_trybot_fuchsia_arm64',
       'fuchsia-fyi-x64-dbg': 'debug_bot_fuchsia',
       'fuchsia-fyi-x64-rel': 'release_trybot_fuchsia',
diff --git a/tools/mb/mb_config_expectations/chromium.fyi.json b/tools/mb/mb_config_expectations/chromium.fyi.json
index 76dcab0..5d68f2d 100644
--- a/tools/mb/mb_config_expectations/chromium.fyi.json
+++ b/tools/mb/mb_config_expectations/chromium.fyi.json
@@ -492,6 +492,15 @@
       "use_goma": true
     }
   },
+  "fuchsia-fyi-arm64-femu": {
+    "gn_args": {
+      "is_component_build": false,
+      "is_debug": false,
+      "target_cpu": "arm64",
+      "target_os": "fuchsia",
+      "use_goma": true
+    }
+  },
   "fuchsia-fyi-arm64-rel": {
     "gn_args": {
       "is_component_build": false,
diff --git a/tools/mb/mb_config_expectations/tryserver.chromium.android.json b/tools/mb/mb_config_expectations/tryserver.chromium.android.json
index 75255b1..06442af 100644
--- a/tools/mb/mb_config_expectations/tryserver.chromium.android.json
+++ b/tools/mb/mb_config_expectations/tryserver.chromium.android.json
@@ -820,7 +820,6 @@
       "is_debug": false,
       "proprietary_codecs": true,
       "symbol_level": 1,
-      "target_cpu": "arm64",
       "target_os": "android",
       "use_goma": true,
       "use_static_angle": true
diff --git a/tools/mb/mb_config_expectations/tryserver.chromium.linux.json b/tools/mb/mb_config_expectations/tryserver.chromium.linux.json
index 622fd83..984a0bb 100644
--- a/tools/mb/mb_config_expectations/tryserver.chromium.linux.json
+++ b/tools/mb/mb_config_expectations/tryserver.chromium.linux.json
@@ -78,6 +78,17 @@
       "use_goma": true
     }
   },
+  "fuchsia-fyi-arm64-femu": {
+    "gn_args": {
+      "dcheck_always_on": true,
+      "is_component_build": false,
+      "is_debug": false,
+      "symbol_level": 1,
+      "target_cpu": "arm64",
+      "target_os": "fuchsia",
+      "use_goma": true
+    }
+  },
   "fuchsia-fyi-arm64-rel": {
     "gn_args": {
       "dcheck_always_on": true,
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 8506411..d9cfc7b6 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -44816,6 +44816,7 @@
   <int value="-2105498697" label="EnableImeSandbox:disabled"/>
   <int value="-2105133782" label="GesturePropertiesDBusService:enabled"/>
   <int value="-2104950596" label="HandwritingGesture:enabled"/>
+  <int value="-2103692632" label="EnhancedNetworkVoices:disabled"/>
   <int value="-2102286055" label="WebViewVulkanIntermediateBuffer:disabled"/>
   <int value="-2101682955" label="EnableNotificationIndicator:enabled"/>
   <int value="-2101338272" label="EnablePciguardUi:disabled"/>
@@ -46091,6 +46092,7 @@
   <int value="-1073479583" label="ShowArcFilesApp:disabled"/>
   <int value="-1069628248" label="OmniboxZeroSuggestionsOnSERP:enabled"/>
   <int value="-1069453905" label="CCTModuleUseIntentExtras:disabled"/>
+  <int value="-1068197506" label="EnhancedNetworkVoices:enabled"/>
   <int value="-1067635248" label="SpeculativeResourcePrefetching:disabled"/>
   <int value="-1065227777" label="CrOSAutoSelect:disabled"/>
   <int value="-1064733740" label="ui-show-composited-layer-borders"/>
@@ -47790,6 +47792,7 @@
   <int value="425072496" label="GridLayoutForNtpShortcuts:enabled"/>
   <int value="426199960" label="Commander:enabled"/>
   <int value="427184788" label="WebFeed:enabled"/>
+  <int value="427945554" label="StrictExtensionIsolation:enabled"/>
   <int value="430776375" label="TextureLayerSkipWaitForActivation:disabled"/>
   <int value="430959979" label="SyncStandaloneTransport:disabled"/>
   <int value="431691805" label="MediaDocumentDownloadButton:enabled"/>
@@ -48314,6 +48317,7 @@
   <int value="877238334" label="WebUITabStripTabDragIntegration:enabled"/>
   <int value="877257918"
       label="InterestFeedV2ClickAndViewActionsConditionalUpload:enabled"/>
+  <int value="877907263" label="StrictExtensionIsolation:disabled"/>
   <int value="878773995" label="ChromeHomeBottomNavLabels:disabled"/>
   <int value="879699575" label="disable-gesture-tap-highlight"/>
   <int value="879992337" label="disable-pull-to-refresh-effect"/>
@@ -86592,6 +86596,11 @@
   <int value="53" label="Show tips for Chrome"/>
 </enum>
 
+<enum name="WrongConfigurationMetric">
+  <int value="0" label="Thunderbolt peripheral - wrong cable"/>
+  <int value="1" label="USB4 peripheral - wrong cable"/>
+</enum>
+
 <enum name="XFrameOptions">
   <int value="0" label="NONE">
     A frame is loaded without any X-Frame-Options header.
diff --git a/tools/metrics/histograms/histograms_xml/accessibility/histograms.xml b/tools/metrics/histograms/histograms_xml/accessibility/histograms.xml
index d7d643f..33e801f 100644
--- a/tools/metrics/histograms/histograms_xml/accessibility/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/accessibility/histograms.xml
@@ -1181,6 +1181,16 @@
   </summary>
 </histogram>
 
+<histogram name="Accessibility.WebSpeech.Duration" units="ms"
+    expires_after="2021-11-30">
+  <owner>evliu@google.com</owner>
+  <owner>chrome-media-ux@google.com</owner>
+  <summary>
+    Measures the duration of a call to the WebSpeech API. Recorded once per call
+    to the Open Speech API that powers the WebSpeech API.
+  </summary>
+</histogram>
+
 <histogram name="Accessibility.Win.AnimationsEnabled" enum="BooleanEnabled"
     expires_after="M85">
   <owner>dmazzoni@chromium.org</owner>
diff --git a/tools/metrics/histograms/histograms_xml/chromeos/histograms.xml b/tools/metrics/histograms/histograms_xml/chromeos/histograms.xml
index c8cdb0db..0afa0c7 100644
--- a/tools/metrics/histograms/histograms_xml/chromeos/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/chromeos/histograms.xml
@@ -38,6 +38,11 @@
     <owner>cros-oac@google.com</owner>
     <owner>chromeos-fingerprint@google.com</owner>
   </variant>
+  <variant name="NearbyShare" summary="Nearby Share">
+    <owner>nohle@chromium.org</owner>
+    <owner>vecore@chromium.org</owner>
+    <owner>nearby-share-chromeos-eng@google.com</owner>
+  </variant>
 </variants>
 
 <histogram name="ChromeOS.Apps.ExternalProtocolDialog"
@@ -1633,6 +1638,17 @@
   </summary>
 </histogram>
 
+<histogram name="ChromeOS.TypeC.WrongConfiguration"
+    enum="WrongConfigurationMetric" expires_after="2021-10-31">
+  <owner>pmalani@chromium.org</owner>
+  <owner>chromeos-power@google.com</owner>
+  <summary>
+    This value represents events where a USB Type-C peripheral couldn't be
+    configured correctly, or experienced some other unexpected behaviour. It is
+    recorded every time a new peripheral is connected.
+  </summary>
+</histogram>
+
 <histogram name="ChromeOS.UrlXattrsCount" units="units"
     expires_after="2021-12-01">
   <owner>jorgelo@chromium.org</owner>
diff --git a/tools/metrics/histograms/histograms_xml/history/histograms.xml b/tools/metrics/histograms/histograms_xml/history/histograms.xml
index dc6dfb1..4ce6bcb 100644
--- a/tools/metrics/histograms/histograms_xml/history/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/history/histograms.xml
@@ -444,6 +444,16 @@
     latest unreported day, while the profile remains open. If no domains are
     visited in a given day, a count of 0 will be reported for that day.
 
+    Warning about delayed data: Chrome may upload logs on a given day without
+    uploading this histogram. This can happen because Chrome uploads logs
+    initially upon startup. This histogram is emitted shortly _after_ startup.
+    In the case of short sessions, it's possible the log with this histogram did
+    not have time to be uploaded. Generally the log will be cached and uploaded
+    the next time the user starts Chrome. We should still get one count per
+    calendar day; it simply may not be on the day the metric was computed.
+    (Exception: on Android before M-91, sometimes these emitted histograms were
+    lost due to lack of robust &quot;background logging&quot;.)
+
     Note: for users syncing between multiple devices, this count may include
     some URLs/domains that weren't visited on this device. In other words, some
     domains may be counted for multiple client_ids even though they were only
@@ -470,6 +480,16 @@
     period, while the profile remains open. If no domains are visited during a
     28-day period, a count of 0 will be reported for that period.
 
+    Warning about delayed data: Chrome may upload logs on a given day without
+    uploading this histogram. This can happen because Chrome uploads logs
+    initially upon startup. This histogram is emitted shortly _after_ startup.
+    In the case of short sessions, it's possible the log with this histogram did
+    not have time to be uploaded. Generally the log will be cached and uploaded
+    the next time the user starts Chrome. We should still get one count per
+    calendar day; it simply may not be on the day the metric was computed.
+    (Exception: on Android before M-91, sometimes these emitted histograms were
+    lost due to lack of robust &quot;background logging&quot;.)
+
     Note: for users syncing between multiple devices, this count may include
     some URLs/domains that weren't visited on this device. In other words, some
     domains may be counted for multiple client_ids even though they were only
@@ -496,6 +516,16 @@
     period, while the profile remains open. If no domains are visited during a
     7-day period, a count 0 will be reported for that period.
 
+    Warning about delayed data: Chrome may upload logs on a given day without
+    uploading this histogram. This can happen because Chrome uploads logs
+    initially upon startup. This histogram is emitted shortly _after_ startup.
+    In the case of short sessions, it's possible the log with this histogram did
+    not have time to be uploaded. Generally the log will be cached and uploaded
+    the next time the user starts Chrome. We should still get one count per
+    calendar day; it simply may not be on the day the metric was computed.
+    (Exception: on Android before M-91, sometimes these emitted histograms were
+    lost due to lack of robust &quot;background logging&quot;.)
+
     Note: for users syncing between multiple devices, this count may include
     some URLs/domains that weren't visited on this device. In other words, some
     domains may be counted for multiple client_ids even though they were only
diff --git a/tools/metrics/histograms/histograms_xml/sb_client/histograms.xml b/tools/metrics/histograms/histograms_xml/sb_client/histograms.xml
index febbeb1..c087c40 100644
--- a/tools/metrics/histograms/histograms_xml/sb_client/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/sb_client/histograms.xml
@@ -548,6 +548,16 @@
   </summary>
 </histogram>
 
+<histogram name="SBClientPhishing.FlatBufferScorer.CreationStatus"
+    enum="SBClientPhishingScorerCreationStatus" expires_after="M94">
+  <owner>bhatiarohit@google.com</owner>
+  <owner>chrome-safebrowsing-alerts@google.com</owner>
+  <summary>
+    Records the status when we create a FlatBuffer scorer object for the
+    client-side phishing detection classifier.
+  </summary>
+</histogram>
+
 <histogram name="SBClientPhishing.IllegalFeatureValue" units="units"
     expires_after="M90">
   <obsolete>
@@ -641,6 +651,16 @@
   </summary>
 </histogram>
 
+<histogram name="SBClientPhishing.ProtobufScorer.CreationStatus"
+    enum="SBClientPhishingScorerCreationStatus" expires_after="M94">
+  <owner>bhatiarohit@google.com</owner>
+  <owner>chrome-safebrowsing-alerts@google.com</owner>
+  <summary>
+    Records the status when we create a Protobuf scorer object for the
+    client-side phishing detection classifier.
+  </summary>
+</histogram>
+
 <histogram name="SBClientPhishing.ReportLimitSkipped" enum="BooleanHit"
     expires_after="2021-01-27">
   <obsolete>
@@ -689,6 +709,10 @@
 
 <histogram name="SBClientPhishing.ScorerCreationStatus"
     enum="SBClientPhishingScorerCreationStatus" expires_after="M94">
+  <obsolete>
+    Removed 05-27-2021. Using SBClientPhishing.ProtobufScorer.CreationStatus and
+    SBClientPhishing.FlatBufferScorer.CreationStatus
+  </obsolete>
   <owner>drubery@chromium.org</owner>
   <owner>chrome-safebrowsing-alerts@google.com</owner>
   <summary>
diff --git a/tools/metrics/ukm/ukm.xml b/tools/metrics/ukm/ukm.xml
index 1db0937d..d7e8fe2 100644
--- a/tools/metrics/ukm/ukm.xml
+++ b/tools/metrics/ukm/ukm.xml
@@ -8828,7 +8828,7 @@
       we don't know the video codec.
     </summary>
   </metric>
-  <metric name="Video.EME.KeySystem">
+  <metric name="Video.EME.KeySystem" enum="MediaKeySystem">
     <summary>
       Name of the KeySystem used during for an encrypted (EME) playback. Will be
       unset when EME is not used.
@@ -8971,6 +8971,12 @@
       once attached can't be removed.
     </summary>
   </metric>
+  <metric name="IsHardwareSecure" enum="Boolean">
+    <summary>
+      Boolean value indicating whether the EME playback is using hardware secure
+      pipeline. For clear playback, this will always be false.
+    </summary>
+  </metric>
   <metric name="IsMSE">
     <summary>
       Boolean value indicating if this event is for an MSE playback. If false it
@@ -8985,6 +8991,12 @@
       playbacks.
     </summary>
   </metric>
+  <metric name="KeySystem" enum="MediaKeySystem">
+    <summary>
+      For EME playback, this is the key system used. For clear playback, this
+      will always be the default value 0 (kUnknownKeySystemForUkm).
+    </summary>
+  </metric>
   <metric name="PlayerID">
     <summary>
       ID which corresponds to a given WebMediaPlayerImpl instance. May be linked
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps.json b/tools/perf/core/perfetto_binary_roller/binary_deps.json
index e473c64d..d4a66ca4 100644
--- a/tools/perf/core/perfetto_binary_roller/binary_deps.json
+++ b/tools/perf/core/perfetto_binary_roller/binary_deps.json
@@ -2,7 +2,7 @@
     "trace_processor_shell": {
         "win": {
             "hash": "6e445a89816f61bdf2942742657ab5542492b01e",
-            "remote_path": "perfetto_binaries/trace_processor_shell/win/e46c9b802f8761df0341aad1d7845b7732a48596/trace_processor_shell.exe"
+            "remote_path": "perfetto_binaries/trace_processor_shell/win/49518bb65f7110253ac1b0e576c2592eb394031e/trace_processor_shell.exe"
         },
         "mac": {
             "hash": "76cfeee650db8a487cfd6c86b74d2a02f732d67a",
@@ -10,7 +10,7 @@
         },
         "linux": {
             "hash": "46ee9624f529202741eaa0126a9fb42641b0ea1f",
-            "remote_path": "perfetto_binaries/trace_processor_shell/linux/e46c9b802f8761df0341aad1d7845b7732a48596/trace_processor_shell"
+            "remote_path": "perfetto_binaries/trace_processor_shell/linux/49518bb65f7110253ac1b0e576c2592eb394031e/trace_processor_shell"
         }
     },
     "power_profile.sql": {
diff --git a/ui/accessibility/accessibility_features.cc b/ui/accessibility/accessibility_features.cc
index 57551faa..51163db3 100644
--- a/ui/accessibility/accessibility_features.cc
+++ b/ui/accessibility/accessibility_features.cc
@@ -127,6 +127,13 @@
   return base::FeatureList::IsEnabled(
       ::features::kExperimentalAccessibilityDictationListening);
 }
+
+const base::Feature kEnhancedNetworkVoices{"EnhancedNetworkVoices",
+                                           base::FEATURE_DISABLED_BY_DEFAULT};
+
+bool IsEnhancedNetworkVoicesEnabled() {
+  return base::FeatureList::IsEnabled(::features::kEnhancedNetworkVoices);
+}
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
 const base::Feature kAugmentExistingImageLabels{
diff --git a/ui/accessibility/accessibility_features.h b/ui/accessibility/accessibility_features.h
index 09e87f2..c0793b8ed 100644
--- a/ui/accessibility/accessibility_features.h
+++ b/ui/accessibility/accessibility_features.h
@@ -110,6 +110,13 @@
 // Returns true if the feature to allow experimental listening features for
 // Dictation is enabled.
 AX_BASE_EXPORT bool IsExperimentalAccessibilityDictationListeningEnabled();
+
+// Enables high-quality, network-based voices in Select-to-speak.
+AX_BASE_EXPORT extern const base::Feature kEnhancedNetworkVoices;
+
+// Returns true if network-based voices are enabled in Select-to-speak.
+AX_BASE_EXPORT bool IsEnhancedNetworkVoicesEnabled();
+
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
 // Enables Get Image Descriptions to augment existing images labels,
diff --git a/ui/aura/native_window_occlusion_tracker_win.cc b/ui/aura/native_window_occlusion_tracker_win.cc
index 7146e55..974ffd8 100644
--- a/ui/aura/native_window_occlusion_tracker_win.cc
+++ b/ui/aura/native_window_occlusion_tracker_win.cc
@@ -180,9 +180,12 @@
   // Filter out "tool windows", which are floating windows that do not appear on
   // the taskbar or ALT-TAB. Floating windows can have larger window rectangles
   // than what is visible to the user, so by filtering them out we will avoid
-  // incorrectly marking native windows as occluded.
-  if (ex_styles & WS_EX_TOOLWINDOW)
-    return false;
+  // incorrectly marking native windows as occluded. We do not filter out the
+  // Windows Taskbar.
+  if (ex_styles & WS_EX_TOOLWINDOW) {
+    if (gfx::GetClassName(hwnd) != L"Shell_TrayWnd")
+      return false;
+  }
 
   // Filter out layered windows that are not opaque or that set a transparency
   // colorkey.
@@ -230,15 +233,36 @@
     return false;
 
   // Ignore popup windows since they're transient unless it is a Chrome Widget
-  // Window.
+  // Window or the Windows Taskbar
   if (::GetWindowLong(hwnd, GWL_STYLE) & WS_POPUP) {
-    if (!base::StartsWith(gfx::GetClassName(hwnd), L"Chrome_WidgetWin_")) {
+    std::wstring hwnd_class_name = gfx::GetClassName(hwnd);
+    if (!base::StartsWith(hwnd_class_name, L"Chrome_WidgetWin_") &&
+        hwnd_class_name != L"Shell_TrayWnd") {
       return false;
     }
   }
 
   *window_rect = gfx::Rect(win_rect);
 
+  WINDOWPLACEMENT window_placement = {0};
+  window_placement.length = sizeof(WINDOWPLACEMENT);
+  ::GetWindowPlacement(hwnd, &window_placement);
+  if (window_placement.showCmd == SW_MAXIMIZE) {
+    // If the window is maximized the window border extends beyond the visible
+    // region of the screen.  Adjust the maximized window rect to fit the
+    // screen dimensions to ensure that fullscreen windows, which do not extend
+    // beyond the screen boundaries since they typically have no borders, will
+    // occlude maximized windows underneath them.
+    HMONITOR hmon = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
+    if (hmon) {
+      MONITORINFO mi;
+      mi.cbSize = sizeof(mi);
+      if (GetMonitorInfo(hmon, &mi)) {
+        (*window_rect).AdjustToFit(gfx::Rect(mi.rcWork));
+      }
+    }
+  }
+
   return true;
 }
 
@@ -695,12 +719,14 @@
     return;
 
   // We generally ignore events for popup windows, except for when the taskbar
-  // is hidden or when the popup is a Chrome Widget, in which case we
-  // recalculate occlusion.
+  // is hidden or when the popup is a Chrome Widget or Windows Taskbar, in
+  // which case we recalculate occlusion.
   bool calculate_occlusion = true;
   if (::GetWindowLong(hwnd, GWL_STYLE) & WS_POPUP) {
+    std::wstring hwnd_class_name = gfx::GetClassName(hwnd);
     calculate_occlusion =
-        base::StartsWith(gfx::GetClassName(hwnd), L"Chrome_WidgetWin_");
+        base::StartsWith(hwnd_class_name, L"Chrome_WidgetWin_") ||
+        hwnd_class_name == L"Shell_TrayWnd";
   }
 
   // Detect if either the alt tab view or the task list thumbnail is being
diff --git a/ui/base/l10n/l10n_util.cc b/ui/base/l10n/l10n_util.cc
index 19a139ad..07d38591e 100644
--- a/ui/base/l10n/l10n_util.cc
+++ b/ui/base/l10n/l10n_util.cc
@@ -237,7 +237,11 @@
 // for on the current platform. Guaranteed to be in sorted order and guaranteed
 // to have no duplicates.
 //
-// Note that this could have false positives at runtime on iOS:
+// Note that this could have false positives at runtime on Android and iOS:
+// - On Android, some locales aren't shipped (|android_apk_omitted_locales| in
+//   GN), and some locales files are dynamically shipped in app bundles
+//   (|android_bundle_only_locales|). Both of these lists are included in
+//   this variable.
 // - On iOS, some locales aren't shipped (|ios_unsupported_locales|) as they are
 //   not supported by the operating system. These locales are included in this
 //   variable.
diff --git a/ui/gfx/color_utils.cc b/ui/gfx/color_utils.cc
index 9849410..01f4902 100644
--- a/ui/gfx/color_utils.cc
+++ b/ui/gfx/color_utils.cc
@@ -64,8 +64,135 @@
                                  : pow((component + 0.055f) / 1.055f, 2.4f);
 }
 
+constexpr size_t kNumGoogleColors = 10;
+constexpr SkColor kGrey[kNumGoogleColors] = {
+    gfx::kGoogleGrey050, gfx::kGoogleGrey100, gfx::kGoogleGrey200,
+    gfx::kGoogleGrey300, gfx::kGoogleGrey400, gfx::kGoogleGrey500,
+    gfx::kGoogleGrey600, gfx::kGoogleGrey700, gfx::kGoogleGrey800,
+    gfx::kGoogleGrey900,
+};
+
+constexpr SkColor kRed[kNumGoogleColors] = {
+    gfx::kGoogleRed050, gfx::kGoogleRed100, gfx::kGoogleRed200,
+    gfx::kGoogleRed300, gfx::kGoogleRed400, gfx::kGoogleRed500,
+    gfx::kGoogleRed600, gfx::kGoogleRed700, gfx::kGoogleRed800,
+    gfx::kGoogleRed900,
+};
+
+constexpr SkColor kOrange[kNumGoogleColors] = {
+    gfx::kGoogleOrange050, gfx::kGoogleOrange100, gfx::kGoogleOrange200,
+    gfx::kGoogleOrange300, gfx::kGoogleOrange400, gfx::kGoogleOrange500,
+    gfx::kGoogleOrange600, gfx::kGoogleOrange700, gfx::kGoogleOrange800,
+    gfx::kGoogleOrange900,
+};
+
+constexpr SkColor kYellow[kNumGoogleColors] = {
+    gfx::kGoogleYellow050, gfx::kGoogleYellow100, gfx::kGoogleYellow200,
+    gfx::kGoogleYellow300, gfx::kGoogleYellow400, gfx::kGoogleYellow500,
+    gfx::kGoogleYellow600, gfx::kGoogleYellow700, gfx::kGoogleYellow800,
+    gfx::kGoogleYellow900,
+};
+
+constexpr SkColor kGreen[kNumGoogleColors] = {
+    gfx::kGoogleGreen050, gfx::kGoogleGreen100, gfx::kGoogleGreen200,
+    gfx::kGoogleGreen300, gfx::kGoogleGreen400, gfx::kGoogleGreen500,
+    gfx::kGoogleGreen600, gfx::kGoogleGreen700, gfx::kGoogleGreen800,
+    gfx::kGoogleGreen900,
+};
+
+constexpr SkColor kBlue[kNumGoogleColors] = {
+    gfx::kGoogleBlue050, gfx::kGoogleBlue100, gfx::kGoogleBlue200,
+    gfx::kGoogleBlue300, gfx::kGoogleBlue400, gfx::kGoogleBlue500,
+    gfx::kGoogleBlue600, gfx::kGoogleBlue700, gfx::kGoogleBlue800,
+    gfx::kGoogleBlue900,
+};
+
+constexpr SkColor kPurple[kNumGoogleColors] = {
+    gfx::kGooglePurple050, gfx::kGooglePurple100, gfx::kGooglePurple200,
+    gfx::kGooglePurple300, gfx::kGooglePurple400, gfx::kGooglePurple500,
+    gfx::kGooglePurple600, gfx::kGooglePurple700, gfx::kGooglePurple800,
+    gfx::kGooglePurple900,
+};
+
+constexpr SkColor kPink[kNumGoogleColors] = {
+    gfx::kGooglePink050, gfx::kGooglePink100, gfx::kGooglePink200,
+    gfx::kGooglePink300, gfx::kGooglePink400, gfx::kGooglePink500,
+    gfx::kGooglePink600, gfx::kGooglePink700, gfx::kGooglePink800,
+    gfx::kGooglePink900,
+};
+
+SkColor PickGoogleColor(const SkColor (&colors)[kNumGoogleColors],
+                        SkColor background_color,
+                        float min_contrast) {
+  // For dark backgrounds we start at 500 and go down (toward brighter colors).
+  constexpr size_t kDarkBackgroundStartIndex = 5;
+  static_assert(kBlue[kDarkBackgroundStartIndex] == gfx::kGoogleBlue500,
+                "The start index needs to match kGoogleBlue500");
+  if (IsDark(background_color)) {
+    for (size_t i = kDarkBackgroundStartIndex; i > 0; --i) {
+      if (GetContrastRatio(colors[i], background_color) > min_contrast)
+        return colors[i];
+    }
+    return colors[0];
+  }
+
+  // For light backgrounds we start at 400 and go up (toward darker colors).
+  constexpr size_t kLightBackgroundStartIndex = 4;
+  static_assert(kBlue[kLightBackgroundStartIndex] == gfx::kGoogleBlue400,
+                "The start index needs to match kGoogleBlue400");
+  for (size_t i = kLightBackgroundStartIndex; i < kNumGoogleColors - 1; ++i) {
+    if (GetContrastRatio(colors[i], background_color) > min_contrast)
+      return colors[i];
+  }
+  return colors[kNumGoogleColors - 1];
+}
+
 }  // namespace
 
+SkColor PickGoogleColor(SkColor color,
+                        SkColor background_color,
+                        float min_contrast) {
+  HSL hsl;
+  SkColorToHSL(color, &hsl);
+  if (hsl.s < 0.1) {
+    // Low saturation, let this be a grey.
+    return PickGoogleColor(kGrey, background_color, min_contrast);
+  }
+
+  // Map hue to angles for readability.
+  const float color_angle = hsl.h * 360;
+
+  // Hues in comments below are accent colors from MacOS 11.3.1 light mode as
+  // point of reference.
+  // TODO(pbos): Complement this with more Google colors and verify the hue
+  // ranges, this currently knows about enough colors to pick a corresponding
+  // color correctly from MacOS accent colors.
+  // RED: 357.654
+  if (color_angle < 20)
+    return PickGoogleColor(kRed, background_color, min_contrast);
+  // ORANGE: 28.0687
+  if (color_angle < 40)
+    return PickGoogleColor(kOrange, background_color, min_contrast);
+  // YELLOW: 44.4156
+  if (color_angle < 70)
+    return PickGoogleColor(kYellow, background_color, min_contrast);
+  // GREEN: 105.484
+  if (color_angle < 160)
+    return PickGoogleColor(kGreen, background_color, min_contrast);
+  // BLUE: 214.672
+  if (color_angle < 250)
+    return PickGoogleColor(kBlue, background_color, min_contrast);
+  // PURPLE: 299.362
+  if (color_angle < 310)
+    return PickGoogleColor(kPurple, background_color, min_contrast);
+  // PINK: 331.685
+  if (color_angle < 345)
+    return PickGoogleColor(kPink, background_color, min_contrast);
+
+  // End of hue wheel is red.
+  return PickGoogleColor(kRed, background_color, min_contrast);
+}
+
 float GetContrastRatio(SkColor color_a, SkColor color_b) {
   return GetContrastRatio(GetRelativeLuminance(color_a),
                           GetRelativeLuminance(color_b));
diff --git a/ui/gfx/color_utils.h b/ui/gfx/color_utils.h
index 64031d8..5d5c430 100644
--- a/ui/gfx/color_utils.h
+++ b/ui/gfx/color_utils.h
@@ -158,6 +158,13 @@
 // surface.
 GFX_EXPORT SkColor DeriveDefaultIconColor(SkColor text_color);
 
+// Gets a Google color that matches the hue of `color` and contrasts well
+// enough against `background_color` to meet `min_contrast`. If `color` isn't
+// very saturated, grey will be used instead.
+GFX_EXPORT SkColor PickGoogleColor(SkColor color,
+                                   SkColor background_color,
+                                   float min_contrast);
+
 // Creates an rgba string for an SkColor. For example: 'rgba(255,0,255,0.5)'.
 GFX_EXPORT std::string SkColorToRgbaString(SkColor color);
 
diff --git a/ui/gtk/gdk.sigs b/ui/gtk/gdk.sigs
index c8223a5..7a393ed 100644
--- a/ui/gtk/gdk.sigs
+++ b/ui/gtk/gdk.sigs
@@ -33,3 +33,4 @@
 void gdk_set_allowed_backends(const gchar* backends);
 void gdk_display_notify_startup_complete(GdkDisplay* display, const char* startup_id);
 guint32 gdk_keyval_to_unicode(guint keyval);
+gdouble gdk_screen_get_resolution(GdkScreen *screen);
diff --git a/ui/gtk/gtk_ui.cc b/ui/gtk/gtk_ui.cc
index 3706fca8..a3302b6 100644
--- a/ui/gtk/gtk_ui.cc
+++ b/ui/gtk/gtk_ui.cc
@@ -379,9 +379,15 @@
                          G_CALLBACK(OnCursorThemeNameChangedThunk), this);
   g_signal_connect_after(settings, "notify::gtk-cursor-theme-size",
                          G_CALLBACK(OnCursorThemeSizeChangedThunk), this);
-  g_signal_connect_after(settings, "notify::gtk-xft-dpi",
-                         G_CALLBACK(OnDeviceScaleFactorMaybeChangedThunk),
-                         this);
+
+  // Listen for DPI changes.
+  auto* dpi_callback = G_CALLBACK(OnDeviceScaleFactorMaybeChangedThunk);
+  if (GtkCheckVersion(4)) {
+    g_signal_connect_after(settings, "notify::gtk-xft-dpi", dpi_callback, this);
+  } else {
+    GdkScreen* screen = gdk_screen_get_default();
+    g_signal_connect_after(screen, "notify::resolution", dpi_callback, this);
+  }
 
   // Listen for scale factor changes.  We would prefer to listen on
   // a GdkScreen, but there is no scale-factor property, so use an
@@ -1065,11 +1071,18 @@
   float scale = gtk_widget_get_scale_factor(GetDummyWindow());
   DCHECK_GT(scale, 0.0);
 
-  auto* settings = gtk_settings_get_default();
-  int resolution = kDefaultDPI;
-  g_object_get(settings, "gtk-xft-dpi", &resolution, nullptr);
+  double resolution = 0;
+  if (GtkCheckVersion(4)) {
+    auto* settings = gtk_settings_get_default();
+    int dpi = 0;
+    g_object_get(settings, "gtk-xft-dpi", &dpi, nullptr);
+    resolution = dpi / 1024.0;
+  } else {
+    GdkScreen* screen = gdk_screen_get_default();
+    resolution = gdk_screen_get_resolution(screen);
+  }
   if (resolution > 0)
-    scale *= resolution / kDefaultDPI / 1024;
+    scale *= resolution / kDefaultDPI;
 
   // Round to the nearest 64th so that UI can losslessly multiply and divide
   // the scale factor.
diff --git a/ui/views/cocoa/native_widget_mac_ns_window_host.h b/ui/views/cocoa/native_widget_mac_ns_window_host.h
index b6d8600..c164c3a8 100644
--- a/ui/views/cocoa/native_widget_mac_ns_window_host.h
+++ b/ui/views/cocoa/native_widget_mac_ns_window_host.h
@@ -146,7 +146,9 @@
   void SetBoundsInScreen(const gfx::Rect& bounds);
 
   // Tell the window to transition to being fullscreen or not-fullscreen.
-  void SetFullscreen(bool fullscreen);
+  // If `delay` is true, this will set the target fullscreen state and then post
+  // a delayed task to request the window transition. See crbug.com/1210548
+  void SetFullscreen(bool fullscreen, bool delay = false);
 
   // The ultimate fullscreen state that is being targeted (irrespective of any
   // active transitions).
diff --git a/ui/views/cocoa/native_widget_mac_ns_window_host.mm b/ui/views/cocoa/native_widget_mac_ns_window_host.mm
index 1de90c3b..d20f9af 100644
--- a/ui/views/cocoa/native_widget_mac_ns_window_host.mm
+++ b/ui/views/cocoa/native_widget_mac_ns_window_host.mm
@@ -464,13 +464,33 @@
   }
 }
 
-void NativeWidgetMacNSWindowHost::SetFullscreen(bool fullscreen) {
+void NativeWidgetMacNSWindowHost::SetFullscreen(bool fullscreen, bool delay) {
   // Note that when the NSWindow begins a fullscreen transition, the value of
   // |target_fullscreen_state_| updates via OnWindowFullscreenTransitionStart.
   // The update here is necessary for the case where we are currently in
   // transition (and therefore OnWindowFullscreenTransitionStart will not be
   // called until the current transition completes).
   target_fullscreen_state_ = fullscreen;
+
+  if (delay) {
+    // Synchronously requesting fullscreen after moving the window to another
+    // display causes the window to resign key. Workaround this OS-specific
+    // quirk by delaying the fullscreen request, after setting the target state,
+    // to encapsulate some of these details from the calling client window code,
+    // i.e. so BrowserView::ProcessFullscreen will still hide its frame, etc.
+    // TODO(crbug.com/1034783): Refine cross-display fullscreen implementations.
+    // Maybe add fullscreen-on-target-display helpers on views::Widget, etc. to
+    // better encapsulate per-platform complexity, or at least wait for the
+    // window move before entering fullscreen, instead of posting a task with a
+    // locally-tested delay.
+    auto callback = base::BindOnce(
+        &remote_cocoa::mojom::NativeWidgetNSWindow::SetFullscreen,
+        base::Unretained(GetNSWindowMojo()), target_fullscreen_state_);
+    base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+        FROM_HERE, std::move(callback), base::TimeDelta::FromMilliseconds(1));
+    return;
+  }
+
   GetNSWindowMojo()->SetFullscreen(target_fullscreen_state_);
 }
 
diff --git a/ui/views/controls/focus_ring.cc b/ui/views/controls/focus_ring.cc
index c72e28e..677bd52 100644
--- a/ui/views/controls/focus_ring.cc
+++ b/ui/views/controls/focus_ring.cc
@@ -50,8 +50,10 @@
 }
 
 SkColor GetColor(View* focus_ring, bool valid) {
+  const ui::NativeTheme* const native_theme = focus_ring->GetNativeTheme();
   const SkColor default_color =
-      focus_ring->GetNativeTheme()->GetSystemColor(ColorIdForValidity(valid));
+      native_theme->GetSystemColor(ColorIdForValidity(valid));
+
   if (!valid)
     return default_color;
 
@@ -64,31 +66,20 @@
           : focus_ring->GetNativeTheme()->GetSystemColor(
                 ui::NativeTheme::kColorId_WindowBackground);
 
-  // This blends towards the fully-opaque version of the focus-ring color until
-  // the minimum contrast is reached (if possible). If `default_color` is
-  // already contrasty enough it will be used directly.
-  const color_utils::BlendResult contrasty_color =
-      color_utils::BlendForMinContrast(
-          default_color, background_color, SkColorSetA(default_color, 0xFF),
-          color_utils::kMinimumVisibleContrastRatio);
+  const SkColor focus_ring_color =
+      color_utils::PickGoogleColor(default_color, background_color,
+                                   color_utils::kMinimumVisibleContrastRatio);
 
-  const float boosted_contrast = color_utils::GetContrastRatio(
-      color_utils::GetResultingPaintColor(contrasty_color.color,
-                                          background_color),
-      background_color);
+  // If the Google color is contrasty enough, use it.
+  if (color_utils::GetContrastRatio(focus_ring_color, background_color) >
+      color_utils::kMinimumVisibleContrastRatio) {
+    return focus_ring_color;
+  }
 
-  // If the contrast of the boosted color is enough, use it. If not, increasing
-  // opacity was insufficient to meet contrast minimums. This happens when
-  // painting a blue focus ring on a blue theme for instance.
-  if (boosted_contrast > color_utils::kMinimumVisibleContrastRatio)
-    return contrasty_color.color;
-
-  // If a fallback color exists, use it. Otherwise use the boosted color as it's
-  // the best we have here for now.
   return fallback_color_view
              ? focus_ring->GetThemeProvider()->GetColor(
                    fallback_color_view->GetProperty(kFocusRingFallbackColorId))
-             : contrasty_color.color;
+             : focus_ring_color;
 }
 
 double GetCornerRadius() {
diff --git a/ui/views/layout/grid_layout.h b/ui/views/layout/grid_layout.h
index 073b313a..c0cd1058 100644
--- a/ui/views/layout/grid_layout.h
+++ b/ui/views/layout/grid_layout.h
@@ -27,8 +27,7 @@
 //                          // resized.
 //                    1.0,  // This column has a resize weight of 1.
 //                    ColumnSize::kUsePreferred, // Use the preferred size of
-//                    the
-//                                          // view.
+//                                               // the view.
 //                    0,   // Ignored for kUsePref.
 //                    0);  // A minimum width of 0.
 // columns->AddPaddingColumn(kFixedSize, // The padding column is not resizable.
diff --git a/ui/views/widget/desktop_aura/desktop_native_widget_aura.cc b/ui/views/widget/desktop_aura/desktop_native_widget_aura.cc
index 45fbf5d4..1c46b1cd 100644
--- a/ui/views/widget/desktop_aura/desktop_native_widget_aura.cc
+++ b/ui/views/widget/desktop_aura/desktop_native_widget_aura.cc
@@ -906,7 +906,7 @@
     desktop_window_tree_host_->Restore();
 }
 
-void DesktopNativeWidgetAura::SetFullscreen(bool fullscreen) {
+void DesktopNativeWidgetAura::SetFullscreen(bool fullscreen, bool delay) {
   if (content_window_)
     desktop_window_tree_host_->SetFullscreen(fullscreen);
 }
diff --git a/ui/views/widget/desktop_aura/desktop_native_widget_aura.h b/ui/views/widget/desktop_aura/desktop_native_widget_aura.h
index 8dd86cd..4be3ddfb 100644
--- a/ui/views/widget/desktop_aura/desktop_native_widget_aura.h
+++ b/ui/views/widget/desktop_aura/desktop_native_widget_aura.h
@@ -167,7 +167,7 @@
   bool IsMaximized() const override;
   bool IsMinimized() const override;
   void Restore() override;
-  void SetFullscreen(bool fullscreen) override;
+  void SetFullscreen(bool fullscreen, bool delay) override;
   bool IsFullscreen() const override;
   void SetCanAppearInExistingFullscreenSpaces(
       bool can_appear_in_existing_fullscreen_spaces) override;
diff --git a/ui/views/widget/native_widget_aura.cc b/ui/views/widget/native_widget_aura.cc
index 3b63e2b9..15c54966 100644
--- a/ui/views/widget/native_widget_aura.cc
+++ b/ui/views/widget/native_widget_aura.cc
@@ -700,7 +700,7 @@
     window_->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL);
 }
 
-void NativeWidgetAura::SetFullscreen(bool fullscreen) {
+void NativeWidgetAura::SetFullscreen(bool fullscreen, bool delay) {
   if (!window_ || IsFullscreen() == fullscreen)
     return;  // Nothing to do.
 
diff --git a/ui/views/widget/native_widget_aura.h b/ui/views/widget/native_widget_aura.h
index db6effaa..701f5b4a 100644
--- a/ui/views/widget/native_widget_aura.h
+++ b/ui/views/widget/native_widget_aura.h
@@ -126,7 +126,7 @@
   bool IsMaximized() const override;
   bool IsMinimized() const override;
   void Restore() override;
-  void SetFullscreen(bool fullscreen) override;
+  void SetFullscreen(bool fullscreen, bool delay) override;
   bool IsFullscreen() const override;
   void SetCanAppearInExistingFullscreenSpaces(
       bool can_appear_in_existing_fullscreen_spaces) override;
diff --git a/ui/views/widget/native_widget_mac.h b/ui/views/widget/native_widget_mac.h
index 088c98a..174f797 100644
--- a/ui/views/widget/native_widget_mac.h
+++ b/ui/views/widget/native_widget_mac.h
@@ -157,7 +157,7 @@
   bool IsMaximized() const override;
   bool IsMinimized() const override;
   void Restore() override;
-  void SetFullscreen(bool fullscreen) override;
+  void SetFullscreen(bool fullscreen, bool delay) override;
   bool IsFullscreen() const override;
   void SetCanAppearInExistingFullscreenSpaces(
       bool can_appear_in_existing_fullscreen_spaces) override;
diff --git a/ui/views/widget/native_widget_mac.mm b/ui/views/widget/native_widget_mac.mm
index 51358d1..e24526ef 100644
--- a/ui/views/widget/native_widget_mac.mm
+++ b/ui/views/widget/native_widget_mac.mm
@@ -644,10 +644,10 @@
   GetNSWindowMojo()->SetMiniaturized(false);
 }
 
-void NativeWidgetMac::SetFullscreen(bool fullscreen) {
+void NativeWidgetMac::SetFullscreen(bool fullscreen, bool delay) {
   if (!ns_window_host_)
     return;
-  ns_window_host_->SetFullscreen(fullscreen);
+  ns_window_host_->SetFullscreen(fullscreen, delay);
 }
 
 bool NativeWidgetMac::IsFullscreen() const {
diff --git a/ui/views/widget/native_widget_private.h b/ui/views/widget/native_widget_private.h
index 5738ffb..8897a1327 100644
--- a/ui/views/widget/native_widget_private.h
+++ b/ui/views/widget/native_widget_private.h
@@ -195,7 +195,7 @@
   virtual bool IsMaximized() const = 0;
   virtual bool IsMinimized() const = 0;
   virtual void Restore() = 0;
-  virtual void SetFullscreen(bool fullscreen) = 0;
+  virtual void SetFullscreen(bool fullscreen, bool delay) = 0;
   virtual bool IsFullscreen() const = 0;
   virtual void SetCanAppearInExistingFullscreenSpaces(
       bool can_appear_in_existing_fullscreen_spaces) = 0;
diff --git a/ui/views/widget/widget.cc b/ui/views/widget/widget.cc
index 0dcab77..e634630 100644
--- a/ui/views/widget/widget.cc
+++ b/ui/views/widget/widget.cc
@@ -769,12 +769,12 @@
   return native_widget_->IsMinimized();
 }
 
-void Widget::SetFullscreen(bool fullscreen) {
+void Widget::SetFullscreen(bool fullscreen, bool delay) {
   if (IsFullscreen() == fullscreen)
     return;
 
   auto weak_ptr = GetWeakPtr();
-  native_widget_->SetFullscreen(fullscreen);
+  native_widget_->SetFullscreen(fullscreen, delay);
   if (!weak_ptr)
     return;
 
diff --git a/ui/views/widget/widget.h b/ui/views/widget/widget.h
index ef3892d6..62df568e 100644
--- a/ui/views/widget/widget.h
+++ b/ui/views/widget/widget.h
@@ -686,7 +686,11 @@
   bool IsMinimized() const;
 
   // Accessors for fullscreen state.
-  void SetFullscreen(bool fullscreen);
+  // If `delay` is true, some underlying implementations will set their target
+  // fullscreen state and then post a delayed task to request the actual window
+  // transition, in order to handle some platform-specific quirks in specific
+  // fullscreen scenarios. See crbug.com/1210548 and crbug.com/1034783.
+  void SetFullscreen(bool fullscreen, bool delay = false);
   bool IsFullscreen() const;
 
   // macOS: Sets whether the window can share fullscreen windows' spaces.