diff --git a/DEPS b/DEPS
index bf4c843..dc79661 100644
--- a/DEPS
+++ b/DEPS
@@ -280,11 +280,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': 'f6a84dfb53fb1cfe18fd8808061e6f3833a54096',
+  'skia_revision': 'd9eeef0790b96953a58142c888b7947cb80858b9',
   # 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': 'eb6319184087b52ae118c63e47b7f90442673ba7',
+  'v8_revision': 'cbd169bf3e664a2a34be5bc6a22e9b6bc878ae72',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ANGLE
   # and whatever else without interference from each other.
@@ -292,7 +292,7 @@
   # 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': '90fdde3824890723821b9ed5d45c1eecda57b502',
+  'swiftshader_revision': 'cb0b3c682314655e80ae94bef24b0bd8b8aeb935',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling PDFium
   # and whatever else without interference from each other.
@@ -307,7 +307,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Fuchsia sdk
   # and whatever else without interference from each other.
-  'fuchsia_version': 'version:8.20220630.0.1',
+  'fuchsia_version': 'version:8.20220630.1.1',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling google-toolbox-for-mac
   # and whatever else without interference from each other.
@@ -359,7 +359,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': '71ea20dc9691ce2bc8bb3212a3086ded40535936',
+  'devtools_frontend_revision': 'd85ad8d4193a3d0184c09b4236c9766f8e6a95cc',
   # 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.
@@ -395,11 +395,11 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'dawn_revision': '830e75b40159f92ee1eba8ba71ebe9524ccd9a24',
+  'dawn_revision': 'c5b7ff0089e7d437928cbe827bddea0cb1ba4656',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'quiche_revision': 'cb3863a8c4837ea98b48cb8d467adc22f2b937d1',
+  'quiche_revision': '61526b08ab69002558e62781eaeeca9aee8eada4',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ios_webkit
   # and whatever else without interference from each other.
@@ -780,7 +780,7 @@
   },
 
   'src/ios/third_party/material_components_ios/src': {
-      'url': Var('chromium_git') + '/external/github.com/material-components/material-components-ios.git' + '@' + '51910f42e48bdbb5ac8169a2c1761fabc78e7735',
+      'url': Var('chromium_git') + '/external/github.com/material-components/material-components-ios.git' + '@' + 'fdb05bb379b73a67cea8269e5948b9197a037086',
       'condition': 'checkout_ios',
   },
 
@@ -805,7 +805,7 @@
   },
 
   'src/ios/third_party/material_text_accessibility_ios/src': {
-      'url': Var('chromium_git') + '/external/github.com/material-foundation/material-text-accessibility-ios.git' + '@' + '197375261e25ee5d473219d0f353a1f635f5393d',
+      'url': Var('chromium_git') + '/external/github.com/material-foundation/material-text-accessibility-ios.git' + '@' + '8cd910c1c8bbae261ae0d7e873ed96c69a386448',
       'condition': 'checkout_ios',
   },
 
@@ -1156,7 +1156,7 @@
   },
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '0e5881fb4e5d156ffb81ba56758dbed26ad7aed2',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '6628661eba73cdb8e1ce6387837f948d0360fe48',
 
   'src/third_party/devtools-frontend/src':
     Var('chromium_git') + '/devtools/devtools-frontend' + '@' + Var('devtools_frontend_revision'),
@@ -1381,7 +1381,7 @@
     Var('chromium_git') + '/external/libaddressinput.git' + '@' + 'df35d6c42da4fa2759e4cfb592afe33817993b89',
 
   'src/third_party/libaom/source/libaom':
-    Var('aomedia_git') + '/aom.git' + '@' +  '42223eee8546d8f0d6a16c3f5f78a01298139976',
+    Var('aomedia_git') + '/aom.git' + '@' +  '7ace1184f1101e859e1cc9de317b48065f73d2c7',
 
   'src/third_party/libavif/src':
     Var('chromium_git') + '/external/github.com/AOMediaCodec/libavif.git' + '@' + Var('libavif_revision'),
@@ -1439,7 +1439,7 @@
   },
 
   'src/third_party/libvpx/source/libvpx':
-    Var('chromium_git') + '/webm/libvpx.git' + '@' +  'b355ab504667c352d96ab70bcb92165b8fc32813',
+    Var('chromium_git') + '/webm/libvpx.git' + '@' +  '711bef67400f096416cb1ba7f6560e533871490f',
 
   'src/third_party/libwebm/source':
     Var('chromium_git') + '/webm/libwebm.git' + '@' + 'e4fbea0c9751ae8aa86629b197a28d8276a2b0da',
@@ -1553,7 +1553,7 @@
   },
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' + '2c4a70d95140d15a5bc92ad2554e340adf075931',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' + '7bcfdf24e8e973d85a646811ddd6751e8dc8f543',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '6f3e5028eb65d0b4c5fdd792106ac4c84eee1eb3',
@@ -1720,7 +1720,7 @@
     Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + 'e58ed2132aa47ac110a4cce1763abfa34f4fa34e',
 
   'src/third_party/webgpu-cts/src':
-    Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + '7cdef87417356cf64e1b3b596cbe6e7568fd302a',
+    Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + '0bf1255e9d42c27be5b45c506efe434477b2cbef',
 
   'src/third_party/webrtc':
     Var('webrtc_git') + '/src.git' + '@' + 'ed665521e4f1852c3756afc8ac46b2d59224f4b4',
@@ -1796,7 +1796,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@5f5d085354a039d51457cc1a357e0894163d2e3e',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@f5446186d95f214c48b294002701d0646ddf3998',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/PRESUBMIT.py b/PRESUBMIT.py
index 044fc06..e3fa11b 100644
--- a/PRESUBMIT.py
+++ b/PRESUBMIT.py
@@ -1085,6 +1085,8 @@
     r'^third_party[\\/]blink[\\/]web_tests[\\/]external[\\/]wpt[\\/]',
     r'^tools[\\/]perf[\\/]',
     r'^tools[\\/]traceline[\\/]svgui[\\/]startup-release.json',
+    # vscode configuration files allow comments
+    r'^tools[\\/]vscode[\\/]',
 ]
 
 # These are not checked on the public chromium-presubmit trybot.
diff --git a/android_webview/nonembedded/java/src/org/chromium/android_webview/nonembedded/NetworkFetcherTaskTest.java b/android_webview/nonembedded/java/src/org/chromium/android_webview/nonembedded/NetworkFetcherTaskTest.java
index 4c7a73ae3..c12e592 100644
--- a/android_webview/nonembedded/java/src/org/chromium/android_webview/nonembedded/NetworkFetcherTaskTest.java
+++ b/android_webview/nonembedded/java/src/org/chromium/android_webview/nonembedded/NetworkFetcherTaskTest.java
@@ -40,6 +40,7 @@
 
 /** Test NetworkFetcherTask. */
 @RunWith(BaseRobolectricTestRunner.class)
+@SuppressWarnings("DoNotMock") // Mocks GURL.
 public class NetworkFetcherTaskTest {
     private HttpURLConnection mConnection;
     private Context mContext;
diff --git a/ash/app_list/views/app_list_bubble_apps_page.cc b/ash/app_list/views/app_list_bubble_apps_page.cc
index 1e7ce47..8ef0057 100644
--- a/ash/app_list/views/app_list_bubble_apps_page.cc
+++ b/ash/app_list/views/app_list_bubble_apps_page.cc
@@ -639,11 +639,7 @@
           /*has_border=*/false));
   // The icon is scaled down since the button is tiny.
   toggle_continue_section_button_->SetIconSize(16);
-  // TODO(jamescook): Allow the toggle continue button to be keyboard focused.
-  // This will involve fixing custom arrow key focus handling behavior (e.g.
-  // arrow down when focus is in search box).
-  toggle_continue_section_button_->SetFocusBehavior(
-      views::View::FocusBehavior::ACCESSIBLE_ONLY);
+  // See ButtonFocusSkipper in app_list_bubble_view.cc for focus handling.
 }
 
 void AppListBubbleAppsPage::UpdateContinueSectionVisibility() {
diff --git a/ash/app_list/views/app_list_bubble_apps_page.h b/ash/app_list/views/app_list_bubble_apps_page.h
index 89aa15a..bd3b24c 100644
--- a/ash/app_list/views/app_list_bubble_apps_page.h
+++ b/ash/app_list/views/app_list_bubble_apps_page.h
@@ -141,6 +141,9 @@
   void UpdateContinueSectionVisibility();
 
   views::ScrollView* scroll_view() { return scroll_view_; }
+  IconButton* toggle_continue_section_button() {
+    return toggle_continue_section_button_;
+  }
   ScrollableAppsGridView* scrollable_apps_grid_view() {
     return scrollable_apps_grid_view_;
   }
@@ -151,9 +154,6 @@
   views::View* continue_label_container_for_test() {
     return continue_label_container_;
   }
-  IconButton* toggle_continue_section_button_for_test() {
-    return toggle_continue_section_button_;
-  }
   RecentAppsView* recent_apps_for_test() { return recent_apps_; }
   views::Separator* separator_for_test() { return separator_; }
   AppListToastContainerView* toast_container_for_test() {
diff --git a/ash/app_list/views/app_list_bubble_apps_page_unittest.cc b/ash/app_list/views/app_list_bubble_apps_page_unittest.cc
index d42ebca..9ff92c6 100644
--- a/ash/app_list/views/app_list_bubble_apps_page_unittest.cc
+++ b/ash/app_list/views/app_list_bubble_apps_page_unittest.cc
@@ -286,7 +286,7 @@
   // The toggle continue section button has the "hide" tooltip.
   auto* apps_page = helper->GetBubbleAppsPage();
   IconButton* toggle_continue_section_button =
-      apps_page->toggle_continue_section_button_for_test();
+      apps_page->toggle_continue_section_button();
   ASSERT_TRUE(toggle_continue_section_button);
   EXPECT_EQ(toggle_continue_section_button->GetTooltipText(),
             u"Hide all suggestions");
@@ -321,7 +321,7 @@
 
   // Hide the continue section.
   auto* apps_page = helper->GetBubbleAppsPage();
-  LeftClickOn(apps_page->toggle_continue_section_button_for_test());
+  LeftClickOn(apps_page->toggle_continue_section_button());
 
   // Separator and apps grid are animating.
   auto* separator = apps_page->separator_for_test();
@@ -351,7 +351,7 @@
   // The toggle continue section button has the "show" tooltip.
   auto* apps_page = helper->GetBubbleAppsPage();
   IconButton* toggle_continue_section_button =
-      apps_page->toggle_continue_section_button_for_test();
+      apps_page->toggle_continue_section_button();
   ASSERT_TRUE(toggle_continue_section_button);
   EXPECT_EQ(toggle_continue_section_button->GetTooltipText(),
             u"Show all suggestions");
@@ -391,7 +391,7 @@
 
   // Click the show continue section button.
   auto* apps_page = helper->GetBubbleAppsPage();
-  LeftClickOn(apps_page->toggle_continue_section_button_for_test());
+  LeftClickOn(apps_page->toggle_continue_section_button());
 
   // Animations play for continue section, recent apps, separator and apps grid.
   auto* continue_section = helper->GetBubbleContinueSectionView();
diff --git a/ash/app_list/views/app_list_bubble_view.cc b/ash/app_list/views/app_list_bubble_view.cc
index 6a96008..4290825 100644
--- a/ash/app_list/views/app_list_bubble_view.cc
+++ b/ash/app_list/views/app_list_bubble_view.cc
@@ -7,6 +7,7 @@
 #include <algorithm>
 #include <memory>
 #include <utility>
+#include <vector>
 
 #include "ash/accessibility/accessibility_controller_impl.h"
 #include "ash/app_list/app_list_model_provider.h"
@@ -24,6 +25,7 @@
 #include "ash/app_list/views/search_box_view.h"
 #include "ash/app_list/views/search_result_page_dialog_controller.h"
 #include "ash/bubble/bubble_constants.h"
+#include "ash/constants/ash_features.h"
 #include "ash/keyboard/ui/keyboard_ui_controller.h"
 #include "ash/public/cpp/app_list/app_list_config_provider.h"
 #include "ash/public/cpp/metrics_util.h"
@@ -33,6 +35,7 @@
 #include "ash/search_box/search_box_constants.h"
 #include "ash/shell.h"
 #include "ash/style/ash_color_provider.h"
+#include "ash/style/icon_button.h"
 #include "base/bind.h"
 #include "base/check.h"
 #include "base/check_op.h"
@@ -129,27 +132,30 @@
 
 }  // namespace
 
-// Makes focus traversal skip the assistant button when pressing the down arrow
-// key or the up arrow key. Normally views would move focus from the search box
-// to the assistant button on arrow down. However, the assistant button is
-// visually to the right, so this feels weird. Likewise, on arrow up from
-// continue tasks it feels better to put focus directly in the search box.
-class AssistantButtonFocusSkipper : public ui::EventHandler {
+// Makes focus traversal skip the assistant button and the hide continue section
+// button when pressing the down arrow key or the up arrow key. Normally views
+// would move focus from the search box to the assistant button on arrow down.
+// However, these buttons are visually to the right, so this feels weird.
+// Likewise, on arrow up from continue tasks it feels better to put focus
+// directly in the search box.
+class ButtonFocusSkipper : public ui::EventHandler {
  public:
-  explicit AssistantButtonFocusSkipper(views::View* button) : button_(button) {
-    DCHECK(button_);
-    Shell::Get()->AddPreTargetHandler(this);
-  }
+  ButtonFocusSkipper() { Shell::Get()->AddPreTargetHandler(this); }
 
-  ~AssistantButtonFocusSkipper() override {
-    Shell::Get()->RemovePreTargetHandler(this);
+  ~ButtonFocusSkipper() override { Shell::Get()->RemovePreTargetHandler(this); }
+
+  void AddButton(views::View* button) {
+    DCHECK(button);
+    buttons_.push_back(button);
   }
 
   // ui::EventHandler:
   void OnEvent(ui::Event* event) override {
     // Don't adjust focus behavior if the user already focused the button.
-    if (button_->HasFocus())
-      return;
+    for (views::View* button : buttons_) {
+      if (button->HasFocus())
+        return;
+    }
 
     bool skip_focus = false;
     // This class overrides OnEvent() to examine all events so that focus
@@ -160,12 +166,14 @@
         skip_focus = true;
       }
     }
-    button_->SetFocusBehavior(skip_focus ? views::View::FocusBehavior::NEVER
-                                         : views::View::FocusBehavior::ALWAYS);
+    for (views::View* button : buttons_) {
+      button->SetFocusBehavior(skip_focus ? views::View::FocusBehavior::NEVER
+                                          : views::View::FocusBehavior::ALWAYS);
+    }
   }
 
  private:
-  views::View* const button_;
+  std::vector<views::View*> buttons_;
 };
 
 AppListBubbleView::AppListBubbleView(
@@ -246,9 +254,9 @@
   params.increase_child_view_padding = true;
   search_box_view_->Init(params);
 
-  assistant_button_focus_skipper_ =
-      std::make_unique<AssistantButtonFocusSkipper>(
-          search_box_view_->assistant_button());
+  // Skip the assistant button on arrow up/down in app list.
+  button_focus_skipper_ = std::make_unique<ButtonFocusSkipper>();
+  button_focus_skipper_->AddButton(search_box_view_->assistant_button());
 
   // The main view has a solid color layer, so the separator needs its own
   // layer to visibly paint.
@@ -277,6 +285,11 @@
           view_delegate_, drag_and_drop_host, GetAppListConfig(),
           a11y_announcer_.get(), /*folder_controller=*/this,
           /*search_box=*/search_box_view_));
+  if (features::IsLauncherHideContinueSectionEnabled()) {
+    // Skip the "hide continue section" button on arrow up/down in app list.
+    button_focus_skipper_->AddButton(
+        apps_page_->toggle_continue_section_button());
+  }
 
   search_page_ =
       pages_container->AddChildView(std::make_unique<AppListBubbleSearchPage>(
@@ -634,8 +647,8 @@
 
 void AppListBubbleView::OnSearchBoxKeyEvent(ui::KeyEvent* event) {
   // Nothing to do. Search box starts focused, and FocusManager handles arrow
-  // key traversal from there. AssistantButtonFocusSkipper above handles
-  // skipping the assistant button on arrow up and arrow down.
+  // key traversal from there. ButtonFocusSkipper above handles skipping the
+  // assistant and hide continue section buttons on arrow up and arrow down.
 }
 
 bool AppListBubbleView::CanSelectSearchResults() {
diff --git a/ash/app_list/views/app_list_bubble_view.h b/ash/app_list/views/app_list_bubble_view.h
index 4fe03803..04bb18d 100644
--- a/ash/app_list/views/app_list_bubble_view.h
+++ b/ash/app_list/views/app_list_bubble_view.h
@@ -25,7 +25,7 @@
 class AppListFolderItem;
 class AppListFolderView;
 class AppListViewDelegate;
-class AssistantButtonFocusSkipper;
+class ButtonFocusSkipper;
 class FolderBackgroundView;
 class SearchBoxView;
 class SearchResultPageDialogController;
@@ -197,7 +197,7 @@
   base::OnceClosure on_hide_animation_ended_;
 
   // See class comment in .cc file.
-  std::unique_ptr<AssistantButtonFocusSkipper> assistant_button_focus_skipper_;
+  std::unique_ptr<ButtonFocusSkipper> button_focus_skipper_;
 
   base::WeakPtrFactory<AppListBubbleView> weak_factory_{this};
 };
diff --git a/ash/capture_mode/capture_mode_unittests.cc b/ash/capture_mode/capture_mode_unittests.cc
index e1426cb..9012771a 100644
--- a/ash/capture_mode/capture_mode_unittests.cc
+++ b/ash/capture_mode/capture_mode_unittests.cc
@@ -1174,6 +1174,33 @@
   // Test passes if no crash.
 }
 
+// Tests behavior of a capture mode session if the active display is removed,
+// countdown running, fullscreen window, and in overview mode.
+TEST_F(CaptureModeTest,
+       DisplayRemovalWithCountdownVisibleFullscreenWindowAndInOverview) {
+  UpdateDisplay("800x700,801+0-800x700");
+
+  // Start capture mode on the secondary display.
+  auto recorded_window = CreateTestWindow(gfx::Rect(1000, 200, 400, 400));
+  auto* controller =
+      StartCaptureSession(CaptureModeSource::kWindow, CaptureModeType::kVideo);
+  GetEventGenerator()->MoveMouseToCenterOf(recorded_window.get());
+  // Make the window fullscreen. This is important as the corner case is
+  // moving a fullscreen window triggers the shelf to occur, which changes
+  // display metrics.
+  recorded_window->SetProperty(aura::client::kShowStateKey,
+                               ui::SHOW_STATE_FULLSCREEN);
+
+  auto* session = controller->capture_mode_session();
+
+  EnterOverview();
+  RemoveSecondaryDisplay();
+
+  ASSERT_EQ(Shell::GetAllRootWindows()[0], session->current_root());
+
+  // Test passes if no crash.
+}
+
 // Tests that using fullscreen or window source, moving the mouse across
 // displays will change the root window of the capture session.
 TEST_F(CaptureModeTest, MultiDisplayFullscreenOrWindowSourceRootWindow) {
diff --git a/ash/components/arc/input_overlay/resources/com.NikSanTech.FireDots3D.json b/ash/components/arc/input_overlay/resources/com.NikSanTech.FireDots3D.json
index 83060e0e..3613eaf8 100644
--- a/ash/components/arc/input_overlay/resources/com.NikSanTech.FireDots3D.json
+++ b/ash/components/arc/input_overlay/resources/com.NikSanTech.FireDots3D.json
@@ -20,7 +20,7 @@
           ],
           "anchor_to_target": [
             0.4957102,
-            -0.38648364
+            -0.2344245
           ]
         }
       ]
diff --git a/ash/components/arc/input_overlay/resources/com.Stellar.StackFall.json b/ash/components/arc/input_overlay/resources/com.Stellar.StackFall.json
index 83060e0e..9b4648e 100644
--- a/ash/components/arc/input_overlay/resources/com.Stellar.StackFall.json
+++ b/ash/components/arc/input_overlay/resources/com.Stellar.StackFall.json
@@ -20,10 +20,32 @@
           ],
           "anchor_to_target": [
             0.4957102,
-            -0.38648364
+            -0.24762407
           ]
         }
       ]
+    },
+    {
+      "id": 1,
+      "input_sources": [
+        "keyboard"
+      ],
+      "name": "boost",
+      "key": "Enter",
+      "location": [
+        {
+          "type": "position",
+          "anchor": [
+            0,
+            1
+          ],
+          "anchor_to_target": [
+            0.04766444,
+            -0.38648364
+          ],
+          "max_x": 50
+        }
+      ]
     }
   ]
 }
diff --git a/ash/components/arc/input_overlay/resources/com.azurgames.stackball.json b/ash/components/arc/input_overlay/resources/com.azurgames.stackball.json
index 83060e0e..3be705e 100644
--- a/ash/components/arc/input_overlay/resources/com.azurgames.stackball.json
+++ b/ash/components/arc/input_overlay/resources/com.azurgames.stackball.json
@@ -20,7 +20,7 @@
           ],
           "anchor_to_target": [
             0.4957102,
-            -0.38648364
+            -0.19482577
           ]
         }
       ]
diff --git a/ash/components/arc/input_overlay/resources/com.elegant.stack.ball.blast.crush.json b/ash/components/arc/input_overlay/resources/com.elegant.stack.ball.blast.crush.json
index 83060e0e..42301c9 100644
--- a/ash/components/arc/input_overlay/resources/com.elegant.stack.ball.blast.crush.json
+++ b/ash/components/arc/input_overlay/resources/com.elegant.stack.ball.blast.crush.json
@@ -20,7 +20,7 @@
           ],
           "anchor_to_target": [
             0.4957102,
-            -0.38648364
+            -0.20802534
           ]
         }
       ]
diff --git a/ash/components/arc/input_overlay/resources/com.hiroba.helix.json b/ash/components/arc/input_overlay/resources/com.hiroba.helix.json
index 4cee715..b42e8e7 100644
--- a/ash/components/arc/input_overlay/resources/com.hiroba.helix.json
+++ b/ash/components/arc/input_overlay/resources/com.hiroba.helix.json
@@ -20,7 +20,7 @@
           ],
           "anchor_to_target": [
             0.5004859,
-            -0.18508287
+            -0.16022099
           ]
         }
       ]
diff --git a/ash/components/arc/input_overlay/resources/com.match3blaster.DropStackBallFall.json b/ash/components/arc/input_overlay/resources/com.match3blaster.DropStackBallFall.json
index 83060e0e..3030843 100644
--- a/ash/components/arc/input_overlay/resources/com.match3blaster.DropStackBallFall.json
+++ b/ash/components/arc/input_overlay/resources/com.match3blaster.DropStackBallFall.json
@@ -20,7 +20,7 @@
           ],
           "anchor_to_target": [
             0.4957102,
-            -0.38648364
+            -0.2872228
           ]
         }
       ]
diff --git a/ash/components/arc/input_overlay/resources/com.stack.ball.destroy.wood.json b/ash/components/arc/input_overlay/resources/com.stack.ball.destroy.wood.json
index 83060e0e..3e6d63b 100644
--- a/ash/components/arc/input_overlay/resources/com.stack.ball.destroy.wood.json
+++ b/ash/components/arc/input_overlay/resources/com.stack.ball.destroy.wood.json
@@ -20,7 +20,7 @@
           ],
           "anchor_to_target": [
             0.4957102,
-            -0.38648364
+            -0.18690602
           ]
         }
       ]
diff --git a/ash/components/arc/input_overlay/resources/com.twist.stackball.json b/ash/components/arc/input_overlay/resources/com.twist.stackball.json
index 83060e0e..3e6d63b 100644
--- a/ash/components/arc/input_overlay/resources/com.twist.stackball.json
+++ b/ash/components/arc/input_overlay/resources/com.twist.stackball.json
@@ -20,7 +20,7 @@
           ],
           "anchor_to_target": [
             0.4957102,
-            -0.38648364
+            -0.18690602
           ]
         }
       ]
diff --git a/ash/components/audio/BUILD.gn b/ash/components/audio/BUILD.gn
index f9deca2..757bb6f 100644
--- a/ash/components/audio/BUILD.gn
+++ b/ash/components/audio/BUILD.gn
@@ -40,6 +40,23 @@
   ]
 }
 
+component("in_process_audio_config") {
+  sources = [
+    "in_process_instance.cc",
+    "in_process_instance.h",
+  ]
+
+  defines = [ "IS_IN_PROCESS_AUDIO_CONFIG_IMPL" ]
+
+  public_deps = [
+    "//ash/components/audio/public/mojom",
+    "//ash/constants",
+    "//mojo/public/cpp/bindings",
+  ]
+
+  deps = [ ":audio" ]
+}
+
 source_set("unit_tests") {
   testonly = true
   deps = [
diff --git a/ash/components/audio/in_process_instance.cc b/ash/components/audio/in_process_instance.cc
new file mode 100644
index 0000000..e6359e9
--- /dev/null
+++ b/ash/components/audio/in_process_instance.cc
@@ -0,0 +1,21 @@
+// Copyright 2022 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 "ash/components/audio/in_process_instance.h"
+
+#include "ash/components/audio/cros_audio_config_impl.h"
+#include "ash/constants/ash_features.h"
+#include "base/check.h"
+#include "base/no_destructor.h"
+
+namespace ash::audio_config {
+
+void BindToInProcessInstance(
+    mojo::PendingReceiver<mojom::CrosAudioConfig> pending_receiver) {
+  CHECK(ash::features::IsAudioSettingsPageEnabled());
+  static base::NoDestructor<CrosAudioConfigImpl> instance;
+  instance->BindPendingReceiver(std::move(pending_receiver));
+}
+
+}  // namespace ash::audio_config
diff --git a/ash/components/audio/in_process_instance.h b/ash/components/audio/in_process_instance.h
new file mode 100644
index 0000000..a7d7ae56
--- /dev/null
+++ b/ash/components/audio/in_process_instance.h
@@ -0,0 +1,21 @@
+// Copyright 2022 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 ASH_COMPONENTS_AUDIO_IN_PROCESS_INSTANCE_H_
+#define ASH_COMPONENTS_AUDIO_IN_PROCESS_INSTANCE_H_
+
+#include "ash/components/audio/public/mojom/cros_audio_config.mojom.h"
+#include "base/component_export.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+
+namespace ash::audio_config {
+
+// Binds to an instance of CrosAudioConfig from within the browser process.
+COMPONENT_EXPORT(IN_PROCESS_AUDIO_CONFIG)
+void BindToInProcessInstance(
+    mojo::PendingReceiver<mojom::CrosAudioConfig> pending_receiver);
+
+}  // namespace ash::audio_config
+
+#endif  // ASH_COMPONENTS_AUDIO_IN_PROCESS_INSTANCE_H_
diff --git a/ash/constants/ash_pref_names.cc b/ash/constants/ash_pref_names.cc
index 9ab6cec..f458f26 100644
--- a/ash/constants/ash_pref_names.cc
+++ b/ash/constants/ash_pref_names.cc
@@ -721,6 +721,9 @@
 // A boolean pref indicating whether the camera is allowed to be used.
 const char kUserCameraAllowed[] = "ash.user.camera_allowed";
 
+// A boolean pref indicating whether the microphone is allowed to be used.
+const char kUserMicrophoneAllowed[] = "ash.user.microphone_allowed";
+
 // A boolean pref which determines whether tap-dragging is enabled.
 const char kTapDraggingEnabled[] = "settings.touchpad.enable_tap_dragging";
 
diff --git a/ash/constants/ash_pref_names.h b/ash/constants/ash_pref_names.h
index 6d6da85..d0f41519 100644
--- a/ash/constants/ash_pref_names.h
+++ b/ash/constants/ash_pref_names.h
@@ -328,6 +328,7 @@
 extern const char kSystemTrayExpanded[];
 
 COMPONENT_EXPORT(ASH_CONSTANTS) extern const char kUserCameraAllowed[];
+COMPONENT_EXPORT(ASH_CONSTANTS) extern const char kUserMicrophoneAllowed[];
 
 COMPONENT_EXPORT(ASH_CONSTANTS) extern const char kTapDraggingEnabled[];
 COMPONENT_EXPORT(ASH_CONSTANTS) extern const char kTouchpadEnabled[];
diff --git a/ash/display/window_tree_host_manager.cc b/ash/display/window_tree_host_manager.cc
index f3e756f..fe290b37 100644
--- a/ash/display/window_tree_host_manager.cc
+++ b/ash/display/window_tree_host_manager.cc
@@ -612,6 +612,8 @@
   RootWindowController* controller =
       RootWindowController::ForWindow(root_being_deleted);
   DCHECK(controller);
+  // Some code relies on this being called before MoveWindowsTo().
+  Shell::Get()->OnRootWindowWillShutdown(root_being_deleted);
   aura::Window* primary_root_after_host_deletion =
       GetRootWindowForDisplayId(GetPrimaryDisplayId());
   controller->MoveWindowsTo(primary_root_after_host_deletion);
diff --git a/ash/media/media_controller_impl.cc b/ash/media/media_controller_impl.cc
index 9b1c5718..65d6d21 100644
--- a/ash/media/media_controller_impl.cc
+++ b/ash/media/media_controller_impl.cc
@@ -50,6 +50,7 @@
 void MediaControllerImpl::RegisterProfilePrefs(PrefRegistrySimple* registry) {
   registry->RegisterBooleanPref(prefs::kLockScreenMediaControlsEnabled, true);
   registry->RegisterBooleanPref(prefs::kUserCameraAllowed, true);
+  registry->RegisterBooleanPref(prefs::kUserMicrophoneAllowed, true);
 }
 
 bool MediaControllerImpl::AreLockScreenMediaKeysEnabled() const {
diff --git a/ash/public/cpp/BUILD.gn b/ash/public/cpp/BUILD.gn
index bbb3ee9ed..982c23d 100644
--- a/ash/public/cpp/BUILD.gn
+++ b/ash/public/cpp/BUILD.gn
@@ -98,6 +98,8 @@
     "assistant/controller/assistant_suggestions_controller.h",
     "assistant/controller/assistant_ui_controller.cc",
     "assistant/controller/assistant_ui_controller.h",
+    "audio_config_service.cc",
+    "audio_config_service.h",
     "back_gesture_contextual_nudge_controller.h",
     "back_gesture_contextual_nudge_delegate.h",
     "bluetooth_config_service.cc",
@@ -364,6 +366,7 @@
 
   deps = [
     "//ash/components/audio",
+    "//ash/components/audio:in_process_audio_config",
     "//ash/constants",
     "//ash/public/cpp/ambient/proto",
     "//ash/resources/vector_icons",
diff --git a/ash/public/cpp/audio_config_service.cc b/ash/public/cpp/audio_config_service.cc
new file mode 100644
index 0000000..add4a883
--- /dev/null
+++ b/ash/public/cpp/audio_config_service.cc
@@ -0,0 +1,16 @@
+// Copyright 2022 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 "ash/public/cpp/audio_config_service.h"
+
+#include "ash/components/audio/in_process_instance.h"
+
+namespace ash {
+
+void GetAudioConfigService(
+    mojo::PendingReceiver<ash::audio_config::mojom::CrosAudioConfig> receiver) {
+  ash::audio_config::BindToInProcessInstance(std::move(receiver));
+}
+
+}  // namespace ash
diff --git a/ash/public/cpp/audio_config_service.h b/ash/public/cpp/audio_config_service.h
new file mode 100644
index 0000000..5929811
--- /dev/null
+++ b/ash/public/cpp/audio_config_service.h
@@ -0,0 +1,26 @@
+// Copyright 2022 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 ASH_PUBLIC_CPP_AUDIO_CONFIG_SERVICE_H_
+#define ASH_PUBLIC_CPP_AUDIO_CONFIG_SERVICE_H_
+
+#include "ash/components/audio/public/mojom/cros_audio_config.mojom.h"
+#include "ash/public/cpp/ash_public_export.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+
+namespace ash {
+
+// Binds |pending_receiver| to CrosAudioConfig. Clients should use this
+// function as the main entrypoint to the Mojo API.
+//
+// Internally, this function delegates to an implementation in the browser
+// process. We declare this function in //ash to ensure that clients do not have
+// any direct dependencies on the implementation.
+ASH_PUBLIC_EXPORT void GetAudioConfigService(
+    mojo::PendingReceiver<ash::audio_config::mojom::CrosAudioConfig>
+        pending_receiver);
+
+}  // namespace ash
+
+#endif  // ASH_PUBLIC_CPP_AUDIO_CONFIG_SERVICE_H_
diff --git a/ash/root_window_controller.cc b/ash/root_window_controller.cc
index 7694ff3..06d97d5 100644
--- a/ash/root_window_controller.cc
+++ b/ash/root_window_controller.cc
@@ -645,8 +645,6 @@
 }
 
 void RootWindowController::Shutdown() {
-  Shell::Get()->OnRootWindowWillShutdown(GetRootWindow());
-
   auto targeter = GetRootWindow()->SetEventTargeter(
       std::make_unique<aura::NullWindowTargeter>());
 
diff --git a/ash/webui/os_feedback_ui/resources/help_content.html b/ash/webui/os_feedback_ui/resources/help_content.html
index 038a101e..e0211f51 100644
--- a/ash/webui/os_feedback_ui/resources/help_content.html
+++ b/ash/webui/os_feedback_ui/resources/help_content.html
@@ -17,8 +17,9 @@
     text-decoration: none;
   }
 
-  iron-icon {
+  .help-item-icon {
     padding-inline-end: 12px;
+    --iron-icon-fill-color: var(--cros-text-color-secondary);
   }
 </style>
 <div id="helpContentContainer">
@@ -27,7 +28,7 @@
     <template>
       <div class="help-item">
         <a href="[[getUrl_(item)]]" target="_blank">
-          <iron-icon icon="[[getIcon_(item.contentType)]]"></iron-icon>
+          <iron-icon icon="[[getIcon_(item.contentType)]]" class="help-item-icon"></iron-icon>
           <span>[[getTitle_(item)]]</span>
         </a>
       </div>
diff --git a/ash/webui/os_feedback_ui/resources/help_resources_icons.html b/ash/webui/os_feedback_ui/resources/help_resources_icons.html
index d5eef59..3fa438a 100644
--- a/ash/webui/os_feedback_ui/resources/help_resources_icons.html
+++ b/ash/webui/os_feedback_ui/resources/help_resources_icons.html
@@ -294,11 +294,10 @@
       xmlns="http://www.w3.org/2000/svg">
     <defs>
       <g id="article">
-        <path d="M7 7H13V9H7V7Z" fill="#202124"></path>
-        <path d="M11 10H7V12H11V10Z" fill="#202124"></path>
+        <path d="M7 7H13V9H7V7Z"></path>
+        <path d="M11 10H7V12H11V10Z"></path>
         <path fill-rule="evenodd" clip-rule="evenodd"
-            d="M5 3H15C16.1 3 17 3.9 17 5V15C17 16.1 16.1 17 15 17H5C3.9 17 3 16.1 3 15V5C3 3.9 3.9 3 5 3ZM5 15H15V5H5V15Z"
-            fill="#202124"></path>
+            d="M5 3H15C16.1 3 17 3.9 17 5V15C17 16.1 16.1 17 15 17H5C3.9 17 3 16.1 3 15V5C3 3.9 3.9 3 5 3ZM5 15H15V5H5V15Z"></path>
       </g>
     </defs>
   </svg>
@@ -307,8 +306,7 @@
           <defs>
       <g id="forum">
         <path fill-rule="evenodd" clip-rule="evenodd"
-            d="M4 4V10H13V4H4ZM3 2C2.44772 2 2 2.44772 2 3V11V15L5 12H14C14.5523 12 15 11.5523 15 11V3C15 2.44772 14.5523 2 14 2H3ZM16 6V13H6V14C6 14.5523 6.44771 15 7 15H14L18 19V15V13V7C18 6.44772 17.5523 6 17 6H16Z"
-            fill="#202124"></path>
+            d="M4 4V10H13V4H4ZM3 2C2.44772 2 2 2.44772 2 3V11V15L5 12H14C14.5523 12 15 11.5523 15 11V3C15 2.44772 14.5523 2 14 2H3ZM16 6V13H6V14C6 14.5523 6.44771 15 7 15H14L18 19V15V13V7C18 6.44772 17.5523 6 17 6H16Z"></path>
       </g>
     </defs>
   </svg>
diff --git a/ash/webui/os_feedback_ui/resources/share_data_page.html b/ash/webui/os_feedback_ui/resources/share_data_page.html
index f6c285a..034c9c4 100644
--- a/ash/webui/os_feedback_ui/resources/share_data_page.html
+++ b/ash/webui/os_feedback_ui/resources/share_data_page.html
@@ -38,6 +38,10 @@
     width: 50%;
   }
 
+  #screenshotCheckLabel {
+    flex: 1;
+  }
+
   #screenshotImage {
     display: block;
     height: auto;
diff --git a/ash/webui/personalization_app/personalization_app_ui.cc b/ash/webui/personalization_app/personalization_app_ui.cc
index 1541601..043c34c 100644
--- a/ash/webui/personalization_app/personalization_app_ui.cc
+++ b/ash/webui/personalization_app/personalization_app_ui.cc
@@ -77,7 +77,8 @@
       {"dailyRefresh", IDS_PERSONALIZATION_APP_DAILY_REFRESH},
       {"unknownImageAttribution",
        IDS_PERSONALIZATION_APP_UNKNOWN_IMAGE_ATTRIBUTION},
-      {"networkError", IDS_PERSONALIZATION_APP_NETWORK_ERROR},
+      {"wallpaperNetworkError",
+       IDS_PERSONALIZATION_APP_WALLPAPER_NETWORK_ERROR},
       {"ariaLabelLoading", IDS_PERSONALIZATION_APP_ARIA_LABEL_LOADING},
       // Using old wallpaper app error string pending final revision.
       // TODO(b/195609442)
@@ -199,6 +200,8 @@
        IDS_PERSONALIZATION_APP_AMBIENT_MODE_TURN_ON_LABEL},
       {"ariaLabelChangeScreensaver",
        IDS_PERSONALIZATION_APP_ARIA_LABEL_CHANGE_SCREENSAVER},
+      {"ambientModeNetworkError",
+       IDS_PERSONALIZATION_APP_AMBIENT_MODE_NETWORK_ERROR},
 
       // Keyboard backlight strings
       {"keyboardBacklightTitle",
diff --git a/ash/webui/personalization_app/resources/trusted/ambient/ambient_preview_element.ts b/ash/webui/personalization_app/resources/trusted/ambient/ambient_preview_element.ts
index 6972f057..f85a621 100644
--- a/ash/webui/personalization_app/resources/trusted/ambient/ambient_preview_element.ts
+++ b/ash/webui/personalization_app/resources/trusted/ambient/ambient_preview_element.ts
@@ -17,6 +17,7 @@
 import {Url} from 'chrome://resources/mojo/url/mojom/url.mojom-webui.js';
 
 import {isNonEmptyArray} from '../../common/utils.js';
+import {setErrorAction} from '../personalization_actions.js';
 import {AmbientModeAlbum, TopicSource} from '../personalization_app.mojom-webui.js';
 import {logAmbientModeOptInUMA} from '../personalization_metrics_logger.js';
 import {Paths, PersonalizationRouter} from '../personalization_router_element.js';
@@ -64,6 +65,7 @@
         type: Boolean,
         computed:
             'computeLoading_(ambientModeEnabled_, albums_, topicSource_, googlePhotosAlbumsPreviews_)',
+        observer: 'onLoadingChanged_',
       },
       googlePhotosAlbumsPreviews_: {
         type: Array,
@@ -88,6 +90,8 @@
   private googlePhotosAlbumsPreviews_: Url[]|null;
   private collageImages_: Url[];
 
+  private loadingTimeoutId_: number|null = null;
+
   override ready() {
     super.ready();
     AmbientObserver.initAmbientObserverIfNeeded();
@@ -110,6 +114,20 @@
         this.topicSource_ === null || this.googlePhotosAlbumsPreviews_ === null;
   }
 
+  private onLoadingChanged_(value: boolean) {
+    if (!value && this.loadingTimeoutId_) {
+      window.clearTimeout(this.loadingTimeoutId_);
+      this.loadingTimeoutId_ = null;
+      return;
+    }
+    if (value && !this.loadingTimeoutId_) {
+      this.loadingTimeoutId_ = window.setTimeout(
+          () => this.dispatch(
+              setErrorAction({message: this.i18n('ambientModeNetworkError')})),
+          60 * 1000);
+    }
+  }
+
   /** Enable ambient mode and navigates to the ambient subpage. */
   private async onClickAmbientModeButton_(event: Event) {
     assert(this.ambientModeEnabled_ === false);
diff --git a/ash/webui/personalization_app/resources/trusted/personalization_reducers.ts b/ash/webui/personalization_app/resources/trusted/personalization_reducers.ts
index 532ff193..04f077b 100644
--- a/ash/webui/personalization_app/resources/trusted/personalization_reducers.ts
+++ b/ash/webui/personalization_app/resources/trusted/personalization_reducers.ts
@@ -74,7 +74,8 @@
       if (isNonEmptyArray(images) &&
           !globalState.wallpaper.loading.collections &&
           !isNonEmptyArray(globalState.wallpaper.backdrop.collections)) {
-        return state || {message: loadTimeData.getString('networkError')};
+        return state ||
+            {message: loadTimeData.getString('wallpaperNetworkError')};
       }
       return state;
     case WallpaperActionName.SET_COLLECTIONS:
@@ -82,7 +83,8 @@
       if (!globalState.wallpaper.loading.local.images &&
           isNonEmptyArray(globalState.wallpaper.local.images) &&
           !isNonEmptyArray(collections)) {
-        return state || {message: loadTimeData.getString('networkError')};
+        return state ||
+            {message: loadTimeData.getString('wallpaperNetworkError')};
       }
       return state;
     case PersonalizationActionName.SET_ERROR:
diff --git a/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_error_element.html b/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_error_element.html
index ca51260..d9bdc2e2 100644
--- a/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_error_element.html
+++ b/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_error_element.html
@@ -69,4 +69,4 @@
     </clipPath>
   </defs>
 </svg>
-<p>$i18n{networkError}</p>
+<p>$i18n{wallpaperNetworkError}</p>
diff --git a/ash/wm/float/float_controller_unittest.cc b/ash/wm/float/float_controller_unittest.cc
index 28239ca..dd5542a 100644
--- a/ash/wm/float/float_controller_unittest.cc
+++ b/ash/wm/float/float_controller_unittest.cc
@@ -12,12 +12,16 @@
 #include "ash/public/cpp/shelf_config.h"
 #include "ash/shell.h"
 #include "ash/test/ash_test_base.h"
+#include "ash/wm/overview/overview_test_util.h"
 #include "ash/wm/tablet_mode/tablet_mode_controller.h"
 #include "base/test/scoped_feature_list.h"
 #include "chromeos/ui/frame/immersive/immersive_fullscreen_controller.h"
 #include "chromeos/ui/wm/features.h"
 #include "ui/aura/test/test_window_delegate.h"
 #include "ui/base/hit_test.h"
+#include "ui/compositor/layer.h"
+#include "ui/compositor/layer_animator.h"
+#include "ui/compositor/scoped_animation_duration_scale_mode.h"
 #include "ui/display/test/display_manager_test_api.h"
 #include "ui/wm/core/window_util.h"
 
@@ -268,4 +272,34 @@
             window->bounds().bottom_left());
 }
 
+// Tests that a floated window animates to and from overview.
+TEST_F(WindowFloatTest, FloatWindowAnimatesInOverview) {
+  std::unique_ptr<aura::Window> floated_window = CreateTestWindow();
+  std::unique_ptr<aura::Window> maximized_window = CreateTestWindow();
+
+  wm::ActivateWindow(floated_window.get());
+  PressAndReleaseKey(ui::VKEY_F, ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN);
+  ASSERT_TRUE(WindowState::Get(floated_window.get())->IsFloated());
+  const WMEvent maximize_event(WM_EVENT_MAXIMIZE);
+  WindowState::Get(maximized_window.get())->OnWMEvent(&maximize_event);
+
+  // Activate 'maximized_window'. If the other window was not floated, then it
+  // would be hidden behind the maximized window and not animate.
+  wm::ActivateWindow(maximized_window.get());
+
+  // Enter overview, both windows should animate when entering overview, since
+  // both are visible to the user.
+  ui::ScopedAnimationDurationScaleMode test_duration_mode(
+      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
+  ToggleOverview();
+  EXPECT_TRUE(floated_window->layer()->GetAnimator()->is_animating());
+  EXPECT_TRUE(maximized_window->layer()->GetAnimator()->is_animating());
+
+  // Both windows should animate when exiting overview as well.
+  WaitForOverviewEnterAnimation();
+  ToggleOverview();
+  EXPECT_TRUE(floated_window->layer()->GetAnimator()->is_animating());
+  EXPECT_TRUE(maximized_window->layer()->GetAnimator()->is_animating());
+}
+
 }  // namespace ash
diff --git a/ash/wm/overview/overview_grid.cc b/ash/wm/overview/overview_grid.cc
index 9abc31a..2d7144c 100644
--- a/ash/wm/overview/overview_grid.cc
+++ b/ash/wm/overview/overview_grid.cc
@@ -1103,15 +1103,24 @@
       NOTREACHED();
   }
 
-  auto is_always_on_top_item = [](OverviewItem* item) -> bool {
+  // On top items are items that are higher up on the z-order, or in the always
+  // on top or float containers.
+  auto is_on_top_item = [](OverviewItem* item) -> bool {
     DCHECK(item);
-    return item->GetWindow()->GetProperty(aura::client::kZOrderingKey) !=
-           ui::ZOrderLevel::kNormal;
+    if (item->GetWindow()->GetProperty(aura::client::kZOrderingKey) !=
+        ui::ZOrderLevel::kNormal) {
+      return true;
+    }
+
+    aura::Window* parent = item->GetWindow()->parent();
+    aura::Window* root = parent->GetRootWindow();
+    return parent == root->GetChildById(kShellWindowId_AlwaysOnTopContainer) ||
+           parent == root->GetChildById(kShellWindowId_FloatContainer);
   };
 
-  // Create a copy of `window_list_` which has the selected item and
-  // always on top windows in the front.
-  std::vector<OverviewItem*> always_on_top_items;
+  // Create a copy of `window_list_` which has the selected item and on top
+  // windows in the front.
+  std::vector<OverviewItem*> on_top_items;
   std::vector<OverviewItem*> regular_items;
   for (const std::unique_ptr<OverviewItem>& item : window_list_) {
     OverviewItem* item_ptr = item.get();
@@ -1120,24 +1129,23 @@
     if (item_ptr == selected_item)
       continue;
 
-    if (is_always_on_top_item(item_ptr))
-      always_on_top_items.push_back(item_ptr);
+    if (is_on_top_item(item_ptr))
+      on_top_items.push_back(item_ptr);
     else
       regular_items.push_back(item_ptr);
   }
 
   // Construct `items` so they are ordered like so.
-  //   1) Always on top window that is selected.
-  //   2) Always on top window.
-  //   3) Selected window which is not always on top.
+  //   1) Selected window which is on top.
+  //   2) On top windows.
+  //   3) Selected window which is not on top.
   //   4) Regular window.
   // Windows in the same group maintain their ordering from `window_list`.
   std::vector<OverviewItem*> items;
-  if (selected_item && is_always_on_top_item(selected_item))
+  if (selected_item && is_on_top_item(selected_item))
     items.insert(items.begin(), selected_item);
-  items.insert(items.end(), always_on_top_items.begin(),
-               always_on_top_items.end());
-  if (selected_item && !is_always_on_top_item(selected_item))
+  items.insert(items.end(), on_top_items.begin(), on_top_items.end());
+  if (selected_item && !is_on_top_item(selected_item))
     items.insert(items.end(), selected_item);
   items.insert(items.end(), regular_items.begin(), regular_items.end());
 
diff --git a/base/task/current_thread.h b/base/task/current_thread.h
index 2b8cc52..c1a31795 100644
--- a/base/task/current_thread.h
+++ b/base/task/current_thread.h
@@ -128,7 +128,8 @@
   // Registers a OnceClosure to be called on this thread the next time it goes
   // idle. This is meant for internal usage; callers should use BEST_EFFORT
   // tasks instead of this for generic work that needs to wait until quiescence
-  // to run. There can only be one OnNextIdleCallback at a time.
+  // to run. There can only be one OnNextIdleCallback at a time. Can be called
+  // with a null callback to clear any potentially pending callbacks.
   void RegisterOnNextIdleCallback(OnceClosure on_next_idle_callback);
 
   // Enables nested task processing in scope of an upcoming native message loop.
diff --git a/base/task/sequence_manager/sequence_manager_impl.cc b/base/task/sequence_manager/sequence_manager_impl.cc
index b15815b..61bf915c 100644
--- a/base/task/sequence_manager/sequence_manager_impl.cc
+++ b/base/task/sequence_manager/sequence_manager_impl.cc
@@ -1219,7 +1219,7 @@
 
 void SequenceManagerImpl::RegisterOnNextIdleCallback(
     OnceClosure on_next_idle_callback) {
-  DCHECK(!main_thread_only().on_next_idle_callback);
+  DCHECK(!main_thread_only().on_next_idle_callback || !on_next_idle_callback);
   main_thread_only().on_next_idle_callback = std::move(on_next_idle_callback);
 }
 
diff --git a/build/android/test/incremental_javac_gn/incremental_javac_test_android_library.py b/build/android/test/incremental_javac_gn/incremental_javac_test_android_library.py
index e0f7e1f..c84cff0d 100755
--- a/build/android/test/incremental_javac_gn/incremental_javac_test_android_library.py
+++ b/build/android/test/incremental_javac_gn/incremental_javac_test_android_library.py
@@ -116,9 +116,8 @@
       # GOMA does not work with non-standard output directories.
       'use_goma = false',
   ]
-  _copy_and_append_gn_args(
-      options.gn_args_path, out_gn_args_path,
-      extra_gn_args + ['incremental_javac_test_toggle_gn = false'])
+  _copy_and_append_gn_args(options.gn_args_path, out_gn_args_path,
+                           extra_gn_args)
 
   _run_gn([
       '--root-target=' + options.target_name, 'gen',
@@ -133,19 +132,19 @@
   ninja_args = [_NINJA_PATH, '-C', options.out_dir, gn_path]
   ninja_output = _run_command(ninja_args, env=ninja_env)
   if _USING_PARTIAL_JAVAC_MSG in ninja_output:
-    raise Exception('Incorrectly using partial javac for clean compile.')
+    raise Exception("Incorrectly using partial javac for clean compile.")
 
   _copy_and_append_gn_args(
       options.gn_args_path, out_gn_args_path,
       extra_gn_args + ['incremental_javac_test_toggle_gn = true'])
   ninja_output = _run_command(ninja_args, env=ninja_env)
   if _USING_PARTIAL_JAVAC_MSG not in ninja_output:
-    raise Exception('Not using partial javac for incremental compile.')
+    raise Exception("Not using partial javac for incremental compile.")
 
-  expected_output_path = '{}/obj/{}.javac.jar'.format(options.out_dir,
-                                                      gn_path.replace(':', '/'))
+  expected_output_path = "{}/lib.java/{}.jar".format(options.out_dir,
+                                                     gn_path.replace(':', '/'))
   if not os.path.exists(expected_output_path):
-    raise Exception('{} not created.'.format(expected_output_path))
+    raise Exception("{} not created.".format(expected_output_path))
 
   shutil.copyfile(expected_output_path, options.out_jar)
 
diff --git a/build/config/android/internal_rules.gni b/build/config/android/internal_rules.gni
index b9d2c1e..b34a81e 100644
--- a/build/config/android/internal_rules.gni
+++ b/build/config/android/internal_rules.gni
@@ -20,9 +20,6 @@
 }
 assert(is_android)
 
-default_android_sdk_dep = "//third_party/android_sdk:android_sdk_java"
-_jacoco_dep = "//third_party/jacoco:jacocoagent_java"
-
 # The following _java_*_types variables capture all the existing target types.
 # If a new type is introduced, please add it to one of these categories,
 # preferring the more specific resource/library types.
@@ -116,8 +113,9 @@
   _target_label =
       get_label_info(":${_parent_invoker.target_name}", "label_no_toolchain")
 
-  # Ensure targets match naming patterns so that __assetres, __header, __host,
-  # and __validate targets work properly.
+  # Ensure targets match naming patterns so that __assetres, __header, __impl
+  # targets work properly. Those generated targets allow for effective deps
+  # filtering.
   if (filter_exclude([ _type ], _java_resource_types) == []) {
     if (filter_exclude([ _target_label ], java_resource_patterns) != []) {
       assert(false, "Invalid java resource target name: $_target_label")
@@ -162,6 +160,8 @@
       foreach(_possible_dep, invoker.possible_config_deps) {
         _dep_label = get_label_info(_possible_dep, "label_no_toolchain")
         if (filter_exclude([ _dep_label ], java_target_patterns) == []) {
+          # Put the bug number in the target name so that false-positives
+          # have a hint in the error message about non-existent dependencies.
           deps += [ "$_dep_label$build_config_target_suffix" ]
           _dep_gen_dir = get_label_info(_possible_dep, "target_gen_dir")
           _dep_name = get_label_info(_possible_dep, "name")
@@ -1149,7 +1149,12 @@
   }
 
   template("proguard") {
-    forward_variables_from(invoker, TESTONLY_AND_VISIBILITY)
+    forward_variables_from(invoker,
+                           TESTONLY_AND_VISIBILITY + [
+                                 "data",
+                                 "data_deps",
+                                 "public_deps",
+                               ])
     _script = "//build/android/gyp/proguard.py"
     _deps = invoker.deps
 
@@ -1351,12 +1356,6 @@
       _deps += [ ":$_expectations_target" ]
     }
     action_with_pydeps(target_name) {
-      forward_variables_from(invoker,
-                             [
-                               "data",
-                               "data_deps",
-                               "public_deps",
-                             ])
       script = _script
       deps = _deps
       inputs = _inputs
@@ -1585,9 +1584,6 @@
           _r8_path,
           _custom_d8_path,
         ]
-        if (defined(invoker.inputs)) {
-          inputs += invoker.inputs
-        }
 
         if (!_is_library) {
           # http://crbug.com/725224. Fix for bots running out of memory.
@@ -1880,7 +1876,7 @@
 
   template("bytecode_processor") {
     action_with_pydeps(target_name) {
-      forward_variables_from(invoker, TESTONLY_AND_VISIBILITY + [ "data_deps" ])
+      forward_variables_from(invoker, TESTONLY_AND_VISIBILITY)
       _bytecode_checker_script = "$root_build_dir/bin/helper/bytecode_processor"
       script = "//build/android/gyp/bytecode_processor.py"
       inputs = [
@@ -2976,7 +2972,7 @@
 
       if (target_name == "chrome_java__header") {
         # Regression test for: https://crbug.com/1154302
-        assert_no_deps = [ "//base:base_java__compile_java" ]
+        assert_no_deps = [ "//base:base_java__impl" ]
       }
 
       depfile = "$target_gen_dir/$target_name.d"
@@ -3117,6 +3113,28 @@
     }
   }
 
+  template("java_lib_group") {
+    forward_variables_from(invoker, [ "testonly" ])
+    _group_name = invoker.group_name
+    not_needed([ "_group_name" ])
+    group(target_name) {
+      if (defined(invoker.deps)) {
+        deps = []
+        foreach(_dep, invoker.deps) {
+          _target_label = get_label_info(_dep, "label_no_toolchain")
+          if (filter_exclude([ _target_label ], java_library_patterns) == [] &&
+              filter_exclude([ _target_label ], java_resource_patterns) != []) {
+            # This is a java library dep, so replace it.
+            deps += [ "${_target_label}__${_group_name}" ]
+          } else {
+            # Transitive java group targets should also include direct deps.
+            deps += [ _dep ]
+          }
+        }
+      }
+    }
+  }
+
   # Create an interface jar from a normal jar.
   #
   # Variables
@@ -3288,16 +3306,10 @@
     _type = invoker.type
     _is_annotation_processor = _type == "java_annotation_processor"
     _is_java_binary = _type == "java_binary" || _type == "robolectric_binary"
-    _is_library = _type == "java_library"
     _supports_android =
         defined(invoker.supports_android) && invoker.supports_android
     _requires_android =
         defined(invoker.requires_android) && invoker.requires_android
-    _supports_host = !_requires_android
-    if (_is_java_binary || _is_annotation_processor) {
-      assert(!_requires_android && !_supports_android)
-    }
-
     _bypass_platform_checks = defined(invoker.bypass_platform_checks) &&
                               invoker.bypass_platform_checks
     _is_robolectric = defined(invoker.is_robolectric) && invoker.is_robolectric
@@ -3386,9 +3398,6 @@
         _jacoco_instrument =
             !invoker.jacoco_never_instrument && _jacoco_instrument
       }
-      if (_jacoco_instrument) {
-        _invoker_deps += [ _jacoco_dep ]
-      }
 
       if (_build_host_jar) {
         # Jar files can be needed at runtime (by Robolectric tests or java binaries),
@@ -3446,83 +3455,77 @@
       }
     }
 
-    _java_assetres_deps = filter_include(_invoker_deps, java_resource_patterns)
-
-    # Cannot use minus operator because it does not work when the operand has
-    # repeated entries.
-    _invoker_deps_minus_assetres =
-        filter_exclude(_invoker_deps, _java_assetres_deps)
-    _lib_deps =
-        filter_include(_invoker_deps_minus_assetres, java_library_patterns)
-    _non_java_deps = filter_exclude(_invoker_deps_minus_assetres, _lib_deps)
-
-    _java_header_deps = []  # Turbine / ijar
-
-    # It would be more ideal to split this into __host and __javac, but we
-    # combine the two concepts to save on a group() target.
-    _java_host_deps = []  # Processed host .jar + javac .jar.
-    _java_validate_deps = []  # Bytecode checker & errorprone.
-
-    foreach(_lib_dep, _lib_deps) {
-      # Expand //foo/java -> //foo/java:java
-      _lib_dep = get_label_info(_lib_dep, "label_no_toolchain")
-      _java_assetres_deps += [ "${_lib_dep}__assetres" ]
-      _java_header_deps += [ "${_lib_dep}__header" ]
-      _java_host_deps += [ "${_lib_dep}__host" ]
-      _java_validate_deps += [ "${_lib_dep}__validate" ]
-    }
-
-    # APK and base module targets are special because:
-    # 1) They do not follow java target naming scheme (since they are not
-    #    generally deps, there is no need for them to).
-    # 2) They do not bother to define a __host target.
-    # Since __host is used as an indirect dep for the compile_java artifacts,
-    # add the __compile_java target directly for them.
-    if (defined(invoker.apk_under_test)) {
-      _java_assetres_deps += [ "${invoker.apk_under_test}__java__assetres" ]
-      _java_header_deps += [ "${invoker.apk_under_test}__java__header" ]
-      _java_validate_deps += [ "${invoker.apk_under_test}__java__validate" ]
-      _java_host_deps += [ "${invoker.apk_under_test}__compile_java" ]
-    }
-    if (defined(invoker.base_module_target)) {
-      _java_assetres_deps += [ "${invoker.base_module_target}__java__assetres" ]
-      _java_header_deps += [ "${invoker.base_module_target}__java__header" ]
-      _java_validate_deps += [ "${invoker.base_module_target}__java__validate" ]
-      _java_host_deps += [ "${invoker.base_module_target}__compile_java" ]
-    }
-
-    not_needed([ "_non_java_deps" ])
-
     if (_is_prebuilt || _has_sources) {
-      # Classpath deps are used for header and dex targets, they do not need
-      # __assetres deps.
-      # _non_java_deps are needed for input_jars_paths that are generated.
-      _header_classpath_deps =
-          _java_header_deps + _non_java_deps + [ ":$_build_config_target_name" ]
+      _java_assetres_deps =
+          filter_include(_invoker_deps, java_resource_patterns)
 
-      _javac_classpath_deps =
-          _java_host_deps + _non_java_deps + [ ":$_build_config_target_name" ]
+      # Cannot use minus operator because it does not work when the operand has
+      # repeated entries.
+      _invoker_deps_minus_assetres =
+          filter_exclude(_invoker_deps, _java_assetres_deps)
+      _lib_deps =
+          filter_include(_invoker_deps_minus_assetres, java_library_patterns)
+      _non_java_deps = filter_exclude(_invoker_deps_minus_assetres, _lib_deps)
+
+      _java_header_deps = []
+      _java_impl_deps = []
+      foreach(_lib_dep, _lib_deps) {
+        # Expand //foo/java -> //foo/java:java
+        _lib_dep = get_label_info(_lib_dep, "label_no_toolchain")
+
+        # This is a java library dep, so it has header and impl targets.
+        _java_header_deps += [ "${_lib_dep}__header" ]
+        _java_impl_deps += [ "${_lib_dep}__impl" ]
+        _java_assetres_deps += [ "${_lib_dep}__assetres" ]
+      }
+
+      # Don't need to depend on the apk-under-test to be packaged.
+      if (defined(invoker.apk_under_test)) {
+        _java_header_deps += [ "${invoker.apk_under_test}__java__header" ]
+        _java_impl_deps += [ "${invoker.apk_under_test}__java__impl" ]
+      }
+
+      # These deps cannot be passed via invoker.deps since bundle_module targets
+      # have bundle_module.build_config without the __java suffix, so they are
+      # special and cannot be passed as regular deps to write_build_config.
+      if (defined(invoker.base_module_target)) {
+        _java_header_deps += [ "${invoker.base_module_target}__java__header" ]
+        _java_impl_deps += [ "${invoker.base_module_target}__java__impl" ]
+      }
+
+      _extra_java_deps = []
+      if (_jacoco_instrument) {
+        _extra_java_deps += [ "//third_party/jacoco:jacocoagent_java" ]
+      }
 
       _include_android_sdk = _build_device_jar
       if (defined(invoker.include_android_sdk)) {
         _include_android_sdk = invoker.include_android_sdk
       }
       if (_include_android_sdk) {
+        _sdk_java_dep = "//third_party/android_sdk:android_sdk_java"
         if (defined(invoker.alternative_android_sdk_dep)) {
-          _android_sdk_dep = invoker.alternative_android_sdk_dep
-        } else {
-          _android_sdk_dep = default_android_sdk_dep
+          _sdk_java_dep = invoker.alternative_android_sdk_dep
         }
 
-        _header_classpath_deps += [ "${_android_sdk_dep}__header" ]
-        _javac_classpath_deps += [ "${_android_sdk_dep}" ]
+        # This is an android_system_java_prebuilt target, so no headers.
+        _extra_java_deps += [ _sdk_java_dep ]
       }
+
+      # Classpath deps is used for header and dex targets, they do not need
+      # resource deps.
+      _classpath_deps = _java_header_deps + _non_java_deps + _extra_java_deps +
+                        [ ":$_build_config_target_name" ]
+
+      _full_classpath_deps =
+          _java_impl_deps + _java_assetres_deps + _non_java_deps +
+          _extra_java_deps + [ ":$_build_config_target_name" ]
     }
 
     # Often needed, but too hard to figure out when ahead of time.
     not_needed([
-                 "_header_classpath_deps",
-                 "_javac_classpath_deps",
+                 "_classpath_deps",
+                 "_full_classpath_deps",
                ])
 
     if (_java_files != []) {
@@ -3606,15 +3609,12 @@
       if (defined(invoker.public_deps)) {
         possible_config_public_deps = invoker.public_deps
       }
+      if (defined(_extra_java_deps)) {
+        possible_config_deps += _extra_java_deps
+      }
       if (defined(apk_under_test)) {
         possible_config_deps += [ apk_under_test ]
       }
-      if (defined(_jacoco_instrument) && _jacoco_instrument) {
-        possible_config_deps += [ _jacoco_dep ]
-      }
-      if (defined(_android_sdk_dep)) {
-        possible_config_deps += [ _android_sdk_dep ]
-      }
 
       supports_android = _supports_android
       requires_android = _requires_android
@@ -3661,6 +3661,9 @@
       _header_target_name = "${target_name}__header"
     }
 
+    _public_deps = []
+    _analysis_public_deps = []
+
     if (_has_sources) {
       if (defined(invoker.enable_errorprone)) {
         _enable_errorprone = invoker.enable_errorprone
@@ -3677,9 +3680,10 @@
                    "android_library(), or robolectric_library(). " +
                    "Target=$target_name")
 
-        # Serves double purpose: Generating R.java, as well as being the
-        #__assetres target (instead of using a separate group).
-        _fake_rjava_target = "${target_name}__assetres"
+        # has _resources at the end so it looks like a resources pattern, since
+        # it does act like one (and other resources patterns need to depend on
+        # this before they can read its output R.txt).
+        _fake_rjava_target = "${target_name}__rjava_resources"
         generate_r_java(_fake_rjava_target) {
           deps = [ ":$_build_config_target_name" ] + _java_assetres_deps +
                  _non_java_deps
@@ -3706,17 +3710,10 @@
           # Filtering out generated files resulted in no files left.
           group(target_name) {
             not_needed(invoker, "*")
-            deps = _header_classpath_deps
           }
         } else {
           compile_java(target_name) {
-            forward_variables_from(invoker,
-                                   "*",
-                                   TESTONLY_AND_VISIBILITY + [ "deps" ])
-            deps = _header_classpath_deps
-            if (defined(invoker.deps)) {
-              deps += invoker.deps
-            }
+            forward_variables_from(invoker, "*", TESTONLY_AND_VISIBILITY)
             output_jar_path = invoker.output_jar_path
             enable_errorprone = _enable_errorprone
             use_turbine = defined(invoker.use_turbine) && invoker.use_turbine
@@ -3737,6 +3734,10 @@
             chromium_code = _chromium_code
             supports_android = _supports_android
             include_android_sdk = _is_robolectric || _requires_android
+            if (!defined(deps)) {
+              deps = []
+            }
+            deps += _classpath_deps
           }
         }
       }
@@ -3760,6 +3761,7 @@
         generated_jar_path = _generated_jar_path
         deps = _annotation_processor_deps
       }
+      _public_deps += [ ":$_header_target_name" ]
 
       _compile_java_target = "${_main_target_name}__compile_java"
       compile_java_helper(_compile_java_target) {
@@ -3785,16 +3787,14 @@
           generated_jar_path = _generated_jar_path
           output_jar_path = "$target_out_dir/$target_name.errorprone.stamp"
         }
-        _java_validate_deps += [ ":$_compile_java_errorprone_target" ]
+        _analysis_public_deps += [ ":$_compile_java_errorprone_target" ]
       }
     }  # _has_sources
 
     if (_is_prebuilt || _build_device_jar || _build_host_jar) {
+      _unprocessed_jar_deps = []
       if (_has_sources) {
-        _unprocessed_jar_deps = [ ":$_compile_java_target" ]
-      } else {
-        # jars might be generated by a dep.
-        _unprocessed_jar_deps = _non_java_deps
+        _unprocessed_jar_deps += [ ":$_compile_java_target" ]
       }
     }
 
@@ -3830,7 +3830,7 @@
           "--output-jar",
           rebase_path(_rewritten_jar, root_build_dir),
         ]
-        deps = _unprocessed_jar_deps + _javac_classpath_deps +
+        deps = _unprocessed_jar_deps + _full_classpath_deps +
                [ invoker.bytecode_rewriter_target ]
       }
 
@@ -3847,10 +3847,14 @@
         input_jar = _unprocessed_jar_path
         output_jar = _final_ijar_path
 
-        # ijar needs only _unprocessed_jar_deps, but this also needs to export
-        # __header target from deps.
-        deps = _unprocessed_jar_deps + _java_header_deps
+        # Normally ijar does not require any deps, but:
+        # 1 - Some jars are bytecode rewritten by _unprocessed_jar_deps.
+        # 2 - Other jars need to be unzipped by _non_java_deps.
+        # 3 - It is expected that depending on a header target implies depending
+        #     on its transitive header target deps via _java_header_deps.
+        deps = _unprocessed_jar_deps + _non_java_deps + _java_header_deps
       }
+      _public_deps += [ ":$_header_target_name" ]
     }
 
     if (_build_host_jar || _build_device_jar) {
@@ -3858,15 +3862,11 @@
           (!defined(invoker.enable_bytecode_checks) ||
            invoker.enable_bytecode_checks) && android_static_analysis != "off"
       if (_enable_bytecode_checks) {
-        _validate_target_name = "${target_name}__validate"
-        bytecode_processor(_validate_target_name) {
+        _bytecode_checks_target = "${target_name}__validate_classpath"
+        bytecode_processor(_bytecode_checks_target) {
           forward_variables_from(invoker, [ "missing_classes_allowlist" ])
-          deps = _unprocessed_jar_deps + _javac_classpath_deps +
+          deps = _unprocessed_jar_deps + _full_classpath_deps +
                  [ ":$_build_config_target_name" ]
-          data_deps = _java_validate_deps
-          if (defined(_compile_java_errorprone_target)) {
-            data_deps += [ ":$_compile_java_errorprone_target" ]
-          }
 
           include_android_sdk = _requires_android || _is_robolectric
           target_label =
@@ -3875,12 +3875,13 @@
           build_config = _build_config
           is_prebuilt = _is_prebuilt
         }
+        _analysis_public_deps += [ ":$_bytecode_checks_target" ]
       } else {
         not_needed(invoker, [ "missing_classes_allowlist" ])
       }
 
       if (_build_host_jar) {
-        _process_host_jar_target_name = "${target_name}__host"
+        _process_host_jar_target_name = "${target_name}__process_host"
         process_java_library(_process_host_jar_target_name) {
           forward_variables_from(invoker,
                                  [
@@ -3891,20 +3892,15 @@
           # Robolectric tests require these to be on swarming.
           data = [ _host_processed_jar_path ]
           input_jar_path = _unprocessed_jar_path
-          deps = _unprocessed_jar_deps + _javac_classpath_deps
+          deps = _unprocessed_jar_deps + _full_classpath_deps
           output_jar_path = _host_processed_jar_path
           jacoco_instrument = _jacoco_instrument
           if (_jacoco_instrument) {
             java_files = _java_files
             java_sources_file = _java_sources_file
           }
-
-          # _java_host_deps isn't necessary for process_java_library(), but is
-          # necessary so that this target can be used to depend on transitive
-          # __device targets without the need to create a separate group()
-          # target. This trade-off works because process_java_library is fast.
-          deps += _java_host_deps
         }
+        _public_deps += [ ":${_process_host_jar_target_name}" ]
       }
 
       if (_build_device_jar) {
@@ -3917,8 +3913,7 @@
                                      "jar_included_patterns",
                                    ])
             input_jar_path = _unprocessed_jar_path
-
-            deps = _unprocessed_jar_deps + _javac_classpath_deps
+            deps = _unprocessed_jar_deps + _full_classpath_deps
             output_jar_path = _device_processed_jar_path
             jacoco_instrument = _jacoco_instrument
             if (_jacoco_instrument) {
@@ -3927,13 +3922,14 @@
             }
           }
           _process_device_jar_deps = [ ":${_process_device_jar_target_name}" ]
+          _public_deps += _process_device_jar_deps
         } else {
           assert(_unprocessed_jar_path == _device_processed_jar_path)
-          _process_device_jar_deps = _unprocessed_jar_deps
+          _process_device_jar_deps =
+              _unprocessed_jar_deps + _full_classpath_deps
         }
 
-        _dex_target_name = "${target_name}__dex"
-        dex(_dex_target_name) {
+        dex("${target_name}__dex") {
           forward_variables_from(invoker,
                                  [
                                    "desugar_jars_paths",
@@ -3953,24 +3949,16 @@
             # Desugaring with D8 requires full classpath.
             build_config = _build_config
             final_ijar_path = _final_ijar_path
-            deps += _header_classpath_deps + [ ":$_header_target_name" ]
+            deps += _classpath_deps + [
+                      ":$_build_config_target_name",
+                      ":$_header_target_name",
+                    ]
           }
 
           enable_multidex = false
           is_library = true
-
-          # proguard_configs listed on java_library targets need to be marked
-          # as inputs to at least one target so that "gn analyze" will know
-          # about them. Although this target doesn't use them, it's a convenient spot
-          # to list them.
-          # https://crbug.com/827197
-          if (compute_inputs_for_analyze && defined(invoker.proguard_configs)) {
-            inputs = invoker.proguard_configs
-
-            # For the aapt-generated proguard rules.
-            deps += _non_java_deps + _srcjar_deps
-          }
         }
+        _public_deps += [ ":${target_name}__dex" ]
       }
     }
 
@@ -3995,100 +3983,57 @@
           extra_classpath_jars = [ _robolectric_jar_path ]
         }
       }
+      _public_deps += [ ":$_java_binary_script_target_name" ]
     }
 
-    if (!defined(_validate_target_name)) {
-      _validate_target_name = "${target_name}__validate"
+    # The __impl target contains all non-analysis steps for this template.
+    # Having this separated out from the main target (which contains analysis
+    # steps) allows analysis steps for this target to be run concurrently with
+    # the non-analysis steps of other targets that depend on this one.
+    group("${target_name}__impl") {
+      public_deps = _public_deps
 
-      # Allow other targets to depend on this __validate one.
-      group(_validate_target_name) {
-        deps = _java_validate_deps
+      # proguard_configs listed on java_library targets need to be marked
+      # as inputs to at least one target so that "gn analyze" will know
+      # about them. Although this target doesn't use them, it's a convenient spot
+      # to list them.
+      # https://crbug.com/827197
+      if (compute_inputs_for_analyze && defined(invoker.proguard_configs)) {
+        assert(_build_host_jar || _build_device_jar)
+        data = invoker.proguard_configs
+
+        # For the aapt-generated proguard rules.
+        deps = _non_java_deps + _srcjar_deps
       }
     }
 
-    if (_supports_host && !defined(_process_host_jar_target_name)) {
-      group("${target_name}__host") {
-        deps = _java_host_deps
+    java_lib_group("${target_name}__assetres") {
+      deps = _invoker_deps
+      group_name = "assetres"
+
+      if (defined(_fake_rjava_target)) {
+        deps += [ ":$_fake_rjava_target" ]
       }
     }
 
-    # robolectric_library can depend on java_library, so java_library must
-    # define __assetres.
-    if ((_is_library || _supports_android || _is_robolectric) &&
-        !defined(_fake_rjava_target)) {
-      group("${target_name}__assetres") {
-        if (_supports_android || _is_robolectric) {
-          deps = _java_assetres_deps
-        }
-      }
-    }
-
-    # The top-level group is used:
-    # 1) To allow building the target explicitly via ninja,
-    # 2) To trigger all analysis deps,
-    # 3) By custom action() targets that want to use artifacts as inputs.
     group(target_name) {
       forward_variables_from(invoker,
                              [
                                "assert_no_deps",
                                "data",
                                "data_deps",
+                               "deps",
+                               "public_deps",
                                "visibility",
                              ])
-      if (_requires_android || (_supports_android && _is_library)) {
-        # For non-robolectric targets, depend on other java target's top-level
-        # groups so that the __dex step gets depended on.
-        forward_variables_from(invoker,
-                               [
-                                 "deps",
-                                 "public_deps",
-                               ])
-        if (!defined(deps)) {
-          deps = []
-        }
-        if (!defined(public_deps)) {
-          public_deps = []
-        }
-      } else {
-        # For robolectric targets, depend only on non-java deps and the specific
-        # subtargets below, which will not include __dex.
-        deps = _non_java_deps
+      if (!defined(public_deps)) {
         public_deps = []
-        if (defined(invoker.public_deps)) {
-          public_deps +=
-              filter_exclude(invoker.public_deps, java_target_patterns)
-        }
       }
-      if (defined(_jacoco_instrument) && _jacoco_instrument) {
-        deps += [ _jacoco_dep ]
-      }
-      if (defined(_process_device_jar_target_name)) {
-        public_deps += [ ":$_process_device_jar_target_name" ]
-      }
-      if (defined(_dex_target_name)) {
-        public_deps += [ ":$_dex_target_name" ]
-      }
-      if (_supports_android && _is_library) {
-        # Robolectric targets define __assetres, but there's no need to build it
-        # by default.
-        public_deps += [ ":${target_name}__assetres" ]
-      }
-      if (_supports_host) {
-        # android_* targets define __host, but there's no need to build it by
-        # default.
-        public_deps += [ ":${target_name}__host" ]
-      }
-      if (_is_java_binary) {
-        public_deps += [ ":$_java_binary_script_target_name" ]
-      }
+      public_deps += [ ":${target_name}__impl" ]
       if (!defined(data_deps)) {
         data_deps = []
       }
-      if (defined(_validate_target_name)) {
-        data_deps += [ ":$_validate_target_name" ]
-      } else {
-        data_deps += _java_validate_deps
-      }
+      data_deps += _analysis_public_deps
     }
   }
 }
diff --git a/build/config/android/rules.gni b/build/config/android/rules.gni
index a6adfc4..cf372d1 100644
--- a/build/config/android/rules.gni
+++ b/build/config/android/rules.gni
@@ -1031,9 +1031,9 @@
     }
 
     if (defined(invoker.alternative_android_sdk_dep)) {
-      _android_sdk_dep = invoker.alternative_android_sdk_dep
+      _deps += [ invoker.alternative_android_sdk_dep ]
     } else {
-      _android_sdk_dep = default_android_sdk_dep
+      _deps += [ "//third_party/android_sdk:android_sdk_java" ]
     }
 
     _resource_files = []
@@ -1072,7 +1072,7 @@
                              ])
 
       r_text = _r_text_out_path
-      possible_config_deps = _deps + [ _android_sdk_dep ]
+      possible_config_deps = _deps
 
       # Always merge manifests from resources.
       # * Might want to change this at some point for consistency and clarity,
@@ -1103,7 +1103,7 @@
       }
 
       # Depend on non-library deps and on __assetres subtargets of library deps.
-      deps = filter_exclude(_deps, _lib_deps) + [ _android_sdk_dep ]
+      deps = filter_exclude(_deps, _lib_deps)
       foreach(_lib_dep, _lib_deps) {
         # Expand //foo/java -> //foo/java:java
         _lib_dep = get_label_info(_lib_dep, "label_no_toolchain")
@@ -1237,34 +1237,17 @@
       supports_android = true
       possible_config_deps = _invoker_deps
     }
-
-    _assetres_deps = filter_include(_invoker_deps, java_resource_patterns)
-    _invoker_deps_minus_assetres = filter_exclude(_invoker_deps, _assetres_deps)
-    _lib_deps =
-        filter_include(_invoker_deps_minus_assetres, java_library_patterns)
-
-    _expanded_lib_deps = []
-    foreach(_lib_dep, _lib_deps) {
-      _expanded_lib_deps += [ get_label_info(_lib_dep, "label_no_toolchain") ]
-    }
     foreach(_group_name,
             [
-              "assetres",
               "header",
-              "host",
-              "validate",
+              "impl",
+              "assetres",
             ]) {
-      group("${target_name}__$_group_name") {
-        deps = []
-        foreach(_lib_dep, _expanded_lib_deps) {
-          deps += [ "${_lib_dep}__${_group_name}" ]
-        }
-        if (_group_name == "assetres") {
-          deps += _assetres_deps
-        }
+      java_lib_group("${target_name}__${_group_name}") {
+        deps = _invoker_deps
+        group_name = _group_name
       }
     }
-
     group(target_name) {
       forward_variables_from(invoker,
                              "*",
@@ -1380,7 +1363,7 @@
     if (defined(invoker.alternative_android_sdk_dep)) {
       _android_sdk_dep = invoker.alternative_android_sdk_dep
     } else {
-      _android_sdk_dep = default_android_sdk_dep
+      _android_sdk_dep = "//third_party/android_sdk:android_sdk_java"
     }
 
     # A package name or a manifest is required to have resources. This is
@@ -1517,12 +1500,8 @@
       deps = [
         ":$_apkbuilder_target_name",
         ":$_build_config_target_name",
-        ":${_java_binary_target_name}__host",
-        ":${_java_binary_target_name}__validate",
+        ":$_java_binary_target_name",
       ]
-
-      # Add non-libary deps, since the __host target does not depend on them.
-      deps += filter_exclude(_invoker_deps, java_library_patterns)
     }
   }
 
@@ -1766,7 +1745,7 @@
   #     output = "$root_build_dir/MyLibrary.jar"
   #   }
   template("dist_dex") {
-    _deps = [ default_android_sdk_dep ]
+    _deps = [ "//third_party/android_sdk:android_sdk_java" ]
     if (defined(invoker.deps)) {
       _deps += invoker.deps
     }
@@ -1787,6 +1766,8 @@
       build_config = _build_config
     }
 
+    _deps += [ ":$_build_config_target_name" ]
+
     dex(target_name) {
       forward_variables_from(invoker,
                              TESTONLY_AND_VISIBILITY + [
@@ -1797,7 +1778,7 @@
                                    "proguard_enable_obfuscation",
                                    "min_sdk_version",
                                  ])
-      deps = [ ":$_build_config_target_name" ] + _deps
+      deps = _deps
       build_config = _build_config
       enable_multidex = false
       output = invoker.output
@@ -2617,7 +2598,7 @@
       }
     }
 
-    _final_deps = [ ":$_java_target_name" ]
+    _final_deps = []
 
     _enable_main_dex_list = _enable_multidex && _min_sdk_version < 21
     if (_enable_main_dex_list) {
@@ -2629,7 +2610,7 @@
     if (defined(invoker.alternative_android_sdk_dep)) {
       _android_sdk_dep = invoker.alternative_android_sdk_dep
     } else {
-      _android_sdk_dep = default_android_sdk_dep
+      _android_sdk_dep = "//third_party/android_sdk:android_sdk_java"
     }
 
     if (defined(_shared_resources_allowlist_target)) {
@@ -3090,8 +3071,6 @@
         output = _dist_ijar_path
         data = [ _dist_ijar_path ]
         use_interface_jars = true
-
-        # This will use __header under-the-hood.
         deps = [ ":$_java_target_name" ]
       }
     }
@@ -3099,7 +3078,7 @@
     if (_uses_static_library_synchronized_proguard) {
       _final_dex_target_dep = "${invoker.static_library_provider}__dexsplitter"
     } else if ((_is_bundle_module && _proguard_enabled) || _omit_dex) {
-      # No library dep needed.
+      _final_deps += [ ":$_java_target_name" ]
     } else if (_incremental_apk) {
       if (defined(invoker.enable_proguard_checks)) {
         not_needed(invoker, [ "enable_proguard_checks" ])
@@ -3122,24 +3101,18 @@
           ":$_java_target_name",
         ]
         if (_proguard_enabled) {
-          # Generates proguard configs
-          deps += [ ":$_compile_resources_target" ]
+          deps += _invoker_deps + [ ":$_compile_resources_target" ]
           proguard_mapping_path = _proguard_mapping_path
           proguard_sourcefile_suffix = "$android_channel-$_version_code"
           has_apk_under_test = defined(invoker.apk_under_test)
+        } else if (_min_sdk_version >= default_min_sdk_version) {
+          # Enable dex merging only when min_sdk_version is >= what the library
+          # .dex files were created with.
+          input_dex_filearg =
+              "@FileArg(${_rebased_build_config}:final_dex:all_dex_files)"
         } else {
-          if (_min_sdk_version >= default_min_sdk_version) {
-            # Enable dex merging only when min_sdk_version is >= what the library
-            # .dex files were created with.
-            input_dex_filearg =
-                "@FileArg(${_rebased_build_config}:final_dex:all_dex_files)"
-
-            # Pure dex-merge.
-            enable_desugar = false
-          } else {
-            input_classes_filearg =
-                "@FileArg($_rebased_build_config:deps_info:device_classpath)"
-          }
+          input_classes_filearg =
+              "@FileArg($_rebased_build_config:deps_info:device_classpath)"
         }
 
         if (_is_static_library_provider) {
@@ -3161,14 +3134,11 @@
         # their respective dex steps. False positives that were suppressed at
         # per-target dex steps are emitted here since this may use jar files
         # rather than dex files.
-        if (!defined(enable_desugar)) {
-          ignore_desugar_missing_deps = true
-        }
+        ignore_desugar_missing_deps = true
 
         if (_enable_main_dex_list) {
-          # Generates main-dex config.
-          deps += [ ":$_compile_resources_target" ]
           extra_main_dex_proguard_config = _generated_proguard_main_dex_config
+          deps += [ ":$_compile_resources_target" ]
         }
       }
 
@@ -3305,11 +3275,11 @@
             name = "${invoker.name}.apk"
             build_config = _build_config
             res_size_info_path = _res_size_info_path
-            deps = [
-              ":$_build_config_target",
-              ":$_compile_resources_target",
-              ":$_java_target_name",
-            ]
+            deps = _invoker_deps + [
+                     ":$_build_config_target",
+                     ":$_compile_resources_target",
+                     ":$_java_target_name",
+                   ]
           }
           _final_deps += [ ":$_size_info_target" ]
         } else {
@@ -3467,7 +3437,10 @@
           args += [ "--native-libs=$_rebased_loadable_modules" ]
         }
       }
-      _final_deps += [ ":$_write_installer_json_rule_name" ]
+      _final_deps += [
+        ":$_java_target_name",
+        ":$_write_installer_json_rule_name",
+      ]
     }
 
     # Generate apk operation related script.
@@ -3554,8 +3527,6 @@
                                ])
         build_config = _build_config
         build_config_dep = ":$_build_config_target"
-
-        # This will use library subtargets under-the-hood
         deps = [ ":$_java_target_name" ]
         if (defined(invoker.lint_suppressions_dep)) {
           deps += [ invoker.lint_suppressions_dep ]
@@ -3591,7 +3562,8 @@
       }
 
       # Include unstripped native libraries so tests can symbolize stacks.
-      data_deps += _all_native_libs_deps + [ ":${_java_target_name}__validate" ]
+      data_deps += _all_native_libs_deps
+
       if (_enable_lint) {
         data_deps += [ ":${target_name}__lint" ]
       }
@@ -4975,9 +4947,9 @@
     _rebased_build_config = rebase_path(_build_config, root_build_dir)
     _build_config_target = "$_target_name$build_config_target_suffix"
     if (defined(invoker.proguard_android_sdk_dep)) {
-      _android_sdk_dep = invoker.proguard_android_sdk_dep
+      proguard_android_sdk_dep_ = invoker.proguard_android_sdk_dep
     } else {
-      _android_sdk_dep = default_android_sdk_dep
+      proguard_android_sdk_dep_ = "//third_party/android_sdk:android_sdk_java"
     }
 
     if (_proguard_enabled) {
@@ -4991,7 +4963,7 @@
 
     write_build_config(_build_config_target) {
       type = "android_app_bundle"
-      possible_config_deps = _module_targets + [ _android_sdk_dep ]
+      possible_config_deps = _module_targets + [ proguard_android_sdk_dep_ ]
       build_config = _build_config
       proguard_enabled = _proguard_enabled
       module_build_configs = _module_build_configs
diff --git a/cc/test/layer_tree_pixel_test.cc b/cc/test/layer_tree_pixel_test.cc
index ab975ed..41da8a63 100644
--- a/cc/test/layer_tree_pixel_test.cc
+++ b/cc/test/layer_tree_pixel_test.cc
@@ -231,8 +231,9 @@
   layer->SetHitTestable(true);
   layer->SetBounds(rect.size());
   layer->SetPosition(gfx::PointF(rect.origin()));
-  // TODO(crbug/1308932): Remove FromColor and use just SkColor4f.
   layer->SetOffsetToTransformParent(gfx::Vector2dF(rect.OffsetFromOrigin()));
+  // CreateSolidColorLayer is only being used in tests, so we can live with this
+  // SkColor converted to SkColor4f.
   layer->SetBackgroundColor(SkColor4f::FromColor(color));
   return layer;
 }
diff --git a/cc/trees/layer_tree_host_impl.cc b/cc/trees/layer_tree_host_impl.cc
index 8cc795c..65c468c 100644
--- a/cc/trees/layer_tree_host_impl.cc
+++ b/cc/trees/layer_tree_host_impl.cc
@@ -1116,9 +1116,9 @@
 static void AppendQuadsToFillScreen(
     viz::CompositorRenderPass* target_render_pass,
     const RenderSurfaceImpl* root_render_surface,
-    SkColor screen_background_color,
+    SkColor4f screen_background_color,
     const Region& fill_region) {
-  if (!root_render_surface || !SkColorGetA(screen_background_color))
+  if (!root_render_surface || !screen_background_color.fA)
     return;
   if (fill_region.IsEmpty())
     return;
@@ -1131,13 +1131,12 @@
   gfx::Rect root_target_rect = root_render_surface->content_rect();
   float opacity = 1.f;
   int sorting_context_id = 0;
-  bool are_contents_opaque = SkColorGetA(screen_background_color) == 0xFF;
   viz::SharedQuadState* shared_quad_state =
       target_render_pass->CreateAndAppendSharedQuadState();
   shared_quad_state->SetAll(gfx::Transform(), root_target_rect,
                             root_target_rect, gfx::MaskFilterInfo(),
-                            absl::nullopt, are_contents_opaque, opacity,
-                            SkBlendMode::kSrcOver, sorting_context_id);
+                            absl::nullopt, screen_background_color.isOpaque(),
+                            opacity, SkBlendMode::kSrcOver, sorting_context_id);
 
   for (gfx::Rect screen_space_rect : fill_region) {
     gfx::Rect visible_screen_space_rect = screen_space_rect;
@@ -1145,10 +1144,8 @@
     // occlusion checks.
     auto* quad =
         target_render_pass->CreateAndAppendDrawQuad<viz::SolidColorDrawQuad>();
-    // TODO(crbug.com/1308932): Call this function with SkColor4f
     quad->SetNew(shared_quad_state, screen_space_rect,
-                 visible_screen_space_rect,
-                 SkColor4f::FromColor(screen_background_color), false);
+                 visible_screen_space_rect, screen_background_color, false);
   }
 }
 
@@ -1456,10 +1453,9 @@
     if (num_missing_tiles > 0)
       fill_region = root_render_surface->content_rect();
 
-    // TODO(crbug/1308932): Remove toSkColor and make all SkColor4f.
-    AppendQuadsToFillScreen(
-        frame->render_passes.back().get(), root_render_surface,
-        active_tree_->background_color().toSkColor(), fill_region);
+    AppendQuadsToFillScreen(frame->render_passes.back().get(),
+                            root_render_surface,
+                            active_tree_->background_color(), fill_region);
   }
 
   RemoveRenderPasses(frame);
diff --git a/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/bar_component/KeyboardAccessoryModernViewTest.java b/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/bar_component/KeyboardAccessoryModernViewTest.java
index 01b680ce..38fa706 100644
--- a/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/bar_component/KeyboardAccessoryModernViewTest.java
+++ b/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/bar_component/KeyboardAccessoryModernViewTest.java
@@ -109,6 +109,7 @@
 @RunWith(ChromeJUnit4ClassRunner.class)
 @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
 @EnableFeatures(ChromeFeatureList.AUTOFILL_KEYBOARD_ACCESSORY)
+@SuppressWarnings("DoNotMock") // Mocks GURL
 public class KeyboardAccessoryModernViewTest {
     private static final String CUSTOM_ICON_URL = "https://www.example.com/image.png";
     private static final Bitmap TEST_CARD_ART_IMAGE =
diff --git a/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/CreditCardAccessorySheetViewTest.java b/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/CreditCardAccessorySheetViewTest.java
index a6e25ee..528ae184 100644
--- a/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/CreditCardAccessorySheetViewTest.java
+++ b/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/CreditCardAccessorySheetViewTest.java
@@ -65,6 +65,7 @@
  */
 @RunWith(ChromeJUnit4ClassRunner.class)
 @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
+@SuppressWarnings("DoNotMock") // Mocks GURL
 public class CreditCardAccessorySheetViewTest {
     private static final String CUSTOM_ICON_URL = "https://www.example.com/image.png";
     private static final Bitmap TEST_CARD_ART_IMAGE =
diff --git a/chrome/android/features/start_surface/OWNERS b/chrome/android/features/start_surface/OWNERS
index dd5703a..714053a1 100644
--- a/chrome/android/features/start_surface/OWNERS
+++ b/chrome/android/features/start_surface/OWNERS
@@ -1,3 +1,4 @@
 hanxi@chromium.org
+spdonghao@chromium.org
 
 file://chrome/android/java/src/org/chromium/chrome/browser/tasks/OWNERS
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/mv_tiles/MostVisitedTileNavigationDelegate.java b/chrome/android/features/start_surface/java/src/org/chromium/chrome/features/tasks/MostVisitedTileNavigationDelegate.java
similarity index 95%
rename from chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/mv_tiles/MostVisitedTileNavigationDelegate.java
rename to chrome/android/features/start_surface/java/src/org/chromium/chrome/features/tasks/MostVisitedTileNavigationDelegate.java
index cfd5489a..573d991f 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/mv_tiles/MostVisitedTileNavigationDelegate.java
+++ b/chrome/android/features/start_surface/java/src/org/chromium/chrome/features/tasks/MostVisitedTileNavigationDelegate.java
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-package org.chromium.chrome.browser.tasks.mv_tiles;
+package org.chromium.chrome.features.tasks;
 
 import android.app.Activity;
 
@@ -11,7 +11,6 @@
 import org.chromium.chrome.browser.offlinepages.OfflinePageBridge;
 import org.chromium.chrome.browser.offlinepages.RequestCoordinatorBridge;
 import org.chromium.chrome.browser.profiles.Profile;
-import org.chromium.chrome.browser.suggestions.SuggestionsMetrics;
 import org.chromium.chrome.browser.suggestions.SuggestionsNavigationDelegate;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tabmodel.document.TabDelegate;
@@ -63,7 +62,6 @@
                         windowOpenDisposition
                                 == org.chromium.ui.mojom.WindowOpenDisposition.NEW_BACKGROUND_TAB,
                         /*incognito=*/null, mParentTabSupplier.get());
-                SuggestionsMetrics.recordTileTapped();
                 break;
             case WindowOpenDisposition.OFF_THE_RECORD:
                 ReturnToChromeUtil.handleLoadUrlFromStartSurface(
diff --git a/chrome/android/features/start_surface/java/src/org/chromium/chrome/features/tasks/TasksSurfaceCoordinator.java b/chrome/android/features/start_surface/java/src/org/chromium/chrome/features/tasks/TasksSurfaceCoordinator.java
index facf052..09b3b3c 100644
--- a/chrome/android/features/start_surface/java/src/org/chromium/chrome/features/tasks/TasksSurfaceCoordinator.java
+++ b/chrome/android/features/start_surface/java/src/org/chromium/chrome/features/tasks/TasksSurfaceCoordinator.java
@@ -39,7 +39,6 @@
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tabmodel.TabCreatorManager;
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
-import org.chromium.chrome.browser.tasks.mv_tiles.MostVisitedTileNavigationDelegate;
 import org.chromium.chrome.browser.tasks.tab_management.TabManagementDelegate.TabSwitcherType;
 import org.chromium.chrome.browser.tasks.tab_management.TabManagementModuleProvider;
 import org.chromium.chrome.browser.tasks.tab_management.TabSwitcher;
diff --git a/chrome/android/features/start_surface/start_surface_java_sources.gni b/chrome/android/features/start_surface/start_surface_java_sources.gni
index 5122061..86769cc 100644
--- a/chrome/android/features/start_surface/start_surface_java_sources.gni
+++ b/chrome/android/features/start_surface/start_surface_java_sources.gni
@@ -20,6 +20,7 @@
   "//chrome/android/features/start_surface/java/src/org/chromium/chrome/features/start_surface/StartSurfaceProperties.java",
   "//chrome/android/features/start_surface/java/src/org/chromium/chrome/features/start_surface/TabSwitcherAndStartSurfaceLayout.java",
   "//chrome/android/features/start_surface/java/src/org/chromium/chrome/features/start_surface/TasksSurfaceViewBinder.java",
+  "//chrome/android/features/start_surface/java/src/org/chromium/chrome/features/tasks/MostVisitedTileNavigationDelegate.java",
   "//chrome/android/features/start_surface/java/src/org/chromium/chrome/features/tasks/SingleTabSwitcherCoordinator.java",
   "//chrome/android/features/start_surface/java/src/org/chromium/chrome/features/tasks/SingleTabSwitcherMediator.java",
   "//chrome/android/features/start_surface/java/src/org/chromium/chrome/features/tasks/TasksSurface.java",
diff --git a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabUiUnitTestUtils.java b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabUiUnitTestUtils.java
index 2974cb4..3979b2da 100644
--- a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabUiUnitTestUtils.java
+++ b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabUiUnitTestUtils.java
@@ -20,7 +20,7 @@
  * This is a util class for TabUi unit tests.
  */
 // TODO(crbug.com/1023701): Generalize all prepareTab method from tab_ui/junit directory.
-@SuppressWarnings("ResultOfMethodCallIgnored")
+@SuppressWarnings({"DoNotMock", "ResultOfMethodCallIgnored"}) // Mocks GURL
 public class TabUiUnitTestUtils {
     public static TabImpl prepareTab() {
         TabImpl tab = mock(TabImpl.class);
diff --git a/chrome/android/features/tab_ui/tab_management_java_sources.gni b/chrome/android/features/tab_ui/tab_management_java_sources.gni
index 064af75..fe12ab1c 100644
--- a/chrome/android/features/tab_ui/tab_management_java_sources.gni
+++ b/chrome/android/features/tab_ui/tab_management_java_sources.gni
@@ -3,7 +3,6 @@
 # found in the LICENSE file.
 
 public_tab_management_java_sources = [
-  "//chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/mv_tiles/MostVisitedTileNavigationDelegate.java",
   "//chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/pseudotab/PseudoTab.java",
   "//chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/pseudotab/TabAttributeCache.java",
   "//chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/CloseAllTabsDialog.java",
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/identity_disc/IdentityDiscController.java b/chrome/android/java/src/org/chromium/chrome/browser/identity_disc/IdentityDiscController.java
index a4d8b7b..3193e8de 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/identity_disc/IdentityDiscController.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/identity_disc/IdentityDiscController.java
@@ -185,7 +185,7 @@
         return new ButtonSpec(drawable, buttonSpec.getOnClickListener(),
                 /*onLongClickListener=*/null, buttonSpec.getContentDescriptionResId(),
                 buttonSpec.getSupportsTinting(), buttonSpec.getIPHCommandBuilder(),
-                AdaptiveToolbarButtonVariant.UNKNOWN);
+                AdaptiveToolbarButtonVariant.UNKNOWN, buttonSpec.getActionChipLabelResId());
     }
 
     /**
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/SelectFileDialogTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/SelectFileDialogTest.java
index 36525c44..f09e837 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/SelectFileDialogTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/SelectFileDialogTest.java
@@ -106,8 +106,6 @@
             SelectFileDialog.setWindowAndroidForTests(mActivityWindowAndroidForTest);
 
             mWebContents = mActivityTestRule.getActivity().getCurrentWebContents();
-            // TODO(aurimas) remove this wait once crbug.com/179511 is fixed.
-            // mActivityTestRule.assertWaitForPageScaleFactorMatch(2);
         });
         DOMUtils.waitForNonZeroNodeBounds(mWebContents, "input_file");
     }
@@ -126,6 +124,9 @@
     @Feature({"TextInput", "Main"})
     @DisabledTest(message = "https://crbug.com/724163")
     public void testSelectFileAndCancelRequest() throws Throwable {
+        // TODO(aurimas) remove this wait once crbug.com/179511 is fixed.
+        // Wait for page scale will timeout and causing the test to fail.
+        mActivityTestRule.assertWaitForPageScaleFactorMatch(2);
         {
             DOMUtils.clickNode(mWebContents, "input_file");
             verifyIntentSent();
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/app/tab_activity_glue/ActivityTabWebContentsDelegateAndroidUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/app/tab_activity_glue/ActivityTabWebContentsDelegateAndroidUnitTest.java
index d356583f..249cf2e 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/app/tab_activity_glue/ActivityTabWebContentsDelegateAndroidUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/app/tab_activity_glue/ActivityTabWebContentsDelegateAndroidUnitTest.java
@@ -47,6 +47,7 @@
                 ShadowProfile.class, ShadowGURL.class})
 @EnableFeatures(ChromeFeatureList.DARKEN_WEBSITES_CHECKBOX_IN_THEMES_SETTING)
 @DisableFeatures(ChromeFeatureList.FORCE_WEB_CONTENTS_DARK_MODE)
+@SuppressWarnings("DoNotMock") // Mocking GURL
 public class ActivityTabWebContentsDelegateAndroidUnitTest {
     @Implements(WebContentsDarkModeController.class)
     static class ShadowWebContentsDarkModeController {
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/autofill/AutofillSuggestionTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/autofill/AutofillSuggestionTest.java
index 4abc9e8..542216ed 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/autofill/AutofillSuggestionTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/autofill/AutofillSuggestionTest.java
@@ -19,6 +19,7 @@
 
 /** Unit tests for {@link AutofillSuggestion} */
 @RunWith(BaseRobolectricTestRunner.class)
+@SuppressWarnings("DoNotMock") // Mocks GURL.
 public class AutofillSuggestionTest {
     @Test
     @SmallTest
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/browserservices/digitalgoods/DigitalGoodsUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/browserservices/digitalgoods/DigitalGoodsUnitTest.java
index 8ae5a54..9f930ce 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/browserservices/digitalgoods/DigitalGoodsUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/browserservices/digitalgoods/DigitalGoodsUnitTest.java
@@ -26,6 +26,7 @@
  */
 @RunWith(BaseRobolectricTestRunner.class)
 @Config(manifest = Config.NONE)
+@SuppressWarnings("DoNotMock") // Mocking GURL
 public class DigitalGoodsUnitTest {
     private final DigitalGoodsImpl.Delegate mDelegate = () -> {
         GURL url = Mockito.mock(GURL.class);
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/browserservices/ui/controller/CurrentPageVerifierTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/browserservices/ui/controller/CurrentPageVerifierTest.java
index 37c9c06..10cebb9 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/browserservices/ui/controller/CurrentPageVerifierTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/browserservices/ui/controller/CurrentPageVerifierTest.java
@@ -45,6 +45,7 @@
  * Tests for {@link CurrentPageVerifier}.
  */
 @RunWith(BaseRobolectricTestRunner.class)
+@SuppressWarnings("DoNotMock") // Mocking GURL
 public class CurrentPageVerifierTest {
     private static final Origin TRUSTED_ORIGIN = Origin.create("https://www.origin1.com/");
     private static final Origin OTHER_TRUSTED_ORIGIN = Origin.create("https://www.origin2.com/");
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/history_clusters/HistoryClustersCoordinatorTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/history_clusters/HistoryClustersCoordinatorTest.java
index 7dfc595..87cab2c 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/history_clusters/HistoryClustersCoordinatorTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/history_clusters/HistoryClustersCoordinatorTest.java
@@ -68,6 +68,7 @@
 @Config(manifest = Config.NONE)
 @CommandLineFlags.
 Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE, ChromeSwitches.DISABLE_NATIVE_INITIALIZATION})
+@SuppressWarnings("DoNotMock") // Mocks GURL.
 public class HistoryClustersCoordinatorTest {
     private static final String INCOGNITO_EXTRA = "IN_INCOGNITO";
     private static final String NEW_TAB_EXTRA = "IN_NEW_TAB";
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/history_clusters/HistoryClustersMediatorTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/history_clusters/HistoryClustersMediatorTest.java
index caa1cd5..441e1065 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/history_clusters/HistoryClustersMediatorTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/history_clusters/HistoryClustersMediatorTest.java
@@ -73,6 +73,7 @@
 /** Unit tests for HistoryClustersMediator. */
 @RunWith(BaseRobolectricTestRunner.class)
 @Config(manifest = Config.NONE, shadows = {ShadowAppCompatResources.class, ShadowGURL.class})
+@SuppressWarnings("DoNotMock") // Mocks GURL
 public class HistoryClustersMediatorTest {
     private static final String ITEM_URL_SPEC = "https://www.wombats.com/";
 
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/media/ui/MediaNotificationTestTabHolder.java b/chrome/android/junit/src/org/chromium/chrome/browser/media/ui/MediaNotificationTestTabHolder.java
index edd8d44d..3add0ca4 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/media/ui/MediaNotificationTestTabHolder.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/media/ui/MediaNotificationTestTabHolder.java
@@ -35,6 +35,7 @@
 /**
  * Utility class for holding a Tab and relevant objects for media notification tests.
  */
+@SuppressWarnings("DoNotMock") // Mocks GURL
 public class MediaNotificationTestTabHolder {
     @Mock
     UrlFormatter.Natives mUrlFormatterJniMock;
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/share/ShareButtonControllerUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/share/ShareButtonControllerUnitTest.java
index 404a507..ed5d509 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/share/ShareButtonControllerUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/share/ShareButtonControllerUnitTest.java
@@ -55,6 +55,7 @@
 /** Unit tests for {@link ShareButtonController}. */
 @RunWith(BaseRobolectricTestRunner.class)
 @Config(manifest = Config.NONE)
+@SuppressWarnings("DoNotMock") // Mocks GURL
 public final class ShareButtonControllerUnitTest {
     private static final int WIDTH_DELTA = 50;
 
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/survey/SurveyHttpClientBridgeUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/survey/SurveyHttpClientBridgeUnitTest.java
index 6a4a613..06953ac 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/survey/SurveyHttpClientBridgeUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/survey/SurveyHttpClientBridgeUnitTest.java
@@ -33,6 +33,7 @@
 
 /** Unit test for {@link SurveyHttpClientBridge}. */
 @RunWith(BaseRobolectricTestRunner.class)
+@SuppressWarnings("DoNotMock") // Mocks GURL
 public class SurveyHttpClientBridgeUnitTest {
     private static final long FAKE_NATIVE_POINTER = 123456789L;
 
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/tab/RequestDesktopUtilsUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/tab/RequestDesktopUtilsUnitTest.java
index a5d3d687..3b0fee09 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/tab/RequestDesktopUtilsUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/tab/RequestDesktopUtilsUnitTest.java
@@ -32,6 +32,7 @@
  */
 @RunWith(BaseRobolectricTestRunner.class)
 @Config(manifest = Config.NONE)
+@SuppressWarnings("DoNotMock") // Mocking GURL
 public class RequestDesktopUtilsUnitTest {
     @Rule
     public JniMocker mJniMocker = new JniMocker();
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/toolbar/LocationBarModelUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/toolbar/LocationBarModelUnitTest.java
index 9ac1719..d02125f 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/toolbar/LocationBarModelUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/toolbar/LocationBarModelUnitTest.java
@@ -66,6 +66,7 @@
 @RunWith(BaseRobolectricTestRunner.class)
 @Config(manifest = Config.NONE, shadows = {ShadowGURL.class, ShadowTrustedCdn.class})
 @DisableFeatures({ChromeFeatureList.LOCATION_BAR_MODEL_OPTIMIZATIONS})
+@SuppressWarnings("DoNotMock") // Mocks GURL
 public class LocationBarModelUnitTest {
     @Implements(TrustedCdn.class)
     static class ShadowTrustedCdn {
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/toolbar/top/StartSurfaceToolbarMediatorUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/toolbar/top/StartSurfaceToolbarMediatorUnitTest.java
index fdc5a93a..316401b 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/toolbar/top/StartSurfaceToolbarMediatorUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/toolbar/top/StartSurfaceToolbarMediatorUnitTest.java
@@ -343,7 +343,7 @@
         mButtonData.setButtonSpec(new ButtonSpec(mDrawable, mOnClickListener,
                 /*onLongClickListener*/ null, /*contentDescriptionResId=*/5,
                 /*supportsTinting=*/false, /*iphCommandBuilder=*/null,
-                AdaptiveToolbarButtonVariant.UNKNOWN));
+                AdaptiveToolbarButtonVariant.UNKNOWN, /*actionChipLabelResId=*/Resources.ID_NULL));
         mButtonData.setCanShow(true);
         mMediator.updateIdentityDisc(mButtonData);
         assertTrue(mPropertyModel.get(IDENTITY_DISC_IS_VISIBLE));
@@ -357,7 +357,7 @@
         mButtonData.setButtonSpec(new ButtonSpec(testDrawable2, mOnClickListener,
                 /*onLongClickListener*/ null, /*contentDescriptionResId=*/5,
                 /*supportsTinting=*/false, /*iphCommandBuilder=*/null,
-                AdaptiveToolbarButtonVariant.UNKNOWN));
+                AdaptiveToolbarButtonVariant.UNKNOWN, /*actionChipLabelResId=*/Resources.ID_NULL));
         mMediator.updateIdentityDisc(mButtonData);
         assertEquals(testDrawable2, mPropertyModel.get(IDENTITY_DISC_IMAGE));
 
@@ -397,7 +397,7 @@
         mButtonData.setButtonSpec(new ButtonSpec(mDrawable, mOnClickListener,
                 /*onLongClickListener*/ null, /*contentDescriptionResId=*/0,
                 /*supportsTinting=*/false, /*iphCommandBuilder=*/iphCommandBuilder,
-                AdaptiveToolbarButtonVariant.UNKNOWN));
+                AdaptiveToolbarButtonVariant.UNKNOWN, /*actionChipLabelResId=*/Resources.ID_NULL));
 
         mMediator.updateIdentityDisc(mButtonData);
         assertTrue(mPropertyModel.get(IDENTITY_DISC_IS_VISIBLE));
diff --git a/chrome/app/chrome_command_ids.h b/chrome/app/chrome_command_ids.h
index 5bfab4d..c6aad13 100644
--- a/chrome/app/chrome_command_ids.h
+++ b/chrome/app/chrome_command_ids.h
@@ -339,6 +339,7 @@
 #define IDC_CONTENT_CONTEXT_GENERATEPASSWORD 50166
 #define IDC_CONTENT_CONTEXT_EXIT_FULLSCREEN 50167
 #define IDC_CONTENT_CONTEXT_SHOWALLSAVEDPASSWORDS 50168
+#define IDC_CONTENT_CONTEXT_PARTIAL_TRANSLATE 50169
 // Frame items.
 #define IDC_CONTENT_CONTEXT_RELOADFRAME 50170
 #define IDC_CONTENT_CONTEXT_VIEWFRAMESOURCE 50171
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index a72faff..f0371beb 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -764,6 +764,9 @@
           <message name="IDS_CONTENT_CONTEXT_SELECTALL" desc="The name of the Select All command in the content area context menu">
             Select &amp;all
           </message>
+          <message name="IDS_CONTENT_CONTEXT_PARTIAL_TRANSLATE" desc="The name of the Partial Translate command in the content area context menu">
+            &amp;Translate to <ph name="LANGUAGE">$1<ex>English</ex></ph>
+          </message>
           <message name="IDS_CONTENT_CONTEXT_SEARCHWEBFOR" desc="The name of the Search the Web for “string” command in the content area context menu">
             &amp;Search <ph name="SEARCH_ENGINE">$1<ex>Google</ex></ph> for “<ph name="SEARCH_TERMS">$2<ex>flowers</ex></ph>”
           </message>
@@ -1024,6 +1027,9 @@
           <message name="IDS_CONTENT_CONTEXT_SELECTALL" desc="In Title Case: The name of the Select All command in the content area context menu">
             Select &amp;All
           </message>
+          <message name="IDS_CONTENT_CONTEXT_PARTIAL_TRANSLATE" desc="In Title Case: The name of the Partial Translate command in the content area context menu">
+            &amp;Translate to <ph name="LANGUAGE">$1<ex>English</ex></ph>
+          </message>
           <message name="IDS_CONTENT_CONTEXT_SEARCHWEBFOR" desc="In Title Case: The name of the Search the Web for “string” command in the content area context menu">
             &amp;Search <ph name="SEARCH_ENGINE">$1<ex>Google</ex></ph> for “<ph name="SEARCH_TERMS">$2<ex>flowers</ex></ph>”
           </message>
@@ -4227,15 +4233,15 @@
         <message name="IDS_EXTENSION_BLOCKED_ACTION_BUBBLE_OK_BUTTON" desc="The text of the button to proceed with the page refresh for running an extension.">
           Reload
         </message>
-        <message name="IDS_EXTENSION_BLOCKED_ACTION_BUBBLE_SINGLE_EXTENSION_TITLE" desc="Title of the bubble to tell users that in order to run an extension, they'll need to refresh the page.">
+        <message name="IDS_EXTENSION_BLOCKED_ACTION_BUBBLE_SINGLE_EXTENSION_TITLE" desc="Title of the bubble to tell users that in order to run or block an extension, they'll need to refresh the page.">
           Reload the page to use "<ph name="EXTENSION_NAME">$1<ex>Gmail Checker</ex></ph>"
         </message>
+        <message name="IDS_EXTENSION_BLOCKED_ACTION_BUBBLE_MULTIPLE_EXTENSIONS_TITLE" desc="Title of the bubble to tell users that in order to run or block an extension, they'll need to refresh the page.">
+          Reload the page to use these extensions
+        </message>
         <message name="IDS_EXTENSION_BLOCKED_ACTION_BUBBLE_UPDATE_PERMISSIONS_TITLE" desc="Title of the bubble to tell users that in order to change site permissions, they'll need to refresh the page.">
           Reload the page to apply your changes
         </message>
-        <message name="IDS_EXTENSION_BLOCKED_ACTION_BUBBLE_CHECKBOX_LABEL" desc="The label of the checkbox to always allow the extensions to run on the current site.">
-          Always allow on this site
-        </message>
       </if>
 
       <!-- Extension/App install prompt -->
@@ -12755,29 +12761,6 @@
       Open now
     </message>
 
-    <!-- Tailored security dialog for consented primary accounts -->
-    <message name="IDS_TAILORED_SECURITY_ENABLED_MODAL_TITLE" desc="Title shown in the tab modal dialog when the account tailored security setting changes for a consented primary account">
-      Enhanced Safe Browsing is on
-    </message>
-    <message name="IDS_TAILORED_SECURITY_DISABLED_MODAL_TITLE" desc="Title shown in the tab modal dialog when the account tailored security setting changes for a consented primary account">
-      Enhanced Safe Browsing is off
-    </message>
-
-    <message name="IDS_TAILORED_SECURITY_ENABLED_MODAL_MAIN_TEXT" desc="Title shown in the tab modal dialog when the account tailored security setting changes for a consented primary account">
-      You have Chrome's strongest security against dangerous websites, downloads and extensions
-    </message>
-    <message name="IDS_TAILORED_SECURITY_DISABLED_MODAL_MAIN_TEXT" desc="Title shown in the tab modal dialog when the account tailored security setting changes for a consented primary account">
-      You're getting standard security protection. To get more protection against dangerous websites, downloads, and extensions, turn on Enhanced Safe Browsing.
-    </message>
-    <message name="IDS_TAILORED_SECURITY_MODAL_ACCEPT_BUTTON" desc="The text on the accept button in the tab modal dialog when the account tailored security setting changes for a consented primary account">
-      OK
-    </message>
-    <message name="IDS_TAILORED_SECURITY_MODAL_SETTINGS_BUTTON" desc="The text on the cancel button in the tab modal dialog when the account tailored security setting changes for a consented primary account">
-      Chrome settings
-    </message>
-
-
-
     <!-- Tailored security dialog for unconsented primary accounts -->
     <message name="IDS_TAILORED_SECURITY_UNCONSENTED_MODAL_TITLE" desc="Title shown in the tab modal dialog when the account tailored security setting changes for an unconsented primary account">
       Also turn on Enhanced Safe Browsing for this Chrome profile?
diff --git a/chrome/app/generated_resources_grd/IDS_CONTENT_CONTEXT_PARTIAL_TRANSLATE.png.sha1 b/chrome/app/generated_resources_grd/IDS_CONTENT_CONTEXT_PARTIAL_TRANSLATE.png.sha1
new file mode 100644
index 0000000..391c078
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_CONTENT_CONTEXT_PARTIAL_TRANSLATE.png.sha1
@@ -0,0 +1 @@
+36cc3a3b22c922700bad10dfb582045ceac2b48f
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_EXTENSION_BLOCKED_ACTION_BUBBLE_CHECKBOX_LABEL.png.sha1 b/chrome/app/generated_resources_grd/IDS_EXTENSION_BLOCKED_ACTION_BUBBLE_CHECKBOX_LABEL.png.sha1
deleted file mode 100644
index b2e72ca..0000000
--- a/chrome/app/generated_resources_grd/IDS_EXTENSION_BLOCKED_ACTION_BUBBLE_CHECKBOX_LABEL.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-2e1ac8a4ff62f65509e778e62d8c40347db6a7b0
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_EXTENSION_BLOCKED_ACTION_BUBBLE_MULTIPLE_EXTENSIONS_TITLE.png.sha1 b/chrome/app/generated_resources_grd/IDS_EXTENSION_BLOCKED_ACTION_BUBBLE_MULTIPLE_EXTENSIONS_TITLE.png.sha1
new file mode 100644
index 0000000..97b49a6
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_EXTENSION_BLOCKED_ACTION_BUBBLE_MULTIPLE_EXTENSIONS_TITLE.png.sha1
@@ -0,0 +1 @@
+54c699878482736ca7e7becabfba34c0fe29649b
diff --git a/chrome/app/generated_resources_grd/IDS_TAILORED_SECURITY_DISABLED_MODAL_MAIN_TEXT.png.sha1 b/chrome/app/generated_resources_grd/IDS_TAILORED_SECURITY_DISABLED_MODAL_MAIN_TEXT.png.sha1
deleted file mode 100644
index 9579270..0000000
--- a/chrome/app/generated_resources_grd/IDS_TAILORED_SECURITY_DISABLED_MODAL_MAIN_TEXT.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-d7929629b0ef0c1ebec1d37d39cda7a226320496
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_TAILORED_SECURITY_DISABLED_MODAL_TITLE.png.sha1 b/chrome/app/generated_resources_grd/IDS_TAILORED_SECURITY_DISABLED_MODAL_TITLE.png.sha1
deleted file mode 100644
index 9579270..0000000
--- a/chrome/app/generated_resources_grd/IDS_TAILORED_SECURITY_DISABLED_MODAL_TITLE.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-d7929629b0ef0c1ebec1d37d39cda7a226320496
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_TAILORED_SECURITY_ENABLED_MODAL_MAIN_TEXT.png.sha1 b/chrome/app/generated_resources_grd/IDS_TAILORED_SECURITY_ENABLED_MODAL_MAIN_TEXT.png.sha1
deleted file mode 100644
index e9cd9d9..0000000
--- a/chrome/app/generated_resources_grd/IDS_TAILORED_SECURITY_ENABLED_MODAL_MAIN_TEXT.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-5f09b467c8915ab1506bf046ed01ce83b0b14b8f
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_TAILORED_SECURITY_ENABLED_MODAL_TITLE.png.sha1 b/chrome/app/generated_resources_grd/IDS_TAILORED_SECURITY_ENABLED_MODAL_TITLE.png.sha1
deleted file mode 100644
index e9cd9d9..0000000
--- a/chrome/app/generated_resources_grd/IDS_TAILORED_SECURITY_ENABLED_MODAL_TITLE.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-5f09b467c8915ab1506bf046ed01ce83b0b14b8f
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_TAILORED_SECURITY_MODAL_ACCEPT_BUTTON.png.sha1 b/chrome/app/generated_resources_grd/IDS_TAILORED_SECURITY_MODAL_ACCEPT_BUTTON.png.sha1
deleted file mode 100644
index e9cd9d9..0000000
--- a/chrome/app/generated_resources_grd/IDS_TAILORED_SECURITY_MODAL_ACCEPT_BUTTON.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-5f09b467c8915ab1506bf046ed01ce83b0b14b8f
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_TAILORED_SECURITY_MODAL_SETTINGS_BUTTON.png.sha1 b/chrome/app/generated_resources_grd/IDS_TAILORED_SECURITY_MODAL_SETTINGS_BUTTON.png.sha1
deleted file mode 100644
index e9cd9d9..0000000
--- a/chrome/app/generated_resources_grd/IDS_TAILORED_SECURITY_MODAL_SETTINGS_BUTTON.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-5f09b467c8915ab1506bf046ed01ce83b0b14b8f
\ No newline at end of file
diff --git a/chrome/app/os_settings_strings.grdp b/chrome/app/os_settings_strings.grdp
index e1d8c7b..0366893 100644
--- a/chrome/app/os_settings_strings.grdp
+++ b/chrome/app/os_settings_strings.grdp
@@ -4295,6 +4295,9 @@
   <message name="IDS_OS_SETTINGS_CAMERA_TOGGLE_TITLE" desc="The title of the toggle to enable/disable camera from the privacy hub.">
     Camera
   </message>
+  <message name="IDS_OS_SETTINGS_MICROPHONE_TOGGLE_TITLE" desc="The title of the toggle to enable/disable microphone from the privacy hub.">
+    Microphone
+  </message>
   <message name="IDS_OS_SETTINGS_HW_DATA_USAGE_TOGGLE_TITLE" desc="The label of the checkbox to enable/disable device hardware data collection and usage in ChromeOS Flex.">
     <ph name="DEVICE_OS">$1<ex>ChromeOS Flex</ex></ph> hardware support and stability
   </message>
diff --git a/chrome/app/os_settings_strings_grdp/IDS_OS_SETTINGS_MICROPHONE_TOGGLE_TITLE.png.sha1 b/chrome/app/os_settings_strings_grdp/IDS_OS_SETTINGS_MICROPHONE_TOGGLE_TITLE.png.sha1
new file mode 100644
index 0000000..adefef0
--- /dev/null
+++ b/chrome/app/os_settings_strings_grdp/IDS_OS_SETTINGS_MICROPHONE_TOGGLE_TITLE.png.sha1
@@ -0,0 +1 @@
+5b77a413e8aaad15d210d5d83c0e53fec5f0b693
\ No newline at end of file
diff --git a/chrome/app/theme/default_100_percent/common/safer_with_google_shield.png b/chrome/app/theme/default_100_percent/common/safer_with_google_shield.png
deleted file mode 100644
index 20418ed..0000000
--- a/chrome/app/theme/default_100_percent/common/safer_with_google_shield.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/common/safer_with_google_shield_dark.png b/chrome/app/theme/default_100_percent/common/safer_with_google_shield_dark.png
deleted file mode 100644
index bf9dd97..0000000
--- a/chrome/app/theme/default_100_percent/common/safer_with_google_shield_dark.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_200_percent/common/safer_with_google_shield.png b/chrome/app/theme/default_200_percent/common/safer_with_google_shield.png
deleted file mode 100644
index 480baa1..0000000
--- a/chrome/app/theme/default_200_percent/common/safer_with_google_shield.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_200_percent/common/safer_with_google_shield_dark.png b/chrome/app/theme/default_200_percent/common/safer_with_google_shield_dark.png
deleted file mode 100644
index 7916f1e..0000000
--- a/chrome/app/theme/default_200_percent/common/safer_with_google_shield_dark.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/theme_resources.grd b/chrome/app/theme/theme_resources.grd
index 65339d2..bb9fee8 100644
--- a/chrome/app/theme/theme_resources.grd
+++ b/chrome/app/theme/theme_resources.grd
@@ -335,8 +335,6 @@
         </if>
       </if>
       <if expr="not is_android">
-        <structure type="chrome_scaled_image" name="IDR_TAILORED_SECURITY_CONSENTED" file="common/safer_with_google_shield.png" />
-        <structure type="chrome_scaled_image" name="IDR_TAILORED_SECURITY_CONSENTED_DARK" file="common/safer_with_google_shield_dark.png" />
         <structure type="chrome_scaled_image" name="IDR_TAILORED_SECURITY_UNCONSENTED" file="common/tailored_security_unconsented.png" />
       </if>
     </structures>
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 0a4143f..0ebc891 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -1214,15 +1214,6 @@
      kOmniboxBookmarkPathsDynamicReplaceUrl,
      std::size(kOmniboxBookmarkPathsDynamicReplaceUrl), nullptr}};
 
-const FeatureEntry::FeatureVariation
-    kOmniboxOnFocusSuggestionsContextualWebVariations[] = {
-        {"GOC Only", {}, 0, "t3317583"},
-        {"pSuggest Only", {}, 0, "t3318055"},
-        {"GOC, pSuggest Fallback", {}, 0, "t3317692"},
-        {"GOC, pSuggest Backfill", {}, 0, "t3317694"},
-        {"GOC, Default Hidden", {}, 0, "t3317834"},
-};
-
 const FeatureEntry::FeatureParam kMaxZeroSuggestMatches5[] = {
     {"MaxZeroSuggestMatches", "5"}};
 const FeatureEntry::FeatureParam kMaxZeroSuggestMatches6[] = {
@@ -4952,20 +4943,16 @@
 #endif  // BUILDFLAG(IS_ANDROID)
 
     {"omnibox-on-focus-suggestions-contextual-web",
-     flag_descriptions::kOmniboxOnFocusSuggestionsContextualWebName,
-     flag_descriptions::kOmniboxOnFocusSuggestionsContextualWebDescription,
-     kOsAll,
-     FEATURE_WITH_PARAMS_VALUE_TYPE(
-         omnibox::kOnFocusSuggestionsContextualWeb,
-         kOmniboxOnFocusSuggestionsContextualWebVariations,
-         "OmniboxGoogleOnContent")},
-
-    {"omnibox-on-focus-suggestions-contextual-web-allow-srp",
-     flag_descriptions::kOmniboxOnFocusSuggestionsContextualWebAllowSRPName,
+     flag_descriptions::kOmniboxFocusTriggersContextualWebZeroSuggestName,
      flag_descriptions::
-         kOmniboxOnFocusSuggestionsContextualWebAllowSRPDescription,
+         kOmniboxFocusTriggersContextualWebZeroSuggestDescription,
      kOsAll,
-     FEATURE_VALUE_TYPE(omnibox::kOnFocusSuggestionsContextualWebAllowSRP)},
+     FEATURE_VALUE_TYPE(omnibox::kFocusTriggersContextualWebZeroSuggest)},
+
+    {"omnibox-on-focus-suggestions-srp",
+     flag_descriptions::kOmniboxFocusTriggersSRPZeroSuggestName,
+     flag_descriptions::kOmniboxFocusTriggersSRPZeroSuggestDescription, kOsAll,
+     FEATURE_VALUE_TYPE(omnibox::kFocusTriggersSRPZeroSuggest)},
 
     {"omnibox-experimental-suggest-scoring",
      flag_descriptions::kOmniboxExperimentalSuggestScoringName,
diff --git a/chrome/browser/ash/policy/reporting/user_event_reporter_helper.cc b/chrome/browser/ash/policy/reporting/user_event_reporter_helper.cc
index eb390b1d..09a63bb 100644
--- a/chrome/browser/ash/policy/reporting/user_event_reporter_helper.cc
+++ b/chrome/browser/ash/policy/reporting/user_event_reporter_helper.cc
@@ -42,6 +42,15 @@
   return enabled;
 }
 
+bool UserEventReporterHelper::IsKioskUser() const {
+  DCHECK_CURRENTLY_ON(::content::BrowserThread::UI);
+  auto* const primary = user_manager::UserManager::Get()->GetPrimaryUser();
+  if (!primary) {
+    return false;
+  }
+  return primary->IsKioskType();
+}
+
 void UserEventReporterHelper::ReportEvent(
     std::unique_ptr<const google::protobuf::MessageLite> record,
     Priority priority,
diff --git a/chrome/browser/ash/policy/reporting/user_event_reporter_helper.h b/chrome/browser/ash/policy/reporting/user_event_reporter_helper.h
index cd0773f..cd8291f 100644
--- a/chrome/browser/ash/policy/reporting/user_event_reporter_helper.h
+++ b/chrome/browser/ash/policy/reporting/user_event_reporter_helper.h
@@ -49,6 +49,11 @@
   // Must be called on UI task runner (returned by valid_task_runner() below).
   virtual bool ReportingEnabled(const std::string& policy_path) const;
 
+  // Returns whether the primary account is a kiosk.
+  // Usually called if |ShouldReportUser| returned false.
+  // Must be called on UI task runner (returned by valid_task_runner() below).
+  virtual bool IsKioskUser() const;
+
   // Reports an event to the queue.
   // Thread safe, can be called on any thread.
   virtual void ReportEvent(
diff --git a/chrome/browser/ash/printing/oauth2/authorization_zones_manager.cc b/chrome/browser/ash/printing/oauth2/authorization_zones_manager.cc
index 9a0c28ea..1affbde 100644
--- a/chrome/browser/ash/printing/oauth2/authorization_zones_manager.cc
+++ b/chrome/browser/ash/printing/oauth2/authorization_zones_manager.cc
@@ -4,25 +4,127 @@
 
 #include "chrome/browser/ash/printing/oauth2/authorization_zones_manager.h"
 
+#include <map>
 #include <memory>
+#include <string>
 
+#include "base/check.h"
+#include "base/containers/contains.h"
+#include "chrome/browser/ash/printing/oauth2/authorization_zone.h"
+#include "chrome/browser/ash/printing/oauth2/status_code.h"
 #include "chrome/browser/profiles/profile.h"
+#include "services/network/public/cpp/shared_url_loader_factory.h"
+#include "url/gurl.h"
 
-namespace ash {
-namespace printing {
-namespace oauth2 {
+namespace ash::printing::oauth2 {
 
 namespace {
 
 class AuthorizationZonesManagerImpl : public AuthorizationZonesManager {
  public:
-  explicit AuthorizationZonesManagerImpl(Profile* profile) {}
+  explicit AuthorizationZonesManagerImpl(Profile* profile)
+      : url_loader_factory_(profile->GetURLLoaderFactory()) {}
+
+  StatusCode SaveAuthorizationServerAsTrusted(
+      const GURL& auth_server) override {
+    return ValidateURLAndSave(
+        auth_server,
+        AuthorizationZone::Create(url_loader_factory_, auth_server));
+  }
+
+  StatusCode SaveAuthorizationServerAsTrustedForTesting(
+      const GURL& auth_server,
+      std::unique_ptr<AuthorizationZone> auth_zone) override {
+    return ValidateURLAndSave(auth_server, std::move(auth_zone));
+  }
+
+  void InitAuthorization(const GURL& auth_server,
+                         const std::string& scope,
+                         StatusCallback callback) override {
+    AuthorizationZone* zone = GetAuthorizationZone(auth_server);
+    if (!zone) {
+      std::move(callback).Run(StatusCode::kUnknownAuthorizationServer,
+                              auth_server.possibly_invalid_spec());
+      return;
+    }
+
+    zone->InitAuthorization(scope, std::move(callback));
+  }
+
+  void FinishAuthorization(const GURL& auth_server,
+                           const GURL& redirect_url,
+                           StatusCallback callback) override {
+    AuthorizationZone* zone = GetAuthorizationZone(auth_server);
+    if (!zone) {
+      std::move(callback).Run(StatusCode::kUnknownAuthorizationServer,
+                              auth_server.possibly_invalid_spec());
+      return;
+    }
+
+    zone->FinishAuthorization(redirect_url, std::move(callback));
+  }
+
+  void GetEndpointAccessToken(const GURL& auth_server,
+                              const chromeos::Uri& ipp_endpoint,
+                              const std::string& scope,
+                              StatusCallback callback) override {
+    AuthorizationZone* zone = GetAuthorizationZone(auth_server);
+    if (!zone) {
+      std::move(callback).Run(StatusCode::kUnknownAuthorizationServer,
+                              auth_server.possibly_invalid_spec());
+      return;
+    }
+
+    zone->GetEndpointAccessToken(ipp_endpoint, scope, std::move(callback));
+  }
+
+  void MarkEndpointAccessTokenAsExpired(
+      const GURL& auth_server,
+      const chromeos::Uri& ipp_endpoint,
+      const std::string& endpoint_access_token) override {
+    AuthorizationZone* zone = GetAuthorizationZone(auth_server);
+    if (!zone) {
+      return;
+    }
+
+    zone->MarkEndpointAccessTokenAsExpired(ipp_endpoint, endpoint_access_token);
+  }
+
+ private:
+  // Helper method for adding a new element to `servers_`.
+  StatusCode ValidateURLAndSave(const GURL& auth_server,
+                                std::unique_ptr<AuthorizationZone> auth_zone) {
+    if (!auth_server.is_valid() || !auth_server.SchemeIs("https") ||
+        !auth_server.has_host() || auth_server.has_username() ||
+        auth_server.has_query() || auth_server.has_ref()) {
+      // TODO(pawliczek): log why the URL is invalid
+      return StatusCode::kInvalidURL;
+    }
+    if (!base::Contains(servers_, auth_server)) {
+      servers_.emplace(auth_server, std::move(auth_zone));
+    }
+    return StatusCode::kOK;
+  }
+
+  // Returns a pointer to the corresponding element in `servers_` or nullptr if
+  // `auth_server` is unknown.
+  AuthorizationZone* GetAuthorizationZone(const GURL& auth_server) {
+    auto it_server = servers_.find(auth_server);
+    if (it_server == servers_.end()) {
+      return nullptr;
+    }
+    return it_server->second.get();
+  }
+
+  std::map<GURL, std::unique_ptr<AuthorizationZone>> servers_;
+  scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory_;
 };
 
 }  // namespace
 
 std::unique_ptr<AuthorizationZonesManager> AuthorizationZonesManager::Create(
     Profile* profile) {
+  DCHECK(profile);
   return std::make_unique<AuthorizationZonesManagerImpl>(profile);
 }
 
@@ -30,6 +132,4 @@
 
 AuthorizationZonesManager::AuthorizationZonesManager() = default;
 
-}  // namespace oauth2
-}  // namespace printing
-}  // namespace ash
+}  // namespace ash::printing::oauth2
diff --git a/chrome/browser/ash/printing/oauth2/authorization_zones_manager.h b/chrome/browser/ash/printing/oauth2/authorization_zones_manager.h
index 982cfb6..76fe5d9d 100644
--- a/chrome/browser/ash/printing/oauth2/authorization_zones_manager.h
+++ b/chrome/browser/ash/printing/oauth2/authorization_zones_manager.h
@@ -6,26 +6,123 @@
 #define CHROME_BROWSER_ASH_PRINTING_OAUTH2_AUTHORIZATION_ZONES_MANAGER_H_
 
 #include <memory>
+#include <string>
 
+#include "chrome/browser/ash/printing/oauth2/status_code.h"
 #include "components/keyed_service/core/keyed_service.h"
 
+class GURL;
 class Profile;
 
-namespace ash {
-namespace printing {
-namespace oauth2 {
+namespace chromeos {
+class Uri;
+}  // namespace chromeos
 
+namespace ash::printing::oauth2 {
+
+class AuthorizationZone;
+
+// This class is responsible for managing OAuth2 sessions required to get access
+// to some printers. In the API provided by the class, printers are referred to
+// as IPP Endpoints. IPP Endpoints that require OAuth2 token report the
+// following IPP attributes in the response for Get-Printer-Attributes request:
+//   * oauth-authorization-server-uri - the URL of the Authorization Server;
+//   * oauth-authorization-scope - optional, if missing use an empty string.
+// These two values correspond to the parameters `auth_server` and `scope` in
+// the API below.
+//
+// How to use:
+//  * SaveAuthorizationServerAsTrusted() - this must be called once for each
+//    Authorization Server to mark it as trusted. The list of trusted
+//    Authorization Servers is saved in user's profile. All API calls for any
+//    Authorization Server not included in the trusted list will fail with the
+//    error StatusCode::kUnknownAuthorizationServer.
+//  * InitAuthorization() - the callback returns a URL that must be opened in an
+//    internet browser to allow a user to go through an authorization procedure.
+//  * FinishAuthorization() - this method finalizes the authorization procedure
+//    started by InitAuthorization(). The authorization procedure in the
+//    internet browser is completed when the browser receives a response with
+//    the HTTP 302 status code. The value of the "Location" attribute from the
+//    HTTP header must be passed to this method to finalize the process and
+//    open an OAuth2 session with the Authorization Server.
+//  * GetEndpointAccessToken() - the callback returns an endpoint access token
+//    for given IPP Endpoint. You can call this method as the first one and
+//    then fallback to InitAuthorization()/FinishAuthorization() when it
+//    returns the status StatusCode::kAuthorizationNeeded. This method can
+//    be called repeatedly and will return the same token for the same
+//    parameters until the method MarkEndpointAccessTokenAsExpired() is called.
+//  * MarkEndpointAccessTokenAsExpired() - call this method to mark the
+//    endpoint access token as expired. Then the following call to
+//    GetEndpointAccessToken() will try to obtain a new endpoint access token
+//    or returns StatusCode::kAuthorizationNeeded when a new authorization
+//    procedure is needed (i.e. a new call to InitAuthorization() and
+//    FinishAuthorization() is required).
+//
+// Results and errors are returned by StatusCallback passed as the last
+// parameter. See chrome/browser/ash/printing/oauth2/status_code.h for more
+// details.
 class AuthorizationZonesManager : public KeyedService {
  public:
+  // `profile` must not be nullptr.
   static std::unique_ptr<AuthorizationZonesManager> Create(Profile* profile);
   ~AuthorizationZonesManager() override;
 
+  // Marks `auth_server` as trusted.
+  virtual StatusCode SaveAuthorizationServerAsTrusted(
+      const GURL& auth_server) = 0;
+
+  virtual StatusCode SaveAuthorizationServerAsTrustedForTesting(
+      const GURL& auth_server,
+      std::unique_ptr<AuthorizationZone> auth_zone) = 0;
+
+  // Starts authorization process. If successful, the `callback` is called
+  // with StatusCode::kOK and with an authorization URL that must be opened in
+  // an internet browser to enable the user to complete the authorization
+  // process. Before calling this method the caller should make sure that
+  // creating a new session is necessary by calling the method
+  // GetEndpointAccessToken() first.
+  virtual void InitAuthorization(const GURL& auth_server,
+                                 const std::string& scope,
+                                 StatusCallback callback) = 0;
+
+  // Finalizes authorization process. As an parameter this method takes an URL
+  // that the internet browser was redirected to at the end of the authorization
+  // procedure completed by the user. The return code StatusCode::kOK means
+  // that a new OAuth2 session was created and the caller can now use the method
+  // GetEndpointAccessToken() to get an endpoint access token.
+  virtual void FinishAuthorization(const GURL& auth_server,
+                                   const GURL& redirect_url,
+                                   StatusCallback callback) = 0;
+
+  // Obtains an endpoint access token to use with the given IPP Endpoint. If
+  // succeeded `callback` returns StatusCode::kOK and endpoint access token as
+  // `data`. StatusCode::kAuthorizationNeeded means that the caller must call
+  // methods InitAuthorization() and FinishAuthorization() first to open OAuth2
+  // session. The parameter `scope` is used only when an endpoint access token
+  // for the given IPP endpoint does not exists yet (the parameter has no
+  // effects when the endpoint access token already exists).
+  virtual void GetEndpointAccessToken(const GURL& auth_server,
+                                      const chromeos::Uri& ipp_endpoint,
+                                      const std::string& scope,
+                                      StatusCallback callback) = 0;
+
+  // This method marks the `endpoint_access_token` issued for `ipp_endpoint` as
+  // expired. The next call to GetEndpointAccessToken() will start internally a
+  // procedure to obtain a new endpoint access token. This method should be
+  // called when the IPP Endpoint rejects a request with `endpoint_access_token`
+  // by sending back a response with the HTTP 401 status code. If the HTTP
+  // header of that response contains "error" attribute, you should check if it
+  // equals "invalid_token" before calling this method. See RFC 6750 for more
+  // details.
+  virtual void MarkEndpointAccessTokenAsExpired(
+      const GURL& auth_server,
+      const chromeos::Uri& ipp_endpoint,
+      const std::string& endpoint_access_token) = 0;
+
  protected:
   AuthorizationZonesManager();
 };
 
-}  // namespace oauth2
-}  // namespace printing
-}  // namespace ash
+}  // namespace ash::printing::oauth2
 
 #endif  // CHROME_BROWSER_ASH_PRINTING_OAUTH2_AUTHORIZATION_ZONES_MANAGER_H_
diff --git a/chrome/browser/ash/printing/oauth2/authorization_zones_manager_unittest.cc b/chrome/browser/ash/printing/oauth2/authorization_zones_manager_unittest.cc
new file mode 100644
index 0000000..dd994e7
--- /dev/null
+++ b/chrome/browser/ash/printing/oauth2/authorization_zones_manager_unittest.cc
@@ -0,0 +1,218 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ash/printing/oauth2/authorization_zones_manager.h"
+
+#include <memory>
+#include <string>
+
+#include "base/run_loop.h"
+#include "base/test/mock_callback.h"
+#include "chrome/browser/ash/printing/oauth2/authorization_zone.h"
+#include "chrome/browser/ash/printing/oauth2/status_code.h"
+#include "chrome/browser/ash/printing/oauth2/test_authorization_server.h"
+#include "chrome/test/base/testing_profile.h"
+#include "chromeos/printing/uri.h"
+#include "content/public/test/browser_task_environment.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace ash::printing::oauth2 {
+namespace {
+
+class MockAuthorizationZone : public AuthorizationZone {
+ public:
+  MOCK_METHOD(void,
+              InitAuthorization,
+              (const std::string& scope, StatusCallback callback),
+              (override));
+  MOCK_METHOD(void,
+              FinishAuthorization,
+              (const GURL& redirect_url, StatusCallback callback),
+              (override));
+  MOCK_METHOD(void,
+              GetEndpointAccessToken,
+              (const chromeos::Uri& ipp_endpoint,
+               const std::string& scope,
+               StatusCallback callback),
+              (override));
+  MOCK_METHOD(void,
+              MarkEndpointAccessTokenAsExpired,
+              (const chromeos::Uri& ipp_endpoint,
+               const std::string& endpoint_access_token),
+              (override));
+};
+
+class PrintingOAuth2AuthorizationZonesManagerTest : public testing::Test {
+ protected:
+  using AuthZoneMock = testing::StrictMock<MockAuthorizationZone>;
+
+  PrintingOAuth2AuthorizationZonesManagerTest() = default;
+
+  // Creates a mock of AuthorizationZone, stores it as a trusted server.
+  // Returns a pointer to the mock. The mock is owned by `auth_zones_manager_`.
+  AuthZoneMock* CallSaveAuthorizationServerAsTrusted(const GURL& auth_server) {
+    auto auth_zone = std::make_unique<AuthZoneMock>();
+    auto* auth_zone_ptr = auth_zone.get();
+    const StatusCode sc =
+        auth_zones_manager_->SaveAuthorizationServerAsTrustedForTesting(
+            auth_server, std::move(auth_zone));
+    EXPECT_EQ(sc, StatusCode::kOK);
+    return auth_zone_ptr;
+  }
+
+  // Calls InitAuthorization(...) and waits for the callback.
+  CallbackResult CallInitAuthorization(const GURL& auth_server,
+                                       const std::string& scope) {
+    base::MockOnceCallback<void(StatusCode, const std::string&)> callback;
+    CallbackResult cr;
+    base::RunLoop loop;
+    EXPECT_CALL(callback, Run)
+        .WillOnce([&cr, &loop](StatusCode status, const std::string& data) {
+          cr.status = status;
+          cr.data = data;
+          loop.Quit();
+        });
+    auth_zones_manager_->InitAuthorization(auth_server, scope, callback.Get());
+    loop.Run();
+    return cr;
+  }
+
+  // Calls FinishAuthorization(...) and waits for the callback.
+  CallbackResult CallFinishAuthorization(const GURL& auth_server,
+                                         const GURL& redirect_url) {
+    base::MockOnceCallback<void(StatusCode, const std::string&)> callback;
+    CallbackResult cr;
+    base::RunLoop loop;
+    EXPECT_CALL(callback, Run)
+        .WillOnce([&cr, &loop](StatusCode status, const std::string& data) {
+          cr.status = status;
+          cr.data = data;
+          loop.Quit();
+        });
+    auth_zones_manager_->FinishAuthorization(auth_server, redirect_url,
+                                             callback.Get());
+    loop.Run();
+    return cr;
+  }
+
+  // Calls GetEndpointAccessToken(...) and waits for the callback.
+  CallbackResult CallGetEndpointAccessToken(const GURL& auth_server,
+                                            const chromeos::Uri& ipp_endpoint,
+                                            const std::string& scope) {
+    base::MockOnceCallback<void(StatusCode, const std::string&)> callback;
+    CallbackResult cr;
+    base::RunLoop loop;
+    EXPECT_CALL(callback, Run)
+        .WillOnce([&cr, &loop](StatusCode status, const std::string& data) {
+          cr.status = status;
+          cr.data = data;
+          loop.Quit();
+        });
+    auth_zones_manager_->GetEndpointAccessToken(auth_server, ipp_endpoint,
+                                                scope, callback.Get());
+    loop.Run();
+    return cr;
+  }
+
+  void ExpectCallInitAuthorization(AuthZoneMock* auth_zone,
+                                   const std::string& scope,
+                                   CallbackResult results_to_report) {
+    EXPECT_CALL(*auth_zone, InitAuthorization(scope, testing::_))
+        .WillOnce(
+            [&results_to_report](const std::string&, StatusCallback callback) {
+              std::move(callback).Run(results_to_report.status,
+                                      results_to_report.data);
+            });
+  }
+
+  void ExpectCallFinishAuthorization(AuthZoneMock* auth_zone,
+                                     const GURL& redirect_url,
+                                     CallbackResult results_to_report) {
+    EXPECT_CALL(*auth_zone, FinishAuthorization(redirect_url, testing::_))
+        .WillOnce([&results_to_report](const GURL&, StatusCallback callback) {
+          std::move(callback).Run(results_to_report.status,
+                                  results_to_report.data);
+        });
+  }
+
+  void ExpectCallGetEndpointAccessToken(AuthZoneMock* auth_zone,
+                                        const chromeos::Uri& ipp_endpoint,
+                                        const std::string& scope,
+                                        CallbackResult results_to_report) {
+    EXPECT_CALL(*auth_zone,
+                GetEndpointAccessToken(ipp_endpoint, scope, testing::_))
+        .WillOnce([&results_to_report](const chromeos::Uri&, const std::string&,
+                                       StatusCallback callback) {
+          std::move(callback).Run(results_to_report.status,
+                                  results_to_report.data);
+        });
+  }
+
+  void ExpectCallMarkEndpointAccessTokenAsExpired(
+      AuthZoneMock* auth_zone,
+      const chromeos::Uri& ipp_endpoint,
+      const std::string& endpoint_access_token) {
+    EXPECT_CALL(*auth_zone, MarkEndpointAccessTokenAsExpired(
+                                ipp_endpoint, endpoint_access_token))
+        .Times(1);
+  }
+
+  content::BrowserTaskEnvironment task_environment_;
+  TestingProfile profile_;
+  std::unique_ptr<AuthorizationZonesManager> auth_zones_manager_ =
+      AuthorizationZonesManager::Create(&profile_);
+};
+
+TEST_F(PrintingOAuth2AuthorizationZonesManagerTest, UnknownAuthServer) {
+  GURL url("https://ala.ma.kota/albo/psa");
+  GURL redirect_url("https://abc:123/def?ghi=jkl");
+  chromeos::Uri ipp_endpoint("https://printer");
+
+  CallbackResult cr = CallInitAuthorization(url, "scope");
+  EXPECT_EQ(cr.status, StatusCode::kUnknownAuthorizationServer);
+
+  cr = CallFinishAuthorization(url, redirect_url);
+  EXPECT_EQ(cr.status, StatusCode::kUnknownAuthorizationServer);
+
+  cr = CallGetEndpointAccessToken(url, ipp_endpoint, "scope");
+  EXPECT_EQ(cr.status, StatusCode::kUnknownAuthorizationServer);
+}
+
+TEST_F(PrintingOAuth2AuthorizationZonesManagerTest, PassingCallsToAuthZones) {
+  GURL url_1("https://ala.ma.kota/albo/psa");
+  GURL url_2("https://other.server:1234");
+  GURL redirect_url("https://abc:123/def?ghi=jkl");
+  chromeos::Uri ipp_endpoint("https://printer");
+
+  AuthZoneMock* auth_zone_1 = CallSaveAuthorizationServerAsTrusted(url_1);
+  AuthZoneMock* auth_zone_2 = CallSaveAuthorizationServerAsTrusted(url_2);
+
+  ExpectCallInitAuthorization(auth_zone_1, "scope1",
+                              {StatusCode::kOK, "auth_url"});
+  CallbackResult cr = CallInitAuthorization(url_1, "scope1");
+  EXPECT_EQ(cr.status, StatusCode::kOK);
+  EXPECT_EQ(cr.data, "auth_url");
+
+  ExpectCallFinishAuthorization(auth_zone_2, redirect_url,
+                                {StatusCode::kNoMatchingSession, "abc"});
+  cr = CallFinishAuthorization(url_2, redirect_url);
+  EXPECT_EQ(cr.status, StatusCode::kNoMatchingSession);
+  EXPECT_EQ(cr.data, "abc");
+
+  ExpectCallGetEndpointAccessToken(
+      auth_zone_1, ipp_endpoint, "scope1 scope2",
+      {StatusCode::kServerTemporarilyUnavailable, "123"});
+  cr = CallGetEndpointAccessToken(url_1, ipp_endpoint, "scope1 scope2");
+  EXPECT_EQ(cr.status, StatusCode::kServerTemporarilyUnavailable);
+  EXPECT_EQ(cr.data, "123");
+
+  ExpectCallMarkEndpointAccessTokenAsExpired(auth_zone_2, ipp_endpoint, "zaq1");
+  auth_zones_manager_->MarkEndpointAccessTokenAsExpired(url_2, ipp_endpoint,
+                                                        "zaq1");
+}
+
+}  // namespace
+}  // namespace ash::printing::oauth2
diff --git a/chrome/browser/ash/printing/oauth2/status_code.h b/chrome/browser/ash/printing/oauth2/status_code.h
index 28b842ef..c804108 100644
--- a/chrome/browser/ash/printing/oauth2/status_code.h
+++ b/chrome/browser/ash/printing/oauth2/status_code.h
@@ -16,6 +16,8 @@
 enum class StatusCode {
   // Success - no errors occurred.
   kOK = 0,
+  // The provided server URL is invalid.
+  kInvalidURL,
   // The client is registered to the server but there is no active OAuth2
   // sessions. Run the method InitAuthorization(...) and then
   // FinishAuthorization(...) to start OAuth2 session,
diff --git a/chrome/browser/autofill_assistant/password_change/apc_client.h b/chrome/browser/autofill_assistant/password_change/apc_client.h
index 290e8847..adcf04ef 100644
--- a/chrome/browser/autofill_assistant/password_change/apc_client.h
+++ b/chrome/browser/autofill_assistant/password_change/apc_client.h
@@ -7,6 +7,9 @@
 
 #include <string>
 
+#include "base/callback.h"
+#include "base/callback_helpers.h"
+
 class GURL;
 class PrefRegistrySimple;
 
@@ -17,6 +20,8 @@
 // Abstract interface to encapsulate an automated password change (APC) flow.
 class ApcClient {
  public:
+  using ResultCallback = base::OnceCallback<void(bool)>;
+
   // Registers the prefs that are related to automated password change on
   // Desktop.
   static void RegisterPrefs(PrefRegistrySimple* registry);
@@ -29,15 +34,17 @@
   ApcClient(const ApcClient&) = delete;
   ApcClient& operator=(const ApcClient&) = delete;
 
-  // Starts the automated password change flow. Returns true if the flow start
-  // was successful.
-  virtual bool Start(const GURL& url,
+  // Starts the automated password change flow at `url` with `username`.
+  // Calls `callback` at the termination of the flow with a boolean parameter
+  // that indicates whether the credential was changed successfully.
+  virtual void Start(const GURL& url,
                      const std::string& username,
-                     bool skip_login) = 0;
+                     bool skip_login,
+                     ResultCallback callback = base::DoNothing()) = 0;
 
   // Terminates the current APC flow and sets the internal state to make itself
   // available for future calls to run.
-  virtual void Stop() = 0;
+  virtual void Stop(bool success = false) = 0;
 
   // Returns whether a flow is currently running, regardless of whether it is
   // in the onboarding phase or the execution phase.
diff --git a/chrome/browser/autofill_assistant/password_change/apc_client_impl.cc b/chrome/browser/autofill_assistant/password_change/apc_client_impl.cc
index ca626d9d..7e82ed7 100644
--- a/chrome/browser/autofill_assistant/password_change/apc_client_impl.cc
+++ b/chrome/browser/autofill_assistant/password_change/apc_client_impl.cc
@@ -6,6 +6,7 @@
 
 #include <memory>
 #include <string>
+#include <utility>
 
 #include "base/bind.h"
 #include "base/feature_list.h"
@@ -46,18 +47,24 @@
 
 ApcClientImpl::~ApcClientImpl() = default;
 
-bool ApcClientImpl::Start(const GURL& url,
+void ApcClientImpl::Start(const GURL& url,
                           const std::string& username,
-                          bool skip_login) {
+                          bool skip_login,
+                          ResultCallback callback) {
   // If the unified side panel is not enabled, trying to register an entry in it
   // later on will crash.
-  if (!base::FeatureList::IsEnabled(features::kUnifiedSidePanel))
-    return false;
+  if (!base::FeatureList::IsEnabled(features::kUnifiedSidePanel)) {
+    std::move(callback).Run(false);
+    return;
+  }
 
   // Ensure that only one run is ongoing.
-  if (is_running_)
-    return false;
+  if (is_running_) {
+    std::move(callback).Run(false);
+    return;
+  }
   is_running_ = true;
+  result_callback_ = std::move(callback);
 
   GetRuntimeManager()->SetUIState(autofill_assistant::UIState::kShown);
 
@@ -70,15 +77,14 @@
   onboarding_coordinator_ = CreateOnboardingCoordinator();
   onboarding_coordinator_->PerformOnboarding(base::BindOnce(
       &ApcClientImpl::OnOnboardingComplete, base::Unretained(this)));
-
-  return true;
 }
 
-void ApcClientImpl::Stop() {
+void ApcClientImpl::Stop(bool success) {
   GetRuntimeManager()->SetUIState(autofill_assistant::UIState::kNotShown);
   onboarding_coordinator_.reset();
   external_script_controller_.reset();
   is_running_ = false;
+  std::exchange(result_callback_, base::DoNothing()).Run(success);
 }
 
 bool ApcClientImpl::IsRunning() const {
@@ -89,7 +95,7 @@
 // has been given.
 void ApcClientImpl::OnOnboardingComplete(bool success) {
   if (!success) {
-    Stop();
+    Stop(/*success=*/false);
     return;
   }
 
@@ -118,11 +124,11 @@
 void ApcClientImpl::OnRunComplete(
     autofill_assistant::HeadlessScriptController::ScriptResult result) {
   // TODO(crbug.com/1324089): Handle failed result.
-  Stop();
+  Stop(result.success);
 }
 
 void ApcClientImpl::OnHidden() {
-  Stop();
+  Stop(/*success=*/false);
 
   // The two resets below are not included in `Stop()`, since we may wish to
   // render content in the side panel even for a stopped flow.
diff --git a/chrome/browser/autofill_assistant/password_change/apc_client_impl.h b/chrome/browser/autofill_assistant/password_change/apc_client_impl.h
index 654a5ba..d708a15 100644
--- a/chrome/browser/autofill_assistant/password_change/apc_client_impl.h
+++ b/chrome/browser/autofill_assistant/password_change/apc_client_impl.h
@@ -36,10 +36,11 @@
   ApcClientImpl& operator=(const ApcClientImpl&) = delete;
 
   // ApcClient:
-  bool Start(const GURL& url,
+  void Start(const GURL& url,
              const std::string& username,
-             bool skip_login) override;
-  void Stop() override;
+             bool skip_login,
+             ResultCallback callback) override;
+  void Stop(bool success) override;
   bool IsRunning() const override;
 
  protected:
@@ -102,6 +103,9 @@
   // another is already ongoing in the tab.
   bool is_running_ = false;
 
+  // The callback that signals the end of the run.
+  ResultCallback result_callback_;
+
   // Orchestrates prompting the user for consent if it has not been given
   // previously.
   std::unique_ptr<ApcOnboardingCoordinator> onboarding_coordinator_;
diff --git a/chrome/browser/autofill_assistant/password_change/apc_client_impl_unittest.cc b/chrome/browser/autofill_assistant/password_change/apc_client_impl_unittest.cc
index ef13bf3..8c95f9af 100644
--- a/chrome/browser/autofill_assistant/password_change/apc_client_impl_unittest.cc
+++ b/chrome/browser/autofill_assistant/password_change/apc_client_impl_unittest.cc
@@ -4,8 +4,10 @@
 
 #include "chrome/browser/autofill_assistant/password_change/apc_client_impl.h"
 
+#include "base/callback_helpers.h"
 #include "base/memory/raw_ptr.h"
 #include "base/test/gmock_move_support.h"
+#include "base/test/mock_callback.h"
 #include "base/test/scoped_feature_list.h"
 #include "chrome/browser/autofill_assistant/password_change/apc_onboarding_coordinator_impl.h"
 #include "chrome/browser/autofill_assistant/password_change/mock_apc_onboarding_coordinator.h"
@@ -191,15 +193,20 @@
 
   // Prepare to extract the callback to the coordinator.
   ApcOnboardingCoordinator::Callback coordinator_callback;
+  base::MockCallback<ApcClient::ResultCallback> result_callback1,
+      result_callback2;
   EXPECT_CALL(*coordinator(), PerformOnboarding)
       .WillOnce(MoveArg<0>(&coordinator_callback));
   EXPECT_CALL(*runtime_manager(),
               SetUIState(autofill_assistant::UIState::kShown));
-  EXPECT_TRUE(client->Start(GURL(kUrl1), kUsername1, /*skip_login=*/false));
+  client->Start(GURL(kUrl1), kUsername1, /*skip_login=*/false,
+                result_callback1.Get());
   EXPECT_TRUE(client->IsRunning());
 
   // We cannot start a second flow.
-  EXPECT_FALSE(client->Start(GURL(kUrl1), kUsername1, /*skip_login=*/false));
+  EXPECT_CALL(result_callback2, Run(false));
+  client->Start(GURL(kUrl1), kUsername1, /*skip_login=*/false,
+                result_callback2.Get());
 
   // Prepare to extract the callback to the external script controller.
   base::OnceCallback<void(
@@ -217,6 +224,7 @@
       /* success= */ true};
   EXPECT_CALL(*runtime_manager(),
               SetUIState(autofill_assistant::UIState::kNotShown));
+  EXPECT_CALL(result_callback1, Run(true));
   std::move(external_script_controller_callback).Run(script_result);
   EXPECT_FALSE(client->IsRunning());
 }
@@ -227,8 +235,8 @@
   EXPECT_CALL(*coordinator(), PerformOnboarding)
       .WillOnce(MoveArg<0>(&coordinator_callback));
 
-  EXPECT_TRUE(
-      apc_client()->Start(GURL(kUrl1), kUsername1, /*skip_login=*/false));
+  apc_client()->Start(GURL(kUrl1), kUsername1, /*skip_login=*/false,
+                      base::DoNothing());
 
   // Prepare to extract the script_params to the external script
   // controller.
@@ -252,9 +260,9 @@
       .Times(1)
       .WillOnce(MoveArg<0>(&coordinator_callback));
 
-  // `skip_login` equals to a trigger from leak warning.
-  EXPECT_TRUE(
-      apc_client()->Start(GURL(kUrl1), kUsername1, /*skip_login=*/true));
+  // `skip_login = true` equals a trigger from leak warning.
+  apc_client()->Start(GURL(kUrl1), kUsername1, /*skip_login=*/true,
+                      base::DoNothing());
 
   // Prepare to extract the script_params to the external script
   // controller.
@@ -277,8 +285,8 @@
       .Times(1)
       .WillOnce(MoveArg<0>(&coordinator_callback));
 
-  EXPECT_TRUE(
-      apc_client()->Start(GURL(kUrl1), kUsername1, /*skip_login=*/true));
+  apc_client()->Start(GURL(kUrl1), kUsername1, /*skip_login=*/true,
+                      base::DoNothing());
 
   // Fail onboarding.
   std::move(coordinator_callback).Run(false);
@@ -302,10 +310,26 @@
       .Times(0);
 
   // Starting it does not work.
-  EXPECT_FALSE(client->Start(GURL(kUrl1), kUsername1, /*skip_login=*/true));
+  client->Start(GURL(kUrl1), kUsername1, /*skip_login=*/true,
+                base::DoNothing());
   EXPECT_FALSE(client->IsRunning());
 }
 
+TEST_F(ApcClientImplTest, StopApcFlow) {
+  raw_ptr<ApcClient> client =
+      ApcClient::GetOrCreateForWebContents(web_contents());
+
+  base::MockCallback<ApcClient::ResultCallback> result_callback;
+
+  client->Start(GURL(kUrl1), kUsername1, /*skip_login=*/true,
+                result_callback.Get());
+
+  // Calling `Stop()` twice only triggers the callback the first time around.
+  EXPECT_CALL(result_callback, Run(false)).Times(1);
+  client->Stop();
+  client->Stop();
+}
+
 TEST_F(ApcClientImplTest, OnHidden_WithOngoingApcFlow) {
   ASSERT_FALSE(side_panel_observer());
 
@@ -316,8 +340,8 @@
       .WillOnce(MoveArg<0>(&coordinator_callback));
   EXPECT_CALL(*runtime_manager(),
               SetUIState(autofill_assistant::UIState::kShown));
-  EXPECT_TRUE(
-      apc_client()->Start(GURL(kUrl1), kUsername1, /*skip_login=*/true));
+  apc_client()->Start(GURL(kUrl1), kUsername1, /*skip_login=*/true,
+                      base::DoNothing());
   std::move(coordinator_callback).Run(true);
   EXPECT_TRUE(apc_client()->IsRunning());
 
diff --git a/chrome/browser/browser_resources.grd b/chrome/browser/browser_resources.grd
index 51d9bc45..6292b24 100644
--- a/chrome/browser/browser_resources.grd
+++ b/chrome/browser/browser_resources.grd
@@ -268,7 +268,7 @@
         <include name="IDR_SOUND_DICTATION_START_WAV" file="resources\chromeos\sounds\earcons\audio_initiate.wav" type="BINDATA" />
         <include name="IDR_SOUND_DICTATION_CANCEL_WAV" file="resources\chromeos\sounds\earcons\null_selection.wav" type="BINDATA" />
       </if>
-     <if expr="chromeos_ash">
+      <if expr="chromeos_ash">
         <include name="IDR_ABOUT_POWER_HTML" file="resources\chromeos\power.html" type="BINDATA" />
         <include name="IDR_ABOUT_POWER_JS" file="resources\chromeos\power.js" type="BINDATA" />
         <include name="IDR_ABOUT_POWER_CSS" file="resources\chromeos\power.css" type="BINDATA" />
@@ -338,7 +338,6 @@
         <include name="IDR_SYS_INTERNALS_IMAGE_CPU_SVG" file="resources\chromeos\sys_internals\img\cpu.svg" type="BINDATA" />
         <include name="IDR_SYS_INTERNALS_IMAGE_MEMORY_SVG" file="resources\chromeos\sys_internals\img\memory.svg" type="BINDATA" />
         <include name="IDR_SYS_INTERNALS_IMAGE_ZRAM_SVG" file="resources\chromeos\sys_internals\img\zram.svg" type="BINDATA" />
-        <include name="IDR_NOTIFICATION_TESTER_HTML" file="resources\chromeos\notification_tester\index.html" type="BINDATA" />
         <include name="IDR_ADD_SUPERVISION_HTML" file="resources\chromeos\add_supervision\add_supervision.html" type="chrome_html" />
         <include name="IDR_ADD_SUPERVISION_NETWORK_UNAVAILABLE_SVG" file="resources\chromeos\add_supervision\images\network_unavailable.svg" type="BINDATA" />
         <include name="IDR_ADD_SUPERVISION_UI_JS" file="${root_gen_dir}\chrome\browser\resources\chromeos\add_supervision\add_supervision_ui.js" use_base_dir="false" type="chrome_html" />
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index 2174356c..c305d13 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -2890,6 +2890,7 @@
     "../ash/printing/oauth2/authorization_server_data_unittest.cc",
     "../ash/printing/oauth2/authorization_server_session_unittest.cc",
     "../ash/printing/oauth2/authorization_zone_unittest.cc",
+    "../ash/printing/oauth2/authorization_zones_manager_unittest.cc",
     "../ash/printing/oauth2/http_exchange_unittest.cc",
     "../ash/printing/oauth2/ipp_endpoint_token_fetcher_unittest.cc",
     "../ash/printing/oauth2/profile_auth_servers_sync_bridge_unittest.cc",
diff --git a/chrome/browser/commerce/merchant_viewer/android/javatests/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustBottomSheetCoordinatorTest.java b/chrome/browser/commerce/merchant_viewer/android/javatests/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustBottomSheetCoordinatorTest.java
index 634920b..176fe42 100644
--- a/chrome/browser/commerce/merchant_viewer/android/javatests/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustBottomSheetCoordinatorTest.java
+++ b/chrome/browser/commerce/merchant_viewer/android/javatests/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustBottomSheetCoordinatorTest.java
@@ -53,6 +53,7 @@
 @RunWith(ChromeJUnit4ClassRunner.class)
 @Batch(Batch.PER_CLASS)
 @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
+@SuppressWarnings("DoNotMock") // Mocks GURL
 public class MerchantTrustBottomSheetCoordinatorTest extends BlankUiTestActivityTestCase {
     @Rule
     public final ChromeBrowserTestRule mBrowserTestRule = new ChromeBrowserTestRule();
@@ -177,4 +178,4 @@
                 SheetState.FULL, StateChangeReason.NONE);
         verify(mMockMetrics, times(1)).recordMetricsForBottomSheetFullyOpened();
     }
-}
\ No newline at end of file
+}
diff --git a/chrome/browser/commerce/merchant_viewer/android/javatests/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustBottomSheetMediatorTest.java b/chrome/browser/commerce/merchant_viewer/android/javatests/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustBottomSheetMediatorTest.java
index ff6d5e5..1ff9974 100644
--- a/chrome/browser/commerce/merchant_viewer/android/javatests/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustBottomSheetMediatorTest.java
+++ b/chrome/browser/commerce/merchant_viewer/android/javatests/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustBottomSheetMediatorTest.java
@@ -66,6 +66,7 @@
  */
 @RunWith(BaseRobolectricTestRunner.class)
 @Config(manifest = Config.NONE)
+@SuppressWarnings("DoNotMock") // Mocking GURL
 public class MerchantTrustBottomSheetMediatorTest {
     @Rule
     public TestRule mProcessor = new Features.JUnitProcessor();
diff --git a/chrome/browser/commerce/merchant_viewer/android/javatests/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustMessageContextTest.java b/chrome/browser/commerce/merchant_viewer/android/javatests/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustMessageContextTest.java
index 7d81999..0471981d 100644
--- a/chrome/browser/commerce/merchant_viewer/android/javatests/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustMessageContextTest.java
+++ b/chrome/browser/commerce/merchant_viewer/android/javatests/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustMessageContextTest.java
@@ -29,6 +29,7 @@
  */
 @RunWith(BaseRobolectricTestRunner.class)
 @Config(manifest = Config.NONE)
+@SuppressWarnings("DoNotMock") // Mocking GURL
 public class MerchantTrustMessageContextTest {
     @Rule
     public TestRule mProcessor = new Features.JUnitProcessor();
@@ -112,4 +113,4 @@
                 (new MerchantTrustMessageContext(mMockNavigationHandle, mMockWebContents))
                         .getWebContents());
     }
-}
\ No newline at end of file
+}
diff --git a/chrome/browser/commerce/merchant_viewer/android/javatests/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustSignalsCoordinatorTest.java b/chrome/browser/commerce/merchant_viewer/android/javatests/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustSignalsCoordinatorTest.java
index 65743de3..b15ee80f 100644
--- a/chrome/browser/commerce/merchant_viewer/android/javatests/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustSignalsCoordinatorTest.java
+++ b/chrome/browser/commerce/merchant_viewer/android/javatests/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustSignalsCoordinatorTest.java
@@ -68,6 +68,7 @@
  */
 @RunWith(BaseRobolectricTestRunner.class)
 @Config(manifest = Config.NONE)
+@SuppressWarnings("DoNotMock") // Mocking GURL
 public class MerchantTrustSignalsCoordinatorTest {
     @Rule
     public TestRule mProcessor = new Features.JUnitProcessor();
diff --git a/chrome/browser/commerce/merchant_viewer/android/javatests/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustSignalsDataProviderTest.java b/chrome/browser/commerce/merchant_viewer/android/javatests/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustSignalsDataProviderTest.java
index fe200ed..402f2cd 100644
--- a/chrome/browser/commerce/merchant_viewer/android/javatests/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustSignalsDataProviderTest.java
+++ b/chrome/browser/commerce/merchant_viewer/android/javatests/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustSignalsDataProviderTest.java
@@ -30,11 +30,13 @@
 import org.chromium.url.GURL;
 
 import java.util.concurrent.TimeoutException;
+
 /**
  * Tests for {@link MerchantTrustSignalsDataProvider}.
  */
 @RunWith(BaseRobolectricTestRunner.class)
 @Config(manifest = Config.NONE)
+@SuppressWarnings("DoNotMock") // Mocking GURL
 public class MerchantTrustSignalsDataProviderTest {
     @Rule
     public TestRule mProcessor = new Features.JUnitProcessor();
diff --git a/chrome/browser/commerce/merchant_viewer/android/javatests/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustSignalsMediatorTest.java b/chrome/browser/commerce/merchant_viewer/android/javatests/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustSignalsMediatorTest.java
index ec38c78f..640c3a9 100644
--- a/chrome/browser/commerce/merchant_viewer/android/javatests/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustSignalsMediatorTest.java
+++ b/chrome/browser/commerce/merchant_viewer/android/javatests/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustSignalsMediatorTest.java
@@ -39,6 +39,7 @@
  */
 @RunWith(BaseRobolectricTestRunner.class)
 @Config(manifest = Config.NONE)
+@SuppressWarnings("DoNotMock") // Mocking GURL
 public class MerchantTrustSignalsMediatorTest {
     @Rule
     public TestRule mProcessor = new Features.JUnitProcessor();
diff --git a/chrome/browser/commerce/shopping_list/BUILD.gn b/chrome/browser/commerce/shopping_list/BUILD.gn
index 6ed82892..412ffd5 100644
--- a/chrome/browser/commerce/shopping_list/BUILD.gn
+++ b/chrome/browser/commerce/shopping_list/BUILD.gn
@@ -15,6 +15,7 @@
   deps = [
     "//base",
     "//chrome/common",
+    "//components/commerce/core:feature_list",
     "//components/commerce/core:proto",
     "//components/optimization_guide/content/browser",
     "//components/optimization_guide/proto:optimization_guide_proto",
@@ -39,6 +40,7 @@
   deps = [
     ":shopping_list",
     "//base/test:test_support",
+    "//components/commerce/core:feature_list",
     "//components/commerce/core:proto",
     "//components/power_bookmarks/core:proto",
     "//content/public/browser",
diff --git a/chrome/browser/commerce/shopping_list/shopping_data_provider.cc b/chrome/browser/commerce/shopping_list/shopping_data_provider.cc
index 3d28e98..69896e87 100644
--- a/chrome/browser/commerce/shopping_list/shopping_data_provider.cc
+++ b/chrome/browser/commerce/shopping_list/shopping_data_provider.cc
@@ -14,6 +14,7 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/values.h"
 #include "chrome/common/chrome_isolated_world_ids.h"
+#include "components/commerce/core/commerce_feature_list.h"
 #include "components/commerce/core/proto/price_tracking.pb.h"
 #include "components/grit/components_resources.h"
 #include "components/optimization_guide/core/optimization_guide_util.h"
@@ -116,7 +117,8 @@
     if (parsed_any.has_value() && price_data.IsInitialized()) {
       commerce::BuyableProduct buyable_product = price_data.buyable_product();
 
-      if (buyable_product.has_image_url()) {
+      if (buyable_product.has_image_url() &&
+          base::FeatureList::IsEnabled(commerce::kCommerceAllowServerImages)) {
         meta_for_navigation_->mutable_lead_image()->set_url(
             buyable_product.image_url());
       }
@@ -188,13 +190,17 @@
       // retrieved from the proto received from optimization guide before this
       // callback runs.
       if (!meta->has_lead_image()) {
-        meta->mutable_lead_image()->set_url(it.second.GetString());
+        if (base::FeatureList::IsEnabled(commerce::kCommerceAllowLocalImages)) {
+          meta->mutable_lead_image()->set_url(it.second.GetString());
+        }
         base::UmaHistogramEnumeration(
             "Commerce.PowerBookmarks.ShoppingDataProvider.FallbackDataContent",
             ShoppingDataProviderFallback::kLeadImage,
             ShoppingDataProviderFallback::kMaxValue);
       } else {
-        meta->add_fallback_images()->set_url(it.second.GetString());
+        if (base::FeatureList::IsEnabled(commerce::kCommerceAllowLocalImages)) {
+          meta->add_fallback_images()->set_url(it.second.GetString());
+        }
         base::UmaHistogramEnumeration(
             "Commerce.PowerBookmarks.ShoppingDataProvider.FallbackDataContent",
             ShoppingDataProviderFallback::kFallbackImage,
diff --git a/chrome/browser/commerce/shopping_list/shopping_data_provider_unittest.cc b/chrome/browser/commerce/shopping_list/shopping_data_provider_unittest.cc
index b5112d3..30ff37eb 100644
--- a/chrome/browser/commerce/shopping_list/shopping_data_provider_unittest.cc
+++ b/chrome/browser/commerce/shopping_list/shopping_data_provider_unittest.cc
@@ -4,7 +4,9 @@
 
 #include "chrome/browser/commerce/shopping_list/shopping_data_provider.h"
 #include "base/logging.h"
+#include "base/test/scoped_feature_list.h"
 #include "base/values.h"
+#include "components/commerce/core/commerce_feature_list.h"
 #include "components/commerce/core/proto/price_tracking.pb.h"
 #include "components/power_bookmarks/core/proto/power_bookmark_meta.pb.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -24,6 +26,11 @@
 const uint64_t kClusterId = 67890;
 
 TEST(ShoppingDataProviderTest, TestDataMergeWithLeadImage) {
+  base::test::ScopedFeatureList test_features;
+  test_features.InitWithFeatures({commerce::kCommerceAllowLocalImages,
+                                  commerce::kCommerceAllowServerImages},
+                                 {});
+
   power_bookmarks::PowerBookmarkMeta meta;
   meta.mutable_lead_image()->set_url(kLeadImageUrl);
 
@@ -38,6 +45,11 @@
 }
 
 TEST(ShoppingDataProviderTest, TestDataMergeWithNoLeadImage) {
+  base::test::ScopedFeatureList test_features;
+  test_features.InitWithFeatures({commerce::kCommerceAllowLocalImages,
+                                  commerce::kCommerceAllowServerImages},
+                                 {});
+
   power_bookmarks::PowerBookmarkMeta meta;
 
   base::DictionaryValue data_map;
diff --git a/chrome/browser/commerce/subscriptions/test/android/java/src/org/chromium/chrome/browser/subscriptions/ImplicitPriceDropSubscriptionsManagerUnitTest.java b/chrome/browser/commerce/subscriptions/test/android/java/src/org/chromium/chrome/browser/subscriptions/ImplicitPriceDropSubscriptionsManagerUnitTest.java
index 8d31a17..8a0499a 100644
--- a/chrome/browser/commerce/subscriptions/test/android/java/src/org/chromium/chrome/browser/subscriptions/ImplicitPriceDropSubscriptionsManagerUnitTest.java
+++ b/chrome/browser/commerce/subscriptions/test/android/java/src/org/chromium/chrome/browser/subscriptions/ImplicitPriceDropSubscriptionsManagerUnitTest.java
@@ -51,6 +51,7 @@
  */
 @RunWith(BaseRobolectricTestRunner.class)
 @Config(manifest = Config.NONE, shadows = {ShadowRecordHistogram.class})
+@SuppressWarnings("DoNotMock") // Mocks GURL
 public class ImplicitPriceDropSubscriptionsManagerUnitTest {
     @Rule
     public TestRule mProcessor = new Features.JUnitProcessor();
diff --git a/chrome/browser/component_updater/registration.cc b/chrome/browser/component_updater/registration.cc
index fb84248..06c8a86 100644
--- a/chrome/browser/component_updater/registration.cc
+++ b/chrome/browser/component_updater/registration.cc
@@ -178,7 +178,7 @@
   // on chromium build bots, it is always registered here and
   // RegisterSwReporterComponent() has support for running only in official
   // builds or tests.
-  RegisterSwReporterComponent(cus);
+  RegisterSwReporterComponent(cus, g_browser_process->local_state());
 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
   RegisterThirdPartyModuleListComponent(cus);
 #endif  // BUILDFLAG(GOOGLE_CHROME_BRANDING)
diff --git a/chrome/browser/component_updater/sw_reporter_installer_win.cc b/chrome/browser/component_updater/sw_reporter_installer_win.cc
index 91ac593..ea77de3 100644
--- a/chrome/browser/component_updater/sw_reporter_installer_win.cc
+++ b/chrome/browser/component_updater/sw_reporter_installer_win.cc
@@ -42,6 +42,7 @@
 #include "chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_controller_win.h"
 #include "chrome/browser/safe_browsing/chrome_cleaner/reporter_runner_win.h"
 #include "chrome/browser/safe_browsing/chrome_cleaner/srt_field_trial_win.h"
+#include "chrome/common/channel_info.h"
 #include "components/chrome_cleaner/public/constants/buildflags.h"
 #include "components/chrome_cleaner/public/constants/constants.h"
 #include "components/component_updater/component_updater_paths.h"
@@ -49,8 +50,10 @@
 #include "components/component_updater/pref_names.h"
 #include "components/pref_registry/pref_registry_syncable.h"
 #include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/pref_service.h"
 #include "components/update_client/update_client.h"
 #include "components/update_client/utils.h"
+#include "components/version_info/channel.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
@@ -268,11 +271,18 @@
 }  // namespace
 
 SwReporterInstallerPolicy::SwReporterInstallerPolicy(
+    PrefService* prefs,
     OnComponentReadyCallback on_component_ready_callback)
-    : on_component_ready_callback_(on_component_ready_callback) {}
+    : prefs_(prefs),
+      on_component_ready_callback_(on_component_ready_callback) {}
 
 SwReporterInstallerPolicy::~SwReporterInstallerPolicy() = default;
 
+void SwReporterInstallerPolicy::SetRandomReporterCohortForTesting(
+    const std::string& cohort_name) {
+  random_cohort_for_testing_ = cohort_name;
+}
+
 bool SwReporterInstallerPolicy::VerifyInstallation(
     const base::Value& manifest,
     const base::FilePath& dir) const {
@@ -327,27 +337,63 @@
   update_client::InstallerAttributes attributes;
   // Pass the tag parameter to the installer as the "tag" attribute; it will be
   // used to choose which binary is downloaded.
-  std::string tag = safe_browsing::kReporterDistributionTagParam.Get();
-
-  // If the tag is not a valid attribute (see the regexp in
-  // ComponentInstallerPolicy::InstallerAttributes), set it to a valid but
-  // unrecognized value so that nothing will be downloaded.
-  constexpr size_t kMaxAttributeLength = 256;
-  constexpr char kExtraAttributeChars[] = "-.,;+_=";
-  constexpr char kTagParam[] = "tag";
-  if (tag.empty()) {
-    // TODO(crbug.com/1305048): If the tag isn't assigned by the server,
-    // randomly assign the user to canary or stable.
-    attributes[kTagParam] = "stable";
-  } else if (!ValidateString(tag, kExtraAttributeChars, kMaxAttributeLength)) {
-    ReportConfigurationError(kBadTag);
-    attributes[kTagParam] = "missing_tag";
-  } else {
-    attributes[kTagParam] = tag;
-  }
+  attributes["tag"] = GetReporterCohortTag(prefs_);
   return attributes;
 }
 
+// Returns the reporter cohort tag by checking the feature list, then `prefs`,
+// then assigning the tag randomly if it's not found in either.
+std::string SwReporterInstallerPolicy::GetReporterCohortTag(
+    PrefService* prefs) const {
+  const std::string feature_tag =
+      safe_browsing::kReporterDistributionTagParam.Get();
+  if (!feature_tag.empty()) {
+    // If the tag is not a valid attribute (see the regexp in
+    // ComponentInstallerPolicy::InstallerAttributes), set it to a valid but
+    // unrecognized value so that nothing will be downloaded.
+    constexpr size_t kMaxAttributeLength = 256;
+    constexpr char kExtraAttributeChars[] = "-.,;+_=";
+    if (!ValidateString(feature_tag, kExtraAttributeChars,
+                        kMaxAttributeLength)) {
+      ReportConfigurationError(kBadTag);
+      return "missing_tag";
+    }
+
+    // Any tag that doesn't contain invalid characters is valid, so that the
+    // feature can be used to experiment with new versions.
+    return feature_tag;
+  }
+
+  // Use the tag from preferences. Only "canary" and "stable" are valid, so
+  // that bad values in local prefs don't block access to the reporter. Note
+  // there's no need to clear invalid tags since they'll be overwritten by the
+  // new cohort values.
+  const std::string prefs_tag = prefs->GetString(prefs::kSwReporterCohort);
+  if (prefs_tag == "canary" || prefs_tag == "stable") {
+    // Re-randomize unless the cohort was assigned less than a month ago. Also
+    // ignore invalid selection times that are too far in the future.
+    const base::Time last_selection_time =
+        prefs->GetTime(prefs::kSwReporterCohortSelectionTime);
+    const base::Time now = base::Time::Now();
+    if (now - base::Days(30) < last_selection_time &&
+        last_selection_time < now + base::Days(1)) {
+      return prefs_tag;
+    }
+  }
+  // Chrome Stable users have a 95% chance to get the stable reporter, 5% chance
+  // to get the canary reporter. All other Chrome channels are assigned 50/50.
+  const double stable_reporter_probability =
+      (chrome::GetChannel() == version_info::Channel::STABLE) ? 0.95 : 0.5;
+  const std::string selected_tag =
+      !random_cohort_for_testing_.empty()
+          ? random_cohort_for_testing_
+          : ((base::RandDouble() < stable_reporter_probability) ? "stable"
+                                                                : "canary");
+  prefs->SetString(prefs::kSwReporterCohort, selected_tag);
+  prefs->SetTime(prefs::kSwReporterCohortSelectionTime, base::Time::Now());
+  return selected_tag;
+}
+
 SwReporterOnDemandFetcher::SwReporterOnDemandFetcher(
     ComponentUpdateService* cus,
     base::OnceClosure on_error_callback)
@@ -377,7 +423,8 @@
   }
 }
 
-void RegisterSwReporterComponent(ComponentUpdateService* cus) {
+void RegisterSwReporterComponent(ComponentUpdateService* cus,
+                                 PrefService* prefs) {
   base::ScopedClosureRunner runner(std::move(GetRegistrationCBForTesting()));
 
   // Don't install the component if not allowed by policy.  This prevents
@@ -406,7 +453,8 @@
 
   // Install the component.
   auto installer = base::MakeRefCounted<ComponentInstaller>(
-      std::make_unique<SwReporterInstallerPolicy>(std::move(ready_callback)));
+      std::make_unique<SwReporterInstallerPolicy>(prefs,
+                                                  std::move(ready_callback)));
 
   installer->Register(cus, runner.Release());
 }
@@ -418,10 +466,16 @@
 }
 
 void RegisterPrefsForSwReporter(PrefRegistrySimple* registry) {
+  // The two "LastTime" prefs are Int64 instead of Time for legacy reasons.
+  // Changing the format would need an upgrade path, which is more complicated
+  // than it's worth.
   registry->RegisterInt64Pref(prefs::kSwReporterLastTimeTriggered, 0);
   registry->RegisterIntegerPref(prefs::kSwReporterLastExitCode, -1);
   registry->RegisterInt64Pref(prefs::kSwReporterLastTimeSentReport, 0);
   registry->RegisterBooleanPref(prefs::kSwReporterEnabled, true);
+  registry->RegisterStringPref(prefs::kSwReporterCohort, "");
+  registry->RegisterTimePref(prefs::kSwReporterCohortSelectionTime,
+                             base::Time());
 }
 
 void RegisterProfilePrefsForSwReporter(
diff --git a/chrome/browser/component_updater/sw_reporter_installer_win.h b/chrome/browser/component_updater/sw_reporter_installer_win.h
index 6326163..7bb7d5df 100644
--- a/chrome/browser/component_updater/sw_reporter_installer_win.h
+++ b/chrome/browser/component_updater/sw_reporter_installer_win.h
@@ -17,6 +17,7 @@
 #include "components/component_updater/component_updater_service.h"
 
 class PrefRegistrySimple;
+class PrefService;
 
 namespace base {
 class Value;
@@ -53,7 +54,8 @@
 
 class SwReporterInstallerPolicy : public ComponentInstallerPolicy {
  public:
-  explicit SwReporterInstallerPolicy(OnComponentReadyCallback callback);
+  SwReporterInstallerPolicy(PrefService* prefs,
+                            OnComponentReadyCallback callback);
 
   SwReporterInstallerPolicy(const SwReporterInstallerPolicy&) = delete;
   SwReporterInstallerPolicy& operator=(const SwReporterInstallerPolicy&) =
@@ -61,6 +63,11 @@
 
   ~SwReporterInstallerPolicy() override;
 
+  // Use `cohort_name` instead of a random reporter cohort tag if no tag is set
+  // in a feature param or prefs. In production the policy randomly chooses
+  // "canary" or "stable".
+  void SetRandomReporterCohortForTesting(const std::string& cohort_name);
+
   // ComponentInstallerPolicy implementation.
   bool VerifyInstallation(const base::Value& manifest,
                           const base::FilePath& dir) const override;
@@ -81,7 +88,14 @@
  private:
   friend class SwReporterInstallerTest;
 
+  // Returns a value for the "tag" param.
+  std::string GetReporterCohortTag(PrefService* prefs) const;
+
+  raw_ptr<PrefService> prefs_;
   OnComponentReadyCallback on_component_ready_callback_;
+
+  // This string will be used as the "random" cohort name in tests.
+  std::string random_cohort_for_testing_;
 };
 
 // Forces an update of the reporter component.
@@ -112,7 +126,8 @@
 
 // Call once during startup to make the component update service aware of the
 // SwReporter. Once ready, this may trigger a periodic run of the reporter.
-void RegisterSwReporterComponent(ComponentUpdateService* cus);
+void RegisterSwReporterComponent(ComponentUpdateService* cus,
+                                 PrefService* prefs);
 
 // Allow tests to register a function to be called when the registration
 // of the reporter component is done.
diff --git a/chrome/browser/component_updater/sw_reporter_installer_win_unittest.cc b/chrome/browser/component_updater/sw_reporter_installer_win_unittest.cc
index 0e632a9..57cde64 100644
--- a/chrome/browser/component_updater/sw_reporter_installer_win_unittest.cc
+++ b/chrome/browser/component_updater/sw_reporter_installer_win_unittest.cc
@@ -9,18 +9,21 @@
 #include <set>
 #include <string>
 #include <utility>
+#include <vector>
 
 #include "base/bind.h"
 #include "base/command_line.h"
 #include "base/feature_list.h"
 #include "base/files/file_path.h"
 #include "base/json/json_reader.h"
+#include "base/json/values_util.h"
 #include "base/metrics/field_trial.h"
 #include "base/strings/strcat.h"
 #include "base/strings/stringprintf.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/test/test_reg_util_win.h"
+#include "base/time/time.h"
 #include "base/values.h"
 #include "base/version.h"
 #include "base/win/registry.h"
@@ -28,6 +31,8 @@
 #include "chrome/browser/safe_browsing/chrome_cleaner/srt_field_trial_win.h"
 #include "components/chrome_cleaner/public/constants/constants.h"
 #include "components/component_updater/mock_component_updater_service.h"
+#include "components/component_updater/pref_names.h"
+#include "components/prefs/testing_pref_service.h"
 #include "content/public/test/browser_task_environment.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -36,14 +41,13 @@
 namespace {
 
 constexpr char kErrorHistogramName[] = "SoftwareReporter.ConfigurationErrors";
-constexpr char kDefaultTag[] = "stable";
-constexpr char kExperimentTag[] = "experiment_tag";
 constexpr char kMissingTag[] = "missing_tag";
 
 using safe_browsing::SwReporterInvocation;
 using safe_browsing::SwReporterInvocationSequence;
 using ::testing::_;
 using ::testing::AtLeast;
+using ::testing::Contains;
 using ::testing::ReturnRef;
 
 using Events = update_client::UpdateClient::Observer::Events;
@@ -57,7 +61,9 @@
             &SwReporterInstallerTest::SwReporterComponentReady,
             base::Unretained(this))),
         default_version_("1.2.3"),
-        default_path_(L"C:\\full\\path\\to\\download") {}
+        default_path_(L"C:\\full\\path\\to\\download") {
+    RegisterPrefsForSwReporter(test_prefs_.registry());
+  }
 
   SwReporterInstallerTest(const SwReporterInstallerTest&) = delete;
   SwReporterInstallerTest& operator=(const SwReporterInstallerTest&) = delete;
@@ -95,12 +101,29 @@
         safe_browsing::kChromeCleanupDistributionFeature);
   }
 
-  void ExpectAttributesWithTag(const SwReporterInstallerPolicy& policy,
-                               const std::string& tag) {
+  void SetReporterCohortPrefs(const std::string& name,
+                              base::Time selection_time) {
+    test_prefs_.SetUserPref(prefs::kSwReporterCohort, base::Value(name));
+    test_prefs_.SetUserPref(prefs::kSwReporterCohortSelectionTime,
+                            base::TimeToValue(selection_time));
+  }
+
+  // Expects the "tag" attribute will include any of the values in "tags".
+  // Returns the value of the attribute or the empty string if not found.
+  std::string ExpectAttributesWithTagIn(const SwReporterInstallerPolicy& policy,
+                                        const std::vector<std::string>& tags) {
     update_client::InstallerAttributes attributes =
         policy.GetInstallerAttributes();
     EXPECT_EQ(1U, attributes.size());
-    EXPECT_EQ(tag, attributes["tag"]);
+    std::string tag = attributes["tag"];
+    EXPECT_THAT(tags, Contains(tag));
+    return tag;
+  }
+
+  // Expects the "tag" attribute will be `tag`.
+  void ExpectAttributesWithTag(const SwReporterInstallerPolicy& policy,
+                               const std::string& tag) {
+    ExpectAttributesWithTagIn(policy, {tag});
   }
 
   void ExpectEmptyAttributes(const SwReporterInstallerPolicy& policy) const {
@@ -213,6 +236,7 @@
 
   base::test::ScopedFeatureList scoped_feature_list_;
   base::HistogramTester histograms_;
+  TestingPrefServiceSimple test_prefs_;
 
   // |ComponentReady| asserts that it is run on the UI thread, so we must
   // create test threads before calling it.
@@ -233,59 +257,123 @@
 };
 
 TEST_F(SwReporterInstallerTest, MissingManifest) {
-  SwReporterInstallerPolicy policy(on_component_ready_callback_);
-  CreateFeatureWithTag(kDefaultTag);
-  ExpectAttributesWithTag(policy, kDefaultTag);
+  SwReporterInstallerPolicy policy(&test_prefs_, on_component_ready_callback_);
   policy.ComponentReady(default_version_, default_path_,
                         base::Value(base::Value::Type::DICTIONARY));
   ExpectLaunchError(kMissingPromptSeed);
 }
 
-TEST_F(SwReporterInstallerTest, MissingTagDefaultsToStable) {
-  SwReporterInstallerPolicy policy(on_component_ready_callback_);
+TEST_F(SwReporterInstallerTest, MissingTagRandomCohort) {
+  SwReporterInstallerPolicy policy(&test_prefs_, on_component_ready_callback_);
   CreateFeatureWithoutTag();
-  ExpectAttributesWithTag(policy, kDefaultTag);
+  std::string tag = ExpectAttributesWithTagIn(policy, {"canary", "stable"});
   histograms_.ExpectUniqueSample(kErrorHistogramName, kBadTag, 0);
+  // Randomly assigned tag should be written to prefs.
+  EXPECT_EQ(test_prefs_.GetString(prefs::kSwReporterCohort), tag);
 }
 
 TEST_F(SwReporterInstallerTest, InvalidTag) {
-  SwReporterInstallerPolicy policy(on_component_ready_callback_);
+  SwReporterInstallerPolicy policy(&test_prefs_, on_component_ready_callback_);
   CreateFeatureWithTag("tag with invalid whitespace chars");
   ExpectAttributesWithTag(policy, kMissingTag);
   histograms_.ExpectUniqueSample(kErrorHistogramName, kBadTag, 1);
+  // Invalid tag should NOT be written to prefs.
+  EXPECT_TRUE(test_prefs_.GetString(prefs::kSwReporterCohort).empty());
 }
 
 TEST_F(SwReporterInstallerTest, TagTooLong) {
-  SwReporterInstallerPolicy policy(on_component_ready_callback_);
+  SwReporterInstallerPolicy policy(&test_prefs_, on_component_ready_callback_);
   std::string tag_too_long(500, 'x');
   CreateFeatureWithTag(tag_too_long);
   ExpectAttributesWithTag(policy, kMissingTag);
   histograms_.ExpectUniqueSample(kErrorHistogramName, kBadTag, 1);
+  // Invalid tag should NOT be written to prefs.
+  EXPECT_TRUE(test_prefs_.GetString(prefs::kSwReporterCohort).empty());
 }
 
-TEST_F(SwReporterInstallerTest, EmptyTagDefaultsToStable) {
-  SwReporterInstallerPolicy policy(on_component_ready_callback_);
+TEST_F(SwReporterInstallerTest, EmptyTagRandomCohort) {
+  SwReporterInstallerPolicy policy(&test_prefs_, on_component_ready_callback_);
   CreateFeatureWithTag("");
-  ExpectAttributesWithTag(policy, kDefaultTag);
+  std::string tag = ExpectAttributesWithTagIn(policy, {"canary", "stable"});
   histograms_.ExpectUniqueSample(kErrorHistogramName, kBadTag, 0);
+  // Randomly assigned tag should be written to prefs.
+  EXPECT_EQ(test_prefs_.GetString(prefs::kSwReporterCohort), tag);
 }
 
 TEST_F(SwReporterInstallerTest, ValidTag) {
-  SwReporterInstallerPolicy policy(on_component_ready_callback_);
-  CreateFeatureWithTag(kExperimentTag);
-  ExpectAttributesWithTag(policy, kExperimentTag);
+  SwReporterInstallerPolicy policy(&test_prefs_, on_component_ready_callback_);
+  CreateFeatureWithTag("experiment_tag");
+  ExpectAttributesWithTag(policy, "experiment_tag");
   histograms_.ExpectUniqueSample(kErrorHistogramName, kBadTag, 0);
+  // Tag from feature param should NOT be written to prefs.
+  EXPECT_TRUE(test_prefs_.GetString(prefs::kSwReporterCohort).empty());
 }
 
-TEST_F(SwReporterInstallerTest, TagFeatureDisabledDefaultsToStable) {
-  SwReporterInstallerPolicy policy(on_component_ready_callback_);
+TEST_F(SwReporterInstallerTest, TagFeatureDisabledRandomCohort) {
+  SwReporterInstallerPolicy policy(&test_prefs_, on_component_ready_callback_);
   DisableFeature();
-  ExpectAttributesWithTag(policy, kDefaultTag);
+  std::string tag = ExpectAttributesWithTagIn(policy, {"canary", "stable"});
   histograms_.ExpectUniqueSample(kErrorHistogramName, kBadTag, 0);
+  // Randomly assigned tag should be written to prefs.
+  EXPECT_EQ(test_prefs_.GetString(prefs::kSwReporterCohort), tag);
+}
+
+TEST_F(SwReporterInstallerTest, TagFromCohortPref) {
+  SwReporterInstallerPolicy policy(&test_prefs_, on_component_ready_callback_);
+  SetReporterCohortPrefs("canary", base::Time::Now());
+  // Make sure if the policy generates a random value, the result will be
+  // distinguishable from the cohort.
+  policy.SetRandomReporterCohortForTesting("invalid");
+  DisableFeature();
+  ExpectAttributesWithTag(policy, "canary");
+}
+
+TEST_F(SwReporterInstallerTest, OldCohortPrefReshuffled) {
+  SwReporterInstallerPolicy policy(&test_prefs_, on_component_ready_callback_);
+  SetReporterCohortPrefs("canary", base::Time::Now() - base::Days(40));
+  // Expect "canary" to be ignored because the pref was set >30 days ago. Force
+  // the random result to be "stable" to distinguish it from the pref.
+  policy.SetRandomReporterCohortForTesting("stable");
+  DisableFeature();
+  ExpectAttributesWithTag(policy, "stable");
+  EXPECT_EQ(test_prefs_.GetString(prefs::kSwReporterCohort), "stable");
+}
+
+TEST_F(SwReporterInstallerTest, TooNewCohortPrefReshuffled) {
+  SwReporterInstallerPolicy policy(&test_prefs_, on_component_ready_callback_);
+  SetReporterCohortPrefs("stable", base::Time::Now() + base::Days(2));
+  // Expect "stable" to be ignored because the pref was set >1 day in the
+  // future. Force the random result to be "canary" to distinguish it from the
+  // pref.
+  policy.SetRandomReporterCohortForTesting("canary");
+  DisableFeature();
+  ExpectAttributesWithTag(policy, "canary");
+  EXPECT_EQ(test_prefs_.GetString(prefs::kSwReporterCohort), "canary");
+}
+
+TEST_F(SwReporterInstallerTest, CohortPrefWithoutTimeReshuffled) {
+  SwReporterInstallerPolicy policy(&test_prefs_, on_component_ready_callback_);
+  test_prefs_.SetUserPref(prefs::kSwReporterCohort, base::Value("stable"));
+  // Expect "stable" to be ignored because the kSwReporterCohortSelectionTime
+  // pref is missing. Force the random result to be "canary" to distinguish it
+  // from the kSwReporterCohort pref result.
+  policy.SetRandomReporterCohortForTesting("canary");
+  DisableFeature();
+  ExpectAttributesWithTag(policy, "canary");
+  EXPECT_EQ(test_prefs_.GetString(prefs::kSwReporterCohort), "canary");
+}
+
+TEST_F(SwReporterInstallerTest, InvalidCohortPrefIgnored) {
+  SwReporterInstallerPolicy policy(&test_prefs_, on_component_ready_callback_);
+  SetReporterCohortPrefs("unknown", base::Time::Now());
+  DisableFeature();
+  std::string tag = ExpectAttributesWithTagIn(policy, {"canary", "stable"});
+  // Randomly assigned tag should be written to prefs.
+  EXPECT_EQ(test_prefs_.GetString(prefs::kSwReporterCohort), tag);
 }
 
 TEST_F(SwReporterInstallerTest, SingleInvocation) {
-  SwReporterInstallerPolicy policy(on_component_ready_callback_);
+  SwReporterInstallerPolicy policy(&test_prefs_, on_component_ready_callback_);
 
   static constexpr char kTestManifest[] = R"json(
       {
@@ -328,7 +416,7 @@
 }
 
 TEST_F(SwReporterInstallerTest, MultipleInvocations) {
-  SwReporterInstallerPolicy policy(on_component_ready_callback_);
+  SwReporterInstallerPolicy policy(&test_prefs_, on_component_ready_callback_);
 
   static constexpr char kTestManifest[] = R"json(
       {
@@ -391,7 +479,7 @@
 }
 
 TEST_F(SwReporterInstallerTest, MissingSuffix) {
-  SwReporterInstallerPolicy policy(on_component_ready_callback_);
+  SwReporterInstallerPolicy policy(&test_prefs_, on_component_ready_callback_);
 
   static constexpr char kTestManifest[] = R"json(
       {
@@ -408,7 +496,7 @@
 }
 
 TEST_F(SwReporterInstallerTest, EmptySuffix) {
-  SwReporterInstallerPolicy policy(on_component_ready_callback_);
+  SwReporterInstallerPolicy policy(&test_prefs_, on_component_ready_callback_);
 
   static constexpr char kTestManifest[] = R"json(
       {
@@ -426,7 +514,7 @@
 }
 
 TEST_F(SwReporterInstallerTest, MissingSuffixAndArgs) {
-  SwReporterInstallerPolicy policy(on_component_ready_callback_);
+  SwReporterInstallerPolicy policy(&test_prefs_, on_component_ready_callback_);
 
   static constexpr char kTestManifest[] = R"json(
       {
@@ -442,7 +530,7 @@
 }
 
 TEST_F(SwReporterInstallerTest, EmptySuffixAndArgs) {
-  SwReporterInstallerPolicy policy(on_component_ready_callback_);
+  SwReporterInstallerPolicy policy(&test_prefs_, on_component_ready_callback_);
 
   static constexpr char kTestManifest[] = R"json(
       {
@@ -460,7 +548,7 @@
 }
 
 TEST_F(SwReporterInstallerTest, EmptySuffixAndArgsWithEmptyString) {
-  SwReporterInstallerPolicy policy(on_component_ready_callback_);
+  SwReporterInstallerPolicy policy(&test_prefs_, on_component_ready_callback_);
 
   static constexpr char kTestManifest[] = R"json(
       {
@@ -478,7 +566,7 @@
 }
 
 TEST_F(SwReporterInstallerTest, MissingArguments) {
-  SwReporterInstallerPolicy policy(on_component_ready_callback_);
+  SwReporterInstallerPolicy policy(&test_prefs_, on_component_ready_callback_);
 
   static constexpr char kTestManifest[] = R"json(
       {
@@ -495,7 +583,7 @@
 }
 
 TEST_F(SwReporterInstallerTest, EmptyArguments) {
-  SwReporterInstallerPolicy policy(on_component_ready_callback_);
+  SwReporterInstallerPolicy policy(&test_prefs_, on_component_ready_callback_);
 
   static constexpr char kTestManifest[] = R"json(
       {
@@ -513,7 +601,7 @@
 }
 
 TEST_F(SwReporterInstallerTest, EmptyArgumentsWithEmptyString) {
-  SwReporterInstallerPolicy policy(on_component_ready_callback_);
+  SwReporterInstallerPolicy policy(&test_prefs_, on_component_ready_callback_);
 
   static constexpr char kTestManifest[] = R"json(
       {
@@ -531,7 +619,7 @@
 }
 
 TEST_F(SwReporterInstallerTest, EmptyManifest) {
-  SwReporterInstallerPolicy policy(on_component_ready_callback_);
+  SwReporterInstallerPolicy policy(&test_prefs_, on_component_ready_callback_);
 
   static constexpr char kTestManifest[] = "{}";
   policy.ComponentReady(default_version_, default_path_,
@@ -540,7 +628,7 @@
 }
 
 TEST_F(SwReporterInstallerTest, MissingLaunchParams) {
-  SwReporterInstallerPolicy policy(on_component_ready_callback_);
+  SwReporterInstallerPolicy policy(&test_prefs_, on_component_ready_callback_);
 
   static constexpr char kTestManifest[] = R"json(
       {
@@ -552,7 +640,7 @@
 }
 
 TEST_F(SwReporterInstallerTest, EmptyLaunchParams) {
-  SwReporterInstallerPolicy policy(on_component_ready_callback_);
+  SwReporterInstallerPolicy policy(&test_prefs_, on_component_ready_callback_);
 
   static constexpr char kTestManifest[] = R"json(
       {
@@ -565,7 +653,7 @@
 }
 
 TEST_F(SwReporterInstallerTest, MissingPromptSeed) {
-  SwReporterInstallerPolicy policy(on_component_ready_callback_);
+  SwReporterInstallerPolicy policy(&test_prefs_, on_component_ready_callback_);
 
   static constexpr char kTestManifest[] = R"json(
       {
@@ -582,7 +670,7 @@
 }
 
 TEST_F(SwReporterInstallerTest, BadSuffix) {
-  SwReporterInstallerPolicy policy(on_component_ready_callback_);
+  SwReporterInstallerPolicy policy(&test_prefs_, on_component_ready_callback_);
 
   static constexpr char kTestManifest[] = R"json(
       {
@@ -600,7 +688,7 @@
 }
 
 TEST_F(SwReporterInstallerTest, SuffixTooLong) {
-  SwReporterInstallerPolicy policy(on_component_ready_callback_);
+  SwReporterInstallerPolicy policy(&test_prefs_, on_component_ready_callback_);
 
   static constexpr char kTestManifest[] = R"json(
       {
@@ -621,7 +709,7 @@
 }
 
 TEST_F(SwReporterInstallerTest, BadTypesInManifest_ArgumentsIsNotAList) {
-  SwReporterInstallerPolicy policy(on_component_ready_callback_);
+  SwReporterInstallerPolicy policy(&test_prefs_, on_component_ready_callback_);
 
   // This has a string instead of a list for "arguments".
   static constexpr char kTestManifest[] = R"json(
@@ -640,7 +728,7 @@
 }
 
 TEST_F(SwReporterInstallerTest, BadTypesInManifest_InvocationParamsIsNotAList) {
-  SwReporterInstallerPolicy policy(on_component_ready_callback_);
+  SwReporterInstallerPolicy policy(&test_prefs_, on_component_ready_callback_);
 
   // This has the invocation parameters as direct children of "launch_params",
   // instead of using a list.
@@ -658,7 +746,7 @@
 }
 
 TEST_F(SwReporterInstallerTest, BadTypesInManifest_SuffixIsAList) {
-  SwReporterInstallerPolicy policy(on_component_ready_callback_);
+  SwReporterInstallerPolicy policy(&test_prefs_, on_component_ready_callback_);
 
   // This has a list for suffix as well as for arguments.
   static constexpr char kTestManifest[] = R"json(
@@ -677,7 +765,7 @@
 }
 
 TEST_F(SwReporterInstallerTest, BadTypesInManifest_PromptIsNotABoolean) {
-  SwReporterInstallerPolicy policy(on_component_ready_callback_);
+  SwReporterInstallerPolicy policy(&test_prefs_, on_component_ready_callback_);
 
   // This has an int instead of a bool for prompt.
   static constexpr char kTestManifest[] = R"json(
@@ -697,7 +785,7 @@
 }
 
 TEST_F(SwReporterInstallerTest, BadTypesInManifest_LaunchParamsIsScalar) {
-  SwReporterInstallerPolicy policy(on_component_ready_callback_);
+  SwReporterInstallerPolicy policy(&test_prefs_, on_component_ready_callback_);
 
   static constexpr char kTestManifest[] = R"json(
       {
@@ -710,7 +798,7 @@
 }
 
 TEST_F(SwReporterInstallerTest, BadTypesInManifest_LaunchParamsIsDict) {
-  SwReporterInstallerPolicy policy(on_component_ready_callback_);
+  SwReporterInstallerPolicy policy(&test_prefs_, on_component_ready_callback_);
 
   static constexpr char kTestManifest[] = R"json(
       {
@@ -723,7 +811,7 @@
 }
 
 TEST_F(SwReporterInstallerTest, BadTypesInManifest_PromptSeedIsList) {
-  SwReporterInstallerPolicy policy(on_component_ready_callback_);
+  SwReporterInstallerPolicy policy(&test_prefs_, on_component_ready_callback_);
 
   static constexpr char kTestManifest[] = R"json(
       {
@@ -736,7 +824,7 @@
 }
 
 TEST_F(SwReporterInstallerTest, BadTypesInManifest_PromptSeedIsInt) {
-  SwReporterInstallerPolicy policy(on_component_ready_callback_);
+  SwReporterInstallerPolicy policy(&test_prefs_, on_component_ready_callback_);
 
   static constexpr char kTestManifest[] = R"json(
       {
diff --git a/chrome/browser/download/bubble/download_bubble_controller.cc b/chrome/browser/download/bubble/download_bubble_controller.cc
index ab4eefc..8a34ef41 100644
--- a/chrome/browser/download/bubble/download_bubble_controller.cc
+++ b/chrome/browser/download/bubble/download_bubble_controller.cc
@@ -74,10 +74,9 @@
 using SortedDownloadUIModelSet =
     std::multiset<DownloadUIModelPtrList::iterator, StartTimeComparator>;
 
-template <typename T>
-bool AddModelIfRequired(T model,
+bool AddModelIfRequired(DownloadUIModelPtr model,
                         base::Time cutoff_time,
-                        std::vector<T>& models_aggregate) {
+                        std::vector<DownloadUIModelPtr>& models_aggregate) {
   if (model->ShouldShowInBubble() &&
       DownloadUIModelIsRecent(model.get(), cutoff_time)) {
     models_aggregate.push_back(std::move(model));
@@ -86,8 +85,7 @@
   return false;
 }
 
-template <typename T>
-bool ShouldStopAddingModels(std::vector<T>& models_aggregate) {
+bool ShouldStopAddingModels(std::vector<DownloadUIModelPtr>& models_aggregate) {
   return (models_aggregate.size() >= kMaxDownloadsToShow);
 }
 
@@ -283,29 +281,6 @@
   }
 }
 
-std::vector<std::unique_ptr<DownloadUIModel>>
-DownloadBubbleUIController::GetAllItemsToDisplayWithoutTaskRunnerDeletion() {
-  base::Time cutoff_time =
-      base::Time::Now() - base::Days(kShowDownloadsInBubbleForNumDays);
-  std::vector<std::unique_ptr<DownloadUIModel>> models_aggregate;
-  for (const OfflineItem& item : GetOfflineItems()) {
-    std::unique_ptr<DownloadUIModel> model(
-        new OfflineItemModel(offline_manager_, item));
-    if (AddModelIfRequired(std::move(model), cutoff_time, models_aggregate) &&
-        ShouldStopAddingModels(models_aggregate)) {
-      return models_aggregate;
-    }
-  }
-  for (download::DownloadItem* item : GetDownloadItems()) {
-    std::unique_ptr<DownloadUIModel> model(new DownloadItemModel(item));
-    if (AddModelIfRequired(std::move(model), cutoff_time, models_aggregate) &&
-        ShouldStopAddingModels(models_aggregate)) {
-      return models_aggregate;
-    }
-  }
-  return models_aggregate;
-}
-
 std::vector<DownloadUIModelPtr>
 DownloadBubbleUIController::GetAllItemsToDisplay() {
   base::Time cutoff_time =
diff --git a/chrome/browser/download/bubble/download_bubble_controller.h b/chrome/browser/download/bubble/download_bubble_controller.h
index 5d7e19e..608f0e9 100644
--- a/chrome/browser/download/bubble/download_bubble_controller.h
+++ b/chrome/browser/download/bubble/download_bubble_controller.h
@@ -49,15 +49,7 @@
   std::vector<DownloadUIModelPtr> GetPartialView();
 
   // Get all entries that should be displayed in the UI, including downloads and
-  // offline items, but allow destruction right away. Use this only if you are
-  // certain that the objects are short lived, for example used to compute
-  // progress.
-  std::vector<std::unique_ptr<DownloadUIModel>>
-  GetAllItemsToDisplayWithoutTaskRunnerDeletion();
-
-  // Get all entries that should be displayed in the UI, including downloads and
-  // offline items, with destruction through the task sequencer. This should be
-  // the default method.
+  // offline items.
   std::vector<DownloadUIModelPtr> GetAllItemsToDisplay();
 
   // The list is needed to populate GetAllItemsToDisplay.
diff --git a/chrome/browser/download/bubble/download_display_controller.cc b/chrome/browser/download/bubble/download_display_controller.cc
index 4748142..239acabd 100644
--- a/chrome/browser/download/bubble/download_display_controller.cc
+++ b/chrome/browser/download/bubble/download_display_controller.cc
@@ -79,7 +79,7 @@
 
 void DownloadDisplayController::OnRemovedItem(const ContentId& id) {
   std::vector<std::unique_ptr<DownloadUIModel>> all_models =
-      bubble_controller_->GetAllItemsToDisplayWithoutTaskRunnerDeletion();
+      bubble_controller_->GetAllItemsToDisplay();
   // Hide the button if there is only one download item left and that item is
   // about to be removed.
   if (all_models.size() == 1 && all_models[0]->GetContentId() == id) {
@@ -129,7 +129,7 @@
   bool has_deep_scanning_download = false;
 
   std::vector<std::unique_ptr<DownloadUIModel>> all_models =
-      bubble_controller_->GetAllItemsToDisplayWithoutTaskRunnerDeletion();
+      bubble_controller_->GetAllItemsToDisplay();
   if (all_models.empty()) {
     HideToolbarButton();
     return;
@@ -206,8 +206,7 @@
                                  last_complete_time)) {
     return;
   }
-  if (bubble_controller_->GetAllItemsToDisplayWithoutTaskRunnerDeletion()
-          .empty()) {
+  if (bubble_controller_->GetAllItemsToDisplay().empty()) {
     return;
   }
   // If the last download complete time is less than
@@ -246,7 +245,7 @@
   int64_t total_bytes = 0;
 
   std::vector<std::unique_ptr<DownloadUIModel>> all_models =
-      bubble_controller_->GetAllItemsToDisplayWithoutTaskRunnerDeletion();
+      bubble_controller_->GetAllItemsToDisplay();
   for (const auto& model : all_models) {
     if (IsModelInProgress(model.get())) {
       ++progress_info.download_count;
diff --git a/chrome/browser/enterprise/connectors/analysis/analysis_settings.cc b/chrome/browser/enterprise/connectors/analysis/analysis_settings.cc
index d5c4753..5d04c0b5 100644
--- a/chrome/browser/enterprise/connectors/analysis/analysis_settings.cc
+++ b/chrome/browser/enterprise/connectors/analysis/analysis_settings.cc
@@ -12,6 +12,8 @@
     CloudAnalysisSettings&&) = default;
 CloudAnalysisSettings::CloudAnalysisSettings(const CloudAnalysisSettings&) =
     default;
+CloudAnalysisSettings& CloudAnalysisSettings::operator=(
+    const CloudAnalysisSettings&) = default;
 CloudAnalysisSettings::~CloudAnalysisSettings() = default;
 
 LocalAnalysisSettings::LocalAnalysisSettings() = default;
@@ -20,6 +22,8 @@
     LocalAnalysisSettings&&) = default;
 LocalAnalysisSettings::LocalAnalysisSettings(const LocalAnalysisSettings&) =
     default;
+LocalAnalysisSettings& LocalAnalysisSettings::operator=(
+    const LocalAnalysisSettings&) = default;
 LocalAnalysisSettings::~LocalAnalysisSettings() = default;
 
 CloudOrLocalAnalysisSettings::CloudOrLocalAnalysisSettings() = default;
@@ -37,6 +41,8 @@
     CloudOrLocalAnalysisSettings&&) = default;
 CloudOrLocalAnalysisSettings::CloudOrLocalAnalysisSettings(
     const CloudOrLocalAnalysisSettings&) = default;
+CloudOrLocalAnalysisSettings& CloudOrLocalAnalysisSettings::operator=(
+    const CloudOrLocalAnalysisSettings&) = default;
 CloudOrLocalAnalysisSettings::~CloudOrLocalAnalysisSettings() = default;
 
 bool CloudOrLocalAnalysisSettings::is_cloud_analysis() const {
diff --git a/chrome/browser/enterprise/connectors/analysis/analysis_settings.h b/chrome/browser/enterprise/connectors/analysis/analysis_settings.h
index 6c029814..ee54826 100644
--- a/chrome/browser/enterprise/connectors/analysis/analysis_settings.h
+++ b/chrome/browser/enterprise/connectors/analysis/analysis_settings.h
@@ -9,6 +9,7 @@
 #include <string>
 #include <vector>
 
+#include "base/memory/raw_ptr.h"
 #include "chrome/browser/enterprise/connectors/service_provider_config.h"
 #include "components/enterprise/common/proto/connectors.pb.h"
 #include "third_party/abseil-cpp/absl/types/variant.h"
@@ -44,6 +45,7 @@
   CloudAnalysisSettings(CloudAnalysisSettings&&);
   CloudAnalysisSettings& operator=(CloudAnalysisSettings&&);
   CloudAnalysisSettings(const CloudAnalysisSettings&);
+  CloudAnalysisSettings& operator=(const CloudAnalysisSettings&);
   ~CloudAnalysisSettings();
 
   // The URL of the server that performs an analysis in the cloud.
@@ -60,6 +62,7 @@
   LocalAnalysisSettings(LocalAnalysisSettings&&);
   LocalAnalysisSettings& operator=(LocalAnalysisSettings&&);
   LocalAnalysisSettings(const LocalAnalysisSettings&);
+  LocalAnalysisSettings& operator=(const LocalAnalysisSettings&);
   ~LocalAnalysisSettings();
 
   std::string local_path;
@@ -74,6 +77,7 @@
   CloudOrLocalAnalysisSettings(CloudOrLocalAnalysisSettings&&);
   CloudOrLocalAnalysisSettings& operator=(CloudOrLocalAnalysisSettings&&);
   CloudOrLocalAnalysisSettings(const CloudOrLocalAnalysisSettings&);
+  CloudOrLocalAnalysisSettings& operator=(const CloudOrLocalAnalysisSettings&);
 
   ~CloudOrLocalAnalysisSettings();
 
diff --git a/chrome/browser/extensions/api/passwords_private/password_check_delegate.cc b/chrome/browser/extensions/api/passwords_private/password_check_delegate.cc
index 67e97596..afa0839 100644
--- a/chrome/browser/extensions/api/passwords_private/password_check_delegate.cc
+++ b/chrome/browser/extensions/api/passwords_private/password_check_delegate.cc
@@ -15,6 +15,7 @@
 
 #include "base/bind.h"
 #include "base/containers/flat_set.h"
+#include "base/feature_list.h"
 #include "base/memory/ref_counted.h"
 #include "base/numerics/safe_conversions.h"
 #include "base/strings/escape.h"
@@ -26,8 +27,10 @@
 #include "chrome/browser/extensions/api/passwords_private/passwords_private_utils.h"
 #include "chrome/browser/password_manager/account_password_store_factory.h"
 #include "chrome/browser/password_manager/bulk_leak_check_service_factory.h"
+#include "chrome/browser/password_manager/password_scripts_fetcher_factory.h"
 #include "chrome/browser/password_manager/password_store_factory.h"
 #include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/sync/sync_service_factory.h"
 #include "chrome/common/extensions/api/passwords_private.h"
 #include "chrome/grit/generated_resources.h"
 #include "components/keyed_service/core/service_access_type.h"
@@ -38,10 +41,13 @@
 #include "components/password_manager/core/browser/leak_detection/encryption_utils.h"
 #include "components/password_manager/core/browser/password_change_success_tracker.h"
 #include "components/password_manager/core/browser/password_form.h"
+#include "components/password_manager/core/browser/password_manager_util.h"
+#include "components/password_manager/core/browser/password_scripts_fetcher.h"
 #include "components/password_manager/core/browser/ui/credential_utils.h"
 #include "components/password_manager/core/browser/ui/insecure_credentials_manager.h"
 #include "components/password_manager/core/browser/ui/saved_passwords_presenter.h"
 #include "components/password_manager/core/browser/well_known_change_password_util.h"
+#include "components/password_manager/core/common/password_manager_features.h"
 #include "components/password_manager/core/common/password_manager_pref_names.h"
 #include "components/prefs/pref_service.h"
 #include "components/url_formatter/elide_url.h"
@@ -50,6 +56,7 @@
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/l10n/time_format.h"
 #include "url/gurl.h"
+#include "url/origin.h"
 
 namespace extensions {
 
@@ -632,13 +639,46 @@
   api_credential.signon_realm = entry.signon_realm;
   api_credential.username = base::UTF16ToUTF8(entry.username);
 
+  // For the time being, the automated password change is restricted to
+  // compromised credentials. In the future, this requirement may be relaxed.
+  if ((entry.IsPhished() || entry.IsLeaked()) &&
+      IsAutomatedPasswordChangeFromSettingsEnabled() &&
+      !entry.username.empty()) {
+    GURL url = api_credential.is_android_credential
+                   ? GURL(entry.affiliated_web_realm)
+                   : entry.url;
+    api_credential.has_startable_script =
+        !url.is_empty() && GetPasswordScriptsFetcher()->IsScriptAvailable(
+                               url::Origin::Create(entry.url));
+  } else {
+    api_credential.has_startable_script = false;
+  }
+
   return api_credential;
 }
 
 PasswordChangeSuccessTracker*
-PasswordCheckDelegate::GetPasswordChangeSuccessTracker() {
+PasswordCheckDelegate::GetPasswordChangeSuccessTracker() const {
   return password_manager::PasswordChangeSuccessTrackerFactory::
       GetForBrowserContext(profile_);
 }
 
+password_manager::PasswordScriptsFetcher*
+PasswordCheckDelegate::GetPasswordScriptsFetcher() const {
+  return PasswordScriptsFetcherFactory::GetForBrowserContext(profile_);
+}
+
+bool PasswordCheckDelegate::IsAutomatedPasswordChangeFromSettingsEnabled()
+    const {
+  // Do not offer password change to non-syncing users, as it is required
+  // for generating passwords.
+  if (password_manager_util::GetPasswordSyncState(
+          SyncServiceFactory::GetForProfile(profile_)) ==
+      password_manager::SyncState::kNotSyncing) {
+    return false;
+  }
+  return base::FeatureList::IsEnabled(
+      password_manager::features::kPasswordChange);
+}
+
 }  // namespace extensions
diff --git a/chrome/browser/extensions/api/passwords_private/password_check_delegate.h b/chrome/browser/extensions/api/passwords_private/password_check_delegate.h
index 1280671..f720c99e 100644
--- a/chrome/browser/extensions/api/passwords_private/password_check_delegate.h
+++ b/chrome/browser/extensions/api/passwords_private/password_check_delegate.h
@@ -28,6 +28,7 @@
 
 namespace password_manager {
 class PasswordChangeSuccessTracker;
+class PasswordScriptsFetcher;
 }  // namespace password_manager
 
 namespace extensions {
@@ -160,10 +161,17 @@
   api::passwords_private::InsecureCredential ConstructInsecureCredential(
       const password_manager::CredentialUIEntry& entry);
 
-  // Obtain a raw pointer to the |PasswordChangeSuccessTracker| associated
-  // with |profile_|.
+  // Returns a raw pointer to the `PasswordChangeSuccessTracker` associated
+  // with `profile_`.
   password_manager::PasswordChangeSuccessTracker*
-  GetPasswordChangeSuccessTracker();
+  GetPasswordChangeSuccessTracker() const;
+
+  // Returns a raw pointer to the `PasswordScriptsFetcher` associated with
+  // `profile_`.
+  password_manager::PasswordScriptsFetcher* GetPasswordScriptsFetcher() const;
+
+  // Returns whether automatic password changes are enabled from settings.
+  bool IsAutomatedPasswordChangeFromSettingsEnabled() const;
 
   // Raw pointer to the underlying profile. Needs to outlive this instance.
   raw_ptr<Profile> profile_ = nullptr;
diff --git a/chrome/browser/extensions/api/settings_private/prefs_util.cc b/chrome/browser/extensions/api/settings_private/prefs_util.cc
index c2586111..1369ee773 100644
--- a/chrome/browser/extensions/api/settings_private/prefs_util.cc
+++ b/chrome/browser/extensions/api/settings_private/prefs_util.cc
@@ -798,7 +798,8 @@
       settings_api::PrefType::PREF_TYPE_BOOLEAN;
   (*s_allowlist)[ash::prefs::kUserCameraAllowed] =
       settings_api::PrefType::PREF_TYPE_BOOLEAN;
-
+  (*s_allowlist)[ash::prefs::kUserMicrophoneAllowed] =
+      settings_api::PrefType::PREF_TYPE_BOOLEAN;
 #else
   // System settings.
   (*s_allowlist)[::prefs::kBackgroundModeEnabled] =
diff --git a/chrome/browser/extensions/extension_action_runner.cc b/chrome/browser/extensions/extension_action_runner.cc
index 2b93d1c5..3f97d63 100644
--- a/chrome/browser/extensions/extension_action_runner.cc
+++ b/chrome/browser/extensions/extension_action_runner.cc
@@ -6,6 +6,7 @@
 
 #include <memory>
 #include <tuple>
+#include <vector>
 
 #include "base/auto_reset.h"
 #include "base/bind.h"
@@ -45,6 +46,20 @@
 #include "extensions/common/permissions/permissions_data.h"
 #include "url/origin.h"
 
+namespace {
+
+std::vector<extensions::ExtensionId> GetExtensionIds(
+    std::vector<const extensions::Extension*> extensions) {
+  std::vector<extensions::ExtensionId> extension_ids;
+  extension_ids.reserve(extensions.size());
+  for (auto* extension : extensions) {
+    extension_ids.push_back(extension->id());
+  }
+  return extension_ids;
+}
+
+}  // namespace
+
 namespace extensions {
 
 const int ExtensionActionRunner::kRefreshRequiredActionsMask =
@@ -87,7 +102,7 @@
     bool grant_tab_permissions) {
   if (grant_tab_permissions) {
     int blocked_actions = GetBlockedActions(extension->id());
-    GrantTabPermissions(extension);
+    GrantTabPermissions({extension});
 
     // If the extension had blocked actions before granting tab permissions,
     // granting active tab will have run the extension. Don't execute further
@@ -117,31 +132,43 @@
   return ExtensionAction::ACTION_NONE;
 }
 
-void ExtensionActionRunner::GrantTabPermissions(const Extension* extension) {
-  int blocked_actions = GetBlockedActions(extension->id());
-  if ((blocked_actions & kRefreshRequiredActionsMask) == 0) {
-    // No refresh needed; immediately grant permissions.
-    TabHelper::FromWebContents(web_contents())
-        ->active_tab_permission_granter()
-        ->GrantIfRequested(extension);
+void ExtensionActionRunner::GrantTabPermissions(
+    const std::vector<const Extension*>& extensions) {
+  bool refresh_required = std::any_of(
+      extensions.begin(), extensions.end(), [this](const Extension* extension) {
+        return GetBlockedActions(extension->id()) & kRefreshRequiredActionsMask;
+      });
+
+  if (!refresh_required) {
+    // Immediately grant permissions to every extension.
+    for (auto* extension : extensions) {
+      TabHelper::FromWebContents(web_contents())
+          ->active_tab_permission_granter()
+          ->GrantIfRequested(extension);
+    }
     return;
   }
 
+  // Every extension that wants tab permission is currently blocked and must
+  // have "on click" access.
   const GURL& url = web_contents()->GetLastCommittedURL();
-  // An extension that wants to run but it is currently blocked must have
-  // "on click" access.
   constexpr SitePermissionsHelper::SiteAccess kExpectedSiteAccess =
       SitePermissionsHelper::SiteAccess::kOnClick;
-  DCHECK_EQ(SitePermissionsHelper(Profile::FromBrowserContext(browser_context_))
-                .GetSiteAccess(*extension, url),
-            kExpectedSiteAccess);
+  auto permissions =
+      SitePermissionsHelper(Profile::FromBrowserContext(browser_context_));
+  DCHECK(std::all_of(extensions.begin(), extensions.end(),
+                     [url, &permissions](const Extension* extension) {
+                       return permissions.GetSiteAccess(*extension, url) ==
+                              kExpectedSiteAccess;
+                     }));
 
-  // Running the action does not update permissions.
+  // Running the action a single time does not update permissions.
   constexpr bool update_permissions = false;
+  std::vector<ExtensionId> extension_ids = GetExtensionIds(extensions);
   ShowReloadPageBubble(
-      extension, update_permissions,
+      extension_ids, update_permissions,
       base::BindOnce(&ExtensionActionRunner::OnReloadPageBubbleAccepted,
-                     weak_factory_.GetWeakPtr(), extension->id(), url,
+                     weak_factory_.GetWeakPtr(), extension_ids, url,
                      kExpectedSiteAccess, kExpectedSiteAccess));
 }
 
@@ -163,10 +190,11 @@
   // that common and therefore hopefully is not too noisy.
   if (revoking_permissions || blocked_actions & kRefreshRequiredActionsMask) {
     constexpr bool update_permissions = true;
+    std::vector<ExtensionId> extension_ids{extension->id()};
     ShowReloadPageBubble(
-        extension, update_permissions,
+        extension_ids, update_permissions,
         base::BindOnce(&ExtensionActionRunner::OnReloadPageBubbleAccepted,
-                       weak_factory_.GetWeakPtr(), extension->id(),
+                       weak_factory_.GetWeakPtr(), extension_ids,
                        web_contents()->GetLastCommittedURL(), current_access,
                        new_access));
     return;
@@ -380,9 +408,10 @@
   }
 }
 
-void ExtensionActionRunner::ShowReloadPageBubble(const Extension* extension,
-                                                 bool update_permissions,
-                                                 base::OnceClosure callback) {
+void ExtensionActionRunner::ShowReloadPageBubble(
+    const std::vector<ExtensionId>& extension_ids,
+    bool update_permissions,
+    base::OnceClosure callback) {
   // For testing, simulate the bubble being accepted by directly invoking the
   // callback, or rejected by skipping the callback.
   if (accept_bubble_for_testing_.has_value()) {
@@ -402,12 +431,16 @@
   if (!extensions_container)
     return;
 
-  ShowReloadPageDialog(browser, extension->id(), update_permissions,
+  ShowReloadPageDialog(browser, extension_ids, update_permissions,
                        std::move(callback));
 }
 
+// TODO(emiliapaz): Changing user site settings should also trigger the reload
+// page. Once it is implemented, the callback needs to grant user site settings
+// access. Consider separating callback for each purpose (e.g -ForTabPermission,
+// -ForSiteAccessChange, - ForUserSiteSettingChange).
 void ExtensionActionRunner::OnReloadPageBubbleAccepted(
-    const std::string& extension_id,
+    const std::vector<ExtensionId>& extension_ids,
     const GURL& page_url,
     SitePermissionsHelper::SiteAccess current_access,
     SitePermissionsHelper::SiteAccess new_access) {
@@ -415,21 +448,35 @@
   if (!url::IsSameOriginWith(page_url, web_contents()->GetLastCommittedURL()))
     return;
 
-  const Extension* extension = ExtensionRegistry::Get(browser_context_)
-                                   ->enabled_extensions()
-                                   .GetByID(extension_id);
-  if (!extension)
-    return;
+  auto* registry = ExtensionRegistry::Get(browser_context_);
+  auto get_extension = [registry](ExtensionId extension_id) {
+    return registry->enabled_extensions().GetByID(extension_id);
+  };
 
   if (current_access != new_access) {
+    // Only a single extension can update its site access since multiple
+    // extensions cannot change their site access at the same time.
+    DCHECK_EQ(int(extension_ids.size()), 1);
+    const Extension* extension = get_extension(extension_ids[0]);
+    // Extension could have been removed while the reload page bubble was open.
+    if (!extension)
+      return;
+
     UpdatePageAccessSettings(extension, current_access, new_access);
   } else {
-    // Ignore the active tab permission being granted because we don't want
-    // to run scripts right before we refresh the page.
-    base::AutoReset<bool> ignore_active_tab(&ignore_active_tab_granted_, true);
-    TabHelper::FromWebContents(web_contents())
-        ->active_tab_permission_granter()
-        ->GrantIfRequested(extension);
+    // Multiple extension and a single extension that doesn't change its site
+    // access are granted one time access.
+    for (const auto& extension_id : extension_ids) {
+      const Extension* extension = get_extension(extension_id);
+      if (!extension)
+        continue;
+
+      base::AutoReset<bool> ignore_active_tab(&ignore_active_tab_granted_,
+                                              true);
+      TabHelper::FromWebContents(web_contents())
+          ->active_tab_permission_granter()
+          ->GrantIfRequested(extension);
+    }
   }
 
   web_contents()->GetController().Reload(content::ReloadType::NORMAL, false);
diff --git a/chrome/browser/extensions/extension_action_runner.h b/chrome/browser/extensions/extension_action_runner.h
index ce4925e..719ea08 100644
--- a/chrome/browser/extensions/extension_action_runner.h
+++ b/chrome/browser/extensions/extension_action_runner.h
@@ -66,10 +66,10 @@
   ExtensionAction::ShowAction RunAction(const Extension* extension,
                                         bool grant_tab_permissions);
 
-  // Grants activeTab to |extension| (this should only be done if this is
-  // through a direct user action). If the action needs a page refresh to run,
-  // this will show a dialog instead of immediately granting permissions.
-  void GrantTabPermissions(const Extension* extension);
+  // Grants activeTab to |extensions| (this should only be done if this is
+  // through a direct user action). If any extension needs a page refresh to
+  // run, this will show a dialog instead of immediately granting permissions.
+  void GrantTabPermissions(const std::vector<const Extension*>& extensions);
 
   // Notifies the ExtensionActionRunner that the page access for |extension| has
   // changed.
@@ -182,15 +182,15 @@
   void LogUMA() const;
 
   // Shows the bubble to prompt the user to refresh the page to run or not the
-  // action for the given |extension|. |callback| is invoked when the
+  // action for the given |extension_ids|. |callback| is invoked when the
   // bubble is closed.
-  void ShowReloadPageBubble(const Extension* extension,
+  void ShowReloadPageBubble(const std::vector<ExtensionId>& extension_ids,
                             bool update_permissions,
                             base::OnceClosure callback);
 
   // Called when the reload page bubble is accepted.
   void OnReloadPageBubbleAccepted(
-      const std::string& extension_id,
+      const std::vector<ExtensionId>& extension_ids,
       const GURL& page_url,
       SitePermissionsHelper::SiteAccess current_access,
       SitePermissionsHelper::SiteAccess new_access);
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index e47e202..3ec9d7e 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -2125,7 +2125,7 @@
   {
     "name": "enable-fake-keyboard-heuristic",
     "owners": ["wmahon", "chromeos-tango@google.com"],
-    "expiry_milestone": 104
+    "expiry_milestone": 108
   },
   {
       "name": "enable-favicon-passwords",
@@ -4670,11 +4670,11 @@
   },
   {
     "name": "omnibox-on-focus-suggestions-contextual-web",
-    "owners": [ "ender", "stkhapugin", "tommycli", "chrome-omnibox-team@google.com" ],
+    "owners": [ "ender", "stkhapugin", "chrome-omnibox-team@google.com" ],
     "expiry_milestone": 110
   },
   {
-    "name": "omnibox-on-focus-suggestions-contextual-web-allow-srp",
+    "name": "omnibox-on-focus-suggestions-srp",
     "owners": [ "ender", "stkhapugin", "chrome-omnibox-team@google.com" ],
     "expiry_milestone": 110
   },
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index be6f601d..3fcb284 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -1917,16 +1917,16 @@
 const char kOmniboxSiteSearchStarterPackDescription[] =
     "Enables @history, @bookmarks, and @tabs scopes in Omnibox Site "
     "Search/Keyword Mode";
-const char kOmniboxOnFocusSuggestionsContextualWebAllowSRPName[] =
+const char kOmniboxFocusTriggersSRPZeroSuggestName[] =
     "Allow Omnibox contextual web on-focus suggestions on the SRP";
-const char kOmniboxOnFocusSuggestionsContextualWebAllowSRPDescription[] =
+const char kOmniboxFocusTriggersSRPZeroSuggestDescription[] =
     "Enables on-focus suggestions on the Search Results page. "
     "Requires on-focus suggestions for the contextual web to be enabled. "
     "Will only work if user is signed-in and syncing.";
 
-const char kOmniboxOnFocusSuggestionsContextualWebName[] =
+const char kOmniboxFocusTriggersContextualWebZeroSuggestName[] =
     "Omnibox on-focus suggestions for the contextual Web";
-const char kOmniboxOnFocusSuggestionsContextualWebDescription[] =
+const char kOmniboxFocusTriggersContextualWebZeroSuggestDescription[] =
     "Enables on-focus suggestions on the Open Web, that are contextual to the "
     "current URL. Will only work if user is signed-in and syncing, or is "
     "otherwise eligible to send the current page URL to the suggest server.";
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index 40c7f30..a83953f9 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -1068,11 +1068,11 @@
 extern const char kOmniboxRichAutocompletionPromisingName[];
 extern const char kOmniboxRichAutocompletionPromisingDescription[];
 
-extern const char kOmniboxOnFocusSuggestionsContextualWebAllowSRPName[];
-extern const char kOmniboxOnFocusSuggestionsContextualWebAllowSRPDescription[];
+extern const char kOmniboxFocusTriggersSRPZeroSuggestName[];
+extern const char kOmniboxFocusTriggersSRPZeroSuggestDescription[];
 
-extern const char kOmniboxOnFocusSuggestionsContextualWebName[];
-extern const char kOmniboxOnFocusSuggestionsContextualWebDescription[];
+extern const char kOmniboxFocusTriggersContextualWebZeroSuggestName[];
+extern const char kOmniboxFocusTriggersContextualWebZeroSuggestDescription[];
 
 extern const char kOmniboxShortBookmarkSuggestionsName[];
 extern const char kOmniboxShortBookmarkSuggestionsDescription[];
diff --git a/chrome/browser/media/webrtc/region_capture_browsertest.cc b/chrome/browser/media/webrtc/region_capture_browsertest.cc
index b9471678..4edd3b52 100644
--- a/chrome/browser/media/webrtc/region_capture_browsertest.cc
+++ b/chrome/browser/media/webrtc/region_capture_browsertest.cc
@@ -442,8 +442,6 @@
             "top-level-crop-success");
 }
 
-// TODO(crbug.com/1336323): Re-enable.
-#if 0
 // The Promise resolves when it's guaranteed that no additional frames will
 // be issued with an earlier crop version. That an actual frame be issued
 // at all, let alone with the new crop version, is not actually required,
@@ -461,7 +459,6 @@
   EXPECT_EQ(tab.CropTo(crop_target, Frame::kTopLevelDocument),
             "top-level-crop-success");
 }
-#endif
 
 IN_PROC_BROWSER_TEST_F(RegionCaptureBrowserTest, MaxCropIdsInTopLevelDocument) {
   SetUpTest(Frame::kNone, /*self_capture=*/false);
diff --git a/chrome/browser/password_check/android/junit/src/org/chromium/chrome/browser/password_check/PasswordCheckControllerTest.java b/chrome/browser/password_check/android/junit/src/org/chromium/chrome/browser/password_check/PasswordCheckControllerTest.java
index 5d4c52e1..547b467 100644
--- a/chrome/browser/password_check/android/junit/src/org/chromium/chrome/browser/password_check/PasswordCheckControllerTest.java
+++ b/chrome/browser/password_check/android/junit/src/org/chromium/chrome/browser/password_check/PasswordCheckControllerTest.java
@@ -82,6 +82,7 @@
  */
 @RunWith(BaseRobolectricTestRunner.class)
 @Config(manifest = Config.NONE, shadows = {ShadowRecordHistogram.class})
+@SuppressWarnings("DoNotMock") // Mocks GURL.
 public class PasswordCheckControllerTest {
     private static final CompromisedCredential ANA =
             new CompromisedCredential("https://m.a.xyz/signin", mock(GURL.class), "Ana", "m.a.xyz",
diff --git a/chrome/browser/password_manager/chrome_webauthn_credentials_delegate.cc b/chrome/browser/password_manager/chrome_webauthn_credentials_delegate.cc
index ce22877..3e657486 100644
--- a/chrome/browser/password_manager/chrome_webauthn_credentials_delegate.cc
+++ b/chrome/browser/password_manager/chrome_webauthn_credentials_delegate.cc
@@ -33,6 +33,18 @@
   return base::FeatureList::IsEnabled(features::kWebAuthConditionalUI);
 }
 
+void ChromeWebAuthnCredentialsDelegate::LaunchWebAuthnFlow() {
+#if !BUILDFLAG(IS_ANDROID)
+  ChromeAuthenticatorRequestDelegate* authenticator_delegate =
+      AuthenticatorRequestScheduler::GetRequestDelegate(
+          client_->web_contents());
+  if (!authenticator_delegate) {
+    return;
+  }
+  authenticator_delegate->dialog_model()->TransitionToModalWebAuthnRequest();
+#endif  // !BUILDFLAG(IS_ANDROID)
+}
+
 void ChromeWebAuthnCredentialsDelegate::SelectWebAuthnCredential(
     std::string backend_id) {
 #if BUILDFLAG(IS_ANDROID)
@@ -102,8 +114,7 @@
         !credential.user.display_name->empty()) {
       name = base::UTF8ToUTF16(*credential.user.display_name);
     } else {
-      // TODO(crbug.com/1179014): go through UX review, choose a string, and
-      // i18n it.
+      // TODO(crbug.com/1329958): i18n this string.
       name = u"Unknown account";
     }
     autofill::Suggestion suggestion(std::move(name));
diff --git a/chrome/browser/password_manager/chrome_webauthn_credentials_delegate.h b/chrome/browser/password_manager/chrome_webauthn_credentials_delegate.h
index 7f703a72..0aebf864 100644
--- a/chrome/browser/password_manager/chrome_webauthn_credentials_delegate.h
+++ b/chrome/browser/password_manager/chrome_webauthn_credentials_delegate.h
@@ -32,6 +32,7 @@
 
   // password_manager::WebAuthnCredentialsDelegate:
   bool IsWebAuthnAutofillEnabled() const override;
+  void LaunchWebAuthnFlow() override;
   void SelectWebAuthnCredential(std::string backend_id) override;
   const std::vector<autofill::Suggestion>& GetWebAuthnSuggestions()
       const override;
diff --git a/chrome/browser/prefetch/search_prefetch/search_prefetch_service.cc b/chrome/browser/prefetch/search_prefetch/search_prefetch_service.cc
index 1b564ca4..af8614dd 100644
--- a/chrome/browser/prefetch/search_prefetch/search_prefetch_service.cc
+++ b/chrome/browser/prefetch/search_prefetch/search_prefetch_service.cc
@@ -35,6 +35,7 @@
 #include "components/omnibox/browser/base_search_provider.h"
 #include "components/omnibox/browser/omnibox_event_global_tracker.h"
 #include "components/omnibox/browser/omnibox_log.h"
+#include "components/omnibox/browser/search_provider.h"
 #include "components/prefs/pref_registry_simple.h"
 #include "components/prefs/pref_service.h"
 #include "components/search_engines/template_url_service.h"
@@ -269,19 +270,6 @@
   if (match_search_terms.size() == 0)
     return;
 
-  if (IsSearchNavigationPrefetchEnabled() &&
-      default_search->data().prefetch_likely_navigations) {
-    auto start = base::TimeTicks::Now();
-    bool started_prefetch = MaybePrefetchURL(opened_url,
-                                             /*navigation_prefetch=*/true);
-
-    // Record the overhead of starting the prefetch earlier.
-    if (started_prefetch) {
-      UMA_HISTOGRAM_TIMES("Omnibox.SearchPrefetch.StartTime.NavigationPrefetch",
-                          (base::TimeTicks::Now() - start));
-    }
-  }
-
   if (prefetches_.find(match_search_terms) == prefetches_.end()) {
     return;
   }
@@ -508,6 +496,35 @@
   }
 }
 
+void SearchPrefetchService::MaybePrefetchLikelyMatch(
+    size_t index,
+    const AutocompleteMatch& match) {
+  if (!IsSearchNavigationPrefetchEnabled())
+    return;
+  // Assume the user is going back to enter more for now.
+  if (index == 0)
+    return;
+  // Only prefetch search types.
+  if (!AutocompleteMatch::IsSearchType(match.type))
+    return;
+  // Check to make sure this is search related and that we can read the search
+  // arguments. For Search history this may be null.
+  if (!match.search_terms_args)
+    return;
+  auto* template_url_service =
+      TemplateURLServiceFactory::GetForProfile(profile_);
+  // The default search provider needs to opt into prefetching behavior.
+  if (!template_url_service ||
+      !template_url_service->GetDefaultSearchProvider() ||
+      !template_url_service->GetDefaultSearchProvider()
+           ->data()
+           .prefetch_likely_navigations) {
+    return;
+  }
+  MaybePrefetchURL(GetPrefetchURLFromMatch(match, template_url_service),
+                   /*navigation_prefetch=*/true);
+}
+
 void SearchPrefetchService::OnTemplateURLServiceChanged() {
   auto* template_url_service =
       TemplateURLServiceFactory::GetForProfile(profile_);
diff --git a/chrome/browser/prefetch/search_prefetch/search_prefetch_service.h b/chrome/browser/prefetch/search_prefetch/search_prefetch_service.h
index d5e6716..1698476 100644
--- a/chrome/browser/prefetch/search_prefetch/search_prefetch_service.h
+++ b/chrome/browser/prefetch/search_prefetch/search_prefetch_service.h
@@ -18,6 +18,7 @@
 #include "base/timer/timer.h"
 #include "chrome/browser/prefetch/search_prefetch/base_search_prefetch_request.h"
 #include "components/keyed_service/core/keyed_service.h"
+#include "components/omnibox/browser/autocomplete_match.h"
 #include "components/search_engines/template_url_data.h"
 #include "components/search_engines/template_url_service.h"
 #include "components/search_engines/template_url_service_observer.h"
@@ -179,6 +180,10 @@
     return weak_factory_.GetWeakPtr();
   }
 
+  // Considers if this prefetch is worth starting, and if so, starts a prefetch
+  // for |match|. |index| is the location within the omnibox drop down.
+  void MaybePrefetchLikelyMatch(size_t index, const AutocompleteMatch& match);
+
  private:
   // Returns whether the prefetch started or not.
   bool MaybePrefetchURL(const GURL& url, bool navigation_prefetch);
diff --git a/chrome/browser/prefetch/search_prefetch/search_prefetch_service_browsertest.cc b/chrome/browser/prefetch/search_prefetch/search_prefetch_service_browsertest.cc
index 50b7bb0..bb976ff 100644
--- a/chrome/browser/prefetch/search_prefetch/search_prefetch_service_browsertest.cc
+++ b/chrome/browser/prefetch/search_prefetch/search_prefetch_service_browsertest.cc
@@ -2805,16 +2805,19 @@
 };
 
 IN_PROC_BROWSER_TEST_F(SearchPrefetchServiceNavigationPrefetchBrowserTest,
-                       DISABLED_NavigationPrefetchIsServed) {
+                       NavigationPrefetchIsServed) {
   SetDSEWithURL(
       GetSearchServerQueryURL("{searchTerms}&{google:prefetchSource}"), true);
   auto* search_prefetch_service =
       SearchPrefetchServiceFactory::GetForProfile(browser()->profile());
-  std::string search_terms = kOmniboxSuggestNonPrefetchQuery;
+  std::string search_terms = "terms of service";
+  std::string user_input = "terms";
+  AddNewSuggestionRule(user_input, {user_input, search_terms},
+                       /*prefetch_index=*/-1, /*prerender_index=*/-1);
 
   // Trigger an omnibox suggest fetch that does not have a prefetch hint.
   AutocompleteInput input(
-      base::ASCIIToUTF16(search_terms), metrics::OmniboxEventProto::BLANK,
+      base::ASCIIToUTF16(user_input), metrics::OmniboxEventProto::BLANK,
       ChromeAutocompleteSchemeClassifier(browser()->profile()));
   LocationBar* location_bar = browser()->window()->GetLocationBar();
   OmniboxView* omnibox = location_bar->GetOmniboxView();
@@ -2829,10 +2832,13 @@
   ui_test_utils::WaitForAutocompleteDone(browser());
   EXPECT_TRUE(autocomplete_controller->done());
 
+  omnibox->model()->SetPopupSelection(OmniboxPopupSelection(1));
+
   auto prefetch_status =
       search_prefetch_service->GetSearchPrefetchStatusForTesting(
           base::ASCIIToUTF16(search_terms));
-  EXPECT_FALSE(prefetch_status.has_value());
+  ASSERT_TRUE(prefetch_status.has_value());
+  EXPECT_EQ(SearchPrefetchStatus::kCanBeServed, prefetch_status.value());
 
   omnibox->model()->AcceptInput(WindowOpenDisposition::CURRENT_TAB);
 
@@ -2853,19 +2859,20 @@
   EXPECT_TRUE(base::Contains(inner_html, "prefetch"));
 }
 
-// TODO(https://crbug.com/1318154): Flaky on Mac bots.
-// TODO(https://crbug.com/1317890): Flaky on other bots as well.
 IN_PROC_BROWSER_TEST_F(SearchPrefetchServiceNavigationPrefetchBrowserTest,
-                       DISABLED_NavigationPrefetchReplacesError) {
+                       NavigationPrefetchReplacesError) {
   SetDSEWithURL(
       GetSearchServerQueryURL("{searchTerms}&{google:prefetchSource}"), true);
   auto* search_prefetch_service =
       SearchPrefetchServiceFactory::GetForProfile(browser()->profile());
   std::string search_terms = kOmniboxErrorQuery;
+  std::string user_input = "terms";
+  AddNewSuggestionRule(user_input, {user_input, search_terms},
+                       /*prefetch_index=*/1, /*prerender_index=*/-1);
 
   // Trigger an omnibox suggest fetch that does not have a prefetch hint.
   AutocompleteInput input(
-      base::ASCIIToUTF16(search_terms), metrics::OmniboxEventProto::BLANK,
+      base::ASCIIToUTF16(user_input), metrics::OmniboxEventProto::BLANK,
       ChromeAutocompleteSchemeClassifier(browser()->profile()));
   LocationBar* location_bar = browser()->window()->GetLocationBar();
   OmniboxView* omnibox = location_bar->GetOmniboxView();
@@ -2888,6 +2895,13 @@
   ASSERT_TRUE(prefetch_status.has_value());
   EXPECT_EQ(SearchPrefetchStatus::kRequestFailed, prefetch_status.value());
 
+  omnibox->model()->SetPopupSelection(OmniboxPopupSelection(1));
+
+  prefetch_status = search_prefetch_service->GetSearchPrefetchStatusForTesting(
+      base::ASCIIToUTF16(search_terms));
+  ASSERT_TRUE(prefetch_status.has_value());
+  EXPECT_EQ(SearchPrefetchStatus::kCanBeServed, prefetch_status.value());
+
   omnibox->model()->AcceptInput(WindowOpenDisposition::CURRENT_TAB);
 
   prefetch_status = search_prefetch_service->GetSearchPrefetchStatusForTesting(
@@ -2903,11 +2917,14 @@
       GetSearchServerQueryURL("{searchTerms}&{google:prefetchSource}"), true);
   auto* search_prefetch_service =
       SearchPrefetchServiceFactory::GetForProfile(browser()->profile());
-  std::string search_terms = kOmniboxSuggestPrefetchQuery;
+  std::string search_terms = "terms of service";
+  std::string user_input = "terms";
+  AddNewSuggestionRule(user_input, {user_input, search_terms},
+                       /*prefetch_index=*/1, /*prerender_index=*/-1);
 
   // Trigger an omnibox suggest fetch that does not have a prefetch hint.
   AutocompleteInput input(
-      base::ASCIIToUTF16(search_terms), metrics::OmniboxEventProto::BLANK,
+      base::ASCIIToUTF16(user_input), metrics::OmniboxEventProto::BLANK,
       ChromeAutocompleteSchemeClassifier(browser()->profile()));
   LocationBar* location_bar = browser()->window()->GetLocationBar();
   OmniboxView* omnibox = location_bar->GetOmniboxView();
@@ -2930,6 +2947,13 @@
   ASSERT_TRUE(prefetch_status.has_value());
   EXPECT_EQ(SearchPrefetchStatus::kComplete, prefetch_status.value());
 
+  omnibox->model()->SetPopupSelection(OmniboxPopupSelection(1));
+
+  prefetch_status = search_prefetch_service->GetSearchPrefetchStatusForTesting(
+      base::ASCIIToUTF16(search_terms));
+  ASSERT_TRUE(prefetch_status.has_value());
+  EXPECT_EQ(SearchPrefetchStatus::kComplete, prefetch_status.value());
+
   omnibox->model()->AcceptInput(WindowOpenDisposition::CURRENT_TAB);
 
   prefetch_status = search_prefetch_service->GetSearchPrefetchStatusForTesting(
@@ -2944,11 +2968,14 @@
       GetSearchServerQueryURL("{searchTerms}&{google:prefetchSource}"), false);
   auto* search_prefetch_service =
       SearchPrefetchServiceFactory::GetForProfile(browser()->profile());
-  std::string search_terms = kOmniboxSuggestNonPrefetchQuery;
+  std::string search_terms = "terms of service";
+  std::string user_input = "terms";
+  AddNewSuggestionRule(user_input, {user_input, search_terms},
+                       /*prefetch_index=*/-1, /*prerender_index=*/-1);
 
   // Trigger an omnibox suggest fetch that does not have a prefetch hint.
   AutocompleteInput input(
-      base::ASCIIToUTF16(search_terms), metrics::OmniboxEventProto::BLANK,
+      base::ASCIIToUTF16(user_input), metrics::OmniboxEventProto::BLANK,
       ChromeAutocompleteSchemeClassifier(browser()->profile()));
   LocationBar* location_bar = browser()->window()->GetLocationBar();
   OmniboxView* omnibox = location_bar->GetOmniboxView();
@@ -2963,6 +2990,8 @@
   ui_test_utils::WaitForAutocompleteDone(browser());
   EXPECT_TRUE(autocomplete_controller->done());
 
+  omnibox->model()->SetPopupSelection(OmniboxPopupSelection(1));
+
   auto prefetch_status =
       search_prefetch_service->GetSearchPrefetchStatusForTesting(
           base::ASCIIToUTF16(search_terms));
diff --git a/chrome/browser/renderer_context_menu/render_view_context_menu.cc b/chrome/browser/renderer_context_menu/render_view_context_menu.cc
index bf9fb13e..9f003cc 100644
--- a/chrome/browser/renderer_context_menu/render_view_context_menu.cc
+++ b/chrome/browser/renderer_context_menu/render_view_context_menu.cc
@@ -92,6 +92,7 @@
 #include "chrome/browser/ui/send_tab_to_self/send_tab_to_self_bubble_controller.h"
 #include "chrome/browser/ui/tab_contents/core_tab_helper.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/browser/ui/translate/partial_translate_bubble_model.h"
 #include "chrome/browser/ui/ui_features.h"
 #include "chrome/browser/ui/web_applications/app_browser_controller.h"
 #include "chrome/browser/ui/web_applications/system_web_app_ui_utils.h"
@@ -143,6 +144,7 @@
 #include "components/translate/core/browser/translate_download_manager.h"
 #include "components/translate/core/browser/translate_manager.h"
 #include "components/translate/core/browser/translate_prefs.h"
+#include "components/translate/core/common/translate_util.h"
 #include "components/url_formatter/url_formatter.h"
 #include "components/user_prefs/user_prefs.h"
 #include "components/web_modal/web_contents_modal_dialog_manager.h"
@@ -440,13 +442,14 @@
        {IDC_UNFOLLOW, 120},
        {IDC_CONTENT_CONTEXT_AUTOFILL_CUSTOM_FIRST, 121},
        {IDC_CONTENT_CONTEXT_RUN_PDF_OCR, 122},
+       {IDC_CONTENT_CONTEXT_PARTIAL_TRANSLATE, 123},
        // To add new items:
        //   - Add one more line above this comment block, using the UMA value
        //     from the line below this comment block.
        //   - Increment the UMA value in that latter line.
        //   - Add the new item to the RenderViewContextMenuItem enum in
        //     tools/metrics/histograms/enums.xml.
-       {0, 123}});
+       {0, 124}});
 
   // These UMA values are for the the ContextMenuOptionDesktop enum, used for
   // the ContextMenu.SelectedOptionDesktop histograms.
@@ -1008,6 +1011,12 @@
     AppendPrintItem();
   }
 
+  if (base::FeatureList::IsEnabled(translate::kDesktopPartialTranslate) &&
+      content_type_->SupportsGroup(
+          ContextMenuContentType::ITEM_GROUP_PARTIAL_TRANSLATE)) {
+    AppendPartialTranslateItem();
+  }
+
   // Spell check and writing direction options are not currently supported by
   // pepper plugins.
   if (editable && params_.misspelled_word.empty() &&
@@ -1311,14 +1320,15 @@
       ->GetExtensionForWebContents(source_web_contents_);
 }
 
-std::string RenderViewContextMenu::GetTargetLanguage() const {
+std::u16string RenderViewContextMenu::GetTargetLanguageDisplayName() const {
   std::unique_ptr<translate::TranslatePrefs> prefs(
       ChromeTranslateClient::CreateTranslatePrefs(GetPrefs(browser_context_)));
   language::LanguageModel* language_model =
       LanguageModelManagerFactory::GetForBrowserContext(browser_context_)
           ->GetPrimaryModel();
-  return translate::TranslateManager::GetTargetLanguage(prefs.get(),
-                                                        language_model);
+  std::string locale = translate::TranslateManager::GetTargetLanguage(
+      prefs.get(), language_model);
+  return l10n_util::GetDisplayNameForLocale(locale, locale, true);
 }
 
 void RenderViewContextMenu::AppendDeveloperItems() {
@@ -1765,19 +1775,10 @@
       chrome_translate_client->GetTranslateManager()->CanManuallyTranslate(
           true);
   if (canTranslate) {
-    language::LanguageModel* language_model =
-        LanguageModelManagerFactory::GetForBrowserContext(browser_context_)
-            ->GetPrimaryModel();
-    std::unique_ptr<translate::TranslatePrefs> prefs(
-        ChromeTranslateClient::CreateTranslatePrefs(
-            GetPrefs(browser_context_)));
-    std::string locale = translate::TranslateManager::GetTargetLanguage(
-        prefs.get(), language_model);
-    std::u16string language =
-        l10n_util::GetDisplayNameForLocale(locale, locale, true);
     menu_model_.AddItem(
         IDC_CONTENT_CONTEXT_TRANSLATE,
-        l10n_util::GetStringFUTF16(IDS_CONTENT_CONTEXT_TRANSLATE, language));
+        l10n_util::GetStringFUTF16(IDS_CONTENT_CONTEXT_TRANSLATE,
+                                   GetTargetLanguageDisplayName()));
   }
 }
 
@@ -1835,6 +1836,13 @@
 #endif  // BUILDFLAG(ENABLE_PRINTING)
 }
 
+void RenderViewContextMenu::AppendPartialTranslateItem() {
+  menu_model_.AddItem(
+      IDC_CONTENT_CONTEXT_PARTIAL_TRANSLATE,
+      l10n_util::GetStringFUTF16(IDS_CONTENT_CONTEXT_PARTIAL_TRANSLATE,
+                                 GetTargetLanguageDisplayName()));
+}
+
 void RenderViewContextMenu::AppendMediaRouterItem() {
   if (media_router::MediaRouterEnabled(browser_context_)) {
     menu_model_.AddItemWithStringId(IDC_ROUTE_MEDIA,
@@ -2270,6 +2278,9 @@
     case IDC_CONTENT_CONTEXT_TRANSLATE:
       return IsTranslateEnabled();
 
+    case IDC_CONTENT_CONTEXT_PARTIAL_TRANSLATE:
+      return true;
+
     case IDC_CONTENT_CONTEXT_OPENLINKNEWTAB:
     case IDC_CONTENT_CONTEXT_OPENLINKNEWWINDOW:
     case IDC_CONTENT_CONTEXT_OPENLINKINPROFILE:
@@ -2741,6 +2752,10 @@
       ExecTranslate();
       break;
 
+    case IDC_CONTENT_CONTEXT_PARTIAL_TRANSLATE:
+      ExecPartialTranslate();
+      break;
+
     case IDC_CONTENT_CONTEXT_RELOADFRAME:
       source_web_contents_->ReloadFocusedFrame();
       break;
@@ -3647,6 +3662,14 @@
                            /*triggered_from_menu=*/true);
 }
 
+void RenderViewContextMenu::ExecPartialTranslate() {
+  // TODO(crbug/1314825): When the PartialTranslateManager is added update this
+  // call to use language information from the page.
+  GetBrowser()->window()->ShowPartialTranslateBubble(
+      PartialTranslateBubbleModel::ViewState::VIEW_STATE_BEFORE_TRANSLATE, "fr",
+      "en", params_.selection_text, translate::TranslateErrors::Type::NONE);
+}
+
 void RenderViewContextMenu::ExecLanguageSettings(int event_flags) {
   WindowOpenDisposition disposition = ui::DispositionFromEventFlags(
       event_flags, WindowOpenDisposition::NEW_FOREGROUND_TAB);
diff --git a/chrome/browser/renderer_context_menu/render_view_context_menu.h b/chrome/browser/renderer_context_menu/render_view_context_menu.h
index 10fbf02..5447107 100644
--- a/chrome/browser/renderer_context_menu/render_view_context_menu.h
+++ b/chrome/browser/renderer_context_menu/render_view_context_menu.h
@@ -190,9 +190,9 @@
   // Gets the extension (if any) associated with the WebContents that we're in.
   const extensions::Extension* GetExtension() const;
 
-  // Queries the translate service to obtain the user's transate target
-  // language.
-  std::string GetTargetLanguage() const;
+  // Queries the Translate service to obtain the user's Translate target
+  // language and returns the language name in its same locale.
+  std::u16string GetTargetLanguageDisplayName() const;
 
   void AppendDeveloperItems();
   void AppendDevtoolsForUnpackedExtensions();
@@ -215,6 +215,7 @@
   void AppendPdfOcrItem();
 #endif
   void AppendPrintItem();
+  void AppendPartialTranslateItem();
   void AppendMediaRouterItem();
   void AppendReadAnythingItem();
   void AppendRotationItems();
@@ -297,6 +298,7 @@
   void ExecPrint();
   void ExecRouteMedia();
   void ExecTranslate();
+  void ExecPartialTranslate();
   void ExecLanguageSettings(int event_flags);
   void ExecProtocolHandlerSettings(int event_flags);
   void ExecPictureInPicture();
diff --git a/chrome/browser/resources/chromeos/notification_tester/BUILD.gn b/chrome/browser/resources/chromeos/notification_tester/BUILD.gn
index 61ec361..a2e18c03 100644
--- a/chrome/browser/resources/chromeos/notification_tester/BUILD.gn
+++ b/chrome/browser/resources/chromeos/notification_tester/BUILD.gn
@@ -17,6 +17,7 @@
     "index.html",
     "form_constants.js",
     "types.js",
+    "images/chromeos_logo_main.png",
   ]
   input_files_base_dir = rebase_path(".", "//")
 
diff --git a/chrome/browser/resources/chromeos/notification_tester/form_constants.js b/chrome/browser/resources/chromeos/notification_tester/form_constants.js
index 381eb633..a2d26ecc 100644
--- a/chrome/browser/resources/chromeos/notification_tester/form_constants.js
+++ b/chrome/browser/resources/chromeos/notification_tester/form_constants.js
@@ -61,20 +61,71 @@
     {displayText: 'Unicode Emojis', value: '🌇😃🍈😆🍜🍻😋⛅⛳😚ඞ'},
     {displayText: 'Empty', value: ''},
   ],
-  BADGE_OPTIONS: [
-    {displayText: 'Cat Badge', value: 'cat'},
-    {displayText: 'No Badge', value: 'none'}
+  SMALL_IMAGE_OPTIONS: [
+    {displayText: 'Default Icon', value: 'kProductIcon'},
+    {displayText: 'Terminal Icon', value: 'kTerminalSshIcon'},
+    {displayText: 'Credit Card Icon', value: 'kCreditCardIcon'},
+    {displayText: 'Smartphone Icon', value: 'kSmartphoneIcon'},
+  ],
+  ICON_OPTIONS: [
+    {displayText: 'No Image', value: 'none'},
+    {displayText: 'Top Hat Cinammon Toast (PNG)', value: 'top_hat'},
+    {displayText: 'Metal Cinammon Toast (JPEG)', value: 'metal'},
+    {displayText: 'Google Icon (SVG)', value: 'google'},
   ],
   IMAGE_OPTIONS: [
     {displayText: 'No Image', value: 'none'},
-    {displayText: 'Cat Image (JPEG, 3072x1728)', value: 'cat'},
+    {displayText: 'CrOS Logo (1218x317, PNG)', value: 'chromeos_logo_main'},
   ],
-  ICON_OPTIONS: [
-    {displayText: 'No Icon', value: 'none'},
-    {displayText: 'Normal PNG Icon (256x256)', value: 'normal_png'},
-    {displayText: 'Static GIF Icon (256x256)', value: 'static_gif'},
-    {displayText: 'Animated GIF Icon (256x256)', value: 'animated_gif'},
-    {displayText: 'Tiny Icon (PNG, 32x32)', value: 'tiny_icon'},
-    {displayText: 'Huge Icon (PNG, 3333x5230)', value: 'huge_icon'},
+  SOURCE_OPTIONS: [
+    {displayText: 'testurl.xyz', value: 'url_ltr'},
+    {displayText: 'Sample Display Source', value: 'dis_ltr'},
+    {displayText: 'اختبار.النهاية', value: 'url_rtl'},
+    {displayText: 'مصدر عرض العينة', value: 'dis_rtl'},
+    {displayText: 'Empty', value: 'none'},
   ],
+  NOTIFICATION_TYPE_OPTIONS: [
+    {displayText: 'Simple', value: 'simple'},
+    {displayText: 'Base Format', value: 'base'},
+    {displayText: 'Image', value: 'image'},
+    {displayText: 'Multiple', value: 'mult'},
+    {displayText: 'Progress', value: 'progress'},
+  ],
+  PRIORITY_OPTIONS: [
+    {displayText: 'Default', value: 'default'},
+    {displayText: 'Minimum', value: 'min'},
+    {displayText: 'Low', value: 'low'},
+    {displayText: 'High', value: 'high'},
+    {displayText: 'Max', value: 'max'},
+    {displayText: 'System', value: 'system'},
+  ],
+  PROGRESS_STATUS_OPTIONS: [
+    {displayText: 'Short Sentence (LTR)', value: 'Progress Status'},
+    {displayText: 'Short Sentence (RTL)', value: 'כותרת הודעה'},
+    {
+      displayText: 'Long Sentence (LTR)',
+      value:
+          'Hamburgers: the cornerstone of any nutritious breakfast. Ch-cheeseburgers'
+    },
+    {
+      displayText: 'Long Sentence (RTL)',
+      value: 'המבורגרים: אבן הפינה של כל ארוחת בוקר מזינה. ציזבורגר'
+    },
+    {
+      displayText: 'Repetitive Characters (LTR)',
+      value: 'sshhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh'
+    },
+    {
+      displayText: 'Repetitive Characters (RTL)',
+      value: 'שששששששששששששששששששששששששששששששששששששששששששששששששששש'
+    },
+    {displayText: 'Unicode Emojis', value: '🌇😃🍈😆🍜🍻😋⛅⛳😚ඞ'},
+    {displayText: 'Empty', value: ''},
+  ],
+  NOTIFICATION_ID_OPTIONS: [
+    {displayText: 'Random', value: 'random'},
+    {displayText: 'Group A', value: 'group_a'},
+    {displayText: 'Group B', value: 'group_b'},
+    {displayText: 'Group C', value: 'group_c'},
+  ]
 };
\ No newline at end of file
diff --git a/chrome/browser/resources/chromeos/notification_tester/images/chromeos_logo_main.png b/chrome/browser/resources/chromeos/notification_tester/images/chromeos_logo_main.png
new file mode 100644
index 0000000..1fc30eab
--- /dev/null
+++ b/chrome/browser/resources/chromeos/notification_tester/images/chromeos_logo_main.png
Binary files differ
diff --git a/chrome/browser/resources/chromeos/notification_tester/notification_tester.html b/chrome/browser/resources/chromeos/notification_tester/notification_tester.html
index 45e0307..cb7d90c 100644
--- a/chrome/browser/resources/chromeos/notification_tester/notification_tester.html
+++ b/chrome/browser/resources/chromeos/notification_tester/notification_tester.html
@@ -22,17 +22,16 @@
   }
 
   .page-container {
-    align-items: flex-start;
     display: flex;
     flex-direction: row;
-    justify-content: space-between;
-    line-height: 1.5;
+    height: 100%;
+    justify-content: center;
   }
 
   .config-form {
-    flex: 1;
     margin: 0 auto;
-    max-width: 24rem;
+    min-width: 28rem;
+    padding: 0 1rem;
     width: 100%;
   }
 
@@ -40,13 +39,13 @@
     align-items: center;
     border-bottom: none;
     display: flex;
-    font-size: 1rem;
+    font-size: 0.8rem;
     padding: 0 0 8px 0;
   }
 
   .form-item>label {
     flex: 1;
-    font-size: 1;
+    font-size: 1rem;
   }
 
   .form-item>select {
@@ -57,44 +56,158 @@
     padding: .1rem;
   }
 
-  #generateNotifBtn {
+  .form-item>cr-radio-group,
+  .form-item>cr-checkbox {
+    display: flex;
+    flex: 2;
+    flex-direction: row;
+    justify-content: center;
+  }
+
+  cr-radio-button {
+    padding: 0 auto;
+    --cr-radio-button-label-spacing: 10px;
+  }
+
+  cr-radio-group#progress-percent > cr-radio-button,
+  cr-radio-group#num-buttons > cr-radio-button {
+    --cr-radio-group-item-padding: 6px;
+  }
+
+  .config-form > button {
     background: rgb(0, 134, 179);
     border-radius: 0.25rem;
     color: #f8f8f8;
     font-size: 1rem;
     font-weight: bold;
+    margin-inline-end: 0.25em;
     margin-top: 1em;
     padding: 0.25em 0.5em;
     width: max-content;
   }
+
 </style>
 <app-header>
   <app-toolbar>Notification Tester</app-toolbar>
 </app-header>
 <div class="page-container">
-  <div class="config-form">
-    <h2> Visual Settings </h2>
+  <div class="column">
+    <div class="config-form">
+      <h2> General Settings </h2>
 
-    <select-custom select-value="{{notifMetadata.title}}" display-label="Title"
+      <div class="form-item">
+        <label for="notifier-type"> Notifier Type </label>
+        <cr-radio-group id="notifier-type" selected="System">
+          <cr-radio-button name="System" label="System"></cr-radio-button>
+          <cr-radio-button name="Web" label="Web"></cr-radio-button>
+        </cr-radio-group>
+      </div>
+
+      <select-custom select-value="" display-label="Notification Type"
+      select-elements="[[notificationTypeSelectList]]"
+      selectid="notification-type" no-custom-input="true"></select-custom>
+
+      <select-custom select-value="" display-label="Notification ID"
+      select-elements="[[notificationIDSelectList]]"
+      selectid="notification-id"></select-custom>
+
+      <select-custom select-value="{{notifMetadata.title}}" display-label="Title"
       select-elements="[[titleSelectList]]" selectid="title"></select-custom>
 
-    <select-custom select-value="{{notifMetadata.message}}"
+      <select-custom select-value="{{notifMetadata.message}}"
       display-label="Message" select-elements="[[messageSelectList]]"
       selectid="message"></select-custom>
 
-    <select-custom select-value="{{notifMetadata.badge}}" display-label="Badge"
-      select-elements="[[badgeSelectList]]" selectid="badge""
-      no-custom-input=" true"></select-custom>
-
-    <select-custom select-value="{{notifMetadata.richDataSmallImage}}"
-      display-label="Icon" select-elements="[[iconSelectList]]" selectid="icon"
+      <select-custom select-value="{{notifMetadata.icon}}" display-label="Icon"
+      select-elements="[[iconSelectList]]" selectid="icon"
       no-custom-input="true"></select-custom>
+    
+      <select-custom select-value="" display-label="Source"
+      select-elements="[[sourceSelectList]]" selectid="source"></select-custom>
+    </div>
+    <div class="config-form">
+      <h2> Rich Notification Data (optional fields) </h2>
 
-    <select-custom select-value="{{notifMetadata.richDataImage}}"
+      <select-custom select-value="{{notifMetadata.richDataImage}}"
       display-label="Image" select-elements="[[imageSelectList]]"
       selectid="image" no-custom-input="true"></select-custom>
 
-    <button type="button" id="generateNotifBtn" on-click="onClickGenerate">
-      Generate </button>
+      <select-custom select-value="" display-label="Small Image" select-elements="[[smallImageSelectList]]"
+      selectid="small-image" no-custom-input="true"></select-custom>
+
+      <select-custom select-value="" display-label="Priority"
+      select-elements="[[prioritySelectList]]" selectid="priority" no-custom-input="true"></select-custom>
+      
+      <div class="form-item">
+        <label for="never-timeout">Never Timeout</label>
+        <cr-checkbox id="never-timeout"></cr-checkbox>
+      </div>
+
+      <div class="form-item">
+        <label for="pinned">Pinned</label>
+        <cr-checkbox id="pinned"></cr-checkbox>
+      </div>
+
+      <div class="form-item">
+        <label for="renotify">Renotify</label>
+        <cr-checkbox id="renotify"></cr-checkbox>
+      </div>
+
+      <h3> Type Specific Fields </h3>
+      
+      <div class="form-item">
+        <label for="num-notif-items"> # Notif Items </label>
+        <cr-radio-group id="num-notif-items" selected="2">
+          <cr-radio-button name="0" label="0"></cr-radio-button>
+          <cr-radio-button name="1" label="1"></cr-radio-button>
+          <cr-radio-button name="2" label="2"></cr-radio-button>
+          <cr-radio-button name="3" label="3"></cr-radio-button>
+          <cr-radio-button name="4" label="4"></cr-radio-button>
+        </cr-radio-group>
+      </div>
+
+      <div class="form-item">
+        <label for="progress-percent"> Progress % </label>
+        <cr-radio-group id="progress-percent" selected="none">
+          <cr-radio-button name="none" label="N/A"></cr-radio-button>
+          <cr-radio-button name="0" label="0%"></cr-radio-button>
+          <cr-radio-button name="50" label="50%"></cr-radio-button>
+          <cr-radio-button name="100" label="100%"></cr-radio-button>
+        </cr-radio-group>
+      </div>
+
+      <select-custom select-value="" display-label="Progress Status"
+      select-elements="[[progressStatusSelectList]]" selectid="prog-status"></select-custom>
+    </div>
+  </div>
+  <div class="column">
+    <div class="config-form">
+      <h2> Buttons </h2>
+      <div class="form-item">
+        <label for="show-settings">Show Settings</label>
+        <cr-checkbox id="show-settings"></cr-checkbox>
+      </div>
+
+      <div class="form-item">
+        <label for="show-snooze">Show Snooze</label>
+        <cr-checkbox id="show-snooze"></cr-checkbox>
+      </div>
+
+      <div class="form-item">
+        <label for="num-buttons"> # Buttons </label>
+        <cr-radio-group id="num-buttons" selected="None">
+          <cr-radio-button name="none" label="None"></cr-radio-button>
+          <cr-radio-button name="1" label="1"></cr-radio-button>
+          <cr-radio-button name="2" label="2"></cr-radio-button>
+          <cr-radio-button name="3" label="3"></cr-radio-button>
+        </cr-radio-group>
+      </div>
+    </div>
+    <div class="config-form">
+      <button type="button" id="generateNotifBtn" on-click="onClickGenerate">
+        Generate </button>
+      <button type="button" id="" on-click="">
+          Reset </button>
+    </div>
   </div>
 </div>
\ No newline at end of file
diff --git a/chrome/browser/resources/chromeos/notification_tester/notification_tester.js b/chrome/browser/resources/chromeos/notification_tester/notification_tester.js
index 1ddacea7..d611513 100644
--- a/chrome/browser/resources/chromeos/notification_tester/notification_tester.js
+++ b/chrome/browser/resources/chromeos/notification_tester/notification_tester.js
@@ -3,6 +3,10 @@
 // found in the LICENSE file.
 
 import './select_custom.js';
+import 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import 'chrome://resources/cr_elements/cr_radio_group/cr_radio_group.m.js';
+import 'chrome://resources/cr_elements/cr_radio_button/cr_radio_button.m.js';
+import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.m.js';
 
 import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
@@ -47,13 +51,6 @@
       /*
        * @private
        */
-      badgeSelectList: {
-        type: Array,
-        value: FormSelectOptions.BADGE_OPTIONS,
-      },
-      /*
-       * @private
-       */
       imageSelectList: {
         type: Array,
         value: FormSelectOptions.IMAGE_OPTIONS,
@@ -65,6 +62,48 @@
         type: Array,
         value: FormSelectOptions.ICON_OPTIONS,
       },
+      /*
+       * @private
+       */
+      smallImageSelectList: {
+        type: Array,
+        value: FormSelectOptions.SMALL_IMAGE_OPTIONS,
+      },
+      /*
+       * @private
+       */
+      sourceSelectList: {
+        type: Array,
+        value: FormSelectOptions.SOURCE_OPTIONS,
+      },
+      /*
+       * @private
+       */
+      notificationTypeSelectList: {
+        type: Array,
+        value: FormSelectOptions.NOTIFICATION_TYPE_OPTIONS,
+      },
+      /*
+       * @private
+       */
+      prioritySelectList: {
+        type: Array,
+        value: FormSelectOptions.PRIORITY_OPTIONS,
+      },
+      /*
+       * @private
+       */
+      progressStatusSelectList: {
+        type: Array,
+        value: FormSelectOptions.PROGRESS_STATUS_OPTIONS,
+      },
+      /*
+       * @private
+       */
+      notificationIDSelectList: {
+        type: Array,
+        value: FormSelectOptions.NOTIFICATION_ID_OPTIONS,
+      },
     };
   }
 
diff --git a/chrome/browser/resources/chromeos/notification_tester/types.js b/chrome/browser/resources/chromeos/notification_tester/types.js
index cf8f7ef7..7cd7335 100644
--- a/chrome/browser/resources/chromeos/notification_tester/types.js
+++ b/chrome/browser/resources/chromeos/notification_tester/types.js
@@ -11,7 +11,6 @@
  *   message: string,
  *   icon: string,
  *   richDataImage: string,
- *   richDataSmallImage: string,
  * }}
  */
 export let Notification;
\ No newline at end of file
diff --git a/chrome/browser/resources/settings/chromeos/BUILD.gn b/chrome/browser/resources/settings/chromeos/BUILD.gn
index cb69051..d00bb3a 100644
--- a/chrome/browser/resources/settings/chromeos/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/BUILD.gn
@@ -16,11 +16,12 @@
 
 # target_gen_dir is "gen/chrome/browser/resources/settings/chromeos"
 
-preprocess_folder = "preprocessed"
+preprocessed_folder = "preprocessed"
 web_components_manifest = "web_components_manifest.json"
 non_web_component_files_manifest = "non_web_component_files_manifest.json"
 browser_settings_tsc_manifest = "browser_settings_tsc_manifest.json"
 mojo_manifest = "mojo_manifest.json"
+mojo_webui_manifest = "mojo_webui_manifest.json"
 external_mojo_manifest = "external_mojo_manifest.json"
 
 if (optimize_webui) {
@@ -28,7 +29,7 @@
 
   optimize_webui("build_polymer3") {
     host = "os-settings"
-    input = rebase_path("$target_gen_dir/$preprocess_folder", root_build_dir)
+    input = rebase_path("$target_gen_dir/$preprocessed_folder", root_build_dir)
     js_module_in_files = [
       "chromeos/lazy_load.js",
       "chromeos/os_settings.js",
@@ -43,6 +44,7 @@
     deps = [
       ":preprocess_browser_settings_tsc",
       ":preprocess_mojo",
+      ":preprocess_mojo_webui",
       ":preprocess_non_web_component_files",
       ":preprocess_web_components",
       "//chrome/browser/resources/nearby_share/shared:preprocess_v3",
@@ -71,6 +73,7 @@
       "chrome://resources/mojo/chromeos/services/network_config/public/mojom/cros_network_config.mojom-webui.js",
       "chrome://resources/mojo/chromeos/services/network_config/public/mojom/network_types.mojom-lite.js",
       "chrome://resources/mojo/chromeos/services/network_config/public/mojom/network_types.mojom-webui.js",
+      "chrome://resources/mojo/mojo/public/js/bindings.js",
       "chrome://resources/mojo/mojo/public/js/mojo_bindings_lite.js",
       "chrome://resources/mojo/mojo/public/mojom/base/big_buffer.mojom-lite.js",
       "chrome://resources/mojo/mojo/public/mojom/base/string16.mojom-lite.js",
@@ -118,7 +121,7 @@
     "//chrome/browser/ui/webui/settings/chromeos/constants:mojom_js",
   ]
   in_folder = get_path_info("../../../ui/webui/settings/chromeos/", "gen_dir")
-  out_folder = "$target_gen_dir/$preprocess_folder"
+  out_folder = "$target_gen_dir/$preprocessed_folder"
   out_manifest = "$target_gen_dir/$mojo_manifest"
   in_files = [
     "constants/routes.mojom-lite.js",
@@ -130,6 +133,20 @@
   ]
 }
 
+# ChromeOS Settings specific WebUI mojom files, bundled in optimized builds.
+preprocess_if_expr("preprocess_mojo_webui") {
+  deps =
+      [ "//chrome/browser/ui/webui/settings/chromeos/constants:mojom_webui_js" ]
+  in_folder =
+      "$root_gen_dir/mojom-webui/chrome/browser/ui/webui/settings/chromeos/"
+  out_folder = "$target_gen_dir/$preprocessed_folder"
+  out_manifest = "$target_gen_dir/$mojo_webui_manifest"
+  in_files = [
+    "constants/routes.mojom-webui.js",
+    "constants/setting.mojom-webui.js",
+  ]
+}
+
 # Mojo files generated by non-OS-settings targets, not bundled.
 preprocess_if_expr("preprocess_external_mojo") {
   deps = [
@@ -144,7 +161,7 @@
   # It does not matter which preprocess folder these files are pasted into, as
   # they are not used for bundling; the purpose of this build rule is to
   # include them in the generated grd file.
-  out_folder = "$target_gen_dir/$preprocess_folder"
+  out_folder = "$target_gen_dir/$preprocessed_folder"
   out_manifest = "$target_gen_dir/$external_mojo_manifest"
   in_files = [
     "chrome/browser/ui/webui/settings/ash/os_apps_page/mojom/app_notification_handler.mojom-lite.js",
@@ -257,6 +274,7 @@
     deps += [
       ":preprocess_browser_settings_tsc",
       ":preprocess_mojo",
+      ":preprocess_mojo_webui",
       ":preprocess_non_web_component_files",
       ":preprocess_web_components",
       "//chrome/browser/resources/nearby_share/shared:build_v3_grdp",
@@ -268,6 +286,7 @@
       "$target_gen_dir/$non_web_component_files_manifest",
       "$target_gen_dir/$browser_settings_tsc_manifest",
       "$target_gen_dir/$mojo_manifest",
+      "$target_gen_dir/$mojo_webui_manifest",
     ]
     resource_path_rewrites += [ "chromeos/os_settings.html|os_settings.html" ]
   }
@@ -276,7 +295,7 @@
 preprocess_if_expr("preprocess_non_web_component_files") {
   defines = chrome_grit_defines
   in_folder = "../"
-  out_folder = "$target_gen_dir/$preprocess_folder"
+  out_folder = "$target_gen_dir/$preprocessed_folder"
   out_manifest = "$target_gen_dir/$non_web_component_files_manifest"
   in_files = [
     "chromeos/deep_linking_behavior.js",
@@ -374,7 +393,7 @@
   defines = chrome_grit_defines
   deps = [ "..:build_ts" ]
   in_folder = "$root_gen_dir/chrome/browser/resources/settings/tsc"
-  out_folder = "$target_gen_dir/$preprocess_folder"
+  out_folder = "$target_gen_dir/$preprocessed_folder"
   out_manifest = "$target_gen_dir/$browser_settings_tsc_manifest"
 
   # Files that have a corresponding HTML wrapper file.
@@ -441,7 +460,7 @@
   defines = chrome_grit_defines
   deps = [ ":generate_web_components" ]
   in_folder = get_path_info("../", "gen_dir")
-  out_folder = "$target_gen_dir/$preprocess_folder"
+  out_folder = "$target_gen_dir/$preprocessed_folder"
   out_manifest = "$target_gen_dir/$web_components_manifest"
   in_files = [
     "chromeos/ambient_mode_page/album_item.js",
@@ -752,7 +771,8 @@
 
 js_type_check("closure_compile_local_module") {
   is_polymer3 = true
-  closure_flags = os_settings_closure_flags + mojom_js_args
+  closure_flags = os_settings_closure_flags
+
   deps = [
     ":combined_search_handler",
     ":deep_linking_behavior",
@@ -816,7 +836,7 @@
   deps = [
     ":os_settings_routes",
     "..:router",
-    "//chrome/browser/ui/webui/settings/chromeos/constants:mojom_js_library_for_compile",
+    "//chrome/browser/ui/webui/settings/chromeos/constants:mojom_webui_js",
     "//ui/webui/resources/js:cr.m",
     "//ui/webui/resources/js:load_time_data.m",
   ]
diff --git a/chrome/browser/resources/settings/chromeos/internet_page/BUILD.gn b/chrome/browser/resources/settings/chromeos/internet_page/BUILD.gn
index b5cffe6d..145c07b 100644
--- a/chrome/browser/resources/settings/chromeos/internet_page/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/internet_page/BUILD.gn
@@ -8,7 +8,7 @@
 import("../os_settings.gni")
 
 js_type_check("closure_compile_module") {
-  closure_flags = os_settings_closure_flags + mojom_js_args
+  closure_flags = os_settings_closure_flags
   is_polymer3 = true
   deps = [
     ":cellular_networks_list",
diff --git a/chrome/browser/resources/settings/chromeos/os_bluetooth_page/BUILD.gn b/chrome/browser/resources/settings/chromeos/os_bluetooth_page/BUILD.gn
index 3c2eaa0..e1d9856b 100644
--- a/chrome/browser/resources/settings/chromeos/os_bluetooth_page/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/os_bluetooth_page/BUILD.gn
@@ -7,7 +7,7 @@
 import("../os_settings.gni")
 
 js_type_check("closure_compile_module") {
-  closure_flags = os_settings_closure_flags + mojom_js_args
+  closure_flags = os_settings_closure_flags
   is_polymer3 = true
   deps = [
     ":os_bluetooth_change_device_name_dialog",
diff --git a/chrome/browser/resources/settings/chromeos/os_privacy_page/privacy_hub_page.html b/chrome/browser/resources/settings/chromeos/os_privacy_page/privacy_hub_page.html
index 631c19e8..48e8f6ed6 100644
--- a/chrome/browser/resources/settings/chromeos/os_privacy_page/privacy_hub_page.html
+++ b/chrome/browser/resources/settings/chromeos/os_privacy_page/privacy_hub_page.html
@@ -5,3 +5,9 @@
     sub-label="[[cameraToggleActive_]]"
     deep-link-focus-id$="[[Setting.kCameraOnOff]]">
 </settings-toggle-button>
+<settings-toggle-button
+    pref="{{prefs.ash.user.microphone_allowed}}"
+    id="microphoneToggle"
+    label="$i18n{microphoneToggleTitle}"
+    deep-link-focus-id$="[[Setting.kMicrophoneOnOff]]">
+</settings-toggle-button>
diff --git a/chrome/browser/resources/settings/chromeos/os_privacy_page/privacy_hub_page.js b/chrome/browser/resources/settings/chromeos/os_privacy_page/privacy_hub_page.js
index 16c572e..935d3e4 100644
--- a/chrome/browser/resources/settings/chromeos/os_privacy_page/privacy_hub_page.js
+++ b/chrome/browser/resources/settings/chromeos/os_privacy_page/privacy_hub_page.js
@@ -84,6 +84,7 @@
         type: Object,
         value: () => new Set([
           chromeos.settings.mojom.Setting.kCameraOnOff,
+          chromeos.settings.mojom.Setting.kMicrophoneOnOff,
         ]),
       },
 
diff --git a/chrome/browser/resources/settings/chromeos/os_route.js b/chrome/browser/resources/settings/chromeos/os_route.js
index 92d2f02..5c9e66a 100644
--- a/chrome/browser/resources/settings/chromeos/os_route.js
+++ b/chrome/browser/resources/settings/chromeos/os_route.js
@@ -3,18 +3,20 @@
 // found in the LICENSE file.
 
 import 'chrome://resources/mojo/mojo/public/js/mojo_bindings_lite.js';
-import '../constants/routes.mojom-lite.js';
 
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
 
+import * as mojom from '../constants/routes.mojom-webui.js';
 import {Route, Router} from '../router.js';
 
 import {OsSettingsRoutes} from './os_settings_routes.js';
 
+const {Section, Subpage} = mojom;
+
 /**
  * @param {!Route} parent
  * @param {string} path
- * @param {!chromeos.settings.mojom.Section} section
+ * @param {!Section} section
  * @return {!Route}
  */
 function createSection(parent, path, section) {
@@ -25,7 +27,7 @@
 /**
  * @param {!Route} parent
  * @param {string} path
- * @param {!chromeos.settings.mojom.Subpage} subpage
+ * @param {!Subpage} subpage
  * @return {!Route}
  */
 function createSubpage(parent, path, subpage) {
@@ -38,10 +40,6 @@
  * @return {!OsSettingsRoutes}
  */
 function createOSSettingsRoutes() {
-  const mojom = chromeos.settings.mojom;
-  const Section = mojom.Section;
-  const Subpage = mojom.Subpage;
-
   const r = /** @type {!OsSettingsRoutes} */ ({});
 
   // Special routes: BASIC is the main page which loads if no path is
diff --git a/chrome/browser/resources/settings/chromeos/os_settings.gni b/chrome/browser/resources/settings/chromeos/os_settings.gni
index 546231d..1bad9dda 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings.gni
+++ b/chrome/browser/resources/settings/chromeos/os_settings.gni
@@ -3,199 +3,28 @@
 # found in the LICENSE file.
 
 import("//third_party/closure_compiler/compile_js.gni")
-import("//ui/webui/resources/cr_components/chromeos/os_cr_components.gni")
-import("//ui/webui/resources/cr_elements/chromeos/os_cr_elements.gni")
 import("../settings.gni")
 
-# Common namespace rewrites for all polymer_modulizer() or js_modulizer()
-# targets in Settings.
-settings_namespace_rewrites = [
-  "Polymer.DomIf|DomIf",
-  "Polymer.IronResizableBehavior|IronResizableBehavior",
-  "settings.AboutPageBrowserProxy|AboutPageBrowserProxy",
-  "settings.Account|Account",
-  "settings.AccountManagerBrowserProxy|AccountManagerBrowserProxy",
-  "settings.ExtensionControlBrowserProxy|ExtensionControlBrowserProxy",
-  "settings.FontsBrowserProxy|FontsBrowserProxy",
-  "settings.LanguagesBrowserProxy|LanguagesBrowserProxy",
-  "settings.LifetimeBrowserProxy|LifetimeBrowserProxy",
-  "settings.MetricsReporting|MetricsReporting",
-  "settings.MinimumRoutes|MinimumRoutes",
-  "settings.OpenWindowProxy|OpenWindowProxy",
-  "settings.PageStatus|PageStatus",
-  "Settings.PrefUtil.prefToString|prefToString",
-  "Settings.PrefUtil.stringToPrefValue|stringToPrefValue",
-  "settings.PrivacyPageBrowserProxy|PrivacyPageBrowserProxy",
-  "settings.ProfileInfo|ProfileInfo",
-  "settings.ResolverOption|ResolverOption",
-  "settings.Route|Route",
-  "settings.SearchEnginesBrowserProxy|SearchEnginesBrowserProxy",
-  "settings.SearchRequest|SearchRequest",
-  "settings.SecureDnsMode|SecureDnsMode",
-  "settings.SecureDnsSetting|SecureDnsSetting",
-  "settings.SecureDnsUiManagementMode|SecureDnsUiManagementMode",
-  "settings.StatusAction|StatusAction",
-  "settings.StoredAccount|StoredAccount",
-  "settings.SyncBrowserProxy|SyncBrowserProxy",
-  "settings.SyncPrefs|SyncPrefs",
-  "settings.SyncStatus|SyncStatus",
-]
-
-os_settings_namespace_rewrites = settings_namespace_rewrites +
-                                 cr_components_chromeos_namespace_rewrites +
-                                 cr_elements_chromeos_namespace_rewrites + [
-                                   "action_link.m.js|action_link.js",
-                                   "nearby_share.Account|Account",
-                                   "nearby_share.getReceiveManager|getReceiveManager",
-                                   "nearby_share.NearbyAccountManagerBrowserProxy|NearbyAccountManagerBrowserProxy",
-                                   "nearby_share.observeReceiveManager|observeReceiveManager",
-                                   "parental_controls.ParentalControlsBrowserProxy|ParentalControlsBrowserProxy",
-                                   "// #polymer3 |",
-                                   "settings.AboutPageBrowserProxyImpl|AboutPageBrowserProxyImpl",
-                                   "settings.AmbientModeBrowserProxy|AmbientModeBrowserProxy",
-                                   "settings.AndroidSmsInfo|AndroidSmsInfo",
-                                   "settings.BatteryStatus|BatteryStatus",
-                                   "settings.browserChannelToI18nId|browserChannelToI18nId",
-                                   "settings.CellularSetupSettingsDelegate|CellularSetupSettingsDelegate",
-                                   "settings.ChangePictureBrowserProxy|ChangePictureBrowserProxy",
-                                   "settings.CrostiniBrowserProxy|CrostiniBrowserProxy",
-                                   "settings.CupsPrintersBrowserProxy|CupsPrintersBrowserProxy",
-                                   "settings.DefaultImage|DefaultImage",
-                                   "settings.DevicePageBrowserProxy|DevicePageBrowserProxy",
-                                   "settings.ExternalStorage|ExternalStorage",
-                                   "settings.FingerprintAttempt|FingerprintAttempt",
-                                   "settings.FingerprintBrowserProxy|FingerprintBrowserProxy",
-                                   "settings.FingerprintInfo|FingerprintInfo",
-                                   "settings.FingerprintResultType|FingerprintResultType",
-                                   "settings.FingerprintScan|FingerprintScan",
-                                   "settings.FingerprintSetupStep|FingerprintSetupStep",
-                                   "settings.FingerprintLocation|FingerprintLocation",
-                                   "settings.getSettingsSearchHandler|getSettingsSearchHandler",
-                                   "settings.GlobalScrollTargetBehavior|GlobalScrollTargetBehavior",
-                                   "settings.GoogleAssistantBrowserProxy|GoogleAssistantBrowserProxy",
-                                   "settings.GoogleAssistantBrowserProxyImpl|GoogleAssistantBrowserProxyImpl",
-                                   "settings.GuestOsBrowserProxy|GuestOsBrowserProxy",
-                                   "settings.IdleBehavior|IdleBehavior",
-                                   "settings.input_method_util.generateOptions|generateOptions",
-                                   "settings.input_method_util.getFirstPartyInputMethodEngineId|getFirstPartyInputMethodEngineId",
-                                   "settings.input_method_util.getOptionLabelName|getOptionLabelName",
-                                   "settings.input_method_util.getOptionMenuItems|getOptionMenuItems",
-                                   "settings.input_method_util.getOptionUiType|getOptionUiType",
-                                   "settings.input_method_util.getOptionUrl|getOptionUrl",
-                                   "settings.input_method_util.hasOptionsPageInSettings|hasOptionsPageInSettings",
-                                   "settings.input_method_util.isNumberValue|isNumberValue",
-                                   "settings.input_method_util.isOptionLabelTranslated|isOptionLabelTranslated",
-                                   "settings.input_method_util.getUntranslatedOptionLabelName|getUntranslatedOptionLabelName",
-                                   "settings.input_method_util.OPTION_DEFAULT|OPTION_DEFAULT",
-                                   "settings.input_method_util.OptionType|OptionType",
-                                   "settings.input_method_util.UiType|UiType",
-                                   "settings.InternetPageBrowserProxy|InternetPageBrowserProxy",
-                                   "settings.isTargetChannelMoreStable|isTargetChannelMoreStable",
-                                   "settings.KerberosAccount|KerberosAccount",
-                                   "settings.KerberosConfigErrorCode|KerberosConfigErrorCode",
-                                   "settings.KerberosErrorType|KerberosErrorType",
-                                   "settings.kMenuCloseDelay|kMenuCloseDelay",
-                                   "settings.LanguagesMetricsProxy|LanguagesMetricsProxy",
-                                   "settings.LanguagesPageInteraction|LanguagesPageInteraction",
-                                   "settings.LidClosedBehavior|LidClosedBehavior",
-                                   "settings.LockScreenProgress|LockScreenProgress",
-                                   "settings.MainPageBehavior|MainPageBehavior",
-                                   "settings.MultiDeviceBrowserProxy|MultiDeviceBrowserProxy",
-                                   "settings.MultiDeviceFeature|MultiDeviceFeature",
-                                   "settings.MultiDeviceFeatureState|MultiDeviceFeatureState",
-                                   "settings.MultiDevicePageContentData|MultiDevicePageContentData",
-                                   "settings.MultiDeviceSettingsMode|MultiDeviceSettingsMode",
-                                   "settings.NoteAppLockScreenSupport|NoteAppLockScreenSupport",
-                                   "settings.NoteAppInfo|NoteAppInfo",
-                                   "settings.OsResetBrowserProxy|OsResetBrowserProxy",
-                                   "settings.OsSyncBrowserProxy|OsSyncBrowserProxy",
-                                   "settings.OsSyncPrefs|OsSyncPrefs",
-                                   "settings.pageVisibility|pageVisibility",
-                                   "settings.PeripheralDataAccessBrowserProxy|PeripheralDataAccessBrowserProxy",
-                                   "settings.DataAccessPolicyState|DataAccessPolicyState",
-                                   "settings.MetricsConsentBrowserProxy|MetricsConsentBrowserProxy",
-                                   "settings.MetricsConsentState|MetricsConsentState",
-                                   "settings.PersonalizationHubBrowserProxy|PersonalizationHubBrowserProxy",
-                                   "settings.PhoneHubFeatureAccessStatus|PhoneHubFeatureAccessStatus",
-                                   "settings.PhoneHubFeatureAccessProhibitedReason|PhoneHubFeatureAccessProhibitedReason",
-                                   "settings.PhoneHubPermissionsSetupMode|PhoneHubPermissionsSetupMode",
-                                   "settings.PowerSource|PowerSource",
-                                   "settings.PowerManagementSettings|PowerManagementSettings",
-                                   "settings.printing.alphabeticalSort|alphabeticalSort",
-                                   "settings.printing.CupsPrintersEntryManager|CupsPrintersEntryManager",
-                                   "settings.printing.findDifference|findDifference",
-                                   "settings.printing.getBaseName|getBaseName",
-                                   "settings.printing.getErrorText|getErrorText",
-                                   "settings.printing.getPrintServerErrorText|getPrintServerErrorText",
-                                   "settings.printing.isNameAndAddressValid|isNameAndAddressValid",
-                                   "settings.printing.isNetworkProtocol|isNetworkProtocol",
-                                   "settings.printing.isPPDInfoValid|isPPDInfoValid",
-                                   "settings.printing.matchesSearchTerm|matchesSearchTerm",
-                                   "settings.printing.sortPrinters|sortPrinters",
-                                   "settings.setUserActionRecorderForTesting|setUserActionRecorderForTesting",
-                                   "settings.recordLockScreenProgress|recordLockScreenProgress",
-                                   "settings.recordSettingChange|recordSettingChange",
-                                   "settings.recordSearch|recordSearch",
-                                   "settings.Router|Router",
-                                   "settings.routes|routes",
-                                   "settings.SecureDnsMode|SecureDnsMode",
-                                   "settings.SecureDnsSetting|SecureDnsSetting",
-                                   "settings.SecureDnsUiManagementMode|SecureDnsUiManagementMode",
-                                   "settings.setSettingsSearchHandlerForTesting|setSettingsSearchHandlerForTesting",
-                                   "settings.SmartLockSignInEnabledState|SmartLockSignInEnabledState",
-                                   "settings.TimeZoneAutoDetectMethod|TimeZoneAutoDetectMethod",
-                                   "settings.TimeZoneBrowserProxy|TimeZoneBrowserProxy",
-                                   "settings.ValidateKerberosConfigResult|ValidateKerberosConfigResult",
-                                   "settings.WallpaperBrowserProxy|WallpaperBrowserProxy",
-                                   "app_management.FakePageHandler|FakePageHandler",
-                                   "smb_shares.SmbBrowserProxyImpl|SmbBrowserProxyImpl",
-                                   "app_management.BrowserProxy|BrowserProxy",
-                                   "app_management.util.createPermission|createPermission",
-                                   "app_management.AppManagementStore|AppManagementStore",
-                                   "app_management.util.createEmptyState|createEmptyState",
-                                   "app_management.reduceAction|reduceAction",
-                                   "app_management.util.createInitialState|createInitialState",
-                                   "app_management.actions.addApp|addApp",
-                                   "app_management.actions.changeApp|changeApp",
-                                   "app_management.actions.removeApp|removeApp",
-                                   "app_management.actions.addApp|addApp",
-                                   "app_management.actions.changeApp|changeApp",
-                                   "app_management.actions.removeApp|removeApp",
-                                   "app_management.actions.updateSelectedAppId|updateSelectedAppId",
-                                   "app_management.AppManagementStoreClient|AppManagementStoreClient",
-                                   "app_management.AppState|AppState",
-                                   "app_management.util.openMainPage|openMainPage",
-                                   "app_management.util.getSelectedApp|getSelectedApp",
-                                   "Polymer.Templatizer|Templatizer",
-                                   "app_management.util.recordAppManagementUserAction|recordAppManagementUserAction",
-                                   "app_management.util.openAppDetailPage|openAppDetailPage",
-                                   "app_management.util.getAppIcon|getAppIcon",
-                                   "app_management.util.getPermission|getPermission",
-                                   "app_management.util.permissionTypeHandle|permissionTypeHandle",
-                                   "app_management.util.toggleOptionalBool|toggleOptionalBool",
-                                   "app_management.util.convertOptionalBoolToBool|convertOptionalBoolToBool",
-                                   "app_management.util.alphabeticalSort|alphabeticalSort",
-                                   "settings.PluginVmBrowserProxy|PluginVmBrowserProxy",
-                                   "settings.AndroidAppsBrowserProxyImpl|AndroidAppsBrowserProxyImpl",
-                                   "settings.AndroidAppsBrowserProxy|AndroidAppsBrowserProxy",
-                                   "settings.StorageSpaceState|StorageSpaceState",
-                                   "settings.StorageSizeStat|StorageSizeStat",
-                                   "settings.ModifierKey|ModifierKey",
-                                   "settings.getDisplayApi|getDisplayApi",
-                                   "settings.defaultResourceLoaded|defaultResourceLoaded",
-                                   "settings.osPageVisibility|osPageVisibility",
-                                   "settings.setGlobalScrollTarget|setGlobalScrollTarget",
-                                   "settings.recordPageFocus|recordPageFocus",
-                                   "settings.recordPageBlur|recordPageBlur",
-                                   "settings.recordClick|recordClick",
-                                   "settings.recordNavigation|recordNavigation",
-                                 ]
-
 # TODO(crbug.com/1121865): browser_resolver_prefix_replacements allows path
 # from ../../shared/* to resolve to ../../../nearby_share/shared/* for closure
 # purposes.
-os_settings_closure_flags = settings_closure_flags + [
-                              "js_module_root=../../chrome/browser/resources/nearby_share",
-                              "js_module_root=./gen/chrome/browser/resources/nearby_share",
-                              "browser_resolver_prefix_replacements=\"../../shared/=../../../nearby_share/shared/\"",
-                            ]
+nearby_share_closure_flags = [
+  "js_module_root=../../chrome/browser/resources/nearby_share",
+  "js_module_root=./gen/chrome/browser/resources/nearby_share",
+  "browser_resolver_prefix_replacements=\"../../shared/=../../../nearby_share/shared/\"",
+]
+
+# TODO(crbug.com/1179821) This allows ChromeOS Settings mojom WebUI paths like
+# "../constants/setting.mojom-webui.js" or "../constants/routes.mojom-webui.js"
+# to resolve properly during closure compilation
+mojom_webui_root_path =
+    rebase_path(
+        "$root_gen_dir/mojom-webui/chrome/browser/ui/webui/settings/chromeos",
+        root_build_dir)
+mojom_webui_closure_flags = [ "js_module_root=" + mojom_webui_root_path ]
+
+# TODO(crbug.com/1315757) Remove once ChromeOS Settings is fully migrated to
+# TypeScript and no longer using Closure compilation
+os_settings_closure_flags =
+    settings_closure_flags + nearby_share_closure_flags + mojom_js_args +
+    mojom_webui_closure_flags
diff --git a/chrome/browser/resources/settings/chromeos/os_settings_main/BUILD.gn b/chrome/browser/resources/settings/chromeos/os_settings_main/BUILD.gn
index 5030c411..245e660 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings_main/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/os_settings_main/BUILD.gn
@@ -7,7 +7,7 @@
 import("../os_settings.gni")
 
 js_type_check("closure_compile_module") {
-  closure_flags = os_settings_closure_flags + mojom_js_args
+  closure_flags = os_settings_closure_flags
   is_polymer3 = true
   deps = [ ":os_settings_main" ]
 }
diff --git a/chrome/browser/resources/settings/chromeos/os_settings_page/BUILD.gn b/chrome/browser/resources/settings/chromeos/os_settings_page/BUILD.gn
index 675101f..e19229a 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings_page/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/os_settings_page/BUILD.gn
@@ -7,7 +7,7 @@
 import("../os_settings.gni")
 
 js_type_check("closure_compile_module") {
-  closure_flags = os_settings_closure_flags + mojom_js_args
+  closure_flags = os_settings_closure_flags
   is_polymer3 = true
   deps = [
     ":main_page_behavior",
diff --git a/chrome/browser/resources/settings/chromeos/os_settings_search_box/BUILD.gn b/chrome/browser/resources/settings/chromeos/os_settings_search_box/BUILD.gn
index d58d77eb..8685942 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings_search_box/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/os_settings_search_box/BUILD.gn
@@ -7,7 +7,7 @@
 import("../os_settings.gni")
 
 js_type_check("closure_compile_module") {
-  closure_flags = os_settings_closure_flags + mojom_js_args
+  closure_flags = os_settings_closure_flags
   is_polymer3 = true
   deps = [
     ":os_search_result_row",
diff --git a/chrome/browser/resources/settings/chromeos/os_settings_ui/BUILD.gn b/chrome/browser/resources/settings/chromeos/os_settings_ui/BUILD.gn
index 030a3b5..87b2bca 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings_ui/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/os_settings_ui/BUILD.gn
@@ -7,7 +7,7 @@
 import("../os_settings.gni")
 
 js_type_check("closure_compile_module") {
-  closure_flags = os_settings_closure_flags + mojom_js_args
+  closure_flags = os_settings_closure_flags
   is_polymer3 = true
   deps = [ ":os_settings_ui" ]
 }
diff --git a/chrome/browser/resources/settings/chromeos/os_toolbar/BUILD.gn b/chrome/browser/resources/settings/chromeos/os_toolbar/BUILD.gn
index 9086b811..c6dd67c46 100644
--- a/chrome/browser/resources/settings/chromeos/os_toolbar/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/os_toolbar/BUILD.gn
@@ -7,7 +7,7 @@
 import("../os_settings.gni")
 
 js_type_check("closure_compile_module") {
-  closure_flags = os_settings_closure_flags + mojom_js_args
+  closure_flags = os_settings_closure_flags
   is_polymer3 = true
   deps = [ ":os_toolbar" ]
 }
diff --git a/chrome/browser/resources/webui_gallery/BUILD.gn b/chrome/browser/resources/webui_gallery/BUILD.gn
index 25ca0be..e0b73cf 100644
--- a/chrome/browser/resources/webui_gallery/BUILD.gn
+++ b/chrome/browser/resources/webui_gallery/BUILD.gn
@@ -30,10 +30,12 @@
   out_grd = "$target_gen_dir/resources.grd"
   input_files = [
     "demos/demo.css",
+    "demos/cr_a11y_announcer_demo.html",
     "demos/cr_button_demo.html",
     "demos/cr_checkbox_demo.html",
     "demos/cr_dialog_demo.html",
     "demos/cr_radio_demo.html",
+    "demos/cr_toggle_demo.html",
     "webui_gallery.html",
   ]
   input_files_base_dir = rebase_path(".", "//")
diff --git a/chrome/browser/resources/webui_gallery/app.ts b/chrome/browser/resources/webui_gallery/app.ts
index ec747cd..d7355b5 100644
--- a/chrome/browser/resources/webui_gallery/app.ts
+++ b/chrome/browser/resources/webui_gallery/app.ts
@@ -27,6 +27,10 @@
         value: function() {
           return [
             {
+              name: 'cr-a11y-announcer demo',
+              url: 'cr_a11y_announcer_demo.html',
+            },
+            {
               name: 'cr-button demo',
               url: 'cr_button_demo.html',
             },
@@ -42,6 +46,10 @@
               name: 'cr-radio demo',
               url: 'cr_radio_demo.html',
             },
+            {
+              name: 'cr-toggle demo',
+              url: 'cr_toggle_demo.html',
+            },
           ];
         },
       },
diff --git a/chrome/browser/resources/webui_gallery/demos/cr_a11y_announcer_demo.html b/chrome/browser/resources/webui_gallery/demos/cr_a11y_announcer_demo.html
new file mode 100644
index 0000000..8850a885
--- /dev/null
+++ b/chrome/browser/resources/webui_gallery/demos/cr_a11y_announcer_demo.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>cr-a11y-announcer demo</title>
+    <link rel="stylesheet" href="demo.css">
+  </head>
+  <body>
+    <cr-a11y-announcer-demo></cr-a11y-announcer-demo>
+    <script src="cr_a11y_announcer_demo_component.js" type="module"></script>
+  </body>
+</html>
diff --git a/chrome/browser/resources/webui_gallery/demos/cr_a11y_announcer_demo_component.html b/chrome/browser/resources/webui_gallery/demos/cr_a11y_announcer_demo_component.html
new file mode 100644
index 0000000..1a642725
--- /dev/null
+++ b/chrome/browser/resources/webui_gallery/demos/cr_a11y_announcer_demo_component.html
@@ -0,0 +1,25 @@
+<link rel="stylesheet" href="demo.css">
+<style>
+  :host([force-show-announcer_]) cr-a11y-announcer {
+    overflow: visible;
+    position: static;
+  }
+</style>
+
+<h1>cr-a11y-announcer</h1>
+
+<div class="demos">
+  <cr-checkbox checked="{{forceShowAnnouncer_}}">
+    Force show announcer
+  </cr-checkbox>
+
+  <cr-button on-click="onAnnounceTextClick_">
+    Announce text
+  </cr-button>
+
+  <cr-button on-click="onAnnounceMultipleTextsClick_">
+    Announce multiple texts
+  </cr-button>
+
+  <div id="announcerContainer"></div>
+</div>
diff --git a/chrome/browser/resources/webui_gallery/demos/cr_a11y_announcer_demo_component.ts b/chrome/browser/resources/webui_gallery/demos/cr_a11y_announcer_demo_component.ts
new file mode 100644
index 0000000..e7c94c7
--- /dev/null
+++ b/chrome/browser/resources/webui_gallery/demos/cr_a11y_announcer_demo_component.ts
@@ -0,0 +1,58 @@
+// Copyright 2022 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 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
+import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.m.js';
+
+import {PolymerElement} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {getInstance as getAnnouncerInstance} from 'chrome://resources/cr_elements/cr_a11y_announcer/cr_a11y_announcer.js';
+
+import {getTemplate} from './cr_a11y_announcer_demo_component.html.js';
+
+interface CrA11yAnnouncerDemoComponent {
+  $: {
+    announcerContainer: HTMLElement,
+  };
+}
+
+class CrA11yAnnouncerDemoComponent extends PolymerElement {
+  static get is() {
+    return 'cr-a11y-announcer-demo';
+  }
+
+  static get template() {
+    return getTemplate();
+  }
+
+  static get properties() {
+    return {
+      forceShowAnnouncer_: {
+        type: Boolean,
+        reflectToAttribute: true,
+        value: false,
+      },
+    };
+  }
+
+  private announcementTextCount_: number = 0;
+  private forceShowAnnouncer_: boolean;
+
+  private onAnnounceTextClick_() {
+    const announcer = this.forceShowAnnouncer_ ?
+        getAnnouncerInstance(this.$.announcerContainer) :
+        getAnnouncerInstance();
+    announcer.announce(`Announcement number ${++this.announcementTextCount_}`);
+  }
+
+  private onAnnounceMultipleTextsClick_() {
+    const announcer = this.forceShowAnnouncer_ ?
+        getAnnouncerInstance(this.$.announcerContainer) :
+        getAnnouncerInstance();
+    announcer.announce('Page is loading...');
+    announcer.announce('Page has loaded.');
+  }
+}
+
+customElements.define(
+    CrA11yAnnouncerDemoComponent.is, CrA11yAnnouncerDemoComponent);
diff --git a/chrome/browser/resources/webui_gallery/demos/cr_toggle_demo.html b/chrome/browser/resources/webui_gallery/demos/cr_toggle_demo.html
new file mode 100644
index 0000000..32f433ed
--- /dev/null
+++ b/chrome/browser/resources/webui_gallery/demos/cr_toggle_demo.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>cr-toggle demo</title>
+    <link rel="stylesheet" href="demo.css">
+  </head>
+  <body>
+    <dom-bind>
+      <template>
+        <h1>cr-toggle</h1>
+        <div class="demos">
+          <div class="row">
+            <cr-toggle
+                aria-label="Label for toggle"
+                checked="{{checked_}}">
+            </cr-toggle>
+            <span aria-hidden="true">Label for toggle</span>
+            <span>
+              Checked? [[checked_]]
+            </span>
+          </div>
+
+          <div class="row">
+            <cr-toggle aria-label="Disabled toggle" disabled></cr-toggle>
+            <span aria-hidden="true">Disabled toggle</span>
+          </div>
+
+          <div class="row">
+            <cr-toggle
+                aria-label="Dark mode toggle"
+                aria-describedby="description"
+                dark>
+            </cr-toggle>
+            <div>
+              <div aria-hidden="true">Dark mode toggle</div>
+              <div id="description"> This toggle has dark mode forced on.</div>
+            </div>
+          </div>
+        </div>
+      </template>
+    </dom-bind>
+
+    <script src="chrome://resources/cr_elements/cr_toggle/cr_toggle.m.js"
+        type="module"></script>
+  </body>
+</html>
diff --git a/chrome/browser/resources/webui_gallery/webui_gallery.gni b/chrome/browser/resources/webui_gallery/webui_gallery.gni
index 4a3e313..e3cf3b1 100644
--- a/chrome/browser/resources/webui_gallery/webui_gallery.gni
+++ b/chrome/browser/resources/webui_gallery/webui_gallery.gni
@@ -5,6 +5,7 @@
 # Files holding a Polymer element definition AND have an equivalent .html file.
 web_component_files = [
   "app.ts",
+  "demos/cr_a11y_announcer_demo_component.ts",
   "demos/cr_dialog_demo_component.ts",
 ]
 
diff --git a/chrome/browser/safe_browsing/chrome_cleaner/BUILD.gn b/chrome/browser/safe_browsing/chrome_cleaner/BUILD.gn
index 68d44af..b6399ad 100644
--- a/chrome/browser/safe_browsing/chrome_cleaner/BUILD.gn
+++ b/chrome/browser/safe_browsing/chrome_cleaner/BUILD.gn
@@ -22,6 +22,7 @@
     "//base",
     "//chrome/common:constants",
     "//components/chrome_cleaner/public/constants",
+    "//components/component_updater",
     "//components/prefs:prefs",
     "//components/variations",
     "//url",
diff --git a/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_controller_impl_win.cc b/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_controller_impl_win.cc
index 37d73e9..0db704b4 100644
--- a/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_controller_impl_win.cc
+++ b/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_controller_impl_win.cc
@@ -151,7 +151,8 @@
   FetchChromeCleaner(
       base::BindOnce(&OnChromeCleanerFetched, std::move(fetched_callback)),
       g_browser_process->system_network_context_manager()
-          ->GetURLLoaderFactory());
+          ->GetURLLoaderFactory(),
+      g_browser_process->local_state());
 }
 
 bool ChromeCleanerControllerDelegate::IsMetricsAndCrashReportingEnabled() {
diff --git a/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_fetcher_win.cc b/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_fetcher_win.cc
index 84a4bcb..628000b0 100644
--- a/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_fetcher_win.cc
+++ b/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_fetcher_win.cc
@@ -29,6 +29,7 @@
 #include "chrome/browser/safe_browsing/chrome_cleaner/srt_field_trial_win.h"
 #include "chrome/install_static/install_details.h"
 #include "chrome/install_static/install_modes.h"
+#include "components/prefs/pref_service.h"
 #include "components/version_info/version_info.h"
 #include "net/base/load_flags.h"
 #include "net/http/http_request_headers.h"
@@ -107,7 +108,8 @@
 class ChromeCleanerFetcher {
  public:
   ChromeCleanerFetcher(ChromeCleanerFetchedCallback fetched_callback,
-                       network::mojom::URLLoaderFactory* url_loader_factory);
+                       network::mojom::URLLoaderFactory* url_loader_factory,
+                       PrefService* prefs);
 
   ChromeCleanerFetcher(const ChromeCleanerFetcher&) = delete;
   ChromeCleanerFetcher& operator=(const ChromeCleanerFetcher&) = delete;
@@ -129,6 +131,7 @@
 
   std::unique_ptr<network::SimpleURLLoader> url_loader_;
   raw_ptr<network::mojom::URLLoaderFactory> url_loader_factory_;
+  GURL download_url_;
 
   // Used for file operations such as creating a new temporary directory.
   scoped_refptr<base::SequencedTaskRunner> blocking_task_runner_;
@@ -142,9 +145,11 @@
 
 ChromeCleanerFetcher::ChromeCleanerFetcher(
     ChromeCleanerFetchedCallback fetched_callback,
-    network::mojom::URLLoaderFactory* url_loader_factory)
+    network::mojom::URLLoaderFactory* url_loader_factory,
+    PrefService* prefs)
     : fetched_callback_(std::move(fetched_callback)),
       url_loader_factory_(url_loader_factory),
+      download_url_(GetSRTDownloadURL(prefs)),
       blocking_task_runner_(base::ThreadPool::CreateSequencedTaskRunner(
           {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
            base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN})),
@@ -179,7 +184,7 @@
       base::ASCIIToWide(base::GenerateGUID()) + L".tmp");
 
   auto request = std::make_unique<network::ResourceRequest>();
-  request->url = GetSRTDownloadURL();
+  request->url = download_url_;
   request->load_flags = net::LOAD_DISABLE_CACHE;
   request->credentials_mode = network::mojom::CredentialsMode::kOmit;
 
@@ -278,8 +283,10 @@
 }  // namespace
 
 void FetchChromeCleaner(ChromeCleanerFetchedCallback fetched_callback,
-                        network::mojom::URLLoaderFactory* url_loader_factory) {
-  new ChromeCleanerFetcher(std::move(fetched_callback), url_loader_factory);
+                        network::mojom::URLLoaderFactory* url_loader_factory,
+                        PrefService* prefs) {
+  new ChromeCleanerFetcher(std::move(fetched_callback), url_loader_factory,
+                           prefs);
 }
 
 }  // namespace safe_browsing
diff --git a/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_fetcher_win.h b/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_fetcher_win.h
index 8c07c4f..ff374c7 100644
--- a/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_fetcher_win.h
+++ b/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_fetcher_win.h
@@ -7,6 +7,8 @@
 
 #include "base/callback.h"
 
+class PrefService;
+
 namespace base {
 class FilePath;
 }
@@ -40,7 +42,8 @@
 // Fetches the Chrome Cleaner binary. This function can be called from any
 // sequence and |fetched_callback| will be called back on that same sequence.
 void FetchChromeCleaner(ChromeCleanerFetchedCallback fetched_callback,
-                        network::mojom::URLLoaderFactory* url_loader_factory);
+                        network::mojom::URLLoaderFactory* url_loader_factory,
+                        PrefService* prefs);
 
 }  // namespace safe_browsing
 
diff --git a/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_fetcher_win_unittest.cc b/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_fetcher_win_unittest.cc
index 88976a0..d5d8fcd1 100644
--- a/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_fetcher_win_unittest.cc
+++ b/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_fetcher_win_unittest.cc
@@ -14,6 +14,9 @@
 #include "base/run_loop.h"
 #include "base/test/task_environment.h"
 #include "chrome/browser/safe_browsing/chrome_cleaner/srt_field_trial_win.h"
+#include "components/component_updater/pref_names.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/testing_pref_service.h"
 #include "content/public/test/browser_task_environment.h"
 #include "net/base/net_errors.h"
 #include "net/base/network_change_notifier.h"
@@ -30,7 +33,10 @@
 class ChromeCleanerFetcherTest : public ::testing::Test {
  public:
   ChromeCleanerFetcherTest()
-      : task_environment_(base::test::TaskEnvironment::MainThreadType::IO) {}
+      : task_environment_(base::test::TaskEnvironment::MainThreadType::IO) {
+    test_prefs_.registry()->RegisterStringPref(prefs::kSwReporterCohort,
+                                               "stable");
+  }
 
   void TearDown() override {
     if (!downloaded_path_.empty()) {
@@ -44,7 +50,7 @@
     FetchChromeCleaner(
         base::BindOnce(&ChromeCleanerFetcherTest::FetchedCallback,
                        base::Unretained(this)),
-        &test_url_loader_factory_);
+        &test_url_loader_factory_, &test_prefs_);
   }
 
   void FetchedCallback(base::FilePath downloaded_path,
@@ -58,7 +64,7 @@
  protected:
   base::test::TaskEnvironment task_environment_;
   network::TestURLLoaderFactory test_url_loader_factory_;
-
+  TestingPrefServiceSimple test_prefs_;
   base::RunLoop run_loop_;
 
   // Variables set by FetchedCallback().
@@ -70,7 +76,7 @@
 
 TEST_F(ChromeCleanerFetcherTest, FetchSuccess) {
   const std::string kFileContents("FileContents");
-  test_url_loader_factory_.AddResponse(GetSRTDownloadURL().spec(),
+  test_url_loader_factory_.AddResponse(GetSRTDownloadURL(&test_prefs_).spec(),
                                        kFileContents);
 
   StartFetch();
@@ -86,8 +92,8 @@
 }
 
 TEST_F(ChromeCleanerFetcherTest, NotFoundOnServer) {
-  test_url_loader_factory_.AddResponse(GetSRTDownloadURL().spec(), "",
-                                       net::HTTP_NOT_FOUND);
+  test_url_loader_factory_.AddResponse(GetSRTDownloadURL(&test_prefs_).spec(),
+                                       "", net::HTTP_NOT_FOUND);
 
   StartFetch();
   run_loop_.Run();
@@ -101,8 +107,8 @@
   // For this test, just use any http response code other than net::HTTP_OK
   // and net::HTTP_NOT_FOUND.
   test_url_loader_factory_.AddResponse(
-      GetSRTDownloadURL(), network::mojom::URLResponseHead::New(), "contents",
-      network::URLLoaderCompletionStatus(net::ERR_ADDRESS_INVALID));
+      GetSRTDownloadURL(&test_prefs_), network::mojom::URLResponseHead::New(),
+      "contents", network::URLLoaderCompletionStatus(net::ERR_ADDRESS_INVALID));
 
   StartFetch();
   run_loop_.Run();
diff --git a/chrome/browser/safe_browsing/chrome_cleaner/srt_field_trial_win.cc b/chrome/browser/safe_browsing/chrome_cleaner/srt_field_trial_win.cc
index dc0dea3..b74260e 100644
--- a/chrome/browser/safe_browsing/chrome_cleaner/srt_field_trial_win.cc
+++ b/chrome/browser/safe_browsing/chrome_cleaner/srt_field_trial_win.cc
@@ -12,6 +12,8 @@
 #include "base/metrics/histogram_macros.h"
 #include "base/strings/strcat.h"
 #include "base/win/windows_version.h"
+#include "components/component_updater/pref_names.h"
+#include "components/prefs/pref_service.h"
 #include "url/gurl.h"
 #include "url/origin.h"
 
@@ -25,6 +27,9 @@
 
 namespace safe_browsing {
 
+// Do not remove this feature, even though it's enabled by default. It's used
+// in testing to override `kReporterDistributionTagParam` and
+// `kCleanerDownloadGroupParam` to fetch pre-release versions of the tool.
 const base::Feature kChromeCleanupDistributionFeature{
     "ChromeCleanupDistribution", base::FEATURE_ENABLED_BY_DEFAULT};
 
@@ -34,12 +39,16 @@
 const base::FeatureParam<std::string> kCleanerDownloadGroupParam{
     &kChromeCleanupDistributionFeature, "cleaner_download_group", ""};
 
-GURL GetSRTDownloadURL() {
+GURL GetSRTDownloadURL(PrefService* prefs) {
   std::string download_group = kCleanerDownloadGroupParam.Get();
   if (download_group.empty()) {
-    // TODO(crbug.com/1305048): If the download group isn't assigned by the
-    // server, randomly assign the user to canary or stable.
-    download_group = "stable";
+    // Use the same string from prefs that was used for the reporter tag. Since
+    // the tag and download group aren't coming from
+    // kChromeCleanupDistributionFeature, the pref will always be set to a valid
+    // value by SwReporterInstallerPolicy when the reporter was fetched.
+    DCHECK(kReporterDistributionTagParam.Get().empty());
+    download_group = prefs->GetString(prefs::kSwReporterCohort);
+    DCHECK(download_group == "canary" || download_group == "stable");
   }
   const std::string architecture = base::win::OSInfo::GetArchitecture() ==
                                            base::win::OSInfo::X86_ARCHITECTURE
diff --git a/chrome/browser/safe_browsing/chrome_cleaner/srt_field_trial_win.h b/chrome/browser/safe_browsing/chrome_cleaner/srt_field_trial_win.h
index ee3ee891..ea86b3b 100644
--- a/chrome/browser/safe_browsing/chrome_cleaner/srt_field_trial_win.h
+++ b/chrome/browser/safe_browsing/chrome_cleaner/srt_field_trial_win.h
@@ -11,6 +11,8 @@
 #include "base/metrics/field_trial_params.h"
 #include "url/gurl.h"
 
+class PrefService;
+
 namespace safe_browsing {
 
 // These values are used to send UMA information and are replicated in the
@@ -67,8 +69,11 @@
 // matching version of the cleaner.
 extern const base::FeatureParam<std::string> kCleanerDownloadGroupParam;
 
-// Returns the correct SRT download URL for the current field trial.
-GURL GetSRTDownloadURL();
+// Returns the correct SRT download URL, based on the current platform and
+// download group. The download group is taken from
+// `kChromeCleanupDistributionFeature` or from `prefs` if the feature is
+// disabled.
+GURL GetSRTDownloadURL(PrefService* prefs);
 
 // Records a value for the SRT Prompt Histogram.
 void RecordSRTPromptHistogram(SRTPromptHistogramValue value);
diff --git a/chrome/browser/safe_browsing/chrome_cleaner/srt_field_trial_win_unittest.cc b/chrome/browser/safe_browsing/chrome_cleaner/srt_field_trial_win_unittest.cc
index 20a40ac2..51746001 100644
--- a/chrome/browser/safe_browsing/chrome_cleaner/srt_field_trial_win_unittest.cc
+++ b/chrome/browser/safe_browsing/chrome_cleaner/srt_field_trial_win_unittest.cc
@@ -9,7 +9,11 @@
 
 #include "base/metrics/field_trial.h"
 #include "base/test/scoped_feature_list.h"
+#include "base/values.h"
 #include "base/win/windows_version.h"
+#include "components/component_updater/pref_names.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/testing_pref_service.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
@@ -29,6 +33,11 @@
 
 class SRTDownloadURLTest : public ::testing::Test {
  protected:
+  SRTDownloadURLTest() {
+    test_prefs_.registry()->RegisterStringPref(prefs::kSwReporterCohort,
+                                               "stable");
+  }
+
   void CreateDownloadFeature(
       const absl::optional<std::string>& download_group_name) {
     base::FieldTrialParams params;
@@ -43,10 +52,15 @@
         kChromeCleanupDistributionFeature);
   }
 
+ protected:
+  TestingPrefServiceSimple test_prefs_;
+
  private:
   base::test::ScopedFeatureList scoped_feature_list_;
 };
 
+// The feature should override the pref. The string "experiment" should be
+// allowed for the feature, even though it's not allowed from a pref.
 TEST_F(SRTDownloadURLTest, Experiment) {
   CreateDownloadFeature("experiment");
   const std::string expected_path =
@@ -55,22 +69,38 @@
           ? "/dl/softwareremovaltool/win/x86/experiment/chrome_cleanup_tool.exe"
           : "/dl/softwareremovaltool/win/x64/experiment/"
             "chrome_cleanup_tool.exe";
-  EXPECT_EQ(expected_path, GetSRTDownloadURL().path());
+  EXPECT_EQ(expected_path, GetSRTDownloadURL(&test_prefs_).path());
 }
 
+// Default to "stable" because the pref is set to that in the test constructor.
 TEST_F(SRTDownloadURLTest, DefaultsToStable) {
   DisableDownloadFeature();
-  EXPECT_EQ(GetStablePath(), GetSRTDownloadURL().path());
+  EXPECT_EQ(GetStablePath(), GetSRTDownloadURL(&test_prefs_).path());
 }
 
+// Default to "stable" because the pref is set to that in the test constructor.
 TEST_F(SRTDownloadURLTest, EmptyParamIsStable) {
   CreateDownloadFeature("");
-  EXPECT_EQ(GetStablePath(), GetSRTDownloadURL().path());
+  EXPECT_EQ(GetStablePath(), GetSRTDownloadURL(&test_prefs_).path());
 }
 
+// Default to "stable" because the pref is set to that in the test constructor.
 TEST_F(SRTDownloadURLTest, MissingParamIsStable) {
   CreateDownloadFeature(absl::nullopt);
-  EXPECT_EQ(GetStablePath(), GetSRTDownloadURL().path());
+  EXPECT_EQ(GetStablePath(), GetSRTDownloadURL(&test_prefs_).path());
+}
+
+// "canary" is also a valid value for the pref.
+TEST_F(SRTDownloadURLTest, CanaryFromPrefs) {
+  DisableDownloadFeature();
+  test_prefs_.SetUserPref(prefs::kSwReporterCohort, base::Value("canary"));
+  const std::string expected_path =
+      (base::win::OSInfo::GetArchitecture() ==
+       base::win::OSInfo::X86_ARCHITECTURE)
+          ? "/dl/softwareremovaltool/win/x86/canary/chrome_cleanup_tool.exe"
+          : "/dl/softwareremovaltool/win/x64/canary/"
+            "chrome_cleanup_tool.exe";
+  EXPECT_EQ(expected_path, GetSRTDownloadURL(&test_prefs_).path());
 }
 
 }  // namespace
diff --git a/chrome/browser/search_resumption/junit/src/org/chromium/chrome/browser/search_resumption/SearchResumptionModuleMediatorUnitTest.java b/chrome/browser/search_resumption/junit/src/org/chromium/chrome/browser/search_resumption/SearchResumptionModuleMediatorUnitTest.java
index e42330d1..c3b2aaf9 100644
--- a/chrome/browser/search_resumption/junit/src/org/chromium/chrome/browser/search_resumption/SearchResumptionModuleMediatorUnitTest.java
+++ b/chrome/browser/search_resumption/junit/src/org/chromium/chrome/browser/search_resumption/SearchResumptionModuleMediatorUnitTest.java
@@ -51,6 +51,7 @@
  */
 @RunWith(BaseRobolectricTestRunner.class)
 @Config(manifest = Config.NONE)
+@SuppressWarnings("DoNotMock") // Mocking GURL
 public class SearchResumptionModuleMediatorUnitTest {
     // The search suggestions are meant to be shown on any website.
     private static final String URL_TO_TRACK = "/foo.com";
diff --git a/chrome/browser/share/android/javatests/src/org/chromium/chrome/browser/share/long_screenshots/bitmap_generation/LongScreenshotsCompositorTest.java b/chrome/browser/share/android/javatests/src/org/chromium/chrome/browser/share/long_screenshots/bitmap_generation/LongScreenshotsCompositorTest.java
index e2c1089..ad63efe 100644
--- a/chrome/browser/share/android/javatests/src/org/chromium/chrome/browser/share/long_screenshots/bitmap_generation/LongScreenshotsCompositorTest.java
+++ b/chrome/browser/share/android/javatests/src/org/chromium/chrome/browser/share/long_screenshots/bitmap_generation/LongScreenshotsCompositorTest.java
@@ -32,6 +32,7 @@
  */
 @RunWith(BaseRobolectricTestRunner.class)
 @Config(manifest = Config.NONE)
+@SuppressWarnings("DoNotMock") // Mocks GURL.
 public class LongScreenshotsCompositorTest {
     private TestPlayerCompositorDelegate mCompositorDelegate;
     private Bitmap mTestBitmap = Bitmap.createBitmap(512, 1024, Bitmap.Config.ARGB_8888);
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index 9efa159a..342cc96 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -4765,8 +4765,6 @@
       "views/safe_browsing/password_reuse_modal_warning_dialog.h",
       "views/safe_browsing/prompt_for_scanning_modal_dialog.cc",
       "views/safe_browsing/prompt_for_scanning_modal_dialog.h",
-      "views/safe_browsing/tailored_security_desktop_modal.cc",
-      "views/safe_browsing/tailored_security_desktop_modal.h",
       "views/safe_browsing/tailored_security_unconsented_modal.cc",
       "views/safe_browsing/tailored_security_unconsented_modal.h",
       "views/send_tab_to_self/manage_account_devices_link_view.cc",
diff --git a/chrome/browser/ui/android/night_mode/java/src/org/chromium/chrome/browser/night_mode/WebContentsDarkModeControllerUnitTest.java b/chrome/browser/ui/android/night_mode/java/src/org/chromium/chrome/browser/night_mode/WebContentsDarkModeControllerUnitTest.java
index cbdbc84..ccaf39b 100644
--- a/chrome/browser/ui/android/night_mode/java/src/org/chromium/chrome/browser/night_mode/WebContentsDarkModeControllerUnitTest.java
+++ b/chrome/browser/ui/android/night_mode/java/src/org/chromium/chrome/browser/night_mode/WebContentsDarkModeControllerUnitTest.java
@@ -38,6 +38,7 @@
 /** Unit tests for {@link WebContentsDarkModeController}. */
 @RunWith(BaseRobolectricTestRunner.class)
 @Config(manifest = Config.NONE, shadows = {ShadowRecordHistogram.class, ShadowColorUtils.class})
+@SuppressWarnings("DoNotMock") // Mocking GURL
 public class WebContentsDarkModeControllerUnitTest {
     @Rule
     public JniMocker mJniMocker = new JniMocker();
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings.grd b/chrome/browser/ui/android/strings/android_chrome_strings.grd
index 575b70f8..710cfd1 100644
--- a/chrome/browser/ui/android/strings/android_chrome_strings.grd
+++ b/chrome/browser/ui/android/strings/android_chrome_strings.grd
@@ -976,10 +976,10 @@
         Add <ph name="SITE">%1$s<ex>example.com</ex></ph>
       </message>
       <message name="IDS_PRIVACY_SANDBOX_ADD_INTEREST_SNACKBAR" desc="Text on a snackbar after an ad personalization interest was added to the Topics API of the Privacy Sandbox.">
-        Interest added
+        Chrome can consider this interest
       </message>
       <message name="IDS_PRIVACY_SANDBOX_ADD_SITE_SNACKBAR" desc="Text on a snackbar after an ad personalization interest was added to the FLEDGE API of the Privacy Sandbox.">
-        Site added
+        Site can define interests
       </message>
       <message name="IDS_PRIVACY_SANDBOX_REMOVE_INTEREST_BUTTON_DESCRIPTION" desc="Content description for button that removes an ad interest for the Topics API of the Privacy Sandbox.">
         Remove <ph name="INTEREST">%1$s<ex>Acting and Theater</ex></ph>
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_PRIVACY_SANDBOX_ADD_INTEREST_SNACKBAR.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_PRIVACY_SANDBOX_ADD_INTEREST_SNACKBAR.png.sha1
index 17e40526..fc05a1c 100644
--- a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_PRIVACY_SANDBOX_ADD_INTEREST_SNACKBAR.png.sha1
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_PRIVACY_SANDBOX_ADD_INTEREST_SNACKBAR.png.sha1
@@ -1 +1 @@
-23b8ad87ec8e2ff3cd8f023dc737dd0cd4645716
\ No newline at end of file
+9d1f7ec7b53a198507a0288826b9f86512a5fa24
\ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_PRIVACY_SANDBOX_ADD_SITE_SNACKBAR.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_PRIVACY_SANDBOX_ADD_SITE_SNACKBAR.png.sha1
index a58518b..50da7de 100644
--- a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_PRIVACY_SANDBOX_ADD_SITE_SNACKBAR.png.sha1
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_PRIVACY_SANDBOX_ADD_SITE_SNACKBAR.png.sha1
@@ -1 +1 @@
-97496ed54a93d6eeb4b10eb6d7af7d3e89377442
\ No newline at end of file
+96b0e1f2b8ef889a07fe329fa0da41db7a1c2a8e
\ No newline at end of file
diff --git a/chrome/browser/ui/android/toolbar/BUILD.gn b/chrome/browser/ui/android/toolbar/BUILD.gn
index 3265acd..9497696 100644
--- a/chrome/browser/ui/android/toolbar/BUILD.gn
+++ b/chrome/browser/ui/android/toolbar/BUILD.gn
@@ -65,6 +65,9 @@
     "java/src/org/chromium/chrome/browser/toolbar/menu_button/MenuButtonViewBinder.java",
     "java/src/org/chromium/chrome/browser/toolbar/menu_button/MenuItemState.java",
     "java/src/org/chromium/chrome/browser/toolbar/menu_button/MenuUiState.java",
+    "java/src/org/chromium/chrome/browser/toolbar/optional_button/OptionalButtonConstants.java",
+    "java/src/org/chromium/chrome/browser/toolbar/optional_button/OptionalButtonView.java",
+    "java/src/org/chromium/chrome/browser/toolbar/optional_button/ShrinkTransition.java",
     "java/src/org/chromium/chrome/browser/toolbar/top/ActionModeController.java",
     "java/src/org/chromium/chrome/browser/toolbar/top/CaptureReadinessResult.java",
     "java/src/org/chromium/chrome/browser/toolbar/top/HomeButtonCoordinator.java",
@@ -246,6 +249,7 @@
     "java/res/layout/control_container.xml",
     "java/res/layout/menu_button.xml",
     "java/res/layout/navigation_popup_item.xml",
+    "java/res/layout/optional_button_layout.xml",
     "java/res/layout/radio_button_group_adaptive_toolbar_preference.xml",
     "java/res/layout/start_top_toolbar.xml",
     "java/res/layout/tab_switcher_toolbar.xml",
@@ -282,6 +286,7 @@
     "java/src/org/chromium/chrome/browser/toolbar/menu_button/MenuButtonCoordinatorTest.java",
     "java/src/org/chromium/chrome/browser/toolbar/menu_button/MenuButtonMediatorTest.java",
     "java/src/org/chromium/chrome/browser/toolbar/menu_button/MenuButtonTest.java",
+    "java/src/org/chromium/chrome/browser/toolbar/optional_button/OptionalButtonViewTest.java",
     "java/src/org/chromium/chrome/browser/toolbar/top/HomeButtonCoordinatorTest.java",
     "java/src/org/chromium/chrome/browser/toolbar/top/OptionalBrowsingModeButtonControllerTest.java",
     "java/src/org/chromium/chrome/browser/toolbar/top/ToggleTabStackButtonCoordinatorTest.java",
@@ -324,6 +329,7 @@
     "//third_party/android_deps:guava_android_java",
     "//third_party/android_support_test_runner:runner_java",
     "//third_party/androidx:androidx_annotation_annotation_java",
+    "//third_party/androidx:androidx_appcompat_appcompat_resources_java",
     "//third_party/androidx:androidx_fragment_fragment_testing_java",
     "//third_party/androidx:androidx_test_core_java",
     "//third_party/androidx:androidx_test_runner_java",
diff --git a/chrome/browser/ui/android/toolbar/java/res/layout/optional_button_layout.xml b/chrome/browser/ui/android/toolbar/java/res/layout/optional_button_layout.xml
new file mode 100644
index 0000000..b753d94
--- /dev/null
+++ b/chrome/browser/ui/android/toolbar/java/res/layout/optional_button_layout.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2022 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. -->
+
+<!-- TODO(salg): Remove tools:ignore once this resource is used in non-test code. -->
+<org.chromium.chrome.browser.toolbar.optional_button.OptionalButtonView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    tools:ignore="UnusedResources"
+    android:paddingStart="@dimen/toolbar_phone_optional_button_padding"
+    android:layout_height="match_parent"
+    android:layout_width="52dp">
+    <org.chromium.ui.widget.ChromeImageView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        style="@style/ToolbarButton"
+        android:scaleType="fitXY"
+        android:id="@+id/swappable_icon_secondary_background"
+        android:layout_gravity="center"
+        android:visibility="gone" />
+    <org.chromium.components.browser_ui.widget.listmenu.ListMenuButton
+        android:layout_width="match_parent"
+        android:layout_height="40dp"
+        android:scaleType="fitStart"
+        android:id="@+id/optional_toolbar_button"
+        style="@style/ToolbarButton"
+        android:layout_gravity="center_vertical|start"
+        android:visibility="gone"
+        android:paddingVertical="8dp"
+        android:paddingStart="8dp"/>
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:id="@+id/action_chip_label"
+        android:layout_marginStart="40dp"
+        android:gravity="center"
+        android:visibility="gone"
+        android:textAppearance="@style/TextAppearance.TextLarge.Primary"/>
+    <org.chromium.ui.widget.ChromeImageView
+        android:layout_width="40dp"
+        android:layout_height="wrap_content"
+        android:id="@+id/swappable_icon_animation_image"
+        android:layout_gravity="center"
+        android:visibility="gone"
+        style="@style/ToolbarButton" />
+</org.chromium.chrome.browser.toolbar.optional_button.OptionalButtonView>
\ No newline at end of file
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/ButtonData.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/ButtonData.java
index 29dd13e1..399d8da 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/ButtonData.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/ButtonData.java
@@ -51,11 +51,13 @@
         @AdaptiveToolbarButtonVariant
         private final int mButtonVariant;
         private final boolean mIsDynamicAction;
+        @StringRes
+        private final int mActionChipLabelResId;
 
         public ButtonSpec(@NonNull Drawable drawable, @NonNull View.OnClickListener onClickListener,
                 @Nullable View.OnLongClickListener onLongClickListener, int contentDescriptionResId,
                 boolean supportsTinting, @Nullable IPHCommandBuilder iphCommandBuilder,
-                @AdaptiveToolbarButtonVariant int buttonVariant) {
+                @AdaptiveToolbarButtonVariant int buttonVariant, int actionChipLabelResId) {
             mDrawable = drawable;
             mOnClickListener = onClickListener;
             mOnLongClickListener = onLongClickListener;
@@ -64,6 +66,7 @@
             mIPHCommandBuilder = iphCommandBuilder;
             mButtonVariant = buttonVariant;
             mIsDynamicAction = AdaptiveToolbarFeatures.isDynamicAction(mButtonVariant);
+            mActionChipLabelResId = actionChipLabelResId;
         }
 
         /** Returns the {@link Drawable} for the button icon. */
@@ -90,6 +93,12 @@
             return mContentDescriptionResId;
         }
 
+        /** Returns the resource ID of the string for the button's action chip label. */
+        @StringRes
+        public int getActionChipLabelResId() {
+            return mActionChipLabelResId;
+        }
+
         /** Returns {@code true} if the button supports tinting. */
         public boolean getSupportsTinting() {
             return mSupportsTinting;
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/ButtonDataImpl.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/ButtonDataImpl.java
index 8b2f3ca..9d56e25 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/ButtonDataImpl.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/ButtonDataImpl.java
@@ -4,6 +4,7 @@
 
 package org.chromium.chrome.browser.toolbar;
 
+import android.content.res.Resources;
 import android.graphics.drawable.Drawable;
 import android.view.View;
 
@@ -29,7 +30,8 @@
         mCanShow = canShow;
         mIsEnabled = isEnabled;
         mButtonSpec = new ButtonSpec(drawable, onClickListener, /*onLongClickListener=*/null,
-                contentDescriptionResId, supportsTinting, iphCommandBuilder, buttonVariant);
+                contentDescriptionResId, supportsTinting, iphCommandBuilder, buttonVariant,
+                /* actionChipLabelResId= */ Resources.ID_NULL);
     }
 
     @Override
@@ -65,7 +67,8 @@
         ButtonSpec newSpec = new ButtonSpec(currentSpec.getDrawable(),
                 currentSpec.getOnClickListener(), currentSpec.getOnLongClickListener(),
                 currentSpec.getContentDescriptionResId(), currentSpec.getSupportsTinting(),
-                iphCommandBuilder, currentSpec.getButtonVariant());
+                iphCommandBuilder, currentSpec.getButtonVariant(),
+                currentSpec.getActionChipLabelResId());
         setButtonSpec(newSpec);
     }
 }
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/VoiceToolbarButtonControllerUnitTest.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/VoiceToolbarButtonControllerUnitTest.java
index f86f849..7c83b0b 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/VoiceToolbarButtonControllerUnitTest.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/VoiceToolbarButtonControllerUnitTest.java
@@ -54,6 +54,7 @@
 @RunWith(LocalRobolectricTestRunner.class)
 @Config(manifest = Config.NONE,
         shadows = {VoiceToolbarButtonControllerUnitTest.ShadowChromeFeatureList.class})
+@SuppressWarnings("DoNotMock") // Mocks GURL
 public final class VoiceToolbarButtonControllerUnitTest {
     // TODO(crbug.com/1199025): Remove this shadow.
     @Implements(ChromeFeatureList.class)
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/adaptive/AdaptiveToolbarButtonController.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/adaptive/AdaptiveToolbarButtonController.java
index a56e341..5745f1bf 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/adaptive/AdaptiveToolbarButtonController.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/adaptive/AdaptiveToolbarButtonController.java
@@ -192,7 +192,8 @@
                     mMenuHandler, receivedButtonSpec.getContentDescriptionResId(),
                     receivedButtonSpec.getSupportsTinting(),
                     receivedButtonSpec.getIPHCommandBuilder(),
-                    receivedButtonSpec.getButtonVariant()));
+                    receivedButtonSpec.getButtonVariant(),
+                    receivedButtonSpec.getActionChipLabelResId()));
         }
         return mButtonData;
     }
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/adaptive/AdaptiveToolbarButtonControllerTest.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/adaptive/AdaptiveToolbarButtonControllerTest.java
index be5a843..605158b 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/adaptive/AdaptiveToolbarButtonControllerTest.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/adaptive/AdaptiveToolbarButtonControllerTest.java
@@ -290,6 +290,6 @@
         return new ButtonSpec(/*drawable=*/null, mock(View.OnClickListener.class),
                 /*onLongClickListener=*/null,
                 /*contentDescriptionResId=*/101, /*supportsTinting=*/false,
-                /*iphCommandBuilder=*/null, variant);
+                /*iphCommandBuilder=*/null, variant, /*actionChipLabelResId=*/0);
     }
 }
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/optional_button/OptionalButtonConstants.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/optional_button/OptionalButtonConstants.java
new file mode 100644
index 0000000..2dc5979
--- /dev/null
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/optional_button/OptionalButtonConstants.java
@@ -0,0 +1,38 @@
+// Copyright 2022 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.toolbar.optional_button;
+
+import androidx.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/** Defines constants used by optional button. */
+public class OptionalButtonConstants {
+    /**
+     * Defines the different transition types for optional button, they are used as arguments to the
+     * transition started/finished callbacks.
+     */
+    @IntDef({TransitionType.SWAPPING, TransitionType.SHOWING, TransitionType.HIDING,
+            TransitionType.EXPANDING_ACTION_CHIP, TransitionType.COLLAPSING_ACTION_CHIP,
+            TransitionType.NONE})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface TransitionType {
+        /** Transitioning from one icon to another. */
+        int SWAPPING = 0;
+        /** Transitioning from hidden to showing an icon. */
+        int SHOWING = 1;
+        /** Transitioning from showing an icon to hidden. */
+        int HIDING = 2;
+        /**
+         * Transitioning from showing an icon to showing another as an action chip (rectangular
+         * button with a label).
+         */
+        int EXPANDING_ACTION_CHIP = 3;
+        /** Transitioning from an expanded action chip to the same icon as a regular button. */
+        int COLLAPSING_ACTION_CHIP = 4;
+        int NONE = 5;
+    }
+}
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/optional_button/OptionalButtonView.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/optional_button/OptionalButtonView.java
new file mode 100644
index 0000000..9abaef8
--- /dev/null
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/optional_button/OptionalButtonView.java
@@ -0,0 +1,632 @@
+// Copyright 2022 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.toolbar.optional_button;
+
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.transition.ChangeBounds;
+import android.transition.Fade;
+import android.transition.Slide;
+import android.transition.Transition;
+import android.transition.Transition.TransitionListener;
+import android.transition.TransitionManager;
+import android.transition.TransitionSet;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+import androidx.appcompat.content.res.AppCompatResources;
+
+import org.chromium.base.ApiCompatibilityUtils;
+import org.chromium.base.Callback;
+import org.chromium.base.supplier.BooleanSupplier;
+import org.chromium.chrome.browser.toolbar.ButtonData;
+import org.chromium.chrome.browser.toolbar.ButtonData.ButtonSpec;
+import org.chromium.chrome.browser.toolbar.R;
+import org.chromium.chrome.browser.toolbar.optional_button.OptionalButtonConstants.TransitionType;
+import org.chromium.components.browser_ui.widget.listmenu.ListMenuButton;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Toolbar button that performs animated transitions between icons.
+ */
+class OptionalButtonView extends FrameLayout implements TransitionListener {
+    private static final int SWAP_TRANSITION_DURATION_MS = 300;
+    private static final int HIDE_TRANSITION_DURATION_MS = 225;
+    /** Delay between action chip expansion end and collapse start. */
+    private static final int ACTION_CHIP_COLLAPSE_DELAY_MS = 3000;
+
+    private final int mCollapsedStateWidthPx;
+    private final int mExpandedStatePaddingPx;
+
+    private TextView mActionChipLabel;
+    private ImageView mBackground;
+    private ListMenuButton mButton;
+    private ImageView mAnimationImage;
+
+    private Drawable mIconDrawable;
+
+    private ViewGroup mTransitionRoot;
+    private String mContentDescription;
+    private String mActionChipLabelString;
+    private int mBackgroundColorFilter;
+    private Runnable mOnBeforeHideTransitionCallback;
+    private Handler mHandlerForTesting;
+
+    private @State int mState;
+
+    private @ButtonType int mCurrentButtonType;
+    private @ButtonType int mNextButtonType;
+
+    private OnClickListener mClickListener;
+    private OnLongClickListener mLongClickListener;
+    private Callback<Integer> mTransitionStartedCallback;
+    private Callback<Integer> mTransitionFinishedCallback;
+    private BooleanSupplier mIsAnimationAllowedPredicate;
+    private final Runnable mCollapseActionChipRunnable = new Runnable() {
+        @Override
+        public void run() {
+            if (mIsAnimationAllowedPredicate.getAsBoolean()) {
+                animateActionChipCollapse();
+            } else {
+                showIcon(false);
+            }
+        }
+    };
+
+    @IntDef({State.HIDDEN, State.SHOWING_ICON, State.SHOWING_ACTION_CHIP,
+            State.RUNNING_SHOW_TRANSITION, State.RUNNING_HIDE_TRANSITION,
+            State.RUNNING_ACTION_CHIP_EXPANSION_TRANSITION,
+            State.RUNNING_ACTION_CHIP_COLLAPSE_TRANSITION, State.RUNNING_SWAP_TRANSITION})
+    @Retention(RetentionPolicy.SOURCE)
+    private @interface State {
+        int HIDDEN = 0;
+        int SHOWING_ICON = 1;
+        int SHOWING_ACTION_CHIP = 2;
+        int RUNNING_SHOW_TRANSITION = 3;
+        int RUNNING_HIDE_TRANSITION = 4;
+        int RUNNING_ACTION_CHIP_EXPANSION_TRANSITION = 5;
+        int RUNNING_ACTION_CHIP_COLLAPSE_TRANSITION = 6;
+        int RUNNING_SWAP_TRANSITION = 7;
+    }
+
+    @IntDef({ButtonType.STATIC, ButtonType.DYNAMIC})
+    @Retention(RetentionPolicy.SOURCE)
+    private @interface ButtonType {
+        int STATIC = 0;
+        int DYNAMIC = 1;
+    }
+
+    void setTransitionStartedCallback(Callback<Integer> callback) {
+        mTransitionStartedCallback = callback;
+    }
+
+    void setTransitionFinishedCallback(Callback<Integer> callback) {
+        mTransitionFinishedCallback = callback;
+    }
+
+    void setIsAnimationAllowedPredicate(BooleanSupplier isAnimationAllowedPredicate) {
+        mIsAnimationAllowedPredicate = isAnimationAllowedPredicate;
+    }
+
+    void setOnBeforeHideTransitionCallback(Runnable callback) {
+        mOnBeforeHideTransitionCallback = callback;
+    }
+
+    void setPaddingStart(int paddingStart) {
+        setPaddingRelative(paddingStart, getPaddingTop(), getPaddingEnd(), getPaddingBottom());
+    }
+
+    public void cancelTransition() {
+        if (isRunningTransition()) {
+            TransitionManager.endTransitions(mTransitionRoot);
+        }
+    }
+
+    /**
+     * Updates the button's icon, click handler, description and other attributes with a transition
+     * animation. The animation that runs depends on the current state of this view (Whether is
+     * hidden or showing another icon) and the attributes of the new icon (Whether it contains an
+     * action chip description).
+     * @param buttonData object containing the new button's icon, handlers, description and other
+     *         attributes. If null then this view starts a hide transition.
+     */
+    void updateButtonWithAnimation(@Nullable ButtonData buttonData) {
+        if (mTransitionRoot == null || mIsAnimationAllowedPredicate == null) {
+            throw new IllegalStateException(
+                    "Both transitionRoot and animationAllowedPredicate must be set before starting "
+                    + "a transition");
+        }
+
+        boolean canAnimate = mIsAnimationAllowedPredicate.getAsBoolean();
+
+        if (isRunningTransition()) {
+            // If we are running any transitions then finish them immediately and jump to the next
+            // state.
+            TransitionManager.endTransitions(mTransitionRoot);
+            // TransitionManager.endTransitions calls onTransitionEnd on its own, but it's done
+            // asynchronously which causes flaky tests. We call it here to ensure the state updates
+            // and callbacks are executed synchronously.
+            onTransitionEnd(null);
+        }
+
+        if (mState == State.SHOWING_ACTION_CHIP) {
+            // If the action chip is expanded then deschedule the collapse task and collapse
+            // immediately.
+            getHandler().removeCallbacks(mCollapseActionChipRunnable);
+            showIcon(false);
+            mState = getNextState();
+        }
+
+        if (buttonData == null || !buttonData.canShow()) {
+            hide(canAnimate);
+            return;
+        }
+
+        ButtonSpec buttonSpec = buttonData.getButtonSpec();
+
+        mIconDrawable = buttonSpec.getDrawable();
+        mNextButtonType = buttonSpec.isDynamicAction() ? ButtonType.DYNAMIC : ButtonType.STATIC;
+        if (buttonSpec.getActionChipLabelResId() == Resources.ID_NULL) {
+            mActionChipLabelString = null;
+        } else {
+            mActionChipLabelString =
+                    getContext().getResources().getString(buttonSpec.getActionChipLabelResId());
+        }
+
+        mClickListener = buttonSpec.getOnClickListener();
+        mLongClickListener = buttonSpec.getOnLongClickListener();
+        mButton.setEnabled(buttonData.isEnabled());
+        mContentDescription =
+                getContext().getResources().getString(buttonSpec.getContentDescriptionResId());
+
+        if (mIconDrawable == null) {
+            hide(canAnimate);
+        } else if (mState == State.HIDDEN && mActionChipLabelString == null) {
+            showIcon(canAnimate);
+        } else if (canAnimate && mActionChipLabelString != null) {
+            animateActionChipExpansion();
+        } else if (canAnimate && mActionChipLabelString == null) {
+            animateSwapToNewIcon();
+        } else {
+            showIcon(false);
+        }
+    }
+
+    /**
+     * Set a view to use as a root for all transition animations. It's used to animate sibling views
+     * when this one changes width.
+     * @param transitionRoot
+     */
+    // TODO(salg): Consider getting rid of this property as it can be awkward to have a view
+    // initiating an animation on its siblings.
+    void setTransitionRoot(ViewGroup transitionRoot) {
+        mTransitionRoot = transitionRoot;
+    }
+
+    void setBackgroundColorFilter(int color) {
+        mBackgroundColorFilter = color;
+        mBackground.setColorFilter(color);
+    }
+
+    void setColorStateList(ColorStateList colorStateList) {
+        ApiCompatibilityUtils.setImageTintList(mButton, colorStateList);
+        ApiCompatibilityUtils.setImageTintList(mAnimationImage, colorStateList);
+        if (colorStateList != null) {
+            mActionChipLabel.setTextColor(colorStateList);
+        }
+    }
+
+    @VisibleForTesting
+    void setHandlerForTesting(Handler handler) {
+        mHandlerForTesting = handler;
+    }
+
+    View getButtonView() {
+        return mButton;
+    }
+
+    /**
+     * Constructor for inflating from XML.
+     * @param context
+     * @param attrs
+     */
+    public OptionalButtonView(@NonNull Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+
+        mState = State.HIDDEN;
+
+        // TODO(salg): Move these dimensions to an XML file.
+        float density = getResources().getDisplayMetrics().density;
+        mCollapsedStateWidthPx = (int) (52 * density);
+        mExpandedStatePaddingPx = (int) (8 * density);
+    }
+
+    /**
+     * Gets a handler used to schedule the action chip collapse animation after the action chip
+     * finishes expanding. Tests can set their own handler with {@code setHandlerForTesting}.
+     */
+    @Override
+    public Handler getHandler() {
+        if (mHandlerForTesting != null) {
+            return mHandlerForTesting;
+        }
+
+        return super.getHandler();
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+
+        mBackground = findViewById(R.id.swappable_icon_secondary_background);
+        mButton = findViewById(R.id.optional_toolbar_button);
+        mAnimationImage = findViewById(R.id.swappable_icon_animation_image);
+        mActionChipLabel = findViewById(R.id.action_chip_label);
+
+        mBackground.setImageDrawable(AppCompatResources.getDrawable(
+                getContext(), R.drawable.modern_toolbar_text_box_background_with_primary_color));
+    }
+
+    /**
+     * Listens to all transition starts. This is called even when animations are disabled.
+     * Implementation of {@link TransitionListener}.
+     * @param transition Transition that started, not used.
+     */
+    @Override
+    public void onTransitionStart(Transition transition) {
+        if (mState != State.RUNNING_ACTION_CHIP_COLLAPSE_TRANSITION) {
+            // Disable click listeners during the transitions (except action chip collapse, which
+            // goes to the same icon/action.
+            mButton.setOnClickListener(null);
+            mButton.setOnLongClickListener(null);
+            mButton.setContentDescription(null);
+        }
+
+        if (mTransitionStartedCallback != null) {
+            mTransitionStartedCallback.onResult(getCurrentTransitionType());
+        }
+    }
+
+    /**
+     * Listens to all transition ends. This is called even if the transition is cancelled or if all
+     * animations are disabled. Implementation of {@link TransitionListener}.
+     * @param transition Transition that ended, not used.
+     */
+    @Override
+    public void onTransitionEnd(Transition transition) {
+        if (mTransitionFinishedCallback != null
+                && getCurrentTransitionType() != TransitionType.NONE) {
+            mTransitionFinishedCallback.onResult(getCurrentTransitionType());
+        }
+
+        mState = getNextState();
+        mCurrentButtonType = mNextButtonType;
+
+        // This image is only used during transitions, it should not be visible afterwards.
+        mAnimationImage.setVisibility(GONE);
+
+        if (mState == State.HIDDEN) {
+            this.setVisibility(GONE);
+        } else {
+            mButton.setVisibility(VISIBLE);
+            mButton.setImageDrawable(mIconDrawable);
+            mButton.setOnClickListener(mClickListener);
+            mButton.setLongClickable(mLongClickListener != null);
+            mButton.setOnLongClickListener(mLongClickListener);
+            mButton.setContentDescription(mContentDescription);
+        }
+
+        // When finished expanding the action chip schedule the collapse transition in 3 seconds.
+        if (mState == State.SHOWING_ACTION_CHIP) {
+            getHandler().postDelayed(mCollapseActionChipRunnable, ACTION_CHIP_COLLAPSE_DELAY_MS);
+        }
+    }
+
+    /** Implementation of {@link TransitionListener}. Not used. */
+    @Override
+    public void onTransitionCancel(Transition transition) {}
+
+    /** Implementation of {@link TransitionListener}. Not used. */
+    @Override
+    public void onTransitionPause(Transition transition) {}
+
+    /** Implementation of {@link TransitionListener}. Not used. */
+    @Override
+    public void onTransitionResume(Transition transition) {}
+
+    private Transition createSwapIconTransition() {
+        TransitionSet transition = new TransitionSet();
+        transition.setOrdering(TransitionSet.ORDERING_TOGETHER);
+
+        // All appearing/disappearing views will fade in/out.
+        Fade fade = new Fade();
+
+        // When appearing/disappearing mButton will shrink/grow.
+        ShrinkTransition shrink = new ShrinkTransition();
+        shrink.addTarget(mButton);
+
+        // When appearing/disappearing mAnimationImage will move from/to the top.
+        Slide slide = new Slide(Gravity.TOP);
+        slide.addTarget(mAnimationImage);
+
+        transition.addTransition(slide).addTransition(shrink).addTransition(fade);
+        transition.setDuration(SWAP_TRANSITION_DURATION_MS);
+        transition.addListener(this);
+
+        return transition;
+    }
+
+    private Transition createShowHideTransition() {
+        TransitionSet transition = new TransitionSet();
+        transition.setOrdering(TransitionSet.ORDERING_TOGETHER);
+
+        Fade fade = new Fade();
+        // When showing/hiding this view we change its width from/to 0dp, this transition animates
+        // that width change.
+        ChangeBounds changeBounds = new ChangeBounds();
+
+        // When mButton shows/hides we use a grow/shrink animation.
+        ShrinkTransition shrink = new ShrinkTransition();
+
+        // When mButton and mBackground show up or hide they slide from/to the end (right in LTR,
+        // left in RTL).
+        Slide slide = new Slide(Gravity.END);
+
+        transition.addTransition(slide).addTransition(shrink).addTransition(fade).addTransition(
+                changeBounds);
+
+        transition.setDuration(HIDE_TRANSITION_DURATION_MS);
+        transition.addListener(this);
+
+        return transition;
+    }
+
+    private Transition createActionChipTransition() {
+        TransitionSet transitionSet = new TransitionSet();
+        transitionSet.setOrdering(TransitionSet.ORDERING_TOGETHER);
+
+        // During the action chip transition we change this view's width to fit the action chip
+        // label, this transition animates that change.
+        ChangeBounds changeBounds = new ChangeBounds();
+
+        // The action chip label and the new icon fade in and grow when showing up.
+        Fade fade = new Fade();
+        ShrinkTransition shrinkTransition = new ShrinkTransition();
+
+        transitionSet.addTransition(changeBounds)
+                .addTransition(fade)
+                .addTransition(shrinkTransition);
+
+        transitionSet.setDuration(SWAP_TRANSITION_DURATION_MS);
+        transitionSet.addListener(this);
+
+        return transitionSet;
+    }
+
+    private @TransitionType int getCurrentTransitionType() {
+        switch (mState) {
+            case State.RUNNING_ACTION_CHIP_COLLAPSE_TRANSITION:
+                return TransitionType.COLLAPSING_ACTION_CHIP;
+            case State.RUNNING_ACTION_CHIP_EXPANSION_TRANSITION:
+                return TransitionType.EXPANDING_ACTION_CHIP;
+            case State.RUNNING_HIDE_TRANSITION:
+                return TransitionType.HIDING;
+            case State.RUNNING_SHOW_TRANSITION:
+                return TransitionType.SHOWING;
+            case State.RUNNING_SWAP_TRANSITION:
+                return TransitionType.SWAPPING;
+            case State.HIDDEN:
+            case State.SHOWING_ACTION_CHIP:
+            case State.SHOWING_ICON:
+                return TransitionType.NONE;
+            default:
+                throw new IllegalStateException("Unexpected value: " + mState);
+        }
+    }
+
+    private void setWidth(int widthPx) {
+        ViewGroup.LayoutParams layoutParams = this.getLayoutParams();
+
+        layoutParams.width = widthPx;
+
+        setLayoutParams(layoutParams);
+    }
+
+    private boolean isRunningTransition() {
+        return mState == State.RUNNING_SHOW_TRANSITION || mState == State.RUNNING_HIDE_TRANSITION
+                || mState == State.RUNNING_ACTION_CHIP_EXPANSION_TRANSITION
+                || mState == State.RUNNING_ACTION_CHIP_COLLAPSE_TRANSITION
+                || mState == State.RUNNING_SWAP_TRANSITION;
+    }
+
+    private @State int getNextState() {
+        switch (mState) {
+            case State.RUNNING_ACTION_CHIP_COLLAPSE_TRANSITION:
+            case State.RUNNING_SWAP_TRANSITION:
+            case State.RUNNING_SHOW_TRANSITION:
+                return State.SHOWING_ICON;
+            case State.RUNNING_ACTION_CHIP_EXPANSION_TRANSITION:
+                return State.SHOWING_ACTION_CHIP;
+            case State.RUNNING_HIDE_TRANSITION:
+                return State.HIDDEN;
+            default:
+                return mState;
+        }
+    }
+
+    private void animateSwapToNewIcon() {
+        if (mState != State.SHOWING_ICON) return;
+
+        boolean isRevertingToStatic =
+                mCurrentButtonType == ButtonType.DYNAMIC && mNextButtonType == ButtonType.STATIC;
+
+        // Set the background color filter before the transition, these changes are done instantly.
+        if (mNextButtonType == ButtonType.DYNAMIC) {
+            mBackground.setColorFilter(mBackgroundColorFilter);
+        }
+
+        // In mSwapIconTransition mAnimationImage always slides from/to the top, and mButton always
+        // grows/shrinks.
+        ImageView slidingIcon = mAnimationImage;
+        ImageView shrinkingIcon = mButton;
+
+        Drawable newIcon = mIconDrawable;
+        Drawable oldIcon = mButton.getDrawable();
+
+        // Prepare icons for the transition, these changes are done instantly.
+        if (!isRevertingToStatic) {
+            // In the default transition we want the new icon to slide from the top and the old one
+            // to shrink.
+            slidingIcon.setImageDrawable(newIcon);
+        } else {
+            // In the reverse transition we want the new icon to grow and the old icon to slide to
+            // the top
+            slidingIcon.setImageDrawable(oldIcon);
+            slidingIcon.setVisibility(VISIBLE);
+            shrinkingIcon.setImageDrawable(newIcon);
+            shrinkingIcon.setVisibility(GONE);
+        }
+
+        // Begin a transition, all layout changes after this call will be animated. The animation
+        // starts at the next frame.
+        TransitionManager.beginDelayedTransition(mTransitionRoot, createSwapIconTransition());
+
+        // Default transition.
+        if (!isRevertingToStatic) {
+            // New icon slides from the top.
+            slidingIcon.setVisibility(VISIBLE);
+            // Old icon shrinks.
+            shrinkingIcon.setVisibility(GONE);
+        }
+        // Reverse transition.
+        else {
+            // Old icon slides to the top.
+            slidingIcon.setVisibility(GONE);
+            // New icon embiggens.
+            shrinkingIcon.setVisibility(VISIBLE);
+        }
+
+        // Background shows/hides with a fade animation.
+        mBackground.setVisibility(mNextButtonType == ButtonType.DYNAMIC ? VISIBLE : GONE);
+
+        mState = State.RUNNING_SWAP_TRANSITION;
+    }
+
+    private void animateActionChipExpansion() {
+        if (mState != State.SHOWING_ICON && mState != State.HIDDEN) {
+            return;
+        }
+
+        if (getVisibility() == GONE) {
+            setVisibility(VISIBLE);
+            setWidth(0);
+        }
+
+        // Prepare views for the transition, these changes aren't animated.
+
+        mActionChipLabel.setVisibility(GONE);
+        mActionChipLabel.setText(mActionChipLabelString);
+
+        mAnimationImage.setImageDrawable(mButton.getDrawable());
+        mAnimationImage.setVisibility(VISIBLE);
+
+        mButton.setImageDrawable(mIconDrawable);
+        mButton.setVisibility(GONE);
+
+        mBackground.setColorFilter(mBackgroundColorFilter);
+
+        // Begin a transition, all layout changes after this call will be animated. The animation
+        // starts at the next frame.
+        TransitionManager.beginDelayedTransition(mTransitionRoot, createActionChipTransition());
+
+        mButton.setVisibility(VISIBLE);
+        mAnimationImage.setVisibility(GONE);
+        mActionChipLabel.setVisibility(VISIBLE);
+        mBackground.setVisibility(VISIBLE);
+
+        // TODO(salg): Add a check to avoid expanding too much.
+        float actionChipLabelTextWidth =
+                mActionChipLabel.getPaint().measureText(mActionChipLabelString);
+
+        int expandedStateWidthPx =
+                (int) (mCollapsedStateWidthPx + actionChipLabelTextWidth + mExpandedStatePaddingPx);
+        setWidth(expandedStateWidthPx);
+
+        mState = State.RUNNING_ACTION_CHIP_EXPANSION_TRANSITION;
+    }
+
+    private void animateActionChipCollapse() {
+        // Begin a transition, all layout changes after this call will be animated. The animation
+        // starts at the next frame.
+        TransitionManager.beginDelayedTransition(mTransitionRoot, createActionChipTransition());
+
+        mActionChipLabel.setVisibility(GONE);
+        setWidth(mCollapsedStateWidthPx);
+
+        mState = State.RUNNING_ACTION_CHIP_COLLAPSE_TRANSITION;
+    }
+
+    private void hide(boolean animate) {
+        Transition transition = createShowHideTransition();
+        if (!animate) {
+            transition.setDuration(0);
+        }
+        // Begin a transition, all layout changes after this call will be animated. The animation
+        // starts at the next frame.
+        TransitionManager.beginDelayedTransition(mTransitionRoot, transition);
+
+        mButton.setVisibility(GONE);
+        mBackground.setVisibility(GONE);
+        setWidth(0);
+
+        if (mOnBeforeHideTransitionCallback != null) {
+            mOnBeforeHideTransitionCallback.run();
+        }
+
+        mState = State.RUNNING_HIDE_TRANSITION;
+    }
+
+    private void showIcon(boolean animate) {
+        Transition transition = createShowHideTransition();
+        if (!animate) {
+            transition.setDuration(0);
+        }
+
+        // Prepare views for the transition, these changes aren't animated.
+        this.setVisibility(VISIBLE);
+        setWidth(0);
+
+        mButton.setVisibility(GONE);
+        mBackground.setVisibility(GONE);
+        mAnimationImage.setVisibility(GONE);
+
+        mButton.setImageDrawable(mIconDrawable);
+
+        // Begin a transition, all layout changes after this call will be animated. The animation
+        // starts at the next frame.
+        TransitionManager.beginDelayedTransition(mTransitionRoot, transition);
+
+        setWidth(mCollapsedStateWidthPx);
+        mButton.setVisibility(VISIBLE);
+
+        mBackground.setVisibility(mNextButtonType == ButtonType.DYNAMIC ? VISIBLE : GONE);
+
+        mState = State.RUNNING_SHOW_TRANSITION;
+    }
+}
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/optional_button/OptionalButtonViewTest.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/optional_button/OptionalButtonViewTest.java
new file mode 100644
index 0000000..f1a2b98
--- /dev/null
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/optional_button/OptionalButtonViewTest.java
@@ -0,0 +1,489 @@
+// Copyright 2022 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.toolbar.optional_button;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.app.Activity;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.os.Looper;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnLongClickListener;
+import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import androidx.appcompat.content.res.AppCompatResources;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InOrder;
+import org.mockito.Mockito;
+import org.robolectric.Robolectric;
+import org.robolectric.Shadows;
+import org.robolectric.shadows.ShadowLooper;
+
+import org.chromium.base.Callback;
+import org.chromium.base.supplier.BooleanSupplier;
+import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.chrome.browser.toolbar.ButtonData;
+import org.chromium.chrome.browser.toolbar.ButtonData.ButtonSpec;
+import org.chromium.chrome.browser.toolbar.ButtonDataImpl;
+import org.chromium.chrome.browser.toolbar.R;
+import org.chromium.chrome.browser.toolbar.adaptive.AdaptiveToolbarFeatures.AdaptiveToolbarButtonVariant;
+import org.chromium.chrome.browser.toolbar.optional_button.OptionalButtonConstants.TransitionType;
+
+/**
+ * Unit tests for OptionalButtonView.
+ */
+@RunWith(BaseRobolectricTestRunner.class)
+public class OptionalButtonViewTest {
+    private Activity mActivity;
+
+    private OptionalButtonView mOptionalButtonView;
+    private ImageButton mInnerButton;
+    private TextView mActionChipLabel;
+    private ImageView mButtonBackground;
+    private ShadowLooper mShadowLooper;
+    private BooleanSupplier mMockAnimationChecker;
+
+    @Before
+    public void setUp() {
+        mActivity = Robolectric.setupActivity(Activity.class);
+        mMockAnimationChecker = Mockito.mock(BooleanSupplier.class);
+        when(mMockAnimationChecker.getAsBoolean()).thenReturn(true);
+
+        mOptionalButtonView = (OptionalButtonView) LayoutInflater.from(mActivity).inflate(
+                R.layout.optional_button_layout, null, false);
+        mOptionalButtonView.setLayoutParams(
+                new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
+        mOptionalButtonView.setIsAnimationAllowedPredicate(mMockAnimationChecker);
+
+        mShadowLooper = Shadows.shadowOf(Looper.getMainLooper());
+        Handler handler = new Handler(Looper.getMainLooper());
+        // This handler is used to schedule the action chip collapse.
+        mOptionalButtonView.setHandlerForTesting(handler);
+
+        mInnerButton = (ImageButton) mOptionalButtonView.getButtonView();
+        mActionChipLabel = mOptionalButtonView.findViewById(R.id.action_chip_label);
+        mButtonBackground =
+                mOptionalButtonView.findViewById(R.id.swappable_icon_secondary_background);
+    }
+
+    private ButtonDataImpl getDataForStaticNewTabIconButton() {
+        Drawable iconDrawable = AppCompatResources.getDrawable(mActivity, R.drawable.new_tab_icon);
+        OnClickListener clickListener = mock(OnClickListener.class);
+        OnLongClickListener longClickListener = mock(OnLongClickListener.class);
+        int contentDescriptionId = R.string.button_new_tab;
+
+        // Whether a button is static or dynamic is determined by the button variant.
+        ButtonSpec buttonSpec = new ButtonSpec(iconDrawable, clickListener, longClickListener,
+                contentDescriptionId, true, null, /* buttonVariant= */
+                AdaptiveToolbarButtonVariant.NEW_TAB, /*actionChipLabelResId=*/Resources.ID_NULL);
+        ButtonDataImpl buttonData = new ButtonDataImpl();
+        buttonData.setButtonSpec(buttonSpec);
+        buttonData.setCanShow(true);
+        buttonData.setEnabled(true);
+
+        return buttonData;
+    }
+
+    private ButtonDataImpl getDataForDynamicPriceTrackingIconButton() {
+        Drawable iconDrawable = AppCompatResources.getDrawable(mActivity, R.drawable.btn_mic);
+        OnClickListener clickListener = mock(OnClickListener.class);
+        OnLongClickListener longClickListener = mock(OnLongClickListener.class);
+        int contentDescriptionId = R.string.menu_track_prices;
+
+        // Whether a button is static or dynamic is determined by the button variant.
+        ButtonSpec buttonSpec = new ButtonSpec(iconDrawable, clickListener, longClickListener,
+                contentDescriptionId, true, null, /* buttonVariant= */
+                AdaptiveToolbarButtonVariant.PRICE_TRACKING,
+                /*actionChipLabelResId=*/Resources.ID_NULL);
+        ButtonDataImpl buttonData = new ButtonDataImpl();
+        buttonData.setButtonSpec(buttonSpec);
+        buttonData.setCanShow(true);
+        buttonData.setEnabled(true);
+
+        return buttonData;
+    }
+
+    private ButtonDataImpl getDataForShareIconActionChip() {
+        Drawable iconDrawable = AppCompatResources.getDrawable(mActivity, R.drawable.new_tab_icon);
+        OnClickListener clickListener = mock(OnClickListener.class);
+        OnLongClickListener longClickListener = mock(OnLongClickListener.class);
+        int contentDescriptionId = R.string.actionbar_share;
+        int actionChipLabelResId = R.string.adaptive_toolbar_button_preference_share;
+
+        ButtonSpec buttonSpec = new ButtonSpec(iconDrawable, clickListener, longClickListener,
+                contentDescriptionId, true, null, /* buttonVariant= */
+                AdaptiveToolbarButtonVariant.PRICE_TRACKING,
+                /*actionChipLabelResId=*/actionChipLabelResId);
+        ButtonDataImpl buttonData = new ButtonDataImpl();
+        buttonData.setButtonSpec(buttonSpec);
+        buttonData.setCanShow(true);
+        buttonData.setEnabled(true);
+
+        return buttonData;
+    }
+
+    @Test
+    public void testSetButtonEnabled() {
+        ButtonDataImpl disabledButton = getDataForDynamicPriceTrackingIconButton();
+        disabledButton.setEnabled(false);
+        ViewGroup transitionRoot = mock(ViewGroup.class);
+
+        mOptionalButtonView.setTransitionRoot(transitionRoot);
+
+        mOptionalButtonView.updateButtonWithAnimation(disabledButton);
+
+        // The enabled property should be reflected immediately.
+        assertFalse(mOptionalButtonView.getButtonView().isEnabled());
+    }
+
+    @Test
+    public void testSetPaddingStart() {
+        mOptionalButtonView.setPaddingStart(42);
+
+        assertEquals(42, mOptionalButtonView.getPaddingStart());
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testSetIconDrawableWithAnimation_throwsExceptionWithNoTransitionRoot() {
+        // If we don't set a transitionRoot then we can't perform transitions.
+        mOptionalButtonView.updateButtonWithAnimation(null);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testSetIconDrawableWithAnimation_throwsExceptionWithNoAnimationPredicate() {
+        // If we don't set an isAnimationAllowedPredicate then we can't perform transitions.
+        mOptionalButtonView.setIsAnimationAllowedPredicate(null);
+        mOptionalButtonView.updateButtonWithAnimation(null);
+    }
+
+    @Test
+    public void testSetIconDrawableWithAnimation_fromHiddenToIcon() {
+        ButtonData buttonData = getDataForDynamicPriceTrackingIconButton();
+        String contentDescriptionString = mActivity.getResources().getString(
+                buttonData.getButtonSpec().getContentDescriptionResId());
+        ViewGroup transitionRoot = mock(ViewGroup.class);
+
+        mOptionalButtonView.setTransitionRoot(transitionRoot);
+        mOptionalButtonView.updateButtonWithAnimation(buttonData);
+
+        // Normally called by TransitionManager.
+        mOptionalButtonView.onTransitionStart(null);
+        mOptionalButtonView.onTransitionEnd(null);
+
+        assertEquals(View.VISIBLE, mOptionalButtonView.getVisibility());
+        assertEquals(View.VISIBLE, mInnerButton.getVisibility());
+        assertEquals(buttonData.getButtonSpec().getDrawable(), mInnerButton.getDrawable());
+        assertEquals(contentDescriptionString, mInnerButton.getContentDescription());
+        // Dynamic buttons have a background.
+        assertEquals(View.VISIBLE, mButtonBackground.getVisibility());
+        assertEquals(View.GONE, mActionChipLabel.getVisibility());
+    }
+
+    @Test
+    public void testSetIconDrawableWithAnimation_swapIcons() {
+        ButtonData firstButtonData = getDataForDynamicPriceTrackingIconButton();
+        ButtonData secondButtonData = getDataForStaticNewTabIconButton();
+
+        ViewGroup transitionRoot = mock(ViewGroup.class);
+        mOptionalButtonView.setTransitionRoot(transitionRoot);
+
+        // Transition from hidden to firstIcon.
+        mOptionalButtonView.updateButtonWithAnimation(firstButtonData);
+
+        // Normally called by TransitionManager.
+        mOptionalButtonView.onTransitionStart(null);
+        mOptionalButtonView.onTransitionEnd(null);
+
+        // Transition from firstIcon to secondIcon.
+        mOptionalButtonView.updateButtonWithAnimation(secondButtonData);
+
+        // Normally called by TransitionManager.
+        mOptionalButtonView.onTransitionStart(null);
+
+        // In the middle of the transition all handlers should be disabled.
+        mInnerButton.performClick();
+        mInnerButton.performLongClick();
+
+        mOptionalButtonView.onTransitionEnd(null);
+
+        mInnerButton.performClick();
+        mInnerButton.performLongClick();
+
+        assertEquals(View.VISIBLE, mOptionalButtonView.getVisibility());
+        assertEquals(View.VISIBLE, mInnerButton.getVisibility());
+        assertEquals(secondButtonData.getButtonSpec().getDrawable(), mInnerButton.getDrawable());
+        // Second button is static, it has no background.
+        assertEquals(View.GONE, mButtonBackground.getVisibility());
+        verify(secondButtonData.getButtonSpec().getOnClickListener()).onClick(any());
+        verify(secondButtonData.getButtonSpec().getOnLongClickListener()).onLongClick(any());
+        verifyNoMoreInteractions(firstButtonData.getButtonSpec().getOnClickListener(),
+                firstButtonData.getButtonSpec().getOnLongClickListener());
+    }
+
+    @Test
+    public void testSetIconDrawableWithAnimation_expandActionChipFromHidden() {
+        ButtonData actionChipButtonData = getDataForShareIconActionChip();
+        String actionChipLabel = mActivity.getResources().getString(
+                actionChipButtonData.getButtonSpec().getActionChipLabelResId());
+
+        ViewGroup transitionRoot = mock(ViewGroup.class);
+        mOptionalButtonView.setTransitionRoot(transitionRoot);
+
+        // Setting an action chip label string indicates that the button should transition to an
+        // action chip
+        mOptionalButtonView.updateButtonWithAnimation(actionChipButtonData);
+
+        // Normally called by TransitionManager.
+        mOptionalButtonView.onTransitionStart(null);
+        // In the middle of the expand transition we shouldn't listen to clicks.
+        mInnerButton.performClick();
+        mOptionalButtonView.onTransitionEnd(null);
+
+        // Click listener should be set when the expand transition is finished.
+        mInnerButton.performClick();
+
+        verify(actionChipButtonData.getButtonSpec().getOnClickListener(), times(1)).onClick(any());
+        assertEquals(View.VISIBLE, mOptionalButtonView.getVisibility());
+        assertEquals(View.VISIBLE, mInnerButton.getVisibility());
+        assertEquals(View.VISIBLE, mActionChipLabel.getVisibility());
+        assertEquals(actionChipLabel, mActionChipLabel.getText());
+    }
+
+    @Test
+    public void testSetIconDrawableWithAnimation_expandAndCollapseActionChipFromHidden() {
+        ButtonData actionChipButtonData = getDataForShareIconActionChip();
+
+        ViewGroup transitionRoot = mock(ViewGroup.class);
+        mOptionalButtonView.setTransitionRoot(transitionRoot);
+
+        // Setting an action chip label string indicates that the button should transition to an
+        // action chip
+        mOptionalButtonView.updateButtonWithAnimation(actionChipButtonData);
+
+        // Normally called by TransitionManager.
+        mOptionalButtonView.onTransitionStart(null);
+        mOptionalButtonView.onTransitionEnd(null);
+
+        // Advance looper to begin collapse transition.
+        mShadowLooper.runOneTask();
+
+        // Normally called by TransitionManager.
+        mOptionalButtonView.onTransitionStart(null);
+        // Click listener should remain active during this transition.
+        mInnerButton.performClick();
+        mOptionalButtonView.onTransitionEnd(null);
+
+        // Click listener should remain the same after the transition
+        mInnerButton.performClick();
+
+        verify(actionChipButtonData.getButtonSpec().getOnClickListener(), times(2)).onClick(any());
+        assertEquals(View.VISIBLE, mOptionalButtonView.getVisibility());
+        assertEquals(View.VISIBLE, mInnerButton.getVisibility());
+        assertEquals(View.GONE, mActionChipLabel.getVisibility());
+    }
+
+    @Test
+    public void testSetIconDrawableWithAnimation_expandActionChipFromAnotherIcon() {
+        ButtonData staticButtonData = getDataForStaticNewTabIconButton();
+        ButtonData actionChipButtonData = getDataForShareIconActionChip();
+
+        ViewGroup transitionRoot = mock(ViewGroup.class);
+        mOptionalButtonView.setTransitionRoot(transitionRoot);
+
+        // Transition from hidden to staticButton.
+        mOptionalButtonView.updateButtonWithAnimation(staticButtonData);
+
+        // Normally called by TransitionManager.
+        mOptionalButtonView.onTransitionStart(null);
+        mOptionalButtonView.onTransitionEnd(null);
+
+        // Transition from firstIcon to an action chip with secondIcon.
+        mOptionalButtonView.updateButtonWithAnimation(actionChipButtonData);
+
+        // Normally called by TransitionManager.
+        mOptionalButtonView.onTransitionStart(null);
+        // Click button in the middle of the action chip expansion transition, nothing should
+        // happen.
+        mInnerButton.performClick();
+        mOptionalButtonView.onTransitionEnd(null);
+
+        // Second click listener should be set after action chip expansion is finished.
+        mInnerButton.performClick();
+
+        verify(actionChipButtonData.getButtonSpec().getOnClickListener(), times(1)).onClick(any());
+        verifyNoMoreInteractions(staticButtonData.getButtonSpec().getOnClickListener());
+        assertEquals(View.VISIBLE, mOptionalButtonView.getVisibility());
+        assertEquals(View.VISIBLE, mInnerButton.getVisibility());
+        assertEquals(View.VISIBLE, mActionChipLabel.getVisibility());
+    }
+
+    @Test
+    public void testSetIconDrawableWithAnimation_hideIcon() {
+        ButtonData buttonData = getDataForStaticNewTabIconButton();
+
+        ViewGroup transitionRoot = mock(ViewGroup.class);
+        mOptionalButtonView.setTransitionRoot(transitionRoot);
+
+        // Transition from hidden to firstIcon.
+        mOptionalButtonView.updateButtonWithAnimation(buttonData);
+
+        // Normally called by TransitionManager.
+        mOptionalButtonView.onTransitionStart(null);
+        mOptionalButtonView.onTransitionEnd(null);
+
+        // Setting the icon drawable to null should trigger a hiding transition.
+        mOptionalButtonView.updateButtonWithAnimation(null);
+
+        // Normally called by TransitionManager.
+        mOptionalButtonView.onTransitionStart(null);
+        mOptionalButtonView.onTransitionEnd(null);
+
+        // Button should now be hidden, and its callbacks should be removed.
+        mInnerButton.performClick();
+
+        verify(buttonData.getButtonSpec().getOnClickListener(), never()).onClick(any());
+        assertEquals(View.GONE, mOptionalButtonView.getVisibility());
+        assertEquals(View.GONE, mInnerButton.getVisibility());
+        assertEquals(View.GONE, mActionChipLabel.getVisibility());
+    }
+
+    @Test
+    public void testTransitionCallbacks() {
+        ButtonData firstButton = getDataForStaticNewTabIconButton();
+        ButtonData secondButton = getDataForDynamicPriceTrackingIconButton();
+        ButtonData actionChipButton = getDataForShareIconActionChip();
+
+        Runnable beforeHideTransitionCallback = mock(Runnable.class);
+        Callback<Integer> transitionStartedCallback = mock(Callback.class);
+        Callback<Integer> transitionFinishedCallback = mock(Callback.class);
+
+        mOptionalButtonView.setTransitionStartedCallback(transitionStartedCallback);
+        mOptionalButtonView.setTransitionFinishedCallback(transitionFinishedCallback);
+        mOptionalButtonView.setOnBeforeHideTransitionCallback(beforeHideTransitionCallback);
+
+        InOrder inOrder = Mockito.inOrder(beforeHideTransitionCallback, transitionStartedCallback,
+                transitionFinishedCallback);
+
+        ViewGroup transitionRoot = mock(ViewGroup.class);
+        mOptionalButtonView.setTransitionRoot(transitionRoot);
+
+        // Transition from hidden to firstButton.
+        mOptionalButtonView.updateButtonWithAnimation(firstButton);
+        mOptionalButtonView.onTransitionStart(null);
+        mOptionalButtonView.onTransitionEnd(null);
+
+        // Transition from firstButton to secondButton
+        mOptionalButtonView.updateButtonWithAnimation(secondButton);
+        mOptionalButtonView.onTransitionStart(null);
+        mOptionalButtonView.onTransitionEnd(null);
+
+        // Transition from secondButton to actionChipButton
+        mOptionalButtonView.updateButtonWithAnimation(actionChipButton);
+        // Run callbacks for expansion transition.
+        mOptionalButtonView.onTransitionStart(null);
+        mOptionalButtonView.onTransitionEnd(null);
+
+        // Advance looper to begin collapse transition.
+        mShadowLooper.runOneTask();
+        // Run callbacks for collapse transition.
+        mOptionalButtonView.onTransitionStart(null);
+        mOptionalButtonView.onTransitionEnd(null);
+
+        // Transition from actionChipButton to hidden.
+        mOptionalButtonView.updateButtonWithAnimation(null);
+        mOptionalButtonView.onTransitionStart(null);
+        mOptionalButtonView.onTransitionEnd(null);
+
+        // Verify that callbacks are called in the expected order with the right arguments.
+        inOrder.verify(transitionStartedCallback).onResult(TransitionType.SHOWING);
+        inOrder.verify(transitionFinishedCallback).onResult(TransitionType.SHOWING);
+        inOrder.verify(transitionStartedCallback).onResult(TransitionType.SWAPPING);
+        inOrder.verify(transitionFinishedCallback).onResult(TransitionType.SWAPPING);
+        inOrder.verify(transitionStartedCallback).onResult(TransitionType.EXPANDING_ACTION_CHIP);
+        inOrder.verify(transitionFinishedCallback).onResult(TransitionType.EXPANDING_ACTION_CHIP);
+        inOrder.verify(transitionStartedCallback).onResult(TransitionType.COLLAPSING_ACTION_CHIP);
+        inOrder.verify(transitionFinishedCallback).onResult(TransitionType.COLLAPSING_ACTION_CHIP);
+        inOrder.verify(beforeHideTransitionCallback).run();
+        inOrder.verify(transitionStartedCallback).onResult(TransitionType.HIDING);
+        inOrder.verify(transitionFinishedCallback).onResult(TransitionType.HIDING);
+    }
+
+    @Test
+    public void testTransitionCallbacks_withAnimationDisabled() {
+        ButtonData firstButton = getDataForStaticNewTabIconButton();
+        ButtonData secondButton = getDataForDynamicPriceTrackingIconButton();
+        ButtonData actionChipButton = getDataForShareIconActionChip();
+        when(mMockAnimationChecker.getAsBoolean()).thenReturn(false);
+
+        Runnable beforeHideTransitionCallback = mock(Runnable.class);
+        Callback<Integer> transitionStartedCallback = mock(Callback.class);
+        Callback<Integer> transitionFinishedCallback = mock(Callback.class);
+
+        mOptionalButtonView.setTransitionStartedCallback(transitionStartedCallback);
+        mOptionalButtonView.setTransitionFinishedCallback(transitionFinishedCallback);
+        mOptionalButtonView.setOnBeforeHideTransitionCallback(beforeHideTransitionCallback);
+
+        InOrder inOrder = Mockito.inOrder(beforeHideTransitionCallback, transitionStartedCallback,
+                transitionFinishedCallback);
+
+        ViewGroup transitionRoot = mock(ViewGroup.class);
+        mOptionalButtonView.setTransitionRoot(transitionRoot);
+
+        // Transition from hidden to firstButton.
+        mOptionalButtonView.updateButtonWithAnimation(firstButton);
+        mOptionalButtonView.onTransitionStart(null);
+        mOptionalButtonView.onTransitionEnd(null);
+
+        // Transition from firstButton to secondButton
+        mOptionalButtonView.updateButtonWithAnimation(secondButton);
+        mOptionalButtonView.onTransitionStart(null);
+        mOptionalButtonView.onTransitionEnd(null);
+
+        // Transition from secondButton to actionChipButton, when animations are disabled we don't
+        // expand the action chip, as the width change would look jarring. Instead we just update
+        // its icon.
+        mOptionalButtonView.updateButtonWithAnimation(actionChipButton);
+        // Run callbacks for update transition.
+        mOptionalButtonView.onTransitionStart(null);
+        mOptionalButtonView.onTransitionEnd(null);
+
+        // Transition from actionChipButton to hidden.
+        mOptionalButtonView.updateButtonWithAnimation(null);
+        mOptionalButtonView.onTransitionStart(null);
+        mOptionalButtonView.onTransitionEnd(null);
+
+        // Verify that callbacks are called in the expected order with the right arguments,
+        // non-animated updates use either SHOWING or HIDING.
+        inOrder.verify(transitionStartedCallback).onResult(TransitionType.SHOWING);
+        inOrder.verify(transitionFinishedCallback).onResult(TransitionType.SHOWING);
+        inOrder.verify(transitionStartedCallback).onResult(TransitionType.SHOWING);
+        inOrder.verify(transitionFinishedCallback).onResult(TransitionType.SHOWING);
+        inOrder.verify(transitionStartedCallback).onResult(TransitionType.SHOWING);
+        inOrder.verify(transitionFinishedCallback).onResult(TransitionType.SHOWING);
+        inOrder.verify(beforeHideTransitionCallback).run();
+        inOrder.verify(transitionStartedCallback).onResult(TransitionType.HIDING);
+        inOrder.verify(transitionFinishedCallback).onResult(TransitionType.HIDING);
+    }
+}
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/optional_button/ShrinkTransition.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/optional_button/ShrinkTransition.java
new file mode 100644
index 0000000..396f01d
--- /dev/null
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/optional_button/ShrinkTransition.java
@@ -0,0 +1,58 @@
+// Copyright 2022 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.toolbar.optional_button;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.graphics.Path;
+import android.transition.TransitionValues;
+import android.transition.Visibility;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * A transition that changes a view's scale when it's appearing or disappearing, it animates both
+ * scaleX and scaleY towards 0 when disappearing and towards 1 when appearing.
+ */
+public class ShrinkTransition extends Visibility {
+    @Override
+    public Animator onAppear(ViewGroup sceneRoot, View view, TransitionValues startValues,
+            TransitionValues endValues) {
+        return createAnimation(view, view.getScaleX(), 1);
+    }
+
+    @Override
+    public Animator onDisappear(ViewGroup sceneRoot, View view, TransitionValues startValues,
+            TransitionValues endValues) {
+        return createAnimation(view, view.getScaleX(), 0);
+    }
+
+    private Animator createAnimation(final View view, float startScale, float endScale) {
+        Path animationPath = new Path();
+        animationPath.moveTo(startScale, startScale);
+        animationPath.lineTo(endScale, endScale);
+
+        final ObjectAnimator animator =
+                ObjectAnimator.ofFloat(view, "ScaleX", "ScaleY", animationPath);
+
+        if (endScale == 0) {
+            animator.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    // We reset the scale to 1 when disappearing because the next time this view is
+                    // visible this transition may not be involved, so it'll be visible but with a
+                    // scale of 0.
+                    view.setScaleX(1);
+                    view.setScaleY(1);
+
+                    super.onAnimationEnd(animation);
+                }
+            });
+        }
+
+        return animator;
+    }
+}
diff --git a/chrome/browser/ui/ash/desks/desks_client.cc b/chrome/browser/ui/ash/desks/desks_client.cc
index 5539ce8..f5299e6 100644
--- a/chrome/browser/ui/ash/desks/desks_client.cc
+++ b/chrome/browser/ui/ash/desks/desks_client.cc
@@ -201,7 +201,8 @@
         std::make_unique<desks_storage::LocalDeskDataManager>(
             active_profile_->GetPath(), account_id);
 
-    if (ash::saved_desk_util::AreDesksTemplatesEnabled()) {
+    if (ash::saved_desk_util::AreDesksTemplatesEnabled() &&
+        chromeos::features::IsDeskTemplateSyncEnabled()) {
       saved_desk_storage_manager_ =
           std::make_unique<desks_storage::DeskModelWrapper>(
               save_and_recall_desks_storage_manager_.get());
@@ -415,7 +416,8 @@
 
 desks_storage::DeskModel* DesksClient::GetDeskModel() {
   if (chromeos::features::IsSavedDesksEnabled()) {
-    if (!ash::saved_desk_util::AreDesksTemplatesEnabled()) {
+    if (!ash::saved_desk_util::AreDesksTemplatesEnabled() ||
+        !chromeos::features::IsDeskTemplateSyncEnabled()) {
       DCHECK(save_and_recall_desks_storage_manager_.get());
       return save_and_recall_desks_storage_manager_.get();
     }
diff --git a/chrome/browser/ui/browser_finder.cc b/chrome/browser/ui/browser_finder.cc
index 1d1f3be..38469e9a 100644
--- a/chrome/browser/ui/browser_finder.cc
+++ b/chrome/browser/ui/browser_finder.cc
@@ -204,6 +204,16 @@
                                         /*match_current_workspace=*/false);
 }
 
+std::vector<Browser*> FindAllTabbedBrowsersWithProfile(Profile* profile) {
+  std::vector<Browser*> browsers;
+  for (auto* browser : *BrowserList::GetInstance()) {
+    if (BrowserMatches(browser, profile, Browser::FEATURE_NONE, kMatchNormal,
+                       display::kInvalidDisplayId))
+      browsers.emplace_back(browser);
+  }
+  return browsers;
+}
+
 Browser* FindBrowserWithID(SessionID desired_id) {
   for (auto* browser : *BrowserList::GetInstance()) {
     if (browser->session_id() == desired_id)
diff --git a/chrome/browser/ui/browser_finder.h b/chrome/browser/ui/browser_finder.h
index 33e2b49c..f885be0 100644
--- a/chrome/browser/ui/browser_finder.h
+++ b/chrome/browser/ui/browser_finder.h
@@ -49,6 +49,10 @@
 // returned. Returns NULL if no such browser currently exists.
 Browser* FindBrowserWithProfile(Profile* profile);
 
+// Find all tabbed browsers with the provided profile. Returns an empty vector
+// if no such browser currently exists.
+std::vector<Browser*> FindAllTabbedBrowsersWithProfile(Profile* profile);
+
 // Find an existing browser with the provided ID. Returns NULL if no such
 // browser currently exists.
 Browser* FindBrowserWithID(SessionID desired_id);
diff --git a/chrome/browser/ui/browser_window.h b/chrome/browser/ui/browser_window.h
index 8b53e07..6c49714 100644
--- a/chrome/browser/ui/browser_window.h
+++ b/chrome/browser/ui/browser_window.h
@@ -24,6 +24,7 @@
 #include "chrome/browser/ui/exclusive_access/exclusive_access_bubble_type.h"
 #include "chrome/browser/ui/hats/hats_service.h"
 #include "chrome/browser/ui/page_action/page_action_icon_type.h"
+#include "chrome/browser/ui/translate/partial_translate_bubble_model.h"
 #include "chrome/common/buildflags.h"
 #include "components/content_settings/core/common/content_settings_types.h"
 #include "components/translate/core/common/translate_errors.h"
@@ -463,6 +464,14 @@
       translate::TranslateErrors::Type error_type,
       bool is_user_gesture) = 0;
 
+  // Shows the Partial Translate bubble.
+  virtual void ShowPartialTranslateBubble(
+      PartialTranslateBubbleModel::ViewState view_state,
+      const std::string& source_language,
+      const std::string& target_language,
+      const std::u16string& text_selection,
+      translate::TranslateErrors::Type error_type) = 0;
+
   // Shows the one-click sign in confirmation UI. |email| holds the full email
   // address of the account that has signed in.
   virtual void ShowOneClickSigninConfirmation(
diff --git a/chrome/browser/ui/extensions/extension_action_view_controller.cc b/chrome/browser/ui/extensions/extension_action_view_controller.cc
index c0266ac3..b1b99a9 100644
--- a/chrome/browser/ui/extensions/extension_action_view_controller.cc
+++ b/chrome/browser/ui/extensions/extension_action_view_controller.cc
@@ -95,23 +95,6 @@
   return false;
 }
 
-// static
-bool ExtensionActionViewController::AnyActionRequiresPageRefreshToRun(
-    const std::vector<ToolbarActionViewController*>& actions,
-    content::WebContents* web_contents) {
-  ExtensionActionRunner* action_runner =
-      ExtensionActionRunner::GetForWebContents(web_contents);
-
-  return std::any_of(
-      actions.begin(), actions.end(),
-      [action_runner](ToolbarActionViewController* action) {
-        auto blocked_actions =
-            action_runner->GetBlockedActions(action->GetId());
-        return blocked_actions &
-               ExtensionActionRunner::kRefreshRequiredActionsMask;
-      });
-}
-
 ExtensionActionViewController::ExtensionActionViewController(
     scoped_refptr<const extensions::Extension> extension,
     Browser* browser,
diff --git a/chrome/browser/ui/extensions/extension_action_view_controller.h b/chrome/browser/ui/extensions/extension_action_view_controller.h
index 1a83d837..7248884 100644
--- a/chrome/browser/ui/extensions/extension_action_view_controller.h
+++ b/chrome/browser/ui/extensions/extension_action_view_controller.h
@@ -54,12 +54,6 @@
       const std::vector<std::unique_ptr<ToolbarActionViewController>>& actions,
       content::WebContents* web_contents);
 
-  // Returns whether any of `actions` need a page refresh to execute its action
-  // in `web_contents`.
-  static bool AnyActionRequiresPageRefreshToRun(
-      const std::vector<ToolbarActionViewController*>& actions,
-      content::WebContents* web_contents);
-
   ExtensionActionViewController(const ExtensionActionViewController&) = delete;
   ExtensionActionViewController& operator=(
       const ExtensionActionViewController&) = delete;
diff --git a/chrome/browser/ui/extensions/extensions_dialogs.h b/chrome/browser/ui/extensions/extensions_dialogs.h
index 5354911..6d255b8 100644
--- a/chrome/browser/ui/extensions/extensions_dialogs.h
+++ b/chrome/browser/ui/extensions/extensions_dialogs.h
@@ -19,14 +19,15 @@
 
 namespace extensions {
 
-// Shows a dialog when an extension requires a refresh for the extension action
-// to run or be blocked. The dialog content is based on whether caller
+// Shows a dialog when extensions require a refresh for their action
+// to be run or blocked. The dialog content is based on whether caller
 // `is_updating_permissions`. When the dialog is accepted, `callback` is
 // invoked.
-void ShowReloadPageDialog(Browser* browser,
-                          const ExtensionId& extension_id,
-                          bool is_updating_permissions,
-                          base::OnceClosure callback);
+void ShowReloadPageDialog(
+    Browser* browser,
+    const std::vector<extensions::ExtensionId>& extension_ids,
+    bool is_updating_permissions,
+    base::OnceClosure callback);
 
 #if BUILDFLAG(IS_CHROMEOS)
 
diff --git a/chrome/browser/ui/omnibox/chrome_omnibox_client.cc b/chrome/browser/ui/omnibox/chrome_omnibox_client.cc
index e75acaf4..5e00f2b2 100644
--- a/chrome/browser/ui/omnibox/chrome_omnibox_client.cc
+++ b/chrome/browser/ui/omnibox/chrome_omnibox_client.cc
@@ -424,6 +424,15 @@
     controller_->GetWebContents()->Focus();
 }
 
+void ChromeOmniboxClient::OnSelectedMatchChanged(
+    size_t index,
+    const AutocompleteMatch& match) {
+  if (SearchPrefetchService* search_prefetch_service =
+          SearchPrefetchServiceFactory::GetForProfile(profile_)) {
+    search_prefetch_service->MaybePrefetchLikelyMatch(index, match);
+  }
+}
+
 void ChromeOmniboxClient::DoPrerender(const AutocompleteMatch& match) {
   content::WebContents* web_contents = controller_->GetWebContents();
 
diff --git a/chrome/browser/ui/omnibox/chrome_omnibox_client.h b/chrome/browser/ui/omnibox/chrome_omnibox_client.h
index dea3323f..1c6d746a 100644
--- a/chrome/browser/ui/omnibox/chrome_omnibox_client.h
+++ b/chrome/browser/ui/omnibox/chrome_omnibox_client.h
@@ -85,6 +85,8 @@
   void DiscardNonCommittedNavigations() override;
   void OpenUpdateChromeDialog() override;
   void FocusWebContents() override;
+  void OnSelectedMatchChanged(size_t index,
+                              const AutocompleteMatch& match) override;
 
   // Update shortcuts when a navigation succeeds.
   static void OnSuccessfulNavigation(Profile* profile,
diff --git a/chrome/browser/ui/quick_answers/quick_answers_state_ash_unittest.cc b/chrome/browser/ui/quick_answers/quick_answers_state_ash_unittest.cc
index 411ca0de..6d0bb64a 100644
--- a/chrome/browser/ui/quick_answers/quick_answers_state_ash_unittest.cc
+++ b/chrome/browser/ui/quick_answers/quick_answers_state_ash_unittest.cc
@@ -7,7 +7,9 @@
 #include "ash/constants/ash_pref_names.h"
 #include "ash/session/session_controller_impl.h"
 #include "ash/shell.h"
+#include "base/test/scoped_feature_list.h"
 #include "chrome/browser/ui/quick_answers/test/chrome_quick_answers_test_base.h"
+#include "chromeos/constants/chromeos_features.h"
 #include "components/language/core/browser/pref_names.h"
 #include "third_party/icu/source/common/unicode/locid.h"
 
@@ -218,6 +220,29 @@
   EXPECT_TRUE(observer()->is_eligible());
 }
 
+TEST_F(QuickAnswersStateAshTest, EligibleLocales) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndEnableFeature(
+      chromeos::features::kQuickAnswersForMoreLocales);
+
+  QuickAnswersState::Get()->AddObserver(observer());
+
+  EXPECT_FALSE(QuickAnswersState::Get()->is_eligible());
+  EXPECT_FALSE(observer()->is_eligible());
+
+  prefs()->SetString(language::prefs::kApplicationLocale, "pt");
+  SimulateUserLogin(kTestUser);
+  EXPECT_TRUE(QuickAnswersState::Get()->is_eligible());
+  EXPECT_TRUE(observer()->is_eligible());
+
+  ClearLogin();
+
+  prefs()->SetString(language::prefs::kApplicationLocale, "en");
+  SimulateUserLogin(kTestUser);
+  EXPECT_TRUE(QuickAnswersState::Get()->is_eligible());
+  EXPECT_TRUE(observer()->is_eligible());
+}
+
 TEST_F(QuickAnswersStateAshTest, LocaleIneligible) {
   QuickAnswersState::Get()->AddObserver(observer());
 
@@ -232,3 +257,26 @@
   EXPECT_FALSE(QuickAnswersState::Get()->is_eligible());
   EXPECT_FALSE(observer()->is_eligible());
 }
+
+TEST_F(QuickAnswersStateAshTest, IneligibleLocales) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndEnableFeature(
+      chromeos::features::kQuickAnswersForMoreLocales);
+
+  QuickAnswersState::Get()->AddObserver(observer());
+
+  EXPECT_FALSE(QuickAnswersState::Get()->is_eligible());
+  EXPECT_FALSE(observer()->is_eligible());
+
+  prefs()->SetString(language::prefs::kApplicationLocale, "zh");
+  SimulateUserLogin(kTestUser);
+  EXPECT_FALSE(QuickAnswersState::Get()->is_eligible());
+  EXPECT_FALSE(observer()->is_eligible());
+
+  ClearLogin();
+
+  prefs()->SetString(language::prefs::kApplicationLocale, "ja");
+  SimulateUserLogin(kTestUser);
+  EXPECT_FALSE(QuickAnswersState::Get()->is_eligible());
+  EXPECT_FALSE(observer()->is_eligible());
+}
diff --git a/chrome/browser/ui/views/extensions/extensions_dialogs_utils.cc b/chrome/browser/ui/views/extensions/extensions_dialogs_utils.cc
index 1f14b55..94653386 100644
--- a/chrome/browser/ui/views/extensions/extensions_dialogs_utils.cc
+++ b/chrome/browser/ui/views/extensions/extensions_dialogs_utils.cc
@@ -16,6 +16,7 @@
 #include "ui/gfx/native_widget_types.h"
 #include "ui/views/controls/image_view.h"
 #include "ui/views/layout/flex_layout_view.h"
+#include "ui/views/widget/widget.h"
 
 namespace {
 
@@ -26,6 +27,23 @@
                       : nullptr;
 }
 
+// Returns the extensions button when multiple `extension_ids` are given.
+// Otherwise returns the action view corresponding to the single extension if it
+// exists, or the extensions menu button.
+views::View* GetDialogAnchorView(
+    ExtensionsToolbarContainer* container,
+    const std::vector<extensions::ExtensionId>& extension_ids) {
+  DCHECK(container);
+  DCHECK(!extension_ids.empty());
+
+  if (extension_ids.size() > 1) {
+    return container->GetExtensionsButton();
+  }
+
+  views::View* const action_view = container->GetViewForId(extension_ids[0]);
+  return action_view ? action_view : container->GetExtensionsButton();
+}
+
 }  // namespace
 
 ExtensionsToolbarContainer* GetExtensionsToolbarContainer(Browser* browser) {
@@ -65,33 +83,36 @@
       base::UnescapeRule::NORMAL, nullptr, nullptr, nullptr);
 }
 
-views::View* GetDialogAnchorView(ExtensionsToolbarContainer* container,
-                                 const extensions::ExtensionId& extension_id) {
-  DCHECK(container);
-  views::View* const action_view = container->GetViewForId(extension_id);
-  return action_view ? action_view : container->GetExtensionsButton();
-}
-
 void ShowDialog(gfx::NativeWindow parent,
                 const extensions::ExtensionId& extension_id,
                 std::unique_ptr<ui::DialogModel> dialog_model) {
   ExtensionsToolbarContainer* container = GetExtensionsToolbarContainer(parent);
   if (container && container->GetVisible()) {
-    ShowDialog(container, extension_id, std::move(dialog_model));
+    ShowDialog(container, {extension_id}, std::move(dialog_model));
   } else {
     constrained_window::ShowBrowserModal(std::move(dialog_model), parent);
   }
 }
 
 void ShowDialog(ExtensionsToolbarContainer* container,
-                const extensions::ExtensionId& extension_id,
+                const std::vector<extensions::ExtensionId>& extension_ids,
                 std::unique_ptr<ui::DialogModel> dialog_model) {
   DCHECK(container);
-  views::View* const anchor_view = GetDialogAnchorView(container, extension_id);
-  auto bubble = std::make_unique<views::BubbleDialogModelHost>(
-      std::move(dialog_model), anchor_view, views::BubbleBorder::TOP_RIGHT);
 
-  container->ShowWidgetForExtension(
-      views::BubbleDialogDelegate::CreateBubble(std::move(bubble)),
-      extension_id);
+  auto bubble = std::make_unique<views::BubbleDialogModelHost>(
+      std::move(dialog_model), GetDialogAnchorView(container, extension_ids),
+      views::BubbleBorder::TOP_RIGHT);
+  views::Widget* widget =
+      views::BubbleDialogDelegate::CreateBubble(std::move(bubble));
+
+  if (extension_ids.size() > 1) {
+    // Show the widget using the default dialog anchor view.
+    widget->Show();
+  } else {
+    // Show the widget using the anchor view of the specific extension (which
+    // the container may need to popup out).
+    // TODO(emiliapaz): Consider moving showing the widget for extension to the
+    // utils to declutter the container file.
+    container->ShowWidgetForExtension(widget, extension_ids[0]);
+  }
 }
diff --git a/chrome/browser/ui/views/extensions/extensions_dialogs_utils.h b/chrome/browser/ui/views/extensions/extensions_dialogs_utils.h
index ca35329..e130c4c 100644
--- a/chrome/browser/ui/views/extensions/extensions_dialogs_utils.h
+++ b/chrome/browser/ui/views/extensions/extensions_dialogs_utils.h
@@ -31,11 +31,6 @@
 // web contents are present.
 std::u16string GetCurrentHost(content::WebContents* web_contents);
 
-// Returns the action view corresponding to `extension_id` in `container`. If it
-// doesn't exist, it defaults to the extensions menu button.
-views::View* GetDialogAnchorView(ExtensionsToolbarContainer* container,
-                                 const extensions::ExtensionId& extension_id);
-
 // Shows the dialog constructed from `dialog_model` anchored to the view
 // corresponding to `extension_id` in the extensions container. If parent does
 // not have an extensions container, it will display a browser-modal dialog
@@ -44,10 +39,10 @@
                 const extensions::ExtensionId& extension_id,
                 std::unique_ptr<ui::DialogModel> dialog_model);
 
-// Shows the dialog constructed from `dialog_model` anchored to the view
-// corresponding to `extension_id` in `container`.
+// Shows the dialog constructed from `dialog_model` for `extension_ids` and
+// is anchored to `container`.
 void ShowDialog(ExtensionsToolbarContainer* container,
-                const extensions::ExtensionId& extension_id,
+                const std::vector<extensions::ExtensionId>& extension_ids,
                 std::unique_ptr<ui::DialogModel> dialog_model);
 
 #endif  // CHROME_BROWSER_UI_VIEWS_EXTENSIONS_EXTENSIONS_DIALOGS_UTILS_H_
diff --git a/chrome/browser/ui/views/extensions/extensions_request_access_button.cc b/chrome/browser/ui/views/extensions/extensions_request_access_button.cc
index 1f16798..89922f0 100644
--- a/chrome/browser/ui/views/extensions/extensions_request_access_button.cc
+++ b/chrome/browser/ui/views/extensions/extensions_request_access_button.cc
@@ -61,6 +61,15 @@
       GetActiveWebContents(), this, extensions_requesting_access_);
 }
 
+std::vector<std::string>
+ExtensionsRequestAccessButton::GetExtensionsNamesForTesting() {
+  std::vector<std::string> extension_names;
+  for (auto* extension : extensions_requesting_access_) {
+    extension_names.push_back(base::UTF16ToUTF8(extension->GetActionName()));
+  }
+  return extension_names;
+}
+
 std::u16string ExtensionsRequestAccessButton::GetTooltipText(
     const gfx::Point& p) const {
   // Request access button hover cards replace tooltips.
@@ -78,18 +87,18 @@
   if (!action_runner)
     return;
 
-  // TODO(crbug.com/1319555): Grant tab permissions for all extensions
-  // requesting access. For this we need to add support for handling multiple
-  // extensions in the ExtensionActionRunner and ReloadPageDialog.
   DCHECK_GT(extensions_requesting_access_.size(), 0u);
-  const extensions::Extension* extension =
+  const extensions::ExtensionSet& enabled_extensions =
       extensions::ExtensionRegistry::Get(browser_->profile())
-          ->enabled_extensions()
-          .GetByID(extensions_requesting_access_[0]->GetId());
-  DCHECK(extension);
+          ->enabled_extensions();
+  std::vector<const extensions::Extension*> extensions;
+  for (auto* extension : extensions_requesting_access_) {
+    extensions.push_back(enabled_extensions.GetByID(extension->GetId()));
+  }
+
   base::RecordAction(base::UserMetricsAction(
-      "Extensions.Toolbar.ExtensionActivatedFromRequestAccessButton"));
-  action_runner->GrantTabPermissions(extension);
+      "Extensions.Toolbar.ExtensionsActivatedFromRequestAccessButton"));
+  action_runner->GrantTabPermissions(extensions);
 }
 
 // Linux enter/leave events are sometimes flaky, so we don't want to "miss"
diff --git a/chrome/browser/ui/views/extensions/extensions_request_access_button.h b/chrome/browser/ui/views/extensions/extensions_request_access_button.h
index 5ec0349..a6376ae 100644
--- a/chrome/browser/ui/views/extensions/extensions_request_access_button.h
+++ b/chrome/browser/ui/views/extensions/extensions_request_access_button.h
@@ -31,6 +31,8 @@
 
   void MaybeShowHoverCard();
 
+  std::vector<std::string> GetExtensionsNamesForTesting();
+
   // views::View:
   void OnMouseMoved(const ui::MouseEvent& event) override;
   void OnMouseEntered(const ui::MouseEvent& event) override;
diff --git a/chrome/browser/ui/views/extensions/extensions_toolbar_container_interactive_uitest.cc b/chrome/browser/ui/views/extensions/extensions_toolbar_container_interactive_uitest.cc
index 5236842..1521ec6 100644
--- a/chrome/browser/ui/views/extensions/extensions_toolbar_container_interactive_uitest.cc
+++ b/chrome/browser/ui/views/extensions/extensions_toolbar_container_interactive_uitest.cc
@@ -2,11 +2,14 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include <string>
 #include "chrome/browser/ui/views/extensions/extensions_toolbar_container.h"
 
 #include "base/memory/raw_ptr.h"
+#include "base/memory/scoped_refptr.h"
 #include "base/strings/stringprintf.h"
 #include "base/test/metrics/histogram_tester.h"
+#include "base/test/metrics/user_action_tester.h"
 #include "build/build_config.h"
 #include "chrome/browser/extensions/chrome_test_extension_loader.h"
 #include "chrome/browser/extensions/extension_action_runner.h"
@@ -19,12 +22,14 @@
 #include "chrome/browser/ui/browser_window.h"
 #include "chrome/browser/ui/toolbar/toolbar_action_view_controller.h"
 #include "chrome/browser/ui/toolbar/toolbar_actions_model.h"
+#include "chrome/browser/ui/views/extensions/extensions_request_access_button.h"
 #include "chrome/browser/ui/views/extensions/extensions_toolbar_button.h"
 #include "chrome/browser/ui/views/extensions/extensions_toolbar_interactive_uitest.h"
 #include "chrome/common/chrome_switches.h"
 #include "chrome/grit/generated_resources.h"
 #include "chrome/test/base/interactive_test_utils.h"
 #include "chrome/test/base/ui_test_utils.h"
+#include "content/public/browser/web_contents.h"
 #include "content/public/test/browser_test.h"
 #include "content/public/test/test_navigation_observer.h"
 #include "content/public/test/test_utils.h"
@@ -36,6 +41,7 @@
 #include "extensions/browser/test_extension_registry_observer.h"
 #include "extensions/browser/uninstall_reason.h"
 #include "extensions/common/extension.h"
+#include "extensions/common/extension_features.h"
 #include "extensions/test/extension_test_message_listener.h"
 #include "extensions/test/test_extension_dir.h"
 #include "net/dns/mock_host_resolver.h"
@@ -85,6 +91,9 @@
 
 }  // namespace
 
+using SiteAccess = extensions::SitePermissionsHelper::SiteAccess;
+using SiteInteraction = extensions::SitePermissionsHelper::SiteInteraction;
+
 class ExtensionsToolbarContainerUITest : public ExtensionsToolbarUITest {
  public:
   enum class ExtensionRemovalMethod {
@@ -843,3 +852,270 @@
   EXPECT_TRUE(permissions_modifier.HasGrantedHostPermission(url));
   EXPECT_EQ(tooltip_has_access, GetActionTooltip());
 }
+
+class ExtensionsToolbarContainerFeatureUITest
+    : public ExtensionsToolbarContainerUITest {
+ public:
+  ExtensionsToolbarContainerFeatureUITest() {
+    scoped_feature_list_.InitAndEnableFeature(
+        extensions_features::kExtensionsMenuAccessControl);
+  }
+  ExtensionsToolbarContainerFeatureUITest(
+      const ExtensionsToolbarContainerFeatureUITest&) = delete;
+  const ExtensionsToolbarContainerFeatureUITest& operator=(
+      const ExtensionsToolbarContainerFeatureUITest&) = delete;
+  ~ExtensionsToolbarContainerFeatureUITest() override = default;
+
+  void SetUpOnMainThread() override {
+    ExtensionsToolbarContainerUITest::SetUpOnMainThread();
+    ASSERT_TRUE(embedded_test_server()->Start());
+    web_contents_ = browser()->tab_strip_model()->GetActiveWebContents();
+  }
+
+  // TODO(emiliapaz): Other interactive_uitests have similar functionality. Move
+  // this method to `ExtensionsToolbarUITest` test base class, and migrate
+  // tests.
+  scoped_refptr<const extensions::Extension>
+  InstallExtensionWithHostPermissions(
+      const std::string& name,
+      const std::string& host_permission,
+      const std::string& content_script_run_location = "") {
+    extensions::TestExtensionDir extension_dir;
+    std::string content_script_entry;
+    if (!content_script_run_location.empty()) {
+      content_script_entry = base::StringPrintf(
+          R"(
+            "content_scripts": [{
+               "matches": ["%s"],
+               "js": ["script.js"],
+               "run_at": "%s"
+            }], )",
+          host_permission.c_str(), content_script_run_location.c_str());
+
+      extension_dir.WriteFile(
+          FILE_PATH_LITERAL("script.js"),
+          base::StringPrintf("chrome.test.sendMessage('%s');",
+                             kInjectionSucceededMessage));
+    }
+
+    extension_dir.WriteManifest(base::StringPrintf(
+        R"({
+              "name": "%s",
+              "manifest_version": 3,
+              "version": "0.1",
+              %s
+              "host_permissions": ["%s"]
+            })",
+        name.c_str(), content_script_entry.c_str(), host_permission.c_str()));
+    scoped_refptr<const extensions::Extension> extension =
+        extensions::ChromeTestExtensionLoader(profile()).LoadExtension(
+            extension_dir.UnpackedPath());
+    AppendExtension(extension);
+    return extension;
+  }
+
+  void NavigateToUrl(const GURL& url) {
+    content::TestNavigationObserver observer(web_contents_);
+    ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
+    EXPECT_TRUE(observer.last_navigation_succeeded());
+    WaitForAnimation();
+  }
+
+  ExtensionsRequestAccessButton* request_access_button() {
+    return GetExtensionsToolbarContainer()
+        ->GetExtensionsToolbarControls()
+        ->request_access_button_for_testing();
+  }
+  content::WebContents* web_contents() { return web_contents_; }
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+
+  content::WebContents* web_contents_ = nullptr;
+};
+
+// Tests that clicking the request access button grants one time access to the
+// extensions listed which requires a page refresh.
+IN_PROC_BROWSER_TEST_F(ExtensionsToolbarContainerFeatureUITest,
+                       ClickingRequestAccessButtonRunsAction_RefreshRequired) {
+  constexpr char kExtensionAName[] = "A Extension";
+  constexpr char kExtensionBName[] = "B Extension";
+  constexpr char kExtensionCName[] = "C Extension";
+  auto extensionA = InstallExtensionWithHostPermissions(
+      kExtensionAName, "<all_urls>",
+      /*content_script_run_location=*/"document_start");
+  auto extensionB = InstallExtensionWithHostPermissions(kExtensionBName,
+                                                        "http://example.com/");
+  auto extensionC =
+      InstallExtensionWithHostPermissions(kExtensionCName, "<all_urls>");
+
+  // Withheld site access for extensions A and B.
+  extensions::ScriptingPermissionsModifier(profile(), extensionA)
+      .SetWithholdHostPermissions(true);
+  extensions::ScriptingPermissionsModifier(profile(), extensionB)
+      .SetWithholdHostPermissions(true);
+
+  // Navigate to a site where extensions A and B have withheld access.
+  GURL url = embedded_test_server()->GetURL("example.com", "/title1.html");
+  NavigateToUrl(url);
+
+  // Verify request access button is visible because extensions A and B have
+  // pending site interaction.
+  extensions::SitePermissionsHelper permissions(browser()->profile());
+  EXPECT_TRUE(request_access_button()->GetVisible());
+  EXPECT_THAT(request_access_button()->GetExtensionsNamesForTesting(),
+              testing::ElementsAre(kExtensionAName, kExtensionBName));
+
+  EXPECT_EQ(permissions.GetSiteInteraction(*extensionA, web_contents()),
+            SiteInteraction::kPending);
+  EXPECT_EQ(permissions.GetSiteInteraction(*extensionB, web_contents()),
+            SiteInteraction::kPending);
+  EXPECT_EQ(permissions.GetSiteInteraction(*extensionC, web_contents()),
+            SiteInteraction::kActive);
+  EXPECT_EQ(permissions.GetSiteAccess(*extensionA, url), SiteAccess::kOnClick);
+  EXPECT_EQ(permissions.GetSiteAccess(*extensionB, url), SiteAccess::kOnClick);
+  EXPECT_EQ(permissions.GetSiteAccess(*extensionC, url),
+            SiteAccess::kOnAllSites);
+
+  // Click the request access button to grant one-time access. A reload page
+  // dialog will appear since extension A needs a page reload to run its action.
+  auto* action_runner =
+      extensions::ExtensionActionRunner::GetForWebContents(web_contents());
+  action_runner->accept_bubble_for_testing(false);
+  ClickButton(request_access_button());
+  WaitForAnimation();
+
+  // Site interaction should stay the same because dialog wasn't accepted.
+  EXPECT_TRUE(request_access_button()->GetVisible());
+  EXPECT_EQ(permissions.GetSiteInteraction(*extensionA, web_contents()),
+            SiteInteraction::kPending);
+  EXPECT_EQ(permissions.GetSiteInteraction(*extensionB, web_contents()),
+            SiteInteraction::kPending);
+  EXPECT_EQ(permissions.GetSiteInteraction(*extensionC, web_contents()),
+            SiteInteraction::kActive);
+
+  // Click the request access button again, and this time accept the dialog and
+  // wait for the page refresh.
+  content::TestNavigationObserver observer(web_contents());
+  extensions::ExtensionActionRunner::GetForWebContents(web_contents())
+      ->accept_bubble_for_testing(true);
+  ClickButton(request_access_button());
+  observer.WaitForNavigationFinished();
+  EXPECT_TRUE(observer.last_navigation_succeeded());
+  WaitForAnimation();
+
+  // Extension A and B should have active site interaction, since their actions
+  // ran, but keep the same site access since this is a one-time access grant.
+  // The request access button should be hidden.
+  EXPECT_FALSE(request_access_button()->GetVisible());
+  EXPECT_EQ(permissions.GetSiteInteraction(*extensionA, web_contents()),
+            SiteInteraction::kActive);
+  EXPECT_EQ(permissions.GetSiteInteraction(*extensionB, web_contents()),
+            SiteInteraction::kActive);
+  EXPECT_EQ(permissions.GetSiteInteraction(*extensionC, web_contents()),
+            SiteInteraction::kActive);
+  EXPECT_EQ(permissions.GetSiteAccess(*extensionA, url), SiteAccess::kOnClick);
+  EXPECT_EQ(permissions.GetSiteAccess(*extensionB, url), SiteAccess::kOnClick);
+  EXPECT_EQ(permissions.GetSiteAccess(*extensionC, url),
+            SiteAccess::kOnAllSites);
+
+  // Re-navigate to the same url. Refreshing the page doesn't remove the action,
+  // thus we need to navigate to another page and then navigate back to the
+  // original page.
+  NavigateToUrl(embedded_test_server()->GetURL("other.com", "/title1.html"));
+  NavigateToUrl(url);
+
+  // Extension A and B should have pending access again and the request access
+  // button should be visible.
+  EXPECT_TRUE(request_access_button()->GetVisible());
+  EXPECT_THAT(request_access_button()->GetExtensionsNamesForTesting(),
+              testing::ElementsAre(kExtensionAName, kExtensionBName));
+  EXPECT_EQ(permissions.GetSiteInteraction(*extensionA, web_contents()),
+            SiteInteraction::kPending);
+  EXPECT_EQ(permissions.GetSiteInteraction(*extensionB, web_contents()),
+            SiteInteraction::kPending);
+  EXPECT_EQ(permissions.GetSiteInteraction(*extensionC, web_contents()),
+            SiteInteraction::kActive);
+}
+
+// Tests that clicking the request access button grants one time access to the
+// extensions listed without needing a page refresh.
+IN_PROC_BROWSER_TEST_F(
+    ExtensionsToolbarContainerFeatureUITest,
+    ClickingRequestAccessButtonRunsAction_RefreshNotRequired) {
+  constexpr char kExtensionAName[] = "A Extension";
+  constexpr char kExtensionBName[] = "B Extension";
+  constexpr char kExtensionCName[] = "C Extension";
+  auto extensionA = InstallExtensionWithHostPermissions(
+      kExtensionAName, "<all_urls>", "document_idle");
+  auto extensionB = InstallExtensionWithHostPermissions(kExtensionBName,
+                                                        "http://example.com/");
+  auto extensionC =
+      InstallExtensionWithHostPermissions(kExtensionCName, "<all_urls>");
+
+  // Withheld site access for extensions A and B.
+  extensions::ScriptingPermissionsModifier(profile(), extensionA)
+      .SetWithholdHostPermissions(true);
+  extensions::ScriptingPermissionsModifier(profile(), extensionB)
+      .SetWithholdHostPermissions(true);
+
+  // Navigate to a site where extensions A and B have withheld access.
+  GURL url = embedded_test_server()->GetURL("example.com", "/title1.html");
+  NavigateToUrl(url);
+
+  // Verify request access button is visible because extensions A and B have
+  // pending site interaction.
+  EXPECT_TRUE(request_access_button()->GetVisible());
+  EXPECT_THAT(request_access_button()->GetExtensionsNamesForTesting(),
+              testing::ElementsAre(kExtensionAName, kExtensionBName));
+  extensions::SitePermissionsHelper permissions(browser()->profile());
+  EXPECT_EQ(permissions.GetSiteInteraction(*extensionA, web_contents()),
+            SiteInteraction::kPending);
+  EXPECT_EQ(permissions.GetSiteInteraction(*extensionB, web_contents()),
+            SiteInteraction::kPending);
+  EXPECT_EQ(permissions.GetSiteInteraction(*extensionC, web_contents()),
+            SiteInteraction::kActive);
+  EXPECT_EQ(permissions.GetSiteAccess(*extensionA, url), SiteAccess::kOnClick);
+  EXPECT_EQ(permissions.GetSiteAccess(*extensionB, url), SiteAccess::kOnClick);
+  EXPECT_EQ(permissions.GetSiteAccess(*extensionC, url),
+            SiteAccess::kOnAllSites);
+
+  // Click the request access button to grant one-time access. Since no
+  // extensions need page refresh to run their actions, it immediately grants
+  // access.
+  ClickButton(request_access_button());
+  WaitForAnimation();
+
+  // Extension A and B should have active site interaction, since their action
+  // run, but keep the same site access since this is a one-time access grant.
+  // The request access button should be hidden.
+  EXPECT_FALSE(request_access_button()->GetVisible());
+  EXPECT_EQ(permissions.GetSiteInteraction(*extensionA, web_contents()),
+            SiteInteraction::kActive);
+  EXPECT_EQ(permissions.GetSiteInteraction(*extensionB, web_contents()),
+            SiteInteraction::kActive);
+  EXPECT_EQ(permissions.GetSiteInteraction(*extensionC, web_contents()),
+            SiteInteraction::kActive);
+  EXPECT_EQ(permissions.GetSiteAccess(*extensionA, url), SiteAccess::kOnClick);
+  EXPECT_EQ(permissions.GetSiteAccess(*extensionB, url), SiteAccess::kOnClick);
+  EXPECT_EQ(permissions.GetSiteAccess(*extensionC, url),
+            SiteAccess::kOnAllSites);
+
+  // Re-navigate to the same url. Refreshing the page doesn't remove the action,
+  // thus we need to navigate to another page and then navigate back to the
+  // original page.
+  NavigateToUrl(embedded_test_server()->GetURL("other.com", "/title1.html"));
+  NavigateToUrl(url);
+
+  // Extension A and B should have pending access again and the request access
+  // button should be visible.
+  EXPECT_TRUE(request_access_button()->GetVisible());
+  EXPECT_THAT(request_access_button()->GetExtensionsNamesForTesting(),
+              testing::ElementsAre(kExtensionAName, kExtensionBName));
+  EXPECT_EQ(permissions.GetSiteInteraction(*extensionA, web_contents()),
+            SiteInteraction::kPending);
+  EXPECT_EQ(permissions.GetSiteInteraction(*extensionB, web_contents()),
+            SiteInteraction::kPending);
+  EXPECT_EQ(permissions.GetSiteInteraction(*extensionC, web_contents()),
+            SiteInteraction::kActive);
+}
diff --git a/chrome/browser/ui/views/extensions/extensions_toolbar_controls_unittest.cc b/chrome/browser/ui/views/extensions/extensions_toolbar_controls_unittest.cc
index f1194f5c..90ee77c 100644
--- a/chrome/browser/ui/views/extensions/extensions_toolbar_controls_unittest.cc
+++ b/chrome/browser/ui/views/extensions/extensions_toolbar_controls_unittest.cc
@@ -386,7 +386,7 @@
   LayoutContainerIfNecessary();
 
   constexpr char kActivatedUserAction[] =
-      "Extensions.Toolbar.ExtensionActivatedFromRequestAccessButton";
+      "Extensions.Toolbar.ExtensionsActivatedFromRequestAccessButton";
   base::UserActionTester user_action_tester;
   extensions::SitePermissionsHelper permissions(browser()->profile());
 
diff --git a/chrome/browser/ui/views/extensions/reload_page_dialog_view.cc b/chrome/browser/ui/views/extensions/reload_page_dialog_view.cc
index f83fe5c..54cebc4a 100644
--- a/chrome/browser/ui/views/extensions/reload_page_dialog_view.cc
+++ b/chrome/browser/ui/views/extensions/reload_page_dialog_view.cc
@@ -23,21 +23,42 @@
 
 namespace extensions {
 
-void ShowReloadPageDialog(Browser* browser,
-                          const ExtensionId& extension_id,
-                          bool is_updating_permissions,
-                          base::OnceClosure callback) {
-  ShowReloadPageDialogView(browser, extension_id, is_updating_permissions,
+void ShowReloadPageDialog(
+    Browser* browser,
+    const std::vector<extensions::ExtensionId>& extension_ids,
+    bool is_updating_permissions,
+    base::OnceClosure callback) {
+  ShowReloadPageDialogView(browser, extension_ids, is_updating_permissions,
                            std::move(callback));
 }
 
 }  // namespace extensions
 
+namespace {
+
+std::u16string GetTitle(const std::vector<ToolbarActionViewController*> actions,
+                        bool is_updating_permissions) {
+  if (is_updating_permissions) {
+    return l10n_util::GetStringUTF16(
+        IDS_EXTENSION_BLOCKED_ACTION_BUBBLE_UPDATE_PERMISSIONS_TITLE);
+  }
+  if (actions.size() == 1) {
+    return l10n_util::GetStringFUTF16(
+        IDS_EXTENSION_BLOCKED_ACTION_BUBBLE_SINGLE_EXTENSION_TITLE,
+        actions[0]->GetActionName());
+  }
+  return l10n_util::GetStringUTF16(
+      IDS_EXTENSION_BLOCKED_ACTION_BUBBLE_MULTIPLE_EXTENSIONS_TITLE);
+}
+
+}  // namespace
+
 // static
-void ShowReloadPageDialogView(Browser* browser,
-                              const extensions::ExtensionId& extension_id,
-                              bool is_updating_permissions,
-                              base::OnceClosure callback) {
+void ShowReloadPageDialogView(
+    Browser* browser,
+    const std::vector<extensions::ExtensionId>& extension_ids,
+    bool is_updating_permissions,
+    base::OnceClosure callback) {
   ExtensionsToolbarContainer* const container =
       GetExtensionsToolbarContainer(browser);
   DCHECK(container);
@@ -45,22 +66,28 @@
   ui::DialogModel::Builder dialog_builder;
   if (base::FeatureList::IsEnabled(
           extensions_features::kExtensionsMenuAccessControl)) {
-    ToolbarActionViewController* extension =
-        container->GetActionForId(extension_id);
-    content::WebContents* web_contents =
-        browser->tab_strip_model()->GetActiveWebContents();
-    dialog_builder
-        .SetTitle(
-            is_updating_permissions
-                ? l10n_util::GetStringUTF16(
-                      IDS_EXTENSION_BLOCKED_ACTION_BUBBLE_UPDATE_PERMISSIONS_TITLE)
-                : l10n_util::GetStringFUTF16(
-                      IDS_EXTENSION_BLOCKED_ACTION_BUBBLE_SINGLE_EXTENSION_TITLE,
-                      extension->GetActionName()))
-        .SetIcon(GetIcon(extension, web_contents))
+    std::vector<ToolbarActionViewController*> actions;
+    for (const auto& extension_id : extension_ids) {
+      actions.push_back(container->GetActionForId(extension_id));
+    }
+
+    dialog_builder.SetTitle(GetTitle(actions, is_updating_permissions))
         .AddOkButton(base::BindOnce(std::move(callback)),
                      l10n_util::GetStringUTF16(
                          IDS_EXTENSION_BLOCKED_ACTION_BUBBLE_OK_BUTTON));
+
+    content::WebContents* web_contents =
+        browser->tab_strip_model()->GetActiveWebContents();
+    if (extension_ids.size() == 1) {
+      dialog_builder.SetIcon(GetIcon(actions[0], web_contents));
+    } else {
+      for (auto* action : actions) {
+        dialog_builder.AddMenuItem(
+            GetIcon(action, web_contents), action->GetActionName(),
+            base::DoNothing(),
+            ui::DialogModelMenuItem::Params().set_is_enabled(false));
+      }
+    }
   } else {
     dialog_builder
         .SetTitle(l10n_util::GetStringUTF16(
@@ -70,5 +97,5 @@
                          IDS_EXTENSION_BLOCKED_ACTION_BUBBLE_OK_BUTTON));
   }
 
-  ShowDialog(container, extension_id, dialog_builder.Build());
+  ShowDialog(container, extension_ids, dialog_builder.Build());
 }
diff --git a/chrome/browser/ui/views/extensions/reload_page_dialog_view.h b/chrome/browser/ui/views/extensions/reload_page_dialog_view.h
index 2a0d30e7..37dcc6ef 100644
--- a/chrome/browser/ui/views/extensions/reload_page_dialog_view.h
+++ b/chrome/browser/ui/views/extensions/reload_page_dialog_view.h
@@ -12,7 +12,7 @@
 
 static void ShowReloadPageDialogView(
     Browser* browser,
-    const extensions::ExtensionId& extension_id,
+    const std::vector<extensions::ExtensionId>& extension_ids,
     bool is_updating_permissions,
     base::OnceClosure callback);
 
diff --git a/chrome/browser/ui/views/extensions/reload_page_dialog_view_browsertest.cc b/chrome/browser/ui/views/extensions/reload_page_dialog_view_browsertest.cc
index c3a7be9..fa69f9db 100644
--- a/chrome/browser/ui/views/extensions/reload_page_dialog_view_browsertest.cc
+++ b/chrome/browser/ui/views/extensions/reload_page_dialog_view_browsertest.cc
@@ -13,8 +13,8 @@
   void ShowUi(const std::string& name) override {
     auto extension = InstallExtension("Extension");
     bool show_checkbox = true;
-    extensions::ShowReloadPageDialog(browser(), extension->id(), show_checkbox,
-                                     base::DoNothing());
+    extensions::ShowReloadPageDialog(browser(), {extension->id()},
+                                     show_checkbox, base::DoNothing());
   }
 };
 
diff --git a/chrome/browser/ui/views/frame/browser_view.cc b/chrome/browser/ui/views/frame/browser_view.cc
index 9d3bff61..21bf0585 100644
--- a/chrome/browser/ui/views/frame/browser_view.cc
+++ b/chrome/browser/ui/views/frame/browser_view.cc
@@ -2484,6 +2484,29 @@
   return ShowTranslateBubbleResult::SUCCESS;
 }
 
+void BrowserView::ShowPartialTranslateBubble(
+    PartialTranslateBubbleModel::ViewState view_state,
+    const std::string& source_language,
+    const std::string& target_language,
+    const std::u16string& text_selection,
+    translate::TranslateErrors::Type error_type) {
+  // Show the Translate icon and enabled the associated command to show the
+  // Translate UI.
+  ChromeTranslateClient::FromWebContents(GetActiveWebContents())
+      ->GetTranslateManager()
+      ->GetLanguageState()
+      ->SetTranslateEnabled(true);
+
+  TranslateBubbleController::GetOrCreate(GetActiveWebContents())
+      ->ShowPartialTranslateBubble(
+          toolbar_button_provider()->GetAnchorView(
+              PageActionIconType::kTranslate),
+          toolbar_button_provider()->GetPageActionIconView(
+              PageActionIconType::kTranslate),
+          view_state, source_language, target_language, text_selection,
+          error_type);
+}
+
 void BrowserView::ShowOneClickSigninConfirmation(
     const std::u16string& email,
     base::OnceCallback<void(bool)> confirmed_callback) {
diff --git a/chrome/browser/ui/views/frame/browser_view.h b/chrome/browser/ui/views/frame/browser_view.h
index 087252b..c768615d 100644
--- a/chrome/browser/ui/views/frame/browser_view.h
+++ b/chrome/browser/ui/views/frame/browser_view.h
@@ -27,6 +27,7 @@
 #include "chrome/browser/ui/exclusive_access/exclusive_access_context.h"
 #include "chrome/browser/ui/tabs/tab_renderer_data.h"
 #include "chrome/browser/ui/tabs/tab_strip_model_observer.h"
+#include "chrome/browser/ui/translate/partial_translate_bubble_model.h"
 #include "chrome/browser/ui/user_education/browser_feature_promo_snooze_service.h"
 #include "chrome/browser/ui/views/exclusive_access_bubble_views_context.h"
 #include "chrome/browser/ui/views/extensions/extension_keybinding_registry_views.h"
@@ -514,6 +515,12 @@
       const std::string& target_language,
       translate::TranslateErrors::Type error_type,
       bool is_user_gesture) override;
+  void ShowPartialTranslateBubble(
+      PartialTranslateBubbleModel::ViewState view_state,
+      const std::string& source_language,
+      const std::string& target_language,
+      const std::u16string& text_selection,
+      translate::TranslateErrors::Type error_type) override;
   void ShowOneClickSigninConfirmation(
       const std::u16string& email,
       base::OnceCallback<void(bool)> confirmed_callback) override;
diff --git a/chrome/browser/ui/views/profiles/profile_menu_view_base.cc b/chrome/browser/ui/views/profiles/profile_menu_view_base.cc
index 001e583..1d4ea09 100644
--- a/chrome/browser/ui/views/profiles/profile_menu_view_base.cc
+++ b/chrome/browser/ui/views/profiles/profile_menu_view_base.cc
@@ -563,15 +563,16 @@
       ->AnimateToState(views::InkDropState::ACTIVATED, nullptr);
 
   SetEnableArrowKeyTraversal(true);
+
+  // TODO(crbug.com/1341017): Using `SetAccessibleRole(kMenu)` here will
+  // result in screenreader to announce the menu having only one item. This is
+  // probably because this API sets the a11y role for the widget, but not root
+  // view in it. This is confusing and prone to misuse. We should unify the two
+  // sets of API for BubbleDialogDelegateView.
   GetViewAccessibility().OverrideRole(ax::mojom::Role::kMenu);
 
   RegisterWindowClosingCallback(base::BindOnce(
       &ProfileMenuViewBase::OnWindowClosing, base::Unretained(this)));
-
-  // Use `ax::mojom::Role::kMenuBar`, because it fits better the kind of UI
-  // contained in this dialog. The top-level container in this dialog uses a
-  // kMenu role to match.
-  SetAccessibleRole(ax::mojom::Role::kMenuBar);
 }
 
 ProfileMenuViewBase::~ProfileMenuViewBase() {
diff --git a/chrome/browser/ui/views/safe_browsing/tailored_security_desktop_modal.cc b/chrome/browser/ui/views/safe_browsing/tailored_security_desktop_modal.cc
deleted file mode 100644
index 43ebe45..0000000
--- a/chrome/browser/ui/views/safe_browsing/tailored_security_desktop_modal.cc
+++ /dev/null
@@ -1,130 +0,0 @@
-// Copyright 2022 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/ui/views/safe_browsing/tailored_security_desktop_modal.h"
-
-#include "base/callback.h"
-#include "base/metrics/histogram_functions.h"
-#include "chrome/browser/safe_browsing/tailored_security/tailored_security_outcome.h"
-#include "chrome/browser/ui/browser_finder.h"
-#include "chrome/browser/ui/chrome_pages.h"
-#include "chrome/grit/generated_resources.h"
-#include "chrome/grit/theme_resources.h"
-#include "components/constrained_window/constrained_window_views.h"
-#include "content/public/browser/web_contents.h"
-#include "ui/base/interaction/element_identifier.h"
-#include "ui/base/l10n/l10n_util.h"
-#include "ui/base/models/dialog_model.h"
-#include "ui/base/models/image_model.h"
-#include "ui/base/resource/resource_bundle.h"
-#include "ui/gfx/image/image_skia.h"
-#include "ui/views/controls/label.h"
-
-namespace safe_browsing {
-
-DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kBodyText);
-
-// Model delegate for the disabled modal. This class implements the click
-// behavior for the disabled modal.
-class DisabledModalModelDelegate : public ui::DialogModelDelegate {
- public:
-  void OnDialogAccepted() {
-    // Just count the click.
-    base::UmaHistogramEnumeration(kModalDisabledOutcome,
-                                  TailoredSecurityOutcome::kAccepted);
-  }
-  void OnDialogRejected(content::WebContents* web_contents) {
-    // Redirect to the Chrome safe browsing settings page.
-    base::UmaHistogramEnumeration(kModalDisabledOutcome,
-                                  TailoredSecurityOutcome::kSettings);
-
-    chrome::ShowSafeBrowsingEnhancedProtection(
-        chrome::FindBrowserWithWebContents(web_contents));
-  }
-};
-
-// Model delegate for the enabled modal. This class implements the click
-// behavior for the enabled modal.
-class EnabledModalModelDelegate : public ui::DialogModelDelegate {
- public:
-  void OnDialogAccepted() {
-    // Just count the click.
-    base::UmaHistogramEnumeration(kModalEnabledOutcome,
-                                  TailoredSecurityOutcome::kAccepted);
-  }
-  void OnDialogRejected(content::WebContents* web_contents) {
-    // Redirect to the Chrome safe browsing settings page.
-    base::UmaHistogramEnumeration(kModalEnabledOutcome,
-                                  TailoredSecurityOutcome::kSettings);
-
-    chrome::ShowSafeBrowsingEnhancedProtection(
-        chrome::FindBrowserWithWebContents(web_contents));
-  }
-};
-
-void ShowEnabledModalForWebContents(content::WebContents* web_contents) {
-  auto model_delegate = std::make_unique<EnabledModalModelDelegate>();
-  auto* model_delegate_ptr = model_delegate.get();
-
-  ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
-
-  auto banner_image_light = ui::ImageModel::FromImageSkia(
-      *bundle.GetImageSkiaNamed(IDR_TAILORED_SECURITY_CONSENTED));
-  auto banner_image_dark = ui::ImageModel::FromImageSkia(
-      *bundle.GetImageSkiaNamed(IDR_TAILORED_SECURITY_CONSENTED_DARK));
-
-  auto body_text = ui::DialogModelLabel(
-      l10n_util::GetStringUTF16(IDS_TAILORED_SECURITY_ENABLED_MODAL_MAIN_TEXT));
-  body_text.set_is_secondary();
-  auto dialog_model =
-      ui::DialogModel::Builder(std::move(model_delegate))
-          .SetTitle(l10n_util::GetStringUTF16(
-              IDS_TAILORED_SECURITY_ENABLED_MODAL_TITLE))
-          .SetInternalName(kTailoredSecurityNoticeModal)
-          .SetBannerImage(std::move(banner_image_light),
-                          std::move(banner_image_dark))
-          .AddBodyText(body_text, kBodyText)
-          .AddOkButton(
-              base::BindOnce(&EnabledModalModelDelegate::OnDialogAccepted,
-                             base::Unretained(model_delegate_ptr)))
-          .AddCancelButton(
-              base::BindOnce(&EnabledModalModelDelegate::OnDialogRejected,
-                             base::Unretained(model_delegate_ptr),
-                             web_contents),
-              l10n_util::GetStringUTF16(
-                  IDS_TAILORED_SECURITY_MODAL_SETTINGS_BUTTON))
-          .Build();
-
-  constrained_window::ShowWebModal(std::move(dialog_model), web_contents);
-}
-
-void ShowDisabledModalForWebContents(content::WebContents* web_contents) {
-  auto model_delegate = std::make_unique<DisabledModalModelDelegate>();
-  auto* model_delegate_ptr = model_delegate.get();
-
-  auto body_text =
-      ui::DialogModelLabel(l10n_util::GetStringUTF16(
-                               IDS_TAILORED_SECURITY_DISABLED_MODAL_MAIN_TEXT))
-          .set_is_secondary();
-
-  auto dialog_model =
-      ui::DialogModel::Builder(std::move(model_delegate))
-          .SetTitle(l10n_util::GetStringUTF16(
-              IDS_TAILORED_SECURITY_DISABLED_MODAL_TITLE))
-          .SetInternalName(kTailoredSecurityNoticeModal)
-          .AddBodyText(body_text, kBodyText)
-          .AddOkButton(
-              base::BindOnce(&DisabledModalModelDelegate::OnDialogAccepted,
-                             base::Unretained(model_delegate_ptr)))
-          .AddCancelButton(
-              base::BindOnce(&DisabledModalModelDelegate::OnDialogRejected,
-                             base::Unretained(model_delegate_ptr),
-                             web_contents),
-              l10n_util::GetStringUTF16(
-                  IDS_TAILORED_SECURITY_MODAL_SETTINGS_BUTTON))
-          .Build();
-
-  constrained_window::ShowWebModal(std::move(dialog_model), web_contents);
-}
-}  // namespace safe_browsing
diff --git a/chrome/browser/ui/views/safe_browsing/tailored_security_desktop_modal.h b/chrome/browser/ui/views/safe_browsing/tailored_security_desktop_modal.h
deleted file mode 100644
index e92c967..0000000
--- a/chrome/browser/ui/views/safe_browsing/tailored_security_desktop_modal.h
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright 2022 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_UI_VIEWS_SAFE_BROWSING_TAILORED_SECURITY_DESKTOP_MODAL_H_
-#define CHROME_BROWSER_UI_VIEWS_SAFE_BROWSING_TAILORED_SECURITY_DESKTOP_MODAL_H_
-
-#include "content/public/browser/web_contents.h"
-#include "ui/views/view.h"
-
-namespace safe_browsing {
-
-// UMA histogram names for the modals.
-const char kModalDisabledOutcome[] =
-    "SafeBrowsing.TailoredSecurity.ConsentedDesktopModalDisabledOutcome";
-const char kModalEnabledOutcome[] =
-    "SafeBrowsing.TailoredSecurity.ConsentedDesktopModalEnabledOutcome";
-
-static constexpr char kTailoredSecurityNoticeModal[] =
-    "TailoredSecurityNoticeModal";
-
-// Creates and shows a modal dialog for when Tailored Security is enabled.
-void ShowEnabledModalForWebContents(content::WebContents* web_contents);
-
-// Creates and shows a modal dialog for when Tailored Security is disabled.
-void ShowDisabledModalForWebContents(content::WebContents* web_contents);
-
-}  // namespace safe_browsing
-
-#endif  // CHROME_BROWSER_UI_VIEWS_SAFE_BROWSING_TAILORED_SECURITY_DESKTOP_MODAL_H_
diff --git a/chrome/browser/ui/views/safe_browsing/tailored_security_desktop_modal_browsertest.cc b/chrome/browser/ui/views/safe_browsing/tailored_security_desktop_modal_browsertest.cc
deleted file mode 100644
index 42a7ada0..0000000
--- a/chrome/browser/ui/views/safe_browsing/tailored_security_desktop_modal_browsertest.cc
+++ /dev/null
@@ -1,227 +0,0 @@
-// Copyright 2022 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 "base/command_line.h"
-#include "base/test/metrics/histogram_tester.h"
-#include "base/test/scoped_feature_list.h"
-#include "chrome/browser/safe_browsing/tailored_security/tailored_security_outcome.h"
-#include "chrome/browser/ui/browser.h"
-#include "chrome/browser/ui/tabs/tab_strip_model.h"
-#include "chrome/browser/ui/test/test_browser_dialog.h"
-#include "chrome/browser/ui/views/safe_browsing/tailored_security_desktop_modal.h"
-#include "chrome/common/chrome_features.h"
-#include "content/public/browser/web_contents.h"
-#include "content/public/test/browser_test.h"
-#include "content/public/test/browser_test_utils.h"
-#include "testing/gtest/include/gtest/gtest.h"
-#include "ui/base/ui_base_switches.h"
-#include "ui/compositor/scoped_animation_duration_scale_mode.h"
-#include "ui/events/base_event_utils.h"
-#include "ui/events/event.h"
-#include "ui/gfx/geometry/point.h"
-#include "ui/views/bubble/bubble_dialog_delegate_view.h"
-#include "ui/views/controls/button/label_button.h"
-#include "ui/views/widget/any_widget_observer.h"
-#include "ui/views/widget/widget.h"
-#include "url/gurl.h"
-
-namespace {
-
-const char kEnhancedProtectionSettingsUrl[] =
-    "chrome://settings/security?q=enhanced";
-
-views::Widget* ShowTailoredSecurityEnabledModal(Browser* browser) {
-  views::NamedWidgetShownWaiter waiter(
-      views::test::AnyWidgetTestPasskey{},
-      safe_browsing::kTailoredSecurityNoticeModal);
-  safe_browsing::ShowEnabledModalForWebContents(
-      browser->tab_strip_model()->GetActiveWebContents());
-
-  return waiter.WaitIfNeededAndGet();
-}
-
-views::Widget* ShowTailoredSecurityDisabledModal(Browser* browser) {
-  views::NamedWidgetShownWaiter waiter(
-      views::test::AnyWidgetTestPasskey{},
-      safe_browsing::kTailoredSecurityNoticeModal);
-  safe_browsing::ShowDisabledModalForWebContents(
-      browser->tab_strip_model()->GetActiveWebContents());
-
-  return waiter.WaitIfNeededAndGet();
-}
-
-// A struct of test parameters that can be used by parameterized tests.
-struct TestParam {
-  // The suffix for the test name.
-  std::string test_suffix = "";
-  // Whether to use a dark theme or not.
-  bool use_dark_theme = false;
-};
-
-// To be passed as 4th argument to `INSTANTIATE_TEST_SUITE_P()`, allows the test
-// to be named like `All/<TestClassName>.InvokeUi_default/<TestSuffix>` instead
-// of using the index of the param in `kTestParam` as suffix.
-std::string ParamToTestSuffix(const ::testing::TestParamInfo<TestParam>& info) {
-  return info.param.test_suffix;
-}
-
-const TestParam kTestParams[] = {{"LightTheme", /*use_dark_theme=*/false},
-                                 {"DarkTheme", /*use_dark_theme=*/true}};
-
-void ClickButton(views::BubbleDialogDelegate* bubble_delegate,
-                 views::View* button) {
-  // Reset the timer to make sure that test click isn't discarded as possibly
-  // unintended.
-  bubble_delegate->ResetViewShownTimeStampForTesting();
-  gfx::Point center(button->width() / 2, button->height() / 2);
-  const ui::MouseEvent event(ui::ET_MOUSE_PRESSED, center, center,
-                             ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON,
-                             ui::EF_LEFT_MOUSE_BUTTON);
-  button->OnMousePressed(event);
-  button->OnMouseReleased(event);
-}
-}  // namespace
-
-class TailoredSecurityDesktopModalTest
-    : public DialogBrowserTest,
-      public testing::WithParamInterface<TestParam> {
- public:
-  TailoredSecurityDesktopModalTest() = default;
-  TailoredSecurityDesktopModalTest(const TailoredSecurityDesktopModalTest&) =
-      delete;
-  TailoredSecurityDesktopModalTest& operator=(
-      const TailoredSecurityDesktopModalTest&) = delete;
-
-  void SetUp() override {
-    DialogBrowserTest::SetUp();
-    if (GetParam().use_dark_theme) {
-      features_.InitAndEnableFeature(features::kWebUIDarkMode);
-    } else {
-      features_.Init();
-    }
-  }
-
-  // DialogBrowserTest:
-  void ShowUi(const std::string& name) override {
-    // Reduce flakes by ensuring that animation is disabled.
-    ui::ScopedAnimationDurationScaleMode disable_animation(
-        ui::ScopedAnimationDurationScaleMode::ZERO_DURATION);
-    const std::string& actual_name = name.substr(0, name.find("/"));
-    if (actual_name == "enabledModal") {
-      safe_browsing::ShowEnabledModalForWebContents(
-          browser()->tab_strip_model()->GetActiveWebContents());
-    } else if (actual_name == "disabledModal") {
-      safe_browsing::ShowDisabledModalForWebContents(
-          browser()->tab_strip_model()->GetActiveWebContents());
-    } else {
-      FAIL() << "No modal case defined for this string: " << name;
-    }
-  }
-
-  void SetUpCommandLine(base::CommandLine* command_line) override {
-    if (GetParam().use_dark_theme) {
-      command_line->AppendSwitch(switches::kForceDarkMode);
-    }
-  }
-
- private:
-  base::test::ScopedFeatureList features_;
-};
-
-IN_PROC_BROWSER_TEST_P(TailoredSecurityDesktopModalTest,
-                       InvokeUi_enabledModal) {
-  ShowAndVerifyUi();
-}
-
-IN_PROC_BROWSER_TEST_P(TailoredSecurityDesktopModalTest,
-                       InvokeUi_disabledModal) {
-  ShowAndVerifyUi();
-}
-
-IN_PROC_BROWSER_TEST_P(TailoredSecurityDesktopModalTest,
-                       EnabledModalOkButtonIncrementsAcknowledgedHistogram) {
-  base::HistogramTester histograms;
-  auto* modal = ShowTailoredSecurityEnabledModal(browser());
-  auto* bubble_delegate = modal->widget_delegate()->AsBubbleDialogDelegate();
-  histograms.ExpectBucketCount(safe_browsing::kModalEnabledOutcome,
-                               TailoredSecurityOutcome::kAccepted, 0);
-  ClickButton(bubble_delegate, bubble_delegate->GetOkButton());
-  histograms.ExpectBucketCount(safe_browsing::kModalEnabledOutcome,
-                               TailoredSecurityOutcome::kAccepted, 1);
-}
-
-IN_PROC_BROWSER_TEST_P(TailoredSecurityDesktopModalTest,
-                       EnabledModalCancelButtonIncrementsSettingsHistogram) {
-  base::HistogramTester histograms;
-  auto* modal = ShowTailoredSecurityEnabledModal(browser());
-  auto* bubble_delegate = modal->widget_delegate()->AsBubbleDialogDelegate();
-  histograms.ExpectBucketCount(safe_browsing::kModalEnabledOutcome,
-                               TailoredSecurityOutcome::kSettings, 0);
-
-  ClickButton(bubble_delegate, bubble_delegate->GetCancelButton());
-  histograms.ExpectBucketCount(safe_browsing::kModalEnabledOutcome,
-                               TailoredSecurityOutcome::kSettings, 1);
-}
-
-IN_PROC_BROWSER_TEST_P(TailoredSecurityDesktopModalTest,
-                       EnabledModalCancelButtonNavigatesToSettings) {
-  base::HistogramTester histograms;
-  auto* modal = ShowTailoredSecurityEnabledModal(browser());
-  auto* bubble_delegate = modal->widget_delegate()->AsBubbleDialogDelegate();
-
-  ClickButton(bubble_delegate, bubble_delegate->GetCancelButton());
-  EXPECT_TRUE(content::WaitForLoadStop(
-      browser()->tab_strip_model()->GetActiveWebContents()));
-  EXPECT_EQ(browser()
-                ->tab_strip_model()
-                ->GetActiveWebContents()
-                ->GetLastCommittedURL(),
-            GURL(kEnhancedProtectionSettingsUrl));
-}
-
-IN_PROC_BROWSER_TEST_P(TailoredSecurityDesktopModalTest,
-                       DisabledModalOkButtonIncrementsAcknowledgedHistogram) {
-  base::HistogramTester histograms;
-  auto* modal = ShowTailoredSecurityDisabledModal(browser());
-  auto* bubble_delegate = modal->widget_delegate()->AsBubbleDialogDelegate();
-  histograms.ExpectBucketCount(safe_browsing::kModalDisabledOutcome,
-                               TailoredSecurityOutcome::kAccepted, 0);
-  ClickButton(bubble_delegate, bubble_delegate->GetOkButton());
-  histograms.ExpectBucketCount(safe_browsing::kModalDisabledOutcome,
-                               TailoredSecurityOutcome::kAccepted, 1);
-}
-
-IN_PROC_BROWSER_TEST_P(TailoredSecurityDesktopModalTest,
-                       DisabledModalCancelButtonIncrementsSettingsHistogram) {
-  base::HistogramTester histograms;
-  auto* modal = ShowTailoredSecurityDisabledModal(browser());
-  auto* bubble_delegate = modal->widget_delegate()->AsBubbleDialogDelegate();
-  histograms.ExpectBucketCount(safe_browsing::kModalDisabledOutcome,
-                               TailoredSecurityOutcome::kSettings, 0);
-
-  ClickButton(bubble_delegate, bubble_delegate->GetCancelButton());
-  histograms.ExpectBucketCount(safe_browsing::kModalDisabledOutcome,
-                               TailoredSecurityOutcome::kSettings, 1);
-}
-
-IN_PROC_BROWSER_TEST_P(TailoredSecurityDesktopModalTest,
-                       DisabledModalCancelButtonNavigatesToSettings) {
-  base::HistogramTester histograms;
-  auto* modal = ShowTailoredSecurityEnabledModal(browser());
-  auto* bubble_delegate = modal->widget_delegate()->AsBubbleDialogDelegate();
-
-  ClickButton(bubble_delegate, bubble_delegate->GetCancelButton());
-  EXPECT_TRUE(content::WaitForLoadStop(
-      browser()->tab_strip_model()->GetActiveWebContents()));
-  EXPECT_EQ(browser()
-                ->tab_strip_model()
-                ->GetActiveWebContents()
-                ->GetLastCommittedURL(),
-            GURL(kEnhancedProtectionSettingsUrl));
-}
-
-INSTANTIATE_TEST_SUITE_P(All,
-                         TailoredSecurityDesktopModalTest,
-                         testing::ValuesIn(kTestParams),
-                         &ParamToTestSuffix);
diff --git a/chrome/browser/ui/views/safe_browsing/tailored_security_unconsented_modal.h b/chrome/browser/ui/views/safe_browsing/tailored_security_unconsented_modal.h
index 7a4079f..f3e3a350 100644
--- a/chrome/browser/ui/views/safe_browsing/tailored_security_unconsented_modal.h
+++ b/chrome/browser/ui/views/safe_browsing/tailored_security_unconsented_modal.h
@@ -19,8 +19,6 @@
 
 // A tab modal dialog that is shown when the user's tailored security bit
 // changes and the user isn't consented to sync.
-// TODO(crbug.com/1336052): Remove this modal after launching
-// `TailoredSecurityDesktopModal`.
 class TailoredSecurityUnconsentedModal : public views::DialogDelegateView {
  public:
   METADATA_HEADER(TailoredSecurityUnconsentedModal);
diff --git a/chrome/browser/ui/views/translate/partial_translate_bubble_view.cc b/chrome/browser/ui/views/translate/partial_translate_bubble_view.cc
index 60da14c8..0e324a3 100644
--- a/chrome/browser/ui/views/translate/partial_translate_bubble_view.cc
+++ b/chrome/browser/ui/views/translate/partial_translate_bubble_view.cc
@@ -74,6 +74,7 @@
 #include "ui/views/layout/flex_layout.h"
 #include "ui/views/layout/flex_layout_types.h"
 #include "ui/views/style/platform_style.h"
+#include "ui/views/style/typography.h"
 #include "ui/views/view_class_properties.h"
 #include "ui/views/widget/widget.h"
 
@@ -328,7 +329,7 @@
 }
 
 void PartialTranslateBubbleView::OnWidgetDestroying(views::Widget* widget) {
-  // Nothing to do. When partial translate metrics get added we may want to log
+  // Nothing to do. When Partial Translate metrics get added we may want to log
   // when and how the bubble is closed similar to TranslateBubbleView.
   return;
 }
@@ -353,10 +354,12 @@
     std::unique_ptr<PartialTranslateBubbleModel> model,
     translate::TranslateErrors::Type error_type,
     content::WebContents* web_contents,
+    const std::u16string& text_selection,
     base::OnceClosure on_closing)
     : LocationBarBubbleDelegateView(anchor_view, web_contents),
       model_(std::move(model)),
       error_type_(error_type),
+      text_selection_(text_selection),
       on_closing_(std::move(on_closing)),
       web_contents_(web_contents) {
   UpdateInsets(PartialTranslateBubbleModel::VIEW_STATE_BEFORE_TRANSLATE);
@@ -494,6 +497,25 @@
       ->SetOrientation(views::LayoutOrientation::kHorizontal);
   auto* horizontal_view = view->AddChildView(std::move(inner_view));
 
+  auto partial_text_row = std::make_unique<views::View>();
+  partial_text_row->SetLayoutManager(std::make_unique<views::FlexLayout>());
+  auto partial_text_label = std::make_unique<views::Label>(
+      text_selection_, views::style::CONTEXT_DIALOG_BODY_TEXT,
+      views::style::STYLE_PRIMARY);
+  const int vertical_spacing =
+      provider->GetDistanceMetric(views::DISTANCE_RELATED_CONTROL_VERTICAL);
+  partial_text_label->SetLineHeight(vertical_spacing * 5);
+  partial_text_label->SetHorizontalAlignment(
+      gfx::HorizontalAlignment::ALIGN_LEFT);
+  partial_text_label->SetProperty(
+      views::kFlexBehaviorKey,
+      views::FlexSpecification(views::MinimumFlexSizeRule::kScaleToZero,
+                               views::MaximumFlexSizeRule::kUnbounded));
+  partial_text_label->SetProperty(views::kMarginsKey,
+                                  gfx::Insets::TLBR(0, 0, vertical_spacing, 0));
+  partial_text_row->AddChildView(std::move(partial_text_label));
+  view->AddChildView(std::move(partial_text_row));
+
   // Button to trigger full page translation.
   auto button_row = std::make_unique<views::BoxLayoutView>();
   button_row->SetMainAxisAlignment(views::BoxLayout::MainAxisAlignment::kEnd);
diff --git a/chrome/browser/ui/views/translate/partial_translate_bubble_view.h b/chrome/browser/ui/views/translate/partial_translate_bubble_view.h
index 252f2bf..26d95504 100644
--- a/chrome/browser/ui/views/translate/partial_translate_bubble_view.h
+++ b/chrome/browser/ui/views/translate/partial_translate_bubble_view.h
@@ -60,6 +60,7 @@
                              std::unique_ptr<PartialTranslateBubbleModel> model,
                              translate::TranslateErrors::Type error_type,
                              content::WebContents* web_contents,
+                             const std::u16string& text_selection,
                              base::OnceClosure on_closing);
 
   PartialTranslateBubbleView(const PartialTranslateBubbleView&) = delete;
@@ -245,6 +246,8 @@
 
   std::unique_ptr<WebContentMouseHandler> mouse_handler_;
 
+  std::u16string text_selection_;
+
   base::OnceClosure on_closing_;
 
   raw_ptr<content::WebContents> web_contents_;
diff --git a/chrome/browser/ui/views/translate/partial_translate_bubble_view_unittest.cc b/chrome/browser/ui/views/translate/partial_translate_bubble_view_unittest.cc
index 013bdaf..c96a425 100644
--- a/chrome/browser/ui/views/translate/partial_translate_bubble_view_unittest.cc
+++ b/chrome/browser/ui/views/translate/partial_translate_bubble_view_unittest.cc
@@ -94,7 +94,8 @@
     std::unique_ptr<PartialTranslateBubbleModel> model(mock_model_);
     bubble_ = new PartialTranslateBubbleView(
         anchor_widget_->GetContentsView(), std::move(model),
-        translate::TranslateErrors::NONE, nullptr, base::DoNothing());
+        translate::TranslateErrors::NONE, nullptr, std::u16string(),
+        base::DoNothing());
     views::BubbleDialogDelegateView::CreateBubble(bubble_)->Show();
   }
 
diff --git a/chrome/browser/ui/views/translate/translate_bubble_controller.cc b/chrome/browser/ui/views/translate/translate_bubble_controller.cc
index 1841b28..70c356e 100644
--- a/chrome/browser/ui/views/translate/translate_bubble_controller.cc
+++ b/chrome/browser/ui/views/translate/translate_bubble_controller.cc
@@ -32,7 +32,7 @@
     const std::string& target_language,
     translate::TranslateErrors::Type error_type,
     LocationBarBubbleDelegateView::DisplayReason reason) {
-  // If the partial translate bubble is already being shown, close it before
+  // If the Partial Translate bubble is already being shown, close it before
   // showing the full translate bubble.
   if (partial_translate_bubble_view_)
     partial_translate_bubble_view_->CloseBubble();
@@ -98,8 +98,9 @@
     PartialTranslateBubbleModel::ViewState view_state,
     const std::string& source_language,
     const std::string& target_language,
+    const std::u16string& text_selection,
     translate::TranslateErrors::Type error_type) {
-  // If the other translate bubble is already being shown, close it before
+  // If the other Translate bubble is already being shown, close it before
   // showing this one.
   if (translate_bubble_view_)
     translate_bubble_view_->CloseBubble();
@@ -135,7 +136,7 @@
   auto partial_translate_bubble_view =
       std::make_unique<PartialTranslateBubbleView>(
           anchor_view, std::move(model), error_type, web_contents,
-          GetOnPartialTranslateBubbleClosedCallback());
+          text_selection, GetOnPartialTranslateBubbleClosedCallback());
   partial_translate_bubble_view_ = partial_translate_bubble_view.get();
 
   if (highlighted_button)
diff --git a/chrome/browser/ui/views/translate/translate_bubble_controller.h b/chrome/browser/ui/views/translate/translate_bubble_controller.h
index 714f5ed..faa7a666 100644
--- a/chrome/browser/ui/views/translate/translate_bubble_controller.h
+++ b/chrome/browser/ui/views/translate/translate_bubble_controller.h
@@ -23,7 +23,7 @@
   static TranslateBubbleController* GetOrCreate(
       content::WebContents* web_contents);
 
-  // Shows the full page Translate bubble. Returns the newly created bubble's
+  // Shows the Full Page Translate bubble. Returns the newly created bubble's
   // Widget or nullptr in cases when the bubble already exists or when the
   // bubble is not created.
   views::Widget* ShowTranslateBubble(
@@ -44,17 +44,18 @@
       PartialTranslateBubbleModel::ViewState view_state,
       const std::string& source_language,
       const std::string& target_language,
+      const std::u16string& text_selection,
       translate::TranslateErrors::Type error_type);
 
-  // Closes the current partial or full page translate bubble, if it exists. At
+  // Closes the current Partial or Full Page Translate bubble, if it exists. At
   // most one of these bubble should be non-null at any given time.
   void CloseBubble();
 
-  // Returns the currently shown full page translate bubble view. Returns
+  // Returns the currently shown Full Page Translate bubble view. Returns
   // nullptr if the bubble is not currently shown.
   TranslateBubbleView* GetTranslateBubble() const;
 
-  // Returns the currently shown partial translate bubble view. Returns nullptr
+  // Returns the currently shown Partial Translate bubble view. Returns nullptr
   // if the bubble is not currently shown.
   PartialTranslateBubbleView* GetPartialTranslateBubble() const;
 
@@ -72,7 +73,7 @@
   explicit TranslateBubbleController(content::WebContents* contents);
 
  private:
-  // Weak references for the two possible translate bubble views. These will be
+  // Weak references for the two possible Translate bubble views. These will be
   // nullptr if no bubble is currently shown. At most one of these pointers
   // should be non-null at any given time.
   raw_ptr<TranslateBubbleView> translate_bubble_view_ = nullptr;
@@ -87,7 +88,7 @@
   base::RepeatingCallback<std::unique_ptr<PartialTranslateBubbleModel>()>
       partial_model_factory_callback_;
 
-  // Handlers for when translate bubbles are closed.
+  // Handlers for when Translate bubbles are closed.
   void OnTranslateBubbleClosed();
   void OnPartialTranslateBubbleClosed();
 
diff --git a/chrome/browser/ui/views/translate/translate_bubble_controller_unittest.cc b/chrome/browser/ui/views/translate/translate_bubble_controller_unittest.cc
index 08600f72..6e9bb47 100644
--- a/chrome/browser/ui/views/translate/translate_bubble_controller_unittest.cc
+++ b/chrome/browser/ui/views/translate/translate_bubble_controller_unittest.cc
@@ -173,8 +173,8 @@
     web_contents_->SetUserData(TranslateBubbleController::UserDataKey(),
                                base::WrapUnique(controller_.get()));
 
-    // Use fake translate bubble models instead of real implementations for
-    // translate bubble view construction in tests.
+    // Use fake Translate bubble models instead of real implementations for
+    // Translate bubble view construction in tests.
     controller_->SetTranslateBubbleModelFactory(base::BindRepeating(
         &TranslateBubbleControllerTest::GetFakeTranslateBubbleModel,
         base::Unretained(this)));
@@ -223,17 +223,17 @@
 
   EXPECT_THAT(controller_->GetTranslateBubble(), testing::NotNull());
 
-  // Showing the partial translate bubble while the Full Page Translate bubble
-  // is open should close the full translate bubble.
+  // Showing the Partial Translate bubble while the Full Page Translate bubble
+  // is open should close the Full Page Translate bubble.
   controller_->ShowPartialTranslateBubble(
       anchor_widget_->GetContentsView(), nullptr,
       PartialTranslateBubbleModel::ViewState::VIEW_STATE_BEFORE_TRANSLATE, "fr",
-      "en", translate::TranslateErrors::Type::NONE);
+      "en", std::u16string(), translate::TranslateErrors::Type::NONE);
   base::RunLoop().RunUntilIdle();
   EXPECT_THAT(controller_->GetPartialTranslateBubble(), testing::NotNull());
   EXPECT_THAT(controller_->GetTranslateBubble(), testing::IsNull());
 
-  // Only the partial translate bubble should remain, close it.
+  // Only the Partial Translate bubble should remain, close it.
   controller_->CloseBubble();
   base::RunLoop().RunUntilIdle();
   EXPECT_THAT(controller_->GetPartialTranslateBubble(), testing::IsNull());
@@ -243,15 +243,15 @@
   EXPECT_THAT(controller_->GetPartialTranslateBubble(), testing::IsNull());
   EXPECT_THAT(controller_->GetTranslateBubble(), testing::IsNull());
 
-  // Show the partial translate bubble first.
+  // Show the Partial Translate bubble first.
   controller_->ShowPartialTranslateBubble(
       anchor_widget_->GetContentsView(), nullptr,
       PartialTranslateBubbleModel::ViewState::VIEW_STATE_BEFORE_TRANSLATE, "fr",
-      "en", translate::TranslateErrors::Type::NONE);
+      "en", std::u16string(), translate::TranslateErrors::Type::NONE);
   EXPECT_THAT(controller_->GetPartialTranslateBubble(), testing::NotNull());
 
-  // Showing the Full Page Translate bubble while the partial translate bubble
-  // is open should close the partial translate bubble.
+  // Showing the Full Page Translate bubble while the Partial Translate bubble
+  // is open should close the Partial Translate bubble.
   controller_->ShowTranslateBubble(
       anchor_widget_->GetContentsView(), nullptr,
       translate::TranslateStep::TRANSLATE_STEP_BEFORE_TRANSLATE, "fr", "en",
diff --git a/chrome/browser/ui/views/translate/translate_icon_view.cc b/chrome/browser/ui/views/translate/translate_icon_view.cc
index dfe1f96d..6364cc60 100644
--- a/chrome/browser/ui/views/translate/translate_icon_view.cc
+++ b/chrome/browser/ui/views/translate/translate_icon_view.cc
@@ -47,6 +47,28 @@
   return nullptr;
 }
 
+views::BubbleDialogDelegate* TranslateIconView::GetPartialTranslateBubble()
+    const {
+  if (GetWebContents()) {
+    TranslateBubbleController* translate_bubble_controller =
+        TranslateBubbleController::FromWebContents(GetWebContents());
+
+    if (translate_bubble_controller)
+      return translate_bubble_controller->GetPartialTranslateBubble();
+  }
+
+  return nullptr;
+}
+
+bool TranslateIconView::IsBubbleShowing() const {
+  // We override the PageActionIconView implementation because there are two
+  // different bubbles that may be shown with the Translate icon, and so this
+  // function should return true if either the Full Page Translate or Partial
+  // Translate bubble are showing. If a bubble is being destroyed, it's
+  // considered showing though it may be already invisible currently.
+  return (GetBubble() != nullptr) || (GetPartialTranslateBubble() != nullptr);
+}
+
 void TranslateIconView::UpdateImpl() {
   if (!GetWebContents())
     return;
diff --git a/chrome/browser/ui/views/translate/translate_icon_view.h b/chrome/browser/ui/views/translate/translate_icon_view.h
index 9fcd017..0bb5dc3 100644
--- a/chrome/browser/ui/views/translate/translate_icon_view.h
+++ b/chrome/browser/ui/views/translate/translate_icon_view.h
@@ -25,12 +25,17 @@
   // PageActionIconView:
   views::BubbleDialogDelegate* GetBubble() const override;
   void UpdateImpl() override;
+  bool IsBubbleShowing() const override;
 
  protected:
   // PageActionIconView:
   void OnExecuting(PageActionIconView::ExecuteSource execute_source) override;
   const gfx::VectorIcon& GetVectorIcon() const override;
   std::u16string GetTextForTooltipAndAccessibleName() const override;
+
+ private:
+  // Returns the Partial Translate bubble instance for the Translate icon.
+  views::BubbleDialogDelegate* GetPartialTranslateBubble() const;
 };
 
 #endif  // CHROME_BROWSER_UI_VIEWS_TRANSLATE_TRANSLATE_ICON_VIEW_H_
diff --git a/chrome/browser/ui/views/translate/translate_icon_view_interactive_uitest.cc b/chrome/browser/ui/views/translate/translate_icon_view_interactive_uitest.cc
new file mode 100644
index 0000000..4acfc64
--- /dev/null
+++ b/chrome/browser/ui/views/translate/translate_icon_view_interactive_uitest.cc
@@ -0,0 +1,66 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/views/translate/translate_icon_view.h"
+
+#include <string>
+#include "chrome/browser/ui/views/frame/browser_view.h"
+#include "chrome/browser/ui/views/frame/toolbar_button_provider.h"
+#include "chrome/browser/ui/views/page_action/page_action_icon_view.h"
+#include "chrome/browser/ui/views/translate/partial_translate_bubble_view.h"
+#include "chrome/browser/ui/views/translate/translate_bubble_controller.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/interactive_test_utils.h"
+#include "content/public/test/browser_test.h"
+#include "ui/base/test/ui_controls.h"
+
+namespace translate {
+
+class TranslateIconViewTest : public InProcessBrowserTest {
+ public:
+  TranslateIconViewTest() = default;
+
+  TranslateIconViewTest(const TranslateIconViewTest&) = delete;
+  TranslateIconViewTest& operator=(const TranslateIconViewTest&) = delete;
+
+  ~TranslateIconViewTest() override = default;
+
+  PageActionIconView* GetTranslateIcon() {
+    return BrowserView::GetBrowserViewForBrowser(browser())
+        ->toolbar_button_provider()
+        ->GetPageActionIconView(PageActionIconType::kTranslate);
+  }
+
+  PartialTranslateBubbleView* GetPartialTranslateBubble() {
+    return TranslateBubbleController::FromWebContents(
+               browser()->tab_strip_model()->GetActiveWebContents())
+        ->GetPartialTranslateBubble();
+  }
+};
+
+// Verifies that clicking the Translate icon closes the Partial Translate bubble
+// and results in neither of the two Translate bubbles being shown.
+IN_PROC_BROWSER_TEST_F(TranslateIconViewTest, ClosePartialTranslateBubble) {
+  browser()->window()->ShowPartialTranslateBubble(
+      PartialTranslateBubbleModel::ViewState::VIEW_STATE_BEFORE_TRANSLATE, "fr",
+      "en", std::u16string(), TranslateErrors::NONE);
+  EXPECT_THAT(GetPartialTranslateBubble(), ::testing::NotNull());
+
+  // The Translate icon should appear with the Partial Translate bubble
+  PageActionIconView* translate_icon = GetTranslateIcon();
+  EXPECT_THAT(translate_icon, ::testing::NotNull());
+
+  // Clicking the icon should close the Partial Translate bubble and should not
+  // open the Full Page Translate bubble.
+  base::RunLoop loop;
+  ui_test_utils::MoveMouseToCenterAndPress(translate_icon, ui_controls::LEFT,
+                                           ui_controls::DOWN | ui_controls::UP,
+                                           loop.QuitClosure());
+  loop.Run();
+
+  EXPECT_THAT(GetPartialTranslateBubble(), ::testing::IsNull());
+  EXPECT_THAT(translate_icon->GetBubble(), ::testing::IsNull());
+}
+
+}  // namespace translate
diff --git a/chrome/browser/ui/webui/chromeos/arc_graphics_tracing/arc_graphics_tracing_handler.cc b/chrome/browser/ui/webui/chromeos/arc_graphics_tracing/arc_graphics_tracing_handler.cc
index b32550b..4ae91e3b 100644
--- a/chrome/browser/ui/webui/chromeos/arc_graphics_tracing/arc_graphics_tracing_handler.cc
+++ b/chrome/browser/ui/webui/chromeos/arc_graphics_tracing/arc_graphics_tracing_handler.cc
@@ -349,22 +349,22 @@
 }
 
 void ArcGraphicsTracingHandler::RegisterMessages() {
-  web_ui()->RegisterDeprecatedMessageCallback(
+  web_ui()->RegisterMessageCallback(
       "ready", base::BindRepeating(&ArcGraphicsTracingHandler::HandleReady,
                                    base::Unretained(this)));
-  web_ui()->RegisterDeprecatedMessageCallback(
+  web_ui()->RegisterMessageCallback(
       "loadFromText",
       base::BindRepeating(&ArcGraphicsTracingHandler::HandleLoadFromText,
                           base::Unretained(this)));
   switch (mode_) {
     case ArcGraphicsTracingMode::kFull:
-      web_ui()->RegisterDeprecatedMessageCallback(
+      web_ui()->RegisterMessageCallback(
           "setStopOnJank",
           base::BindRepeating(&ArcGraphicsTracingHandler::HandleSetStopOnJank,
                               base::Unretained(this)));
       break;
     case ArcGraphicsTracingMode::kOverview:
-      web_ui()->RegisterDeprecatedMessageCallback(
+      web_ui()->RegisterMessageCallback(
           "setMaxTime",
           base::BindRepeating(&ArcGraphicsTracingHandler::HandleSetMaxTime,
                               base::Unretained(this)));
@@ -600,7 +600,7 @@
                          std::move(result.first));
 }
 
-void ArcGraphicsTracingHandler::HandleReady(const base::ListValue* args) {
+void ArcGraphicsTracingHandler::HandleReady(const base::Value::List& args) {
   if (mode_ != ArcGraphicsTracingMode::kFull)
     return;
 
@@ -613,40 +613,40 @@
 }
 
 void ArcGraphicsTracingHandler::HandleSetStopOnJank(
-    const base::ListValue* args) {
-  DCHECK_EQ(1U, args->GetListDeprecated().size());
+    const base::Value::List& args) {
+  DCHECK_EQ(1U, args.size());
   DCHECK_EQ(ArcGraphicsTracingMode::kFull, mode_);
-  if (!args->GetListDeprecated()[0].is_bool()) {
+  if (!args[0].is_bool()) {
     LOG(ERROR) << "Invalid input";
     return;
   }
-  stop_on_jank_ = args->GetListDeprecated()[0].GetBool();
+  stop_on_jank_ = args[0].GetBool();
 }
 
-void ArcGraphicsTracingHandler::HandleSetMaxTime(const base::ListValue* args) {
-  DCHECK_EQ(1U, args->GetListDeprecated().size());
+void ArcGraphicsTracingHandler::HandleSetMaxTime(
+    const base::Value::List& args) {
+  DCHECK_EQ(1U, args.size());
   DCHECK_EQ(ArcGraphicsTracingMode::kOverview, mode_);
 
-  if (!args->GetListDeprecated()[0].is_int()) {
+  if (!args[0].is_int()) {
     LOG(ERROR) << "Invalid input";
     return;
   }
-  max_tracing_time_ = base::Seconds(args->GetListDeprecated()[0].GetInt());
+  max_tracing_time_ = base::Seconds(args[0].GetInt());
   DCHECK_GE(max_tracing_time_, base::Seconds(1));
 }
 
 void ArcGraphicsTracingHandler::HandleLoadFromText(
-    const base::ListValue* args) {
-  DCHECK_EQ(1U, args->GetListDeprecated().size());
-  if (!args->GetListDeprecated()[0].is_string()) {
+    const base::Value::List& args) {
+  DCHECK_EQ(1U, args.size());
+  if (!args[0].is_string()) {
     LOG(ERROR) << "Invalid input";
     return;
   }
 
   base::ThreadPool::PostTaskAndReplyWithResult(
       FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT},
-      base::BindOnce(&LoadGraphicsModel, mode_,
-                     std::move(args->GetListDeprecated()[0].GetString())),
+      base::BindOnce(&LoadGraphicsModel, mode_, std::move(args[0].GetString())),
       base::BindOnce(&ArcGraphicsTracingHandler::OnGraphicsModelReady,
                      weak_ptr_factory_.GetWeakPtr()));
 }
diff --git a/chrome/browser/ui/webui/chromeos/arc_graphics_tracing/arc_graphics_tracing_handler.h b/chrome/browser/ui/webui/chromeos/arc_graphics_tracing/arc_graphics_tracing_handler.h
index 858d4d26..cfc6bea4 100644
--- a/chrome/browser/ui/webui/chromeos/arc_graphics_tracing/arc_graphics_tracing_handler.h
+++ b/chrome/browser/ui/webui/chromeos/arc_graphics_tracing/arc_graphics_tracing_handler.h
@@ -13,6 +13,7 @@
 #include "base/memory/weak_ptr.h"
 #include "base/time/time.h"
 #include "base/timer/timer.h"
+#include "base/values.h"
 #include "chrome/browser/ui/webui/chromeos/arc_graphics_tracing/arc_graphics_tracing.h"
 #include "components/exo/surface_observer.h"
 #include "content/public/browser/web_ui_message_handler.h"
@@ -29,7 +30,6 @@
 
 namespace base {
 class FilePath;
-class ListValue;
 }  // namespace base
 
 namespace exo {
@@ -93,10 +93,10 @@
   void OnGraphicsModelReady(std::pair<base::Value, std::string> result);
 
   // Handlers for calls from JS.
-  void HandleReady(const base::ListValue* args);
-  void HandleSetStopOnJank(const base::ListValue* args);
-  void HandleSetMaxTime(const base::ListValue* args);
-  void HandleLoadFromText(const base::ListValue* args);
+  void HandleReady(const base::Value::List& args);
+  void HandleSetStopOnJank(const base::Value::List& args);
+  void HandleSetMaxTime(const base::Value::List& args);
+  void HandleLoadFromText(const base::Value::List& args);
 
   // Updates title and icon for the active ARC window.
   void UpdateActiveArcWindowInfo();
diff --git a/chrome/browser/ui/webui/chromeos/notification_tester/notification_tester_handler.cc b/chrome/browser/ui/webui/chromeos/notification_tester/notification_tester_handler.cc
index e169b47..74d14b6e 100644
--- a/chrome/browser/ui/webui/chromeos/notification_tester/notification_tester_handler.cc
+++ b/chrome/browser/ui/webui/chromeos/notification_tester/notification_tester_handler.cc
@@ -12,6 +12,10 @@
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/utf_string_conversions.h"
 #include "chrome/app/vector_icons/vector_icons.h"
+#include "chrome/grit/notification_tester_resources.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/gfx/image/image.h"
+#include "ui/gfx/vector_icon_types.h"
 #include "ui/message_center/message_center.h"
 #include "ui/message_center/public/cpp/notification.h"
 
@@ -33,22 +37,31 @@
     const base::Value::List& args) {
   AllowJavascript();
 
-  // Parse JS args.
-  std::u16string title = base::UTF8ToUTF16(args[0].GetString());
-  std::u16string message = base::UTF8ToUTF16(args[1].GetString());
+  // Unpack JS args.
+  const base::Value::Dict* notifObj = args[0].GetIfDict();
+  DCHECK(notifObj);
 
-  GenerateNotification(title, message);
-}
+  const std::string* title = notifObj->FindString("title");
+  DCHECK(title);
 
-void NotificationTesterHandler::GenerateNotification(
-    const std::u16string& title,
-    const std::u16string& message) {
+  const std::string* message = notifObj->FindString("message");
+  DCHECK(message);
+
+  const std::string* icon = notifObj->FindString("icon");
+  DCHECK(icon);
+
+  const std::string* image = notifObj->FindString("richDataImage");
+  DCHECK(image);
+
+  // Generate Notification.
   std::u16string display_source = u"Sample Display Source";
   GURL origin_url("https://test-url.xyz");
   message_center::NotifierId notifier_id(
       message_center::NotifierType::SYSTEM_COMPONENT, "test notifier id",
       ash::NotificationCatalogName::kTestCatalogName);
+
   message_center::RichNotificationData optional_fields;
+  SetNotificationImage(*image, optional_fields);
 
   // Delegate does nothing.
   auto delegate =
@@ -62,13 +75,37 @@
 
   std::unique_ptr<message_center::Notification> notification =
       ash::CreateSystemNotification(
-          message_center::NOTIFICATION_TYPE_SIMPLE, notification_id, title,
-          message, display_source, origin_url, notifier_id, optional_fields,
-          delegate, kTerminalSshIcon,
+          message_center::NOTIFICATION_TYPE_SIMPLE, notification_id,
+          base::UTF8ToUTF16(*title), base::UTF8ToUTF16(*message),
+          display_source, origin_url, notifier_id, optional_fields, delegate,
+          GetNotificationIcon(*icon),
           message_center::SystemNotificationWarningLevel::NORMAL);
 
   message_center::MessageCenter::Get()->AddNotification(
       std::move(notification));
 }
 
+const gfx::VectorIcon& NotificationTesterHandler::GetNotificationIcon(
+    const std::string& icon) {
+  if (icon == "kTerminalSshIcon") {
+    return kTerminalSshIcon;
+  } else if (icon == "kCreditCardIcon") {
+    return kCreditCardIcon;
+  } else if (icon == "kSmartphoneIcon") {
+    return kSmartphoneIcon;
+  }
+
+  return gfx::kNoneIcon;  // Default Case
+}
+
+void NotificationTesterHandler::SetNotificationImage(
+    const std::string& image,
+    message_center::RichNotificationData& optional_fields) {
+  ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
+  if (image == "chromeos_logo_main") {
+    optional_fields.image = rb.GetNativeImageNamed(
+        IDR_NOTIFICATION_TESTER_IMAGES_CHROMEOS_LOGO_MAIN_PNG);
+  }
+}
+
 }  // namespace chromeos
diff --git a/chrome/browser/ui/webui/chromeos/notification_tester/notification_tester_handler.h b/chrome/browser/ui/webui/chromeos/notification_tester/notification_tester_handler.h
index 60956f3..e282557 100644
--- a/chrome/browser/ui/webui/chromeos/notification_tester/notification_tester_handler.h
+++ b/chrome/browser/ui/webui/chromeos/notification_tester/notification_tester_handler.h
@@ -7,6 +7,14 @@
 
 #include "content/public/browser/web_ui_message_handler.h"
 
+namespace gfx {
+struct VectorIcon;
+}  // namespace gfx
+
+namespace message_center {
+class RichNotificationData;
+}  // namespace message_center
+
 namespace chromeos {
 
 // WebUI message handler for chrome://notification-tester from the front-end to
@@ -27,10 +35,15 @@
   // message to generate a notification from the front-end.
   void HandleGenerateNotificationForm(const base::Value::List& args);
 
-  // Generates a notification via the message center with the given title and
-  // body.
-  void GenerateNotification(const std::u16string& title,
-                            const std::u16string& message);
+  // Given the name of an icon such as 'kTerminalSshIcon', return the
+  // corresponding gfx::VectorIcon.
+  const gfx::VectorIcon& GetNotificationIcon(const std::string& icon);
+
+  // Given the name of an image within the notification tester resources, return
+  // the corresponding gfx::Image.
+  void SetNotificationImage(
+      const std::string& image,
+      message_center::RichNotificationData& optional_fields);
 };
 
 }  // namespace chromeos
diff --git a/chrome/browser/ui/webui/settings/chromeos/constants/BUILD.gn b/chrome/browser/ui/webui/settings/chromeos/constants/BUILD.gn
index 24ae25d..bec584b 100644
--- a/chrome/browser/ui/webui/settings/chromeos/constants/BUILD.gn
+++ b/chrome/browser/ui/webui/settings/chromeos/constants/BUILD.gn
@@ -12,4 +12,5 @@
     "routes.mojom",
     "setting.mojom",
   ]
+  webui_module_path = "/"
 }
diff --git a/chrome/browser/ui/webui/settings/chromeos/constants/setting.mojom b/chrome/browser/ui/webui/settings/chromeos/constants/setting.mojom
index 0642fe0..0ebf9fb 100644
--- a/chrome/browser/ui/webui/settings/chromeos/constants/setting.mojom
+++ b/chrome/browser/ui/webui/settings/chromeos/constants/setting.mojom
@@ -206,6 +206,7 @@
   kSnoopingProtection = 1114,
   kQuickDim = 1115,
   kCameraOnOff = 1116,
+  kMicrophoneOnOff = 1117,
 
   // Languages and Input section.
   kAddLanguage = 1200,
diff --git a/chrome/browser/ui/webui/settings/chromeos/privacy_section.cc b/chrome/browser/ui/webui/settings/chromeos/privacy_section.cc
index 86058dbc..99c18c73 100644
--- a/chrome/browser/ui/webui/settings/chromeos/privacy_section.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/privacy_section.cc
@@ -328,6 +328,7 @@
       {"cameraToggleTitle", IDS_OS_SETTINGS_CAMERA_TOGGLE_TITLE},
       {"cameraToggleSublabelActive",
        IDS_OS_SETTINGS_PRIVACY_HUB_CAMERA_HARDWARE_TOGGLE_ACTIVE_SUBTEXT},
+      {"microphoneToggleTitle", IDS_OS_SETTINGS_MICROPHONE_TOGGLE_TITLE},
   };
   html_source->AddLocalizedStrings(kLocalizedStrings);
 
@@ -470,8 +471,10 @@
       IDS_OS_SETTINGS_PRIVACY_HUB_TITLE, mojom::Subpage::kPrivacyHub,
       mojom::SearchResultIcon::kShield, mojom::SearchResultDefaultRank::kMedium,
       mojom::kPrivacyHubSubpagePath);
-  RegisterNestedSettingBulk(mojom::Subpage::kPrivacyHub,
-                            {{mojom::Setting::kCameraOnOff}}, generator);
+  RegisterNestedSettingBulk(
+      mojom::Subpage::kPrivacyHub,
+      {{mojom::Setting::kCameraOnOff, mojom::Setting::kMicrophoneOnOff}},
+      generator);
 }
 
 bool PrivacySection::AreFingerprintSettingsAllowed() {
diff --git a/chrome/browser/user_notes/DEPS b/chrome/browser/user_notes/DEPS
new file mode 100644
index 0000000..cdaed7a
--- /dev/null
+++ b/chrome/browser/user_notes/DEPS
@@ -0,0 +1,8 @@
+include_rules = [
+  # Required to get the interface implementation from BrowserUserData. No other
+  # assumptions about chrome/browser/ui/views are made; consumers use the base
+  # interface to interact with the UI implementation.
+  # TODO(crbug.com/1341055): Find a way to return the coordinator without
+  # needing this dependency.
+  "+chrome/browser/ui/views/side_panel/user_note/user_note_ui_coordinator.h",
+]
diff --git a/chrome/browser/user_notes/user_note_service_delegate_impl.cc b/chrome/browser/user_notes/user_note_service_delegate_impl.cc
index 8388db6f2..920822e 100644
--- a/chrome/browser/user_notes/user_note_service_delegate_impl.cc
+++ b/chrome/browser/user_notes/user_note_service_delegate_impl.cc
@@ -4,13 +4,31 @@
 
 #include "chrome/browser/user_notes/user_note_service_delegate_impl.h"
 
-#include "base/notreached.h"
 #include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/browser_finder.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/browser/ui/views/side_panel/user_note/user_note_ui_coordinator.h"
 #include "components/user_notes/interfaces/user_notes_ui.h"
 #include "content/public/browser/render_frame_host.h"
 
 namespace user_notes {
 
+namespace {
+
+Browser* GetBrowserFromRenderFrameHost(const content::RenderFrameHost* rfh) {
+  content::WebContents* web_contents =
+      content::WebContents::FromRenderFrameHost(
+          const_cast<content::RenderFrameHost*>(rfh));
+
+  if (!web_contents) {
+    return nullptr;
+  }
+
+  return chrome::FindBrowserWithWebContents(web_contents);
+}
+
+}  // namespace
+
 UserNoteServiceDelegateImpl::UserNoteServiceDelegateImpl(Profile* profile)
     : profile_(profile) {}
 
@@ -18,27 +36,54 @@
 
 std::vector<content::RenderFrameHost*>
 UserNoteServiceDelegateImpl::GetAllFramesForUserNotes() {
-  // TODO(crbug.com/1313967): For now, this only looks at the primary main frame
-  // of open tabs, since notes will initially only be supported there. When / if
-  // User Notes are supported in AMP viewers and subframes in general, this will
-  // need to walk the full frame tree of every open tab for the profile.
-  // TODO(gujen): finish implementation.
-  NOTIMPLEMENTED();
-  return std::vector<content::RenderFrameHost*>();
+  // TODO(crbug.com/1313967): This returns only the primary main frame of open
+  // tabs, since User Notes are only supported in the primary main frame for
+  // now. When / if User Notes are supported in AMP viewers and subframes in
+  // general, this will need to walk the full frame tree of every open tab for
+  // the current profile and add each frame to the result, not just the root
+  // frame.
+  const std::vector<Browser*>& browsers =
+      chrome::FindAllTabbedBrowsersWithProfile(profile_);
+  std::vector<content::RenderFrameHost*> results;
+
+  for (Browser* browser : browsers) {
+    TabStripModel* tab_strip_model = browser->tab_strip_model();
+    for (int i = 0; i < tab_strip_model->count(); ++i) {
+      results.emplace_back(
+          tab_strip_model->GetWebContentsAt(i)->GetPrimaryMainFrame());
+    }
+  }
+
+  return results;
 }
 
 UserNotesUI* UserNoteServiceDelegateImpl::GetUICoordinatorForFrame(
     const content::RenderFrameHost* rfh) {
-  // TODO(gujen): finish implementation.
-  NOTIMPLEMENTED();
-  return nullptr;
+  Browser* browser = GetBrowserFromRenderFrameHost(rfh);
+  if (!browser) {
+    return nullptr;
+  }
+
+  return UserNoteUICoordinator::FromBrowser(browser);
 }
 
 bool UserNoteServiceDelegateImpl::IsFrameInActiveTab(
     const content::RenderFrameHost* rfh) {
-  // TODO(gujen): finish implementation.
-  NOTIMPLEMENTED();
-  return false;
+  Browser* browser = GetBrowserFromRenderFrameHost(rfh);
+  if (!browser) {
+    return false;
+  }
+
+  TabStripModel* tab_strip_model = browser->tab_strip_model();
+  content::WebContents* active_web_contents =
+      tab_strip_model->GetActiveWebContents();
+
+  if (active_web_contents) {
+    return active_web_contents->GetPrimaryMainFrame() ==
+           const_cast<content::RenderFrameHost*>(rfh)->GetMainFrame();
+  } else {
+    return false;
+  }
 }
 
 }  // namespace user_notes
diff --git a/chrome/browser/user_notes/user_note_service_delegate_impl_unittest.cc b/chrome/browser/user_notes/user_note_service_delegate_impl_unittest.cc
new file mode 100644
index 0000000..249570a
--- /dev/null
+++ b/chrome/browser/user_notes/user_note_service_delegate_impl_unittest.cc
@@ -0,0 +1,74 @@
+// Copyright 2022 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/user_notes/user_note_service_delegate_impl.h"
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "chrome/browser/ui/browser_list.h"
+#include "chrome/test/base/browser_with_test_window_test.h"
+#include "chrome/test/base/testing_profile.h"
+#include "content/public/browser/render_frame_host.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace user_notes {
+
+namespace {
+
+const char kBaseUrl[] = "http://www.google.com/";
+
+}  // namespace
+
+class UserNoteServiceDelegateImplTest : public BrowserWithTestWindowTest {};
+
+TEST_F(UserNoteServiceDelegateImplTest, GetAllFramesForUserNotes) {
+  const BrowserList* browser_list = BrowserList::GetInstance();
+  EXPECT_EQ(browser_list->size(), 1u);
+
+  Browser* browser1 = browser();
+  std::unique_ptr<BrowserWindow> window2 = CreateBrowserWindow();
+  std::unique_ptr<Browser> browser2 =
+      CreateBrowser(profile(), Browser::TYPE_NORMAL, false, window2.get());
+  EXPECT_EQ(browser_list->size(), 2u);
+
+  GURL url1 = GURL(kBaseUrl + base::NumberToString(1));
+  GURL url2 = GURL(kBaseUrl + base::NumberToString(2));
+  GURL url3 = GURL(kBaseUrl + base::NumberToString(3));
+
+  AddTab(browser1, url1);
+  AddTab(browser1, url1);
+  AddTab(browser1, url2);
+  AddTab(browser2.get(), url1);
+  AddTab(browser2.get(), url2);
+  AddTab(browser2.get(), url3);
+
+  std::vector<content::RenderFrameHost*> expected_frames = {
+      browser1->tab_strip_model()->GetWebContentsAt(0)->GetPrimaryMainFrame(),
+      browser1->tab_strip_model()->GetWebContentsAt(1)->GetPrimaryMainFrame(),
+      browser1->tab_strip_model()->GetWebContentsAt(2)->GetPrimaryMainFrame(),
+      browser2->tab_strip_model()->GetWebContentsAt(0)->GetPrimaryMainFrame(),
+      browser2->tab_strip_model()->GetWebContentsAt(1)->GetPrimaryMainFrame(),
+      browser2->tab_strip_model()->GetWebContentsAt(2)->GetPrimaryMainFrame()};
+
+  auto delegate = std::make_unique<UserNoteServiceDelegateImpl>(profile());
+  std::vector<content::RenderFrameHost*> frames =
+      delegate->GetAllFramesForUserNotes();
+
+  EXPECT_EQ(frames.size(), 6u);
+  for (content::RenderFrameHost* frame : frames) {
+    EXPECT_TRUE(base::Contains(expected_frames, frame));
+  }
+  for (content::RenderFrameHost* frame : expected_frames) {
+    EXPECT_TRUE(base::Contains(frames, frame));
+  }
+
+  browser2->tab_strip_model()->CloseAllTabs();
+  browser2.reset();
+  window2.reset();
+}
+
+}  // namespace user_notes
diff --git a/chrome/browser/webauthn/authenticator_request_dialog_model.cc b/chrome/browser/webauthn/authenticator_request_dialog_model.cc
index 2b3df8d..fa87db5 100644
--- a/chrome/browser/webauthn/authenticator_request_dialog_model.cc
+++ b/chrome/browser/webauthn/authenticator_request_dialog_model.cc
@@ -205,9 +205,23 @@
   SetCurrentStep(Step::kMechanismSelection);
 }
 
+void AuthenticatorRequestDialogModel::TransitionToModalWebAuthnRequest() {
+  DCHECK_EQ(current_step(), Step::kConditionalMediation);
+
+  // Dispatch requests to any plugged in authenticators.
+  for (auto& authenticator :
+       ephemeral_state_.saved_authenticators_.authenticator_list()) {
+    if (authenticator.transport != device::FidoTransportProtocol::kInternal) {
+      DispatchRequestAsync(&authenticator);
+    }
+  }
+  StartGuidedFlowForMostLikelyTransportOrShowMechanismSelection();
+}
+
 void AuthenticatorRequestDialogModel::
     StartGuidedFlowForMostLikelyTransportOrShowMechanismSelection() {
-  DCHECK(current_step() == Step::kNotStarted);
+  DCHECK(current_step() == Step::kNotStarted ||
+         current_step() == Step::kConditionalMediation);
 
   const auto priority_mechanism_it =
       std::find_if(mechanisms_.begin(), mechanisms_.end(),
@@ -396,17 +410,19 @@
 }
 
 void AuthenticatorRequestDialogModel::Cancel() {
+  if (use_conditional_mediation_) {
+    // Conditional UI requests are never cancelled, they restart silently.
+    StartOver();
+    return;
+  }
+
   if (is_request_complete()) {
-    if (use_conditional_mediation_) {
-      // Conditional UI requests are never cancelled, they restart silently.
-      StartOver();
-      return;
-    }
     SetCurrentStep(Step::kClosed);
   }
 
-  for (auto& observer : observers_)
+  for (auto& observer : observers_) {
     observer.OnCancelRequest();
+  }
 }
 
 void AuthenticatorRequestDialogModel::ManageDevices() {
@@ -613,8 +629,8 @@
   ephemeral_state_.responses_ = std::move(responses);
   ephemeral_state_.creds_ = {};
   for (const auto& response : ephemeral_state_.responses_) {
-    ephemeral_state_.creds_.emplace_back(device::DiscoverableCredentialMetadata(
-        response.credential->id, *response.user_entity));
+    ephemeral_state_.creds_.emplace_back(response.credential->id,
+                                         *response.user_entity);
   }
   selection_callback_ = std::move(callback);
   SetCurrentStep(Step::kSelectAccount);
@@ -847,6 +863,7 @@
          current_step() == Step::kUsbInsertAndActivate ||
          current_step() == Step::kCableActivate ||
          current_step() == Step::kAndroidAccessory ||
+         current_step() == Step::kConditionalMediation ||
          current_step() == Step::kNotStarted);
   switch (transport) {
     case AuthenticatorTransport::kUsbHumanInterfaceDevice:
diff --git a/chrome/browser/webauthn/authenticator_request_dialog_model.h b/chrome/browser/webauthn/authenticator_request_dialog_model.h
index 3ac3c6e..c37e24b 100644
--- a/chrome/browser/webauthn/authenticator_request_dialog_model.h
+++ b/chrome/browser/webauthn/authenticator_request_dialog_model.h
@@ -291,8 +291,8 @@
   // Starts the UX flow, by either showing the transport selection screen or
   // the guided flow for them most likely transport.
   //
-  // If |use_location_bar_bubble| is true, a non-modal bubble will be displayed
-  // on the location bar instead of the full-blown page-modal UI.
+  // If |is_conditional_mediation| is true, credentials will be shown on the
+  // password autofill instead of the full-blown page-modal UI.
   //
   // |prefer_native_api| indicates that the UI should jump directly to the
   // system WebAuthn UI if there's no better option. This is currently only
@@ -301,12 +301,18 @@
   //
   // Valid action when at step: kNotStarted.
   void StartFlow(TransportAvailabilityInfo transport_availability,
-                 bool use_location_bar_bubble,
+                 bool is_conditional_mediation,
                  bool prefer_native_api);
 
   // Restarts the UX flow.
   void StartOver();
 
+  // Starts a modal WebAuthn flow (i.e. what you normally get if you call
+  // WebAuthn with no mediation parameter) from a conditional request.
+  //
+  // Valid action when at step: kConditionalMediation.
+  void TransitionToModalWebAuthnRequest();
+
   // Starts the UX flow. Tries to figure out the most likely transport to be
   // used, and starts the guided flow for that transport; or shows the manual
   // transport selection screen if the transport could not be uniquely
@@ -725,8 +731,8 @@
   // True if the modal dialog is being shown right now.
   bool showing_dialog_ = false;
 
-  // True if this request should use the non-modal location bar bubble UI
-  // instead of the page-modal, regular UI.
+  // True if this request should display credentials on the password autofill
+  // prompt instead of the page-modal, regular UI.
   bool use_conditional_mediation_ = false;
 
   // offer_try_again_in_ui_ indicates whether a button to retry the request
diff --git a/chrome/browser/webauthn/authenticator_request_dialog_model_unittest.cc b/chrome/browser/webauthn/authenticator_request_dialog_model_unittest.cc
index 37995ba..5e2961ec 100644
--- a/chrome/browser/webauthn/authenticator_request_dialog_model_unittest.cc
+++ b/chrome/browser/webauthn/authenticator_request_dialog_model_unittest.cc
@@ -302,7 +302,7 @@
 
     model.StartFlow(
         std::move(transports_info),
-        /*use_location_bar_bubble=*/false,
+        /*is_conditional_mediation=*/false,
         /*prefer_native_api=*/
         base::Contains(test.params,
                        TransportAvailabilityParam::kPreferNativeAPI));
@@ -343,7 +343,7 @@
                                    "fido:/1234");
 
     model.StartFlow(std::move(tai),
-                    /*use_location_bar_bubble=*/false, prefer_native_api);
+                    /*is_conditional_mediation=*/false, prefer_native_api);
 
     if (prefer_native_api) {
       // The Windows native UI should have been triggered.
@@ -368,7 +368,7 @@
 
   EXPECT_CALL(mock_observer, OnStepTransition());
   model.StartFlow(TransportAvailabilityInfo(),
-                  /*use_location_bar_bubble=*/false,
+                  /*is_conditional_mediation=*/false,
                   /*prefer_native_api=*/false);
   EXPECT_EQ(Step::kErrorNoAvailableTransports, model.current_step());
   testing::Mock::VerifyAndClearExpectations(&mock_observer);
@@ -446,7 +446,7 @@
                                    absl::nullopt);
 
     model.StartFlow(std::move(transports_info),
-                    /*use_location_bar_bubble=*/false,
+                    /*is_conditional_mediation=*/false,
                     /*prefer_native_api=*/false);
     ASSERT_EQ(model.mechanisms().size(), 2u);
 
@@ -508,7 +508,7 @@
 
     EXPECT_CALL(mock_observer, OnStepTransition());
     model.StartFlow(std::move(transports_info),
-                    /*use_location_bar_bubble=*/false,
+                    /*is_conditional_mediation=*/false,
                     /*prefer_native_api=*/false);
     EXPECT_EQ(Step::kMechanismSelection, model.current_step());
     testing::Mock::VerifyAndClearExpectations(&mock_observer);
@@ -549,7 +549,7 @@
     model.SetBluetoothAdapterPowerOnCallback(power_receiver.GetCallback());
     model.set_cable_transport_info(true, {}, base::DoNothing(), absl::nullopt);
     model.StartFlow(std::move(transports_info),
-                    /*use_location_bar_bubble=*/false,
+                    /*is_conditional_mediation=*/false,
                     /*prefer_native_api=*/false);
     EXPECT_EQ(test_case.expected_final_step, model.current_step());
     EXPECT_TRUE(model.ble_adapter_is_powered());
@@ -580,7 +580,7 @@
     model.SetBluetoothAdapterPowerOnCallback(power_receiver.GetCallback());
     model.set_cable_transport_info(true, {}, base::DoNothing(), absl::nullopt);
     model.StartFlow(std::move(transports_info),
-                    /*use_location_bar_bubble=*/false,
+                    /*is_conditional_mediation=*/false,
                     /*prefer_native_api=*/false);
 
     EXPECT_EQ(Step::kBlePowerOnManual, model.current_step());
@@ -622,7 +622,7 @@
     model.SetBluetoothAdapterPowerOnCallback(power_receiver.GetCallback());
     model.set_cable_transport_info(true, {}, base::DoNothing(), absl::nullopt);
     model.StartFlow(std::move(transports_info),
-                    /*use_location_bar_bubble=*/false,
+                    /*is_conditional_mediation=*/false,
                     /*prefer_native_api=*/false);
 
     EXPECT_EQ(Step::kBlePowerOnAutomatic, model.current_step());
@@ -657,7 +657,7 @@
       /*device_id=*/"authenticator", AuthenticatorTransport::kInternal));
 
   model.StartFlow(std::move(transports_info),
-                  /*use_location_bar_bubble=*/false,
+                  /*is_conditional_mediation=*/false,
                   /*prefer_native_api=*/false);
   EXPECT_EQ(AuthenticatorRequestDialogModel::Step::kMechanismSelection,
             model.current_step());
@@ -701,7 +701,7 @@
       &dispatched_authenticator_ids));
 
   model.StartFlow(std::move(transports_info),
-                  /*use_location_bar_bubble=*/false,
+                  /*is_conditional_mediation=*/false,
                   /*prefer_native_api=*/false);
 
   EXPECT_TRUE(model.should_dialog_be_closed());
@@ -735,7 +735,7 @@
   transports_info.has_platform_authenticator_credential = device::
       FidoRequestHandlerBase::RecognizedCredential::kHasRecognizedCredential;
   model.StartFlow(std::move(transports_info),
-                  /*use_location_bar_bubble=*/true,
+                  /*is_conditional_mediation=*/true,
                   /*prefer_native_api=*/false);
   task_environment_.FastForwardUntilNoTasksRemain();
   EXPECT_EQ(model.current_step(), Step::kConditionalMediation);
@@ -776,7 +776,7 @@
   transports_info.recognized_platform_authenticator_credentials = {cred_1,
                                                                    cred_2};
   model.StartFlow(std::move(transports_info),
-                  /*is_location_bar_bubble_ui==*/true,
+                  /*is_conditional_mediation=*/true,
                   /*prefer_native_api=*/false);
   EXPECT_EQ(model.current_step(), Step::kConditionalMediation);
   EXPECT_TRUE(model.should_dialog_be_closed());
@@ -801,20 +801,13 @@
 
   EXPECT_CALL(mock_observer, OnStepTransition());
   model.StartFlow(std::move(TransportAvailabilityInfo()),
-                  /*is_location_bar_bubble_ui==*/true,
+                  /*is_conditional_mediation=*/true,
                   /*prefer_native_api=*/false);
   EXPECT_EQ(model.current_step(), Step::kConditionalMediation);
   testing::Mock::VerifyAndClearExpectations(&mock_observer);
 
-  // Cancel an ongoing request. It should inform the observers to e.g. stop
-  // discoveries.
-  EXPECT_CALL(mock_observer, OnCancelRequest());
-  model.Cancel();
-  EXPECT_NE(model.current_step(), Step::kClosed);
-  testing::Mock::VerifyAndClearExpectations(&mock_observer);
-
-  // Complete the request and then "cancel" it again (as if e.g. the user
-  // clicked the accept button). The request should be restarted.
+  // Cancel an ongoing request (as if e.g. the user clicked the accept button).
+  // The request should be restarted.
   EXPECT_CALL(mock_observer, OnStartOver());
   EXPECT_CALL(mock_observer, OnStepTransition()).Times(2);
   model.SetCurrentStepForTesting(Step::kKeyAlreadyRegistered);
@@ -836,7 +829,7 @@
 
   EXPECT_CALL(mock_observer, OnStepTransition());
   model.StartFlow(std::move(TransportAvailabilityInfo()),
-                  /*is_location_bar_bubble_ui==*/true,
+                  /*is_conditional_mediation=*/true,
                   /*prefer_native_api=*/false);
   EXPECT_EQ(model.current_step(), Step::kConditionalMediation);
   testing::Mock::VerifyAndClearExpectations(&mock_observer);
diff --git a/chrome/browser/webauthn/chrome_authenticator_request_delegate.cc b/chrome/browser/webauthn/chrome_authenticator_request_delegate.cc
index 671865d..6612b20d 100644
--- a/chrome/browser/webauthn/chrome_authenticator_request_delegate.cc
+++ b/chrome/browser/webauthn/chrome_authenticator_request_delegate.cc
@@ -851,10 +851,23 @@
   // request to an authenticator immediately after it has been
   // discovered, or whether the embedder/UI takes charge of that by
   // invoking its RequestCallback.
+  if (!IsWebAuthnUIEnabled()) {
+    // There is no UI to handle request dispatch.
+    return false;
+  }
+  if (is_conditional_ &&
+      (dialog_model_->current_step() ==
+           AuthenticatorRequestDialogModel::Step::kConditionalMediation ||
+       dialog_model_->current_step() ==
+           AuthenticatorRequestDialogModel::Step::kNotStarted)) {
+    // There is an active conditional request that is not showing any UI. The UI
+    // will dispatch to any plugged in authenticators after the user selects an
+    // option.
+    return true;
+  }
   auto transport = authenticator.AuthenticatorTransport();
-  return (is_conditional_ || IsWebAuthnUIEnabled()) &&
-         (!transport ||  // Windows
-          *transport == device::FidoTransportProtocol::kInternal);
+  return !transport ||  // Windows
+         *transport == device::FidoTransportProtocol::kInternal;
 }
 
 void ChromeAuthenticatorRequestDelegate::FidoAuthenticatorAdded(
diff --git a/chrome/browser/webauthn/chrome_webauthn_browsertest.cc b/chrome/browser/webauthn/chrome_webauthn_browsertest.cc
index 0f9a7ce..365cc5a 100644
--- a/chrome/browser/webauthn/chrome_webauthn_browsertest.cc
+++ b/chrome/browser/webauthn/chrome_webauthn_browsertest.cc
@@ -8,12 +8,15 @@
 #include "base/files/file_util.h"
 #include "base/files/scoped_temp_dir.h"
 #include "base/memory/raw_ptr.h"
+#include "base/run_loop.h"
 #include "base/test/bind.h"
+#include "base/test/scoped_feature_list.h"
 #include "base/time/time.h"
 #include "build/build_config.h"
 #include "chrome/browser/extensions/extension_service.h"
 #include "chrome/browser/extensions/install_verifier.h"
 #include "chrome/browser/extensions/test_extension_system.h"
+#include "chrome/browser/password_manager/chrome_password_manager_client.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/webauthn/authenticator_request_dialog_model.h"
 #include "chrome/browser/webauthn/chrome_authenticator_request_delegate.h"
@@ -27,6 +30,8 @@
 #include "device/fido/cable/cable_discovery_data.h"
 #include "device/fido/cable/v2_test_util.h"
 #include "device/fido/features.h"
+#include "device/fido/fido_transport_protocol.h"
+#include "device/fido/virtual_ctap2_device.h"
 #include "device/fido/virtual_fido_device.h"
 #include "device/fido/virtual_fido_device_factory.h"
 #include "device/fido/virtual_u2f_device.h"
@@ -140,6 +145,142 @@
   EXPECT_EQ("webauthn: OK", result);
 }
 
+class WebAuthnConditionalUITest : public WebAuthnBrowserTest {
+  class Observer : public ChromeAuthenticatorRequestDelegate::TestObserver {
+   public:
+    enum State {
+      kHasNotShowedUI,
+      kWaitingForUI,
+      kShowedUI,
+    };
+    virtual ~Observer() = default;
+    void WaitForUI() {
+      if (state_ != kHasNotShowedUI) {
+        return;
+      }
+      state_ = kWaitingForUI;
+      run_loop_.Run();
+    }
+
+    // ChromeAuthenticatorRequestDelegate::TestObserver:
+    void Created(ChromeAuthenticatorRequestDelegate* delegate) override {
+      delegate_ = delegate;
+    };
+
+    std::vector<std::unique_ptr<device::cablev2::Pairing>>
+    GetCablePairingsFromSyncedDevices() override {
+      return {};
+    };
+
+    void OnTransportAvailabilityEnumerated(
+        ChromeAuthenticatorRequestDelegate* delegate,
+        device::FidoRequestHandlerBase::TransportAvailabilityInfo* tai)
+        override{};
+
+    void UIShown(ChromeAuthenticatorRequestDelegate* delegate) override {
+      if (state_ == kWaitingForUI) {
+        // When the content layer controls authenticator dispatch, dispatching
+        // happens on tasks posted right before the UI is shown. We need to
+        // QuitWhenIdle to make sure that, if an authenticator is dispatched to,
+        // that task has a chance to finish before the test continues. That way
+        // we can catch any potentially unexpected authenticator dispatches.
+        run_loop_.QuitWhenIdle();
+      }
+      state_ = kShowedUI;
+    };
+
+    void CableV2ExtensionSeen(
+        base::span<const uint8_t> server_link_data,
+        base::span<const uint8_t> experiments,
+        AuthenticatorRequestDialogModel::ExperimentServerLinkSheet exp_sheet,
+        AuthenticatorRequestDialogModel::ExperimentServerLinkTitle exp_title)
+        override {}
+
+    raw_ptr<ChromeAuthenticatorRequestDelegate> delegate_ = nullptr;
+
+   private:
+    State state_ = kHasNotShowedUI;
+    base::RunLoop run_loop_;
+  };
+
+  void SetUpOnMainThread() override {
+    WebAuthnBrowserTest::SetUpOnMainThread();
+    observer_ = std::make_unique<Observer>();
+    ASSERT_TRUE(ui_test_utils::NavigateToURL(
+        browser(), https_server_.GetURL("www.example.com", "/title1.html")));
+
+    auto virtual_device_factory =
+        std::make_unique<device::test::VirtualFidoDeviceFactory>();
+    virtual_device_factory_ = virtual_device_factory.get();
+    static constexpr uint8_t kCredentialID[] = {1, 2, 3, 4};
+    virtual_device_factory->mutable_state()->InjectResidentKey(
+        kCredentialID, "www.example.com", std::vector<uint8_t>{5, 6, 7, 8},
+        "flandre", "Flandre Scarlet");
+    virtual_device_factory->mutable_state()->fingerprints_enrolled = true;
+    device::VirtualCtap2Device::Config config;
+    config.resident_key_support = true;
+    config.internal_uv_support = true;
+    virtual_device_factory->SetCtap2Config(std::move(config));
+    content::AuthenticatorEnvironment::GetInstance()
+        ->ReplaceDefaultDiscoveryFactoryForTesting(
+            std::move(virtual_device_factory));
+
+    ChromeAuthenticatorRequestDelegate::SetGlobalObserverForTesting(
+        observer_.get());
+  }
+
+ protected:
+  base::test::ScopedFeatureList scoped_feature_list_{
+      features::kWebAuthConditionalUI};
+  std::unique_ptr<Observer> observer_;
+  raw_ptr<device::test::VirtualFidoDeviceFactory> virtual_device_factory_;
+};
+
+// Tests that the "Sign in with another device…" button dispatches requests to
+// plugged in authenticators.
+IN_PROC_BROWSER_TEST_F(WebAuthnConditionalUITest,
+                       ConditionalUIOtherDeviceButton) {
+  static constexpr char kRequest[] = R"((() => {
+  let cred_id = new Uint8Array([1,2,3,4]);
+  navigator.credentials.get({
+    mediation: 'conditional',
+    publicKey: {
+      challenge: cred_id,
+      timeout: 10000,
+      allowCredentials: [],
+    }}).then(c => window.domAutomationController.send('webauthn: OK'),
+             e => window.domAutomationController.send('error ' + e));
+  })())";
+
+  // Make a Conditional UI request. The authenticator should not be dispatched
+  // to before the user clicks the "Sign in with another device…" button.
+  virtual_device_factory_->mutable_state()->simulate_press_callback =
+      base::BindLambdaForTesting([](device::VirtualFidoDevice* device) {
+        CHECK(false) << "Virtual device should not have been dispatched to";
+        return false;
+      });
+  content::WebContents* web_contents =
+      browser()->tab_strip_model()->GetActiveWebContents();
+  content::DOMMessageQueue message_queue(web_contents);
+  content::ExecuteScriptAsync(web_contents, kRequest);
+  observer_->WaitForUI();
+
+  // Allow the virtual device to respond to requests, then simulate clicking the
+  // "Sign in with another device…" button and wait for a result.
+  base::RunLoop run_loop;
+  virtual_device_factory_->mutable_state()->simulate_press_callback =
+      base::BindLambdaForTesting(
+          [&](device::VirtualFidoDevice* device) { return true; });
+  ChromePasswordManagerClient* password_manager_client =
+      ChromePasswordManagerClient::FromWebContents(web_contents);
+  password_manager_client->GetWebAuthnCredentialsDelegate()
+      ->LaunchWebAuthnFlow();
+
+  std::string result;
+  ASSERT_TRUE(message_queue.WaitForMessage(&result));
+  EXPECT_EQ(result, "\"webauthn: OK\"");
+}
+
 // WebAuthnCableExtension exercises code paths where a server sends a caBLEv2
 // extension in a get() request.
 class WebAuthnCableExtension : public WebAuthnBrowserTest {
diff --git a/chrome/build/linux.pgo.txt b/chrome/build/linux.pgo.txt
index c226dbbb..bc4202b 100644
--- a/chrome/build/linux.pgo.txt
+++ b/chrome/build/linux.pgo.txt
@@ -1 +1 @@
-chrome-linux-main-1656590125-fc44be9ecb9394b321a53fd8ebcddbdb485e79cf.profdata
+chrome-linux-main-1656611842-ec98b627272820fd57d6592bebe434339f3b1e33.profdata
diff --git a/chrome/build/mac.pgo.txt b/chrome/build/mac.pgo.txt
index 41807364..1d0b5a2 100644
--- a/chrome/build/mac.pgo.txt
+++ b/chrome/build/mac.pgo.txt
@@ -1 +1 @@
-chrome-mac-main-1656590125-ee0d4ded2dec91607d078444faffff8b5e79b427.profdata
+chrome-mac-main-1656611842-0d5c930c81e851348c3722be42a3bf8f98c0ffd4.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index 4994f1de7..2688014 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-main-1656601194-0337d3ff1fcd2199f2eb1b7099dfde8c4d643d19.profdata
+chrome-win32-main-1656622803-d6ba1cb8ce093fd9089d6e6168a7b272f9d9cabd.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index a29b736..31815fd5b 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-main-1656601194-970dbe596f8127d1c81f86e9dfa2ce4901ef721c.profdata
+chrome-win64-main-1656622803-9af9e8a429810f92e20d6b19e9c4546591730efd.profdata
diff --git a/chrome/common/extensions/api/passwords_private.idl b/chrome/common/extensions/api/passwords_private.idl
index c1e0c7d..91d37eb 100644
--- a/chrome/common/extensions/api/passwords_private.idl
+++ b/chrome/common/extensions/api/passwords_private.idl
@@ -163,6 +163,10 @@
     // Android apps.
     DOMString? changePasswordUrl;
 
+    // Indicates whether a script exists to change this credential
+    // automatically.
+    boolean hasStartableScript;
+
     // The signon realm of the credential.
     DOMString signonRealm;
 
diff --git a/chrome/renderer/extensions/app_hooks_delegate.cc b/chrome/renderer/extensions/app_hooks_delegate.cc
index db92498..d3bc2cc 100644
--- a/chrome/renderer/extensions/app_hooks_delegate.cc
+++ b/chrome/renderer/extensions/app_hooks_delegate.cc
@@ -109,7 +109,8 @@
     APIRequestHandler::RequestDetails request_details =
         request_handler_->AddPendingRequest(
             context, binding::AsyncResponseType::kCallback,
-            (*parse_result.arguments)[0].As<v8::Function>());
+            (*parse_result.arguments)[0].As<v8::Function>(),
+            binding::ResultModifierFunction());
     GetInstallState(script_context, request_details.request_id);
   } else {
     NOTREACHED();
diff --git a/chrome/renderer/net/net_error_helper_core_unittest.cc b/chrome/renderer/net/net_error_helper_core_unittest.cc
index 575e9ef..6e55021 100644
--- a/chrome/renderer/net/net_error_helper_core_unittest.cc
+++ b/chrome/renderer/net/net_error_helper_core_unittest.cc
@@ -87,6 +87,19 @@
   return ErrorToString(NetError(net_error), false);
 }
 
+error_page::LocalizedError::PageState GetErrorPageState(int error_code,
+                                                        bool is_kiosk_mode) {
+  return error_page::LocalizedError::GetPageState(
+      error_code, error_page::Error::kNetErrorDomain, GURL(kFailedUrl),
+      /*is_post=*/false,
+      /*is_secure_dns_network_error=*/false, /*stale_copy_in_cache=*/false,
+      /*can_show_network_diagnostics_dialog=*/false, /*is_incognito=*/false,
+      /*offline_content_feature_enabled=*/false,
+      /*auto_fetch_feature_enabled=*/false, /*is_kiosk_mode=*/is_kiosk_mode,
+      /*locale=*/"",
+      /*is_blocked_by_extension=*/false);
+}
+
 class NetErrorHelperCoreTest : public testing::Test,
                                public NetErrorHelperCore::Delegate {
  public:
@@ -358,6 +371,36 @@
   EXPECT_EQ(0, update_count());
 }
 
+TEST_F(NetErrorHelperCoreTest,
+       UserModeErrBlockedByAdministratorContainsDetails) {
+  error_page::LocalizedError::PageState page_state = GetErrorPageState(
+      net::ERR_BLOCKED_BY_ADMINISTRATOR, /*is_kiosk_mode=*/false);
+
+  auto* suggestions_details = page_state.strings.FindList("suggestionsDetails");
+  ASSERT_TRUE(suggestions_details);
+  EXPECT_FALSE(suggestions_details->empty());
+
+  auto* suggestions_summary_list =
+      page_state.strings.FindList("suggestionsSummaryList");
+  ASSERT_TRUE(suggestions_summary_list);
+  EXPECT_FALSE(suggestions_summary_list->empty());
+}
+
+TEST_F(NetErrorHelperCoreTest,
+       KioskModeErrBlockedByAdministratorDoenNotContainDetails) {
+  error_page::LocalizedError::PageState page_state = GetErrorPageState(
+      net::ERR_BLOCKED_BY_ADMINISTRATOR, /*is_kiosk_mode=*/true);
+
+  auto* suggestions_details = page_state.strings.FindList("suggestionsDetails");
+  ASSERT_TRUE(suggestions_details);
+  EXPECT_TRUE(suggestions_details->empty());
+
+  auto* suggestions_summary_list =
+      page_state.strings.FindList("suggestionsSummaryList");
+  ASSERT_TRUE(suggestions_summary_list);
+  EXPECT_TRUE(suggestions_summary_list->empty());
+}
+
 TEST_F(NetErrorHelperCoreTest, SubFrameErrorWithCustomErrorPage) {
   // Loading fails, and an error page is requested. |error_html| is null
   // indicating a custom error page. Calls below should not crash.
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 02889cd..af84567 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -3326,7 +3326,6 @@
         "../browser/ui/views/safe_browsing/deep_scanning_failure_modal_dialog_browsertest.cc",
         "../browser/ui/views/safe_browsing/password_reuse_modal_warning_dialog_browsertest.cc",
         "../browser/ui/views/safe_browsing/prompt_for_scanning_modal_dialog_browsertest.cc",
-        "../browser/ui/views/safe_browsing/tailored_security_desktop_modal_browsertest.cc",
         "../browser/ui/views/send_tab_to_self/send_tab_to_self_bubble_browsertest.cc",
         "../browser/ui/views/session_crashed_bubble_view_browsertest.cc",
         "../browser/ui/views/side_search/default_search_icon_source_browsertest.cc",
@@ -6725,6 +6724,7 @@
       "../browser/usb/usb_tab_helper_unittest.cc",
       "../browser/usb/web_usb_detector_unittest.cc",
       "../browser/usb/web_usb_service_impl_unittest.cc",
+      "../browser/user_notes/user_note_service_delegate_impl_unittest.cc",
       "../browser/user_notes/user_notes_tab_helper_unittest.cc",
       "../browser/webauthn/authenticator_request_dialog_model_unittest.cc",
       "../browser/webauthn/authenticator_request_scheduler_unittest.cc",
@@ -9319,6 +9319,7 @@
         "../browser/ui/views/toolbar/toolbar_view_interactive_uitest.cc",
         "../browser/ui/views/translate/translate_bubble_test_utils_views.cc",
         "../browser/ui/views/translate/translate_bubble_view_interactive_uitest.cc",
+        "../browser/ui/views/translate/translate_icon_view_interactive_uitest.cc",
         "../browser/ui/views/user_education/browser_feature_promo_controller_interactive_uitest.cc",
         "../browser/ui/views/user_education/feature_promo_dialog_interactive_uitest.cc",
         "../browser/ui/views/user_education/feature_promo_snooze_interactive_uitest.cc",
diff --git a/chrome/test/base/test_browser_window.cc b/chrome/test/base/test_browser_window.cc
index c427b34..a3e8d87 100644
--- a/chrome/test/base/test_browser_window.cc
+++ b/chrome/test/base/test_browser_window.cc
@@ -246,6 +246,13 @@
   return ShowTranslateBubbleResult::SUCCESS;
 }
 
+void TestBrowserWindow::ShowPartialTranslateBubble(
+    PartialTranslateBubbleModel::ViewState view_state,
+    const std::string& source_language,
+    const std::string& target_language,
+    const std::u16string& text_selection,
+    translate::TranslateErrors::Type error_type) {}
+
 qrcode_generator::QRCodeGeneratorBubbleView*
 TestBrowserWindow::ShowQRCodeGeneratorBubble(content::WebContents* contents,
                                              const GURL& url,
diff --git a/chrome/test/base/test_browser_window.h b/chrome/test/base/test_browser_window.h
index af143e76..c3bc101 100644
--- a/chrome/test/base/test_browser_window.h
+++ b/chrome/test/base/test_browser_window.h
@@ -17,6 +17,7 @@
 #include "chrome/browser/ui/browser_dialogs.h"
 #include "chrome/browser/ui/browser_window.h"
 #include "chrome/browser/ui/location_bar/location_bar.h"
+#include "chrome/browser/ui/translate/partial_translate_bubble_model.h"
 #include "chrome/common/buildflags.h"
 #include "ui/base/interaction/element_identifier.h"
 
@@ -180,6 +181,12 @@
       const std::string& target_language,
       translate::TranslateErrors::Type error_type,
       bool is_user_gesture) override;
+  void ShowPartialTranslateBubble(
+      PartialTranslateBubbleModel::ViewState view_state,
+      const std::string& source_language,
+      const std::string& target_language,
+      const std::u16string& text_selection,
+      translate::TranslateErrors::Type error_type) override;
   void ShowOneClickSigninConfirmation(
       const std::u16string& email,
       base::OnceCallback<void(bool)> confirmed_callback) override {}
diff --git a/chrome/test/data/extensions/api_test/passwords_private/test.js b/chrome/test/data/extensions/api_test/passwords_private/test.js
index 964b7e7..8e649c2 100644
--- a/chrome/test/data/extensions/api_test/passwords_private/test.js
+++ b/chrome/test/data/extensions/api_test/passwords_private/test.js
@@ -432,6 +432,7 @@
       formattedOrigin: 'example.com',
       detailedOrigin: 'https://example.com',
       isAndroidCredential: false,
+      hasStartableScript: false,
       signonRealm: 'https://example.com',
       username: 'alice',
       compromisedInfo: {
@@ -456,6 +457,7 @@
       formattedOrigin: 'example.com',
       detailedOrigin: 'https://example.com',
       isAndroidCredential: false,
+      hasStartableScript: false,
       signonRealm: 'https://example.com',
       username: 'alice',
       compromisedInfo: {
@@ -483,6 +485,7 @@
           formattedOrigin: 'example.com',
           detailedOrigin: 'https://example.com',
           isAndroidCredential: false,
+          hasStartableScript: false,
           signonRealm: 'https://example.com',
           username: 'alice',
           compromisedInfo: {
@@ -507,6 +510,7 @@
           formattedOrigin: 'example.com',
           detailedOrigin: 'https://example.com',
           isAndroidCredential: false,
+          hasStartableScript: false,
           signonRealm: 'https://example.com',
           username: 'alice',
           compromisedInfo: {
@@ -531,6 +535,7 @@
           formattedOrigin: 'example.com',
           detailedOrigin: 'https://example.com',
           isAndroidCredential: false,
+          hasStartableScript: false,
           signonRealm: 'https://example.com',
           username: 'alice',
           compromisedInfo: {
@@ -553,6 +558,7 @@
           formattedOrigin: 'example.com',
           detailedOrigin: 'https://example.com',
           isAndroidCredential: false,
+          hasStartableScript: false,
           signonRealm: 'https://example.com',
           username: 'alice',
           compromisedInfo: {
@@ -578,6 +584,7 @@
           formattedOrigin: 'example.com',
           detailedOrigin: 'https://example.com',
           isAndroidCredential: false,
+          hasStartableScript: false,
           signonRealm: 'https://example.com',
           username: 'alice',
           compromisedInfo: {
@@ -601,6 +608,7 @@
           formattedOrigin: 'example.com',
           detailedOrigin: 'https://example.com',
           isAndroidCredential: false,
+          hasStartableScript: false,
           signonRealm: 'https://example.com',
           username: 'alice',
           compromisedInfo: {
@@ -624,6 +632,7 @@
           formattedOrigin: 'example.com',
           detailedOrigin: 'https://example.com',
           isAndroidCredential: false,
+          hasStartableScript: false,
           signonRealm: 'https://example.com',
           username: 'alice',
           compromisedInfo: {
@@ -649,6 +658,7 @@
           formattedOrigin: 'example.com',
           detailedOrigin: 'https://example.com',
           isAndroidCredential: false,
+          hasStartableScript: false,
           signonRealm: 'https://example.com',
           username: 'alice',
           compromisedInfo: {
@@ -672,6 +682,7 @@
           formattedOrigin: 'example.com',
           detailedOrigin: 'https://example.com',
           isAndroidCredential: false,
+          hasStartableScript: false,
           signonRealm: 'https://example.com',
           username: 'alice',
           compromisedInfo: {
@@ -697,6 +708,7 @@
           formattedOrigin: 'example.com',
           detailedOrigin: 'https://example.com',
           isAndroidCredential: false,
+          hasStartableScript: false,
           signonRealm: 'https://example.com',
           username: 'alice',
           changePasswordUrl: 'https://example.com/.well-known/change-password',
@@ -720,6 +732,7 @@
           formattedOrigin: 'example.com',
           detailedOrigin: 'https://example.com',
           isAndroidCredential: false,
+          hasStartableScript: false,
           signonRealm: 'https://example.com',
           username: 'alice',
           changePasswordUrl: 'https://example.com/.well-known/change-password',
@@ -743,6 +756,7 @@
           formattedOrigin: 'App (com.example.app)',
           detailedOrigin: 'com.example.app',
           isAndroidCredential: true,
+          hasStartableScript: false,
           signonRealm: '',
           username: 'alice',
           compromisedInfo: {
diff --git a/chrome/test/data/webui/chromeos/personalization_app/personalization_app_controller_test.ts b/chrome/test/data/webui/chromeos/personalization_app/personalization_app_controller_test.ts
index 30a73ef..4d2b44e 100644
--- a/chrome/test/data/webui/chromeos/personalization_app/personalization_app_controller_test.ts
+++ b/chrome/test/data/webui/chromeos/personalization_app/personalization_app_controller_test.ts
@@ -717,7 +717,7 @@
   test(
       'error displays when fetch collections failed but local images loaded',
       async () => {
-        loadTimeData.overrideValues({['networkError']: 'someError'});
+        loadTimeData.overrideValues({['wallpaperNetworkError']: 'someError'});
 
         // Set collections to null to simulate collections failure.
         wallpaperProvider.setCollectionsToFail();
@@ -771,7 +771,8 @@
               // Set local images.
               // Error displays once local images are loaded.
               {
-                'error': {message: loadTimeData.getString('networkError')},
+                'error':
+                    {message: loadTimeData.getString('wallpaperNetworkError')},
               },
             ],
             personalizationStore.states.map(filterAndFlattenState(['error'])));
diff --git a/chrome/test/data/webui/settings/chromeos/device_page_tests.js b/chrome/test/data/webui/settings/chromeos/device_page_tests.js
index d0c190430..0cce273 100644
--- a/chrome/test/data/webui/settings/chromeos/device_page_tests.js
+++ b/chrome/test/data/webui/settings/chromeos/device_page_tests.js
@@ -470,6 +470,14 @@
 
     DevicePageBrowserProxyImpl.setInstanceForTesting(
         new TestDevicePageBrowserProxy());
+
+    // Allow the light DOM to be distributed to settings-animated-pages.
+    setTimeout(done);
+  });
+
+  async function init() {
+    // await is necessary in order for setup() to complete.
+    await flushTasks();
     devicePage = document.createElement('settings-device-page');
     devicePage.prefs = getFakePrefs();
 
@@ -478,10 +486,7 @@
     basicPage.dataset.page = 'basic';
     basicPage.appendChild(devicePage);
     document.body.appendChild(basicPage);
-
-    // Allow the light DOM to be distributed to settings-animated-pages.
-    setTimeout(done);
-  });
+  }
 
   /** @return {!Promise<!HTMLElement>} */
   function showAndGetDeviceSubpage(subpage, expectedRoute) {
@@ -648,7 +653,8 @@
         `${elementDesc} should be focused for settingId=${settingId}.`);
   }
 
-  test(assert(TestNames.DevicePage), function() {
+  test(assert(TestNames.DevicePage), async function() {
+    await init();
     assertTrue(isVisible(devicePage.shadowRoot.querySelector('#pointersRow')));
     assertTrue(isVisible(devicePage.shadowRoot.querySelector('#keyboardRow')));
     assertTrue(isVisible(devicePage.shadowRoot.querySelector('#displayRow')));
@@ -669,7 +675,8 @@
   suite(assert(TestNames.Pointers), function() {
     let pointersPage;
 
-    setup(function() {
+    setup(async function() {
+      await init();
       return showAndGetDeviceSubpage('pointers', routes.POINTERS)
           .then(function(page) {
             pointersPage = page;
@@ -909,6 +916,7 @@
     let keyboardPage;
 
     setup(async () => {
+      await init();
       keyboardPage = await showAndGetDeviceSubpage('keyboard', routes.KEYBOARD);
     });
 
@@ -1064,6 +1072,7 @@
     let browserProxy;
 
     setup(async () => {
+      await init();
       displayPage = await showAndGetDeviceSubpage('display', routes.DISPLAY);
       browserProxy = DevicePageBrowserProxyImpl.getInstance();
       await fakeSystemDisplay.getInfoCalled.promise;
@@ -1381,6 +1390,7 @@
 
   test(assert(TestNames.NightLight), async function() {
     // Set up a single display.
+    await init();
     const displayPage =
         await showAndGetDeviceSubpage('display', routes.DISPLAY);
     await fakeSystemDisplay.getInfoCalled.promise;
@@ -1442,7 +1452,8 @@
         });
       });
 
-      setup(function() {
+      setup(async function() {
+        await init();
         return showAndGetDeviceSubpage('power', routes.POWER)
             .then(function(page) {
               powerPage = page;
@@ -1482,7 +1493,8 @@
             });
       });
 
-      test('no battery', function() {
+      test('no battery', async function() {
+        await init();
         const batteryStatus = {
           present: false,
           charging: false,
@@ -2043,7 +2055,8 @@
       });
     });
 
-    setup(function() {
+    setup(async function() {
+      await init();
       return showAndGetDeviceSubpage('stylus', routes.STYLUS)
           .then(function(page) {
             stylusPage = page;
@@ -2623,7 +2636,8 @@
       testing.Test.disableAnimationsAndTransitions();
     });
 
-    setup(function() {
+    setup(async function() {
+      await init();
       return showAndGetDeviceSubpage('storage', routes.STORAGE)
           .then(function(page) {
             storagePage = page;
diff --git a/chrome/test/data/webui/settings/chromeos/os_namespace_rewrites.gni b/chrome/test/data/webui/settings/chromeos/os_namespace_rewrites.gni
deleted file mode 100644
index 326ceb10..0000000
--- a/chrome/test/data/webui/settings/chromeos/os_namespace_rewrites.gni
+++ /dev/null
@@ -1,39 +0,0 @@
-import("//chrome/browser/resources/settings/chromeos/os_settings.gni")
-
-os_test_namespace_rewrites = os_settings_namespace_rewrites + [
-                               "multidevice.createFakePageContentData|createFakePageContentData",
-                               "multidevice.HOST_DEVICE|HOST_DEVICE",
-                               "multidevice.TestMultideviceBrowserProxy|TestMultideviceBrowserProxy",
-                               "parental_controls.ParentalControlsBrowserProxy|ParentalControlsBrowserProxy",
-                               "reset_page.TestOsResetBrowserProxy|TestOsResetBrowserProxy",
-                               "settings.FakeBluetooth|FakeBluetooth",
-                               "settings.FakeBluetoothPrivate|FakeBluetoothPrivate",
-                               "settings.FakeQuickUnlockPrivate|FakeQuickUnlockPrivate",
-                               "settings.FakeQuickUnlockUma|FakeQuickUnlockUma",
-                               "settings.FakeLanguageSettingsPrivate|FakeLanguageSettingsPrivate",
-                               "settings.FakeInputMethodPrivate|FakeInputMethodPrivate",
-                               "settings.FakeSettingsPrivate|FakeSettingsPrivate",
-                               "settings.FakeSettingsSearchHandler|FakeSettingsSearchHandler",
-                               "settings.FakeUserActionRecorder|FakeUserActionRecorder",
-                               "settings.FakeUsersPrivate|FakeUsersPrivate",
-                               "settings.getFakeLanguagePrefs|getFakeLanguagePrefs",
-                               "settings.MultiDeviceSettingsMode|MultiDeviceSettingsMode",
-                               "settings.TestGuestOsBrowserProxy|TestGuestOsBrowserProxy",
-                               "settings.TestLanguagesBrowserProxy|TestLanguagesBrowserProxy",
-                               "settings.TestLanguagesMetricsProxy|TestLanguagesMetricsProxy",
-                               "settings.TestLifetimeBrowserProxy|TestLifetimeBrowserProxy",
-                               "settings.TestPersonalizationHubBrowserProxy|TestPersonalizationHubBrowserProxy",
-                               "settings.TestWallpaperBrowserProxy|TestWallpaperBrowserProxy",
-                               "printerBrowserProxy.TestCupsPrintersBrowserProxy|TestCupsPrintersBrowserProxy",
-                               "test_util.eventToPromise|eventToPromise",
-                               "test_util.fakeDataBind|fakeDataBind",
-                               "test_util.flushTasks|flushTasks",
-                               "test_util.isChildVisible|isChildVisible",
-                               "test_util.waitAfterNextRender|waitAfterNextRender",
-                               "test_util.waitBeforeNextRender|waitBeforeNextRender",
-                               "MockInteractions.keyEventOn|keyEventOn",
-                               "cups_printer_test_util.createCupsPrinterInfo|createCupsPrinterInfo",
-                               "cups_printer_test_util.createPrinterListEntry|createPrinterListEntry",
-                               "cups_printer_test_util.getPrinterEntries|getPrinterEntries",
-                               "cellular_setup.FakeESimManagerRemote|FakeESimManagerRemote",
-                             ]
diff --git a/chrome/test/data/webui/settings/chromeos/privacy_hub_subpage_tests.js b/chrome/test/data/webui/settings/chromeos/privacy_hub_subpage_tests.js
index 041df7c6..0d7df13 100644
--- a/chrome/test/data/webui/settings/chromeos/privacy_hub_subpage_tests.js
+++ b/chrome/test/data/webui/settings/chromeos/privacy_hub_subpage_tests.js
@@ -76,6 +76,22 @@
         'Camera toggle should be focused for settingId=1116.');
   });
 
+  test('Deep link to microphone toggle on privacy hub', async () => {
+    const params = new URLSearchParams();
+    params.append('settingId', '1117');
+    Router.getInstance().navigateTo(routes.PRIVACY_HUB, params);
+
+    flush();
+
+    const deepLinkElement =
+        privacyHubSubpage.shadowRoot.querySelector('#microphoneToggle')
+            .shadowRoot.querySelector('cr-toggle');
+    await waitAfterNextRender(deepLinkElement);
+    assertEquals(
+        deepLinkElement, getDeepActiveElement(),
+        'Microphone toggle should be focused for settingId=1117.');
+  });
+
   test('Update camera setting sub-label', async () => {
     const params = new URLSearchParams();
     params.append('settingId', '1116');
diff --git a/chrome/test/data/webui/settings/passwords_and_autofill_fake_data.ts b/chrome/test/data/webui/settings/passwords_and_autofill_fake_data.ts
index 18e14e7..1f9ee0f 100644
--- a/chrome/test/data/webui/settings/passwords_and_autofill_fake_data.ts
+++ b/chrome/test/data/webui/settings/passwords_and_autofill_fake_data.ts
@@ -273,6 +273,7 @@
     id: id || 0,
     formattedOrigin: url,
     changePasswordUrl: `http://${url}/`,
+    hasStartableScript: false,
     username: username,
     detailedOrigin: '',
     isAndroidCredential: false,
diff --git a/chrome/updater/BUILD.gn b/chrome/updater/BUILD.gn
index b82e6883..5084348 100644
--- a/chrome/updater/BUILD.gn
+++ b/chrome/updater/BUILD.gn
@@ -670,6 +670,7 @@
 
       data_deps = [
         ":updater_selfupdate_test_crx",
+        "//chrome/updater/win:updater",
         "//chrome/updater/win:updater_test",
         "//chrome/updater/win/installer:installer_test",
         "//chrome/updater/win/installer:installer_unittest",
diff --git a/chrome/updater/app/server/win/com_classes_legacy.cc b/chrome/updater/app/server/win/com_classes_legacy.cc
index f3e324b..3ec12fae 100644
--- a/chrome/updater/app/server/win/com_classes_legacy.cc
+++ b/chrome/updater/app/server/win/com_classes_legacy.cc
@@ -35,6 +35,7 @@
 #include "chrome/updater/update_service.h"
 #include "chrome/updater/updater_scope.h"
 #include "chrome/updater/win/app_command_runner.h"
+#include "chrome/updater/win/setup/setup_util.h"
 #include "chrome/updater/win/win_constants.h"
 #include "chrome/updater/win/win_util.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
@@ -598,6 +599,10 @@
     return hr;
   }
 
+  if (HRESULT hr = web->InitializeTypeInfo(); FAILED(hr)) {
+    return hr;
+  }
+
   web_impl.Swap(web);
   return S_OK;
 }
@@ -651,37 +656,84 @@
         StringFromVariant(substitution);
     if (!substitution_string)
       break;
+
+    VLOG(2) << __func__
+            << " substitution_string: " << substitution_string.value();
     substitutions.push_back(substitution_string.value());
   }
 
   return app_command_runner_.Run(substitutions, process_);
 }
 
-STDMETHODIMP LegacyAppCommandWebImpl::GetTypeInfoCount(UINT*) {
-  return E_NOTIMPL;
+STDMETHODIMP LegacyAppCommandWebImpl::GetTypeInfoCount(UINT* type_info_count) {
+  *type_info_count = 1;
+  return S_OK;
 }
 
-STDMETHODIMP LegacyAppCommandWebImpl::GetTypeInfo(UINT, LCID, ITypeInfo**) {
-  return E_NOTIMPL;
+STDMETHODIMP LegacyAppCommandWebImpl::GetTypeInfo(UINT type_info_index,
+                                                  LCID locale_id,
+                                                  ITypeInfo** type_info) {
+  if (type_info_index != 0)
+    return E_INVALIDARG;
+
+  return type_info_.CopyTo(type_info);
 }
 
-STDMETHODIMP LegacyAppCommandWebImpl::GetIDsOfNames(REFIID,
-                                                    LPOLESTR*,
-                                                    UINT,
-                                                    LCID,
-                                                    DISPID*) {
-  return E_NOTIMPL;
+STDMETHODIMP LegacyAppCommandWebImpl::GetIDsOfNames(
+    REFIID iid,
+    LPOLESTR* names_to_be_mapped,
+    UINT count_of_names_to_be_mapped,
+    LCID locale_id,
+    DISPID* dispatch_ids) {
+  return type_info_->GetIDsOfNames(names_to_be_mapped,
+                                   count_of_names_to_be_mapped, dispatch_ids);
 }
 
-STDMETHODIMP LegacyAppCommandWebImpl::Invoke(DISPID,
-                                             REFIID,
-                                             LCID,
-                                             WORD,
-                                             DISPPARAMS*,
-                                             VARIANT*,
-                                             EXCEPINFO*,
-                                             UINT*) {
-  return E_NOTIMPL;
+STDMETHODIMP LegacyAppCommandWebImpl::Invoke(DISPID dispatch_id,
+                                             REFIID iid,
+                                             LCID locale_id,
+                                             WORD flags,
+                                             DISPPARAMS* dispatch_parameters,
+                                             VARIANT* result,
+                                             EXCEPINFO* exception_info,
+                                             UINT* arg_error_index) {
+  HRESULT hr = type_info_->Invoke(
+      Microsoft::WRL::ComPtr<IAppCommandWeb>(this).Get(), dispatch_id, flags,
+      dispatch_parameters, result, exception_info, arg_error_index);
+  if (FAILED(hr)) {
+    LOG(ERROR) << __func__ << " type_info_->Invoke failed: " << dispatch_id
+               << ": " << std::hex << hr;
+  }
+
+  return hr;
+}
+
+HRESULT LegacyAppCommandWebImpl::InitializeTypeInfo() {
+  base::FilePath typelib_path;
+  if (!base::PathService::Get(base::DIR_EXE, &typelib_path))
+    return E_UNEXPECTED;
+
+  typelib_path =
+      typelib_path.Append(kUpdaterProcessName)
+          .Append(GetComTypeLibResourceIndex(__uuidof(IAppCommandWeb)));
+
+  Microsoft::WRL::ComPtr<ITypeLib> type_lib;
+  if (HRESULT hr = ::LoadTypeLib(typelib_path.value().c_str(), &type_lib);
+      FAILED(hr)) {
+    LOG(ERROR) << __func__ << " ::LoadTypeLib failed: " << typelib_path << ": "
+               << std::hex << hr;
+    return hr;
+  }
+
+  if (HRESULT hr =
+          type_lib->GetTypeInfoOfGuid(__uuidof(IAppCommandWeb), &type_info_);
+      FAILED(hr)) {
+    LOG(ERROR) << __func__ << " ::GetTypeInfoOfGuid failed"
+               << ": " << std::hex << hr;
+    return hr;
+  }
+
+  return S_OK;
 }
 
 }  // namespace updater
diff --git a/chrome/updater/app/server/win/com_classes_legacy.h b/chrome/updater/app/server/win/com_classes_legacy.h
index 972b5c2..f033f57 100644
--- a/chrome/updater/app/server/win/com_classes_legacy.h
+++ b/chrome/updater/app/server/win/com_classes_legacy.h
@@ -216,8 +216,6 @@
 //
 // Placeholders may be embedded within words, and appropriate quoting of
 // back-slash, double-quotes, space, and tab is applied if necessary.
-//
-// TODO(crbug/1316682): Implement AutoRunOnOSUpgrade app commands.
 class LegacyAppCommandWebImpl
     : public Microsoft::WRL::RuntimeClass<
           Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::ClassicCom>,
@@ -261,19 +259,23 @@
                          VARIANT substitution9) override;
 
   // Overrides for IDispatch.
-  // TODO(crbug/1316683): Implement the IDispatch methods for the AppCommand
-  // implementation.
-  IFACEMETHODIMP GetTypeInfoCount(UINT*) override;
-  IFACEMETHODIMP GetTypeInfo(UINT, LCID, ITypeInfo**) override;
-  IFACEMETHODIMP GetIDsOfNames(REFIID, LPOLESTR*, UINT, LCID, DISPID*) override;
-  IFACEMETHODIMP Invoke(DISPID,
-                        REFIID,
-                        LCID,
-                        WORD,
-                        DISPPARAMS*,
-                        VARIANT*,
-                        EXCEPINFO*,
-                        UINT*) override;
+  IFACEMETHODIMP GetTypeInfoCount(UINT* type_info_count) override;
+  IFACEMETHODIMP GetTypeInfo(UINT type_info_index,
+                             LCID locale_id,
+                             ITypeInfo** type_info) override;
+  IFACEMETHODIMP GetIDsOfNames(REFIID iid,
+                               LPOLESTR* names_to_be_mapped,
+                               UINT count_of_names_to_be_mapped,
+                               LCID locale_id,
+                               DISPID* dispatch_ids) override;
+  IFACEMETHODIMP Invoke(DISPID dispatch_id,
+                        REFIID iid,
+                        LCID locale_id,
+                        WORD flags,
+                        DISPPARAMS* dispatch_parameters,
+                        VARIANT* result,
+                        EXCEPINFO* exception_info,
+                        UINT* arg_error_index) override;
 
  private:
   ~LegacyAppCommandWebImpl() override;
@@ -284,8 +286,11 @@
       const std::wstring& command_id,
       Microsoft::WRL::ComPtr<LegacyAppCommandWebImpl>& web_impl);
 
+  HRESULT InitializeTypeInfo();
+
   base::Process process_;
   AppCommandRunner app_command_runner_;
+  Microsoft::WRL::ComPtr<ITypeInfo> type_info_;
 
   friend class LegacyAppCommandWebImplTest;
 };
diff --git a/chrome/updater/test/integration_tests_win.cc b/chrome/updater/test/integration_tests_win.cc
index d2bf5ed..837f3c4 100644
--- a/chrome/updater/test/integration_tests_win.cc
+++ b/chrome/updater/test/integration_tests_win.cc
@@ -7,6 +7,7 @@
 
 #include <regstr.h>
 
+#include <algorithm>
 #include <iostream>
 #include <memory>
 #include <string>
@@ -410,6 +411,57 @@
   base::RepeatingCallback<void(HWND hwnd)> action_;
 };
 
+DISPID GetDispId(Microsoft::WRL::ComPtr<IDispatch> dispatch,
+                 const std::wstring& of_name) {
+  DISPID id = 0;
+  std::wstring name = of_name;
+  LPOLESTR name_ptr = &name[0];
+  EXPECT_HRESULT_SUCCEEDED(dispatch->GetIDsOfNames(IID_NULL, &name_ptr, 1,
+                                                   LOCALE_USER_DEFAULT, &id));
+  VLOG(2) << __func__ << ": " << name << ": " << id;
+  return id;
+}
+
+void CallDispatchMethod(
+    Microsoft::WRL::ComPtr<IDispatch> dispatch,
+    const std::wstring& method_name,
+    const std::vector<base::win::ScopedVariant>& variant_params) {
+  std::vector<VARIANT> params;
+  params.reserve(variant_params.size());
+
+  // IDispatch::Invoke() expects the parameters in reverse order.
+  std::transform(variant_params.rbegin(), variant_params.rend(),
+                 std::back_inserter(params),
+                 [](const auto& param) { return param.Copy(); });
+
+  DISPPARAMS dp = {};
+  if (!params.empty()) {
+    dp.rgvarg = &params[0];
+    dp.cArgs = params.size();
+  }
+
+  EXPECT_HRESULT_SUCCEEDED(dispatch->Invoke(
+      GetDispId(dispatch, method_name), IID_NULL, LOCALE_USER_DEFAULT,
+      DISPATCH_METHOD, &dp, nullptr, nullptr, nullptr));
+
+  std::for_each(params.begin(), params.end(),
+                [&](auto& param) { ::VariantClear(&param); });
+  return;
+}
+
+base::win::ScopedVariant GetDispatchProperty(
+    Microsoft::WRL::ComPtr<IDispatch> dispatch,
+    const std::wstring& property_name) {
+  DISPPARAMS dp = {};
+  base::win::ScopedVariant result;
+
+  EXPECT_HRESULT_SUCCEEDED(dispatch->Invoke(
+      GetDispId(dispatch, property_name), IID_NULL, LOCALE_USER_DEFAULT,
+      DISPATCH_PROPERTYGET, &dp, result.Receive(), nullptr, nullptr));
+
+  return result;
+}
+
 }  // namespace
 
 base::FilePath GetSetupExecutablePath() {
@@ -932,6 +984,24 @@
   EXPECT_HRESULT_SUCCEEDED(app_command_web->get_exitCode(&exit_code));
   EXPECT_EQ(exit_code, static_cast<DWORD>(expected_exit_code));
 
+  // Now also run the AppCommand using the IDispatch methods.
+  command_dispatch.Reset();
+  ASSERT_HRESULT_SUCCEEDED(app->get_command(
+      base::win::ScopedBstr(commandid).Get(), &command_dispatch));
+
+  CallDispatchMethod(command_dispatch, L"execute", variant_params);
+
+  EXPECT_TRUE(WaitFor(base::BindLambdaForTesting([&]() {
+    base::win::ScopedVariant status =
+        GetDispatchProperty(command_dispatch, L"status");
+    return V_UINT(status.ptr()) == COMMAND_STATUS_COMPLETE;
+  })));
+
+  base::win::ScopedVariant command_exit_code =
+      GetDispatchProperty(command_dispatch, L"exitCode");
+  EXPECT_EQ(V_UI4(command_exit_code.ptr()),
+            static_cast<DWORD>(expected_exit_code));
+
   DeleteAppClientKey(scope, appid);
 }
 
diff --git a/chrome/updater/win/app_command_runner.cc b/chrome/updater/win/app_command_runner.cc
index d0f7e3761..07da5f36 100644
--- a/chrome/updater/win/app_command_runner.cc
+++ b/chrome/updater/win/app_command_runner.cc
@@ -305,7 +305,8 @@
     absl::optional<std::wstring> formatted_parameter =
         FormatParameter(substitutions, parameters[i]);
     if (!formatted_parameter) {
-      VLOG(1) << __func__ << " FormatParameter failed";
+      LOG(ERROR) << __func__ << " FormatParameter failed: " << parameters[i]
+                 << ": " << substitutions.size();
       return absl::nullopt;
     }
 
diff --git a/chromecast/media/cma/backend/alsa/scoped_alsa_mixer.cc b/chromecast/media/cma/backend/alsa/scoped_alsa_mixer.cc
index 1703304..8b88563 100644
--- a/chromecast/media/cma/backend/alsa/scoped_alsa_mixer.cc
+++ b/chromecast/media/cma/backend/alsa/scoped_alsa_mixer.cc
@@ -41,7 +41,8 @@
     LOG(INFO) << "Opening mixer element \"" << mixer_element_name_
               << "\" on device \"" << mixer_device_name_ << "\"";
   }
-
+  mixer = nullptr;
+  element = nullptr;
   int alsa_err = alsa_->MixerOpen(&mixer, 0);
   if (alsa_err < 0) {
     LOG(ERROR) << "MixerOpen error: " << alsa_->StrError(alsa_err);
diff --git a/chromeos/chromeos_strings.grd b/chromeos/chromeos_strings.grd
index 778beebc..bf35fed1 100644
--- a/chromeos/chromeos_strings.grd
+++ b/chromeos/chromeos_strings.grd
@@ -2151,7 +2151,7 @@
       <message name="IDS_PERSONALIZATION_APP_ARIA_LABEL_LOADING" desc="Aria label for wallpaper collection while it is still loading and unable to be selected">
         Loading
       </message>
-      <message name="IDS_PERSONALIZATION_APP_NETWORK_ERROR" desc="Label for the error page when wallpaper information cannot be downloaded">
+      <message name="IDS_PERSONALIZATION_APP_WALLPAPER_NETWORK_ERROR" desc="Error message displayed when wallpaper information cannot be downloaded">
         Please connect to a network and reload the page to view wallpaper.
       </message>
       <message name="IDS_PERSONALIZATION_APP_LOAD_WALLPAPER_ERROR" desc="Label for the error toast notification when loading current wallpaper failed">
@@ -2317,6 +2317,9 @@
       <message name="IDS_PERSONALIZATION_APP_AMBIENT_MODE_TURN_ON_LABEL" desc="Label of button to turn on ambient mode.">
         Turn on
       </message>
+      <message name="IDS_PERSONALIZATION_APP_AMBIENT_MODE_NETWORK_ERROR" desc="Error message displayed when screen saver information cannot be downloaded.">
+        Please connect to a network and reload the page to view screen saver.
+      </message>
       <message name="IDS_PERSONALIZATION_APP_MANAGED_SETTING" desc="Message to show in the personalization hub for a setting which is enterprise managed by administrators.">
         This setting is managed by your administrators
       </message>
diff --git a/chromeos/chromeos_strings_grd/IDS_PERSONALIZATION_APP_AMBIENT_MODE_NETWORK_ERROR.png.sha1 b/chromeos/chromeos_strings_grd/IDS_PERSONALIZATION_APP_AMBIENT_MODE_NETWORK_ERROR.png.sha1
new file mode 100644
index 0000000..f98e94f
--- /dev/null
+++ b/chromeos/chromeos_strings_grd/IDS_PERSONALIZATION_APP_AMBIENT_MODE_NETWORK_ERROR.png.sha1
@@ -0,0 +1 @@
+d5b0eaa3632d3094682cfa06938b6ee75c8cedbc
\ No newline at end of file
diff --git a/chromeos/chromeos_strings_grd/IDS_PERSONALIZATION_APP_NETWORK_ERROR.png.sha1 b/chromeos/chromeos_strings_grd/IDS_PERSONALIZATION_APP_WALLPAPER_NETWORK_ERROR.png.sha1
similarity index 100%
rename from chromeos/chromeos_strings_grd/IDS_PERSONALIZATION_APP_NETWORK_ERROR.png.sha1
rename to chromeos/chromeos_strings_grd/IDS_PERSONALIZATION_APP_WALLPAPER_NETWORK_ERROR.png.sha1
diff --git a/chromeos/components/quick_answers/public/cpp/quick_answers_state.cc b/chromeos/components/quick_answers/public/cpp/quick_answers_state.cc
index 64e0d48..245b418 100644
--- a/chromeos/components/quick_answers/public/cpp/quick_answers_state.cc
+++ b/chromeos/components/quick_answers/public/cpp/quick_answers_state.cc
@@ -11,6 +11,7 @@
 #include "base/system/sys_info.h"
 #include "chromeos/constants/chromeos_features.h"
 #include "third_party/icu/source/common/unicode/locid.h"
+#include "ui/base/l10n/l10n_util.h"
 
 namespace {
 
@@ -21,11 +22,11 @@
 const char kQuickAnswersConsentImpression[] =
     "QuickAnswers.V2.Consent.Impression";
 
+// Supported languages of the Quick Answers feature.
+const std::string kSupportedLanguages[] = {"en", "es", "it", "fr", "pt", "de"};
+
 bool IsQuickAnswersAllowedForLocale(const std::string& locale,
                                     const std::string& runtime_locale) {
-  if (chromeos::features::IsQuickAnswersForMoreLocalesEnabled())
-    return true;
-
   // String literals used in some cases in the array because their
   // constant equivalents don't exist in:
   // third_party/icu/source/common/unicode/uloc.h
@@ -77,6 +78,10 @@
          use_text_annotator_for_testing_;
 }
 
+bool QuickAnswersState::IsSupportedLanguage(const std::string& language) {
+  return base::Contains(kSupportedLanguages, language);
+}
+
 void QuickAnswersState::InitializeObserver(
     QuickAnswersStateObserver* observer) {
   if (prefs_initialized_) {
@@ -94,6 +99,11 @@
   bool is_eligible = IsQuickAnswersAllowedForLocale(
       resolved_application_locale_, icu::Locale::getDefault().getName());
 
+  if (chromeos::features::IsQuickAnswersForMoreLocalesEnabled()) {
+    is_eligible = IsSupportedLanguage(
+        l10n_util::GetLanguage(resolved_application_locale_));
+  }
+
   if (is_eligible_ == is_eligible)
     return;
   is_eligible_ = is_eligible;
diff --git a/chromeos/components/quick_answers/public/cpp/quick_answers_state.h b/chromeos/components/quick_answers/public/cpp/quick_answers_state.h
index 18a1a23..c668ba1d 100644
--- a/chromeos/components/quick_answers/public/cpp/quick_answers_state.h
+++ b/chromeos/components/quick_answers/public/cpp/quick_answers_state.h
@@ -59,6 +59,8 @@
 
   bool ShouldUseQuickAnswersTextAnnotator();
 
+  bool IsSupportedLanguage(const std::string& language);
+
   bool settings_enabled() const { return settings_enabled_; }
   quick_answers::prefs::ConsentStatus consent_status() const {
     return consent_status_;
diff --git a/chromeos/components/quick_answers/utils/spell_checker.cc b/chromeos/components/quick_answers/utils/spell_checker.cc
index 2d1f936..29d5a90 100644
--- a/chromeos/components/quick_answers/utils/spell_checker.cc
+++ b/chromeos/components/quick_answers/utils/spell_checker.cc
@@ -77,16 +77,19 @@
     return;
   }
 
+  // Add application language.
   std::set<std::string> languages;
   languages.insert(l10n_util::GetLanguage(application_locale_.value()));
 
-  // Add preferred languages.
+  // Add preferred languages if supported.
   if (chromeos::features::IsQuickAnswersForMoreLocalesEnabled()) {
     auto preferred_languages_list =
         base::SplitString(preferred_languages_.value(), ",",
                           base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
     for (const std::string& locale : preferred_languages_list) {
-      languages.insert(l10n_util::GetLanguage(locale));
+      auto language = l10n_util::GetLanguage(locale);
+      if (QuickAnswersState::Get()->IsSupportedLanguage(language))
+        languages.insert(language);
     }
   }
 
diff --git a/chromeos/profiles/orderfile.newest.txt b/chromeos/profiles/orderfile.newest.txt
index 79b3c5c..090bb3a 100644
--- a/chromeos/profiles/orderfile.newest.txt
+++ b/chromeos/profiles/orderfile.newest.txt
@@ -1 +1 @@
-chromeos-chrome-orderfile-field-104-5112.9-1655719671-benchmark-104.0.5112.25-r1.orderfile.xz
+chromeos-chrome-orderfile-field-104-5112.9-1656324473-benchmark-104.0.5112.29-r1.orderfile.xz
diff --git a/components/app_restore/app_restore_data.cc b/components/app_restore/app_restore_data.cc
index ca8f8b9..abd56c2 100644
--- a/components/app_restore/app_restore_data.cc
+++ b/components/app_restore/app_restore_data.cc
@@ -539,6 +539,7 @@
   app_launch_info->container = container;
   app_launch_info->disposition = disposition;
   app_launch_info->display_id = display_id;
+  app_launch_info->active_tab_index = active_tab_index;
   app_launch_info->handler_id = handler_id;
   app_launch_info->urls = urls;
   app_launch_info->first_non_pinned_tab_index = first_non_pinned_tab_index;
diff --git a/components/autofill/core/browser/ui/popup_item_ids.h b/components/autofill/core/browser/ui/popup_item_ids.h
index 10d1a3e..b967da6 100644
--- a/components/autofill/core/browser/ui/popup_item_ids.h
+++ b/components/autofill/core/browser/ui/popup_item_ids.h
@@ -37,7 +37,8 @@
   POPUP_ITEM_ID_VIRTUAL_CREDIT_CARD_ENTRY = -27,
   POPUP_ITEM_ID_WEBAUTHN_CREDENTIAL = -28,
   POPUP_ITEM_ID_MERCHANT_PROMO_CODE_ENTRY = -29,
-  POPUP_ITEM_ID_SEE_PROMO_CODE_DETAILS = -30
+  POPUP_ITEM_ID_SEE_PROMO_CODE_DETAILS = -30,
+  POPUP_ITEM_ID_WEBAUTHN_SIGN_IN_WITH_ANOTHER_DEVICE = -31
 };
 
 // List of `PopupItemId` that trigger filling a value into an input element
diff --git a/components/certificate_transparency/data/log_list.json b/components/certificate_transparency/data/log_list.json
index 44f47a4..7e7b37d 100644
--- a/components/certificate_transparency/data/log_list.json
+++ b/components/certificate_transparency/data/log_list.json
@@ -1,6 +1,6 @@
 {
-  "version": "10.27",
-  "log_list_timestamp": "2022-06-29T12:55:50Z",
+  "version": "10.28",
+  "log_list_timestamp": "2022-06-30T12:53:54Z",
   "operators": [
     {
       "name": "Google",
diff --git a/components/commerce/core/commerce_feature_list.cc b/components/commerce/core/commerce_feature_list.cc
index dba1aaf..9e3b0eb 100644
--- a/components/commerce/core/commerce_feature_list.cc
+++ b/components/commerce/core/commerce_feature_list.cc
@@ -64,6 +64,12 @@
 
 }  // namespace
 
+const base::Feature kCommerceAllowLocalImages{
+    "CommerceAllowLocalImages", base::FEATURE_DISABLED_BY_DEFAULT};
+
+const base::Feature kCommerceAllowServerImages{
+    "CommerceAllowServerImages", base::FEATURE_ENABLED_BY_DEFAULT};
+
 const base::Feature kCommerceCoupons{"CommerceCoupons",
                                      base::FEATURE_DISABLED_BY_DEFAULT};
 
diff --git a/components/commerce/core/commerce_feature_list.h b/components/commerce/core/commerce_feature_list.h
index 37aa1d9..6032e5d2 100644
--- a/components/commerce/core/commerce_feature_list.h
+++ b/components/commerce/core/commerce_feature_list.h
@@ -65,6 +65,8 @@
          std::size(kCommercePriceTrackingWithOptimizationGuideAndOptOut),
          nullptr}};
 
+extern const base::Feature kCommerceAllowLocalImages;
+extern const base::Feature kCommerceAllowServerImages;
 extern const base::Feature kCommerceCoupons;
 extern const base::Feature kCommerceMerchantViewer;
 extern const base::FeatureParam<bool> kDeleteAllMerchantsOnClearBrowsingHistory;
diff --git a/components/commerce/core/shopping_service.cc b/components/commerce/core/shopping_service.cc
index 4e6b29bf..2913a0d 100644
--- a/components/commerce/core/shopping_service.cc
+++ b/components/commerce/core/shopping_service.cc
@@ -254,8 +254,10 @@
       if (buyable_product.has_title())
         info->title = buyable_product.title();
 
-      if (buyable_product.has_image_url())
+      if (buyable_product.has_image_url() &&
+          base::FeatureList::IsEnabled(kCommerceAllowServerImages)) {
         info->image_url = GURL(buyable_product.image_url());
+      }
 
       if (buyable_product.has_offer_id())
         info->offer_id = buyable_product.offer_id();
diff --git a/components/commerce/core/shopping_service_unittest.cc b/components/commerce/core/shopping_service_unittest.cc
index 60db5b7..c887c709 100644
--- a/components/commerce/core/shopping_service_unittest.cc
+++ b/components/commerce/core/shopping_service_unittest.cc
@@ -106,7 +106,7 @@
 // Test that the product info cache only keeps track of live tabs.
 TEST_F(ShoppingServiceTest, TestProductInfoCacheURLCount) {
   base::test::ScopedFeatureList test_features;
-  test_features.InitAndEnableFeature(commerce::kShoppingList);
+  test_features.InitAndEnableFeature(kShoppingList);
 
   std::string url = "http://example.com/foo";
   MockWebWrapper web1(GURL(url), false);
@@ -154,7 +154,8 @@
 // necessarily querying for it.
 TEST_F(ShoppingServiceTest, TestProductInfoCacheFullLifecycle) {
   base::test::ScopedFeatureList test_features;
-  test_features.InitAndEnableFeature(commerce::kShoppingList);
+  test_features.InitWithFeatures({kShoppingList, kCommerceAllowServerImages},
+                                 {});
 
   MockWebWrapper web(GURL(kProductUrl), false);
 
diff --git a/components/component_updater/pref_names.cc b/components/component_updater/pref_names.cc
index 712c63f..c2b590f 100644
--- a/components/component_updater/pref_names.cc
+++ b/components/component_updater/pref_names.cc
@@ -34,6 +34,15 @@
 // Enable running the SwReporter.
 const char kSwReporterEnabled[] = "software_reporter.enabled";
 
+// Which cohort of the SwReporter should be downloaded, unless overridden by the
+// safe_browsing::kReporterDistributionTagParam feature parameter.
+const char kSwReporterCohort[] = "software_reporter.cohort";
+
+// The time when kSwReporterCohort was last changed. Used to periodically
+// re-randomize which cohort users fall into.
+const char kSwReporterCohortSelectionTime[] =
+    "software_reporter.cohort_selection_time";
+
 // Control whether SwReporter and Chrome Cleanup results are reported to Google.
 const char kSwReporterReportingEnabled[] = "software_reporter.reporting";
 
diff --git a/components/component_updater/pref_names.h b/components/component_updater/pref_names.h
index f486a55..01c8bb03 100644
--- a/components/component_updater/pref_names.h
+++ b/components/component_updater/pref_names.h
@@ -20,6 +20,8 @@
 extern const char kSwReporterLastTimeTriggered[];
 extern const char kSwReporterLastTimeSentReport[];
 extern const char kSwReporterEnabled[];
+extern const char kSwReporterCohort[];
+extern const char kSwReporterCohortSelectionTime[];
 
 // Profile prefs.
 extern const char kSwReporterPromptReason[];
diff --git a/components/error_page/common/localized_error.cc b/components/error_page/common/localized_error.cc
index b1c7194..d003567 100644
--- a/components/error_page/common/localized_error.cc
+++ b/components/error_page/common/localized_error.cc
@@ -964,6 +964,7 @@
   if (is_kiosk_mode) {
     options.suggestions &= ~SUGGEST_DIAGNOSE_TOOL;
     options.suggestions &= ~SUGGEST_LEARNMORE;
+    options.suggestions &= ~SUGGEST_CONTACT_ADMINISTRATOR;
   }
 
   std::u16string failed_url_string(url_formatter::FormatUrl(
diff --git a/components/error_page/common/localized_error.h b/components/error_page/common/localized_error.h
index 274cb31..edaa2307 100644
--- a/components/error_page/common/localized_error.h
+++ b/components/error_page/common/localized_error.h
@@ -39,21 +39,20 @@
 
   // Returns a |PageState| that describes the elements that should be shown on
   // on HTTP errors, like 404 or connection reset.
-  static PageState GetPageState(
-      int error_code,
-      const std::string& error_domain,
-      const GURL& failed_url,
-      bool is_post,
-      bool is_secure_dns_network_error,
-      bool stale_copy_in_cache,
-      bool can_show_network_diagnostics_dialog,
-      bool is_incognito,
-      bool offline_content_feature_enabled,
-      bool auto_fetch_feature_enabled,
-      bool is_kiosk_mode,  // whether device is currently in single app (kiosk)
-                           // mode
-      const std::string& locale,
-      bool is_blocked_by_extension);
+  // |is_kiosk_mode| whether device is currently in the Kiosk session mode.
+  static PageState GetPageState(int error_code,
+                                const std::string& error_domain,
+                                const GURL& failed_url,
+                                bool is_post,
+                                bool is_secure_dns_network_error,
+                                bool stale_copy_in_cache,
+                                bool can_show_network_diagnostics_dialog,
+                                bool is_incognito,
+                                bool offline_content_feature_enabled,
+                                bool auto_fetch_feature_enabled,
+                                bool is_kiosk_mode,
+                                const std::string& locale,
+                                bool is_blocked_by_extension);
 
   // Returns a |PageState| that describes the elements that should be shown on
   // when default offline page is shown.
diff --git a/components/omnibox/browser/autocomplete_provider.cc b/components/omnibox/browser/autocomplete_provider.cc
index abab36b..8dae86c9 100644
--- a/components/omnibox/browser/autocomplete_provider.cc
+++ b/components/omnibox/browser/autocomplete_provider.cc
@@ -35,6 +35,10 @@
 
 // static
 const char* AutocompleteProvider::TypeToString(Type type) {
+  // When creating a new provider, add the provider type to this function and
+  // make sure to also add the appropriate suffix entry to OmniboxProviderTime
+  // histogram_suffixes (in histogram_suffixes_list.xml) so that the run-time
+  // metrics associated with the relevant provider can be properly analyzed.
   switch (type) {
     case TYPE_BOOKMARK:
       return "Bookmark";
diff --git a/components/omnibox/browser/omnibox_client.h b/components/omnibox/browser/omnibox_client.h
index 4f7b50d..253ae6c8 100644
--- a/components/omnibox/browser/omnibox_client.h
+++ b/components/omnibox/browser/omnibox_client.h
@@ -76,6 +76,12 @@
   // Returns the session ID of the current page.
   virtual const SessionID& GetSessionID() const = 0;
 
+  // Called when the user changes the selected |index| in the result list.
+  // |match| is the suggestion corresponding to that index. Currently
+  // experimental and only called from select omnibox implementations.
+  virtual void OnSelectedMatchChanged(size_t index,
+                                      const AutocompleteMatch& match) {}
+
   virtual bookmarks::BookmarkModel* GetBookmarkModel();
   virtual OmniboxControllerEmitter* GetOmniboxControllerEmitter();
   virtual TemplateURLService* GetTemplateURLService();
diff --git a/components/omnibox/browser/omnibox_edit_model.cc b/components/omnibox/browser/omnibox_edit_model.cc
index 1e66c17..f3359b57 100644
--- a/components/omnibox/browser/omnibox_edit_model.cc
+++ b/components/omnibox/browser/omnibox_edit_model.cc
@@ -1853,6 +1853,10 @@
   }
 
   const AutocompleteMatch& match = result().match_at(popup_selection_.line);
+
+  // Inform the client that a new row is now selected.
+  client_->OnSelectedMatchChanged(popup_selection_.line, match);
+
   DCHECK((popup_selection_.state != OmniboxPopupSelection::KEYWORD_MODE) ||
          match.associated_keyword.get());
   if (popup_selection_.IsButtonFocused()) {
diff --git a/components/omnibox/browser/zero_suggest_provider.cc b/components/omnibox/browser/zero_suggest_provider.cc
index ace8356..16a7f815 100644
--- a/components/omnibox/browser/zero_suggest_provider.cc
+++ b/components/omnibox/browser/zero_suggest_provider.cc
@@ -643,9 +643,7 @@
       can_send_current_url) {
     if (input.focus_type() == OmniboxFocusType::ON_FOCUS &&
         (base::FeatureList::IsEnabled(
-             omnibox::kOnFocusSuggestionsContextualWeb) ||
-         base::FeatureList::IsEnabled(
-             omnibox::kOnFocusSuggestionsContextualWebOnContent))) {
+            omnibox::kFocusTriggersContextualWebZeroSuggest))) {
       return REMOTE_SEND_URL;
     }
 
@@ -660,8 +658,7 @@
   if (IsSearchResultsPage(current_page_classification) &&
       can_send_current_url) {
     if (input.focus_type() == OmniboxFocusType::ON_FOCUS &&
-        base::FeatureList::IsEnabled(
-            omnibox::kOnFocusSuggestionsContextualWebAllowSRP)) {
+        base::FeatureList::IsEnabled(omnibox::kFocusTriggersSRPZeroSuggest)) {
       return REMOTE_SEND_URL;
     }
 
diff --git a/components/omnibox/browser/zero_suggest_provider_unittest.cc b/components/omnibox/browser/zero_suggest_provider_unittest.cc
index 0fc3d97d..f4dea53e 100644
--- a/components/omnibox/browser/zero_suggest_provider_unittest.cc
+++ b/components/omnibox/browser/zero_suggest_provider_unittest.cc
@@ -379,7 +379,7 @@
   {
     base::test::ScopedFeatureList features;
     features.InitWithFeatures(
-        {omnibox::kOnFocusSuggestionsContextualWeb},         // Enabled
+        {omnibox::kFocusTriggersContextualWebZeroSuggest},   // Enabled
         {omnibox::kClobberTriggersContextualWebZeroSuggest}  // Disabled
     );
 
@@ -407,7 +407,7 @@
   {
     base::test::ScopedFeatureList features;
     features.InitWithFeatures(
-        {omnibox::kOnFocusSuggestionsContextualWeb,
+        {omnibox::kFocusTriggersContextualWebZeroSuggest,
          omnibox::kClobberTriggersContextualWebZeroSuggest},  // Enabled
         {}                                                    // Disabled
     );
diff --git a/components/omnibox/common/omnibox_features.cc b/components/omnibox/common/omnibox_features.cc
index a2f3b0b..d9f555f 100644
--- a/components/omnibox/common/omnibox_features.cc
+++ b/components/omnibox/common/omnibox_features.cc
@@ -89,19 +89,30 @@
 const base::Feature kDynamicMaxAutocomplete{"OmniboxDynamicMaxAutocomplete",
                                             enabled_by_default_desktop_android};
 
-// If enabled, when the user clears the whole omnibox text (i.e. via Backspace),
-// Chrome will request remote ZeroSuggest suggestions for the OTHER page
-// classification (contextual web), which does NOT include the SRP.
+// Enable on-clobber (i.e., when the user clears the whole omnibox text)
+// zero-prefix suggestions on the Open Web, that are contextual to the current
+// URL. Will only work if user is signed-in and syncing, or is otherwise
+// eligible to send the current page URL to the suggest server.
 const base::Feature kClobberTriggersContextualWebZeroSuggest{
     "OmniboxClobberTriggersContextualWebZeroSuggest",
     enabled_by_default_desktop_only};
 
-// If enabled, when the user clears the whole omnibox text (i.e. via Backspace),
-// Chrome will request remote ZeroSuggest suggestions for the SRP (search
-// results page).
+// Enable on-clobber (i.e., when the user clears the whole omnibox text)
+// zero-prefix suggestions on the SRP.
 const base::Feature kClobberTriggersSRPZeroSuggest{
     "OmniboxClobberTriggersSRPZeroSuggest", enabled_by_default_desktop_only};
 
+// Enable on-focus zero-prefix suggestions on the Open Web, that are contextual
+// to the current URL. Will only work if user is signed-in and syncing, or is
+// otherwise eligible to send the current page URL to the suggest server.
+const base::Feature kFocusTriggersContextualWebZeroSuggest{
+    "OmniboxFocusTriggersContextualWebZeroSuggest",
+    enabled_by_default_android_only};
+
+// Enables on-focus zero-prefix suggestions on the SRP.
+const base::Feature kFocusTriggersSRPZeroSuggest{
+    "OmniboxFocusTriggersSRPZeroSuggest", enabled_by_default_android_only};
+
 // Used to adjust the age threshold since the last visit in order to consider a
 // normalized keyword search term as a zero-prefix suggestion. If disabled, the
 // default value of 60 days for Desktop and 7 days for Android and iOS is used.
@@ -118,29 +129,6 @@
     "OmniboxTrendingZeroPrefixSuggestionsOnNTP",
     enabled_by_default_desktop_android};
 
-// Enables on-focus suggestions on the Open Web, that are contextual to the
-// current URL. Will only work if user is signed-in and syncing, or is
-// otherwise eligible to send the current page URL to the suggest server.
-//
-// There's multiple flags here for multiple backend configurations:
-//  - Default (search queries)
-//  - SRP specific toggle (enables SRP on top of Web Pages for features below)
-//  - On-Content Suggestions
-//
-// TODO(tommycli): It's confusing whether Contextual Web includes SRP or not.
-// `kOnFocusSuggestionsContextualWebAllowSRP` suggests it's included, but
-// `kClobberTriggersContextualWebZeroSuggest` suggests it's not. Make this
-// consistent, probably by renaming flags to distinguish between OTHER and SRP.
-const base::Feature kOnFocusSuggestionsContextualWeb{
-    "OmniboxOnFocusSuggestionsContextualWeb",
-    base::FEATURE_DISABLED_BY_DEFAULT};
-const base::Feature kOnFocusSuggestionsContextualWebAllowSRP{
-    "OmniboxOnFocusSuggestionsContextualWebAllowSRP",
-    enabled_by_default_android_only};
-const base::Feature kOnFocusSuggestionsContextualWebOnContent{
-    "OmniboxOnFocusSuggestionsContextualWebOnContent",
-    enabled_by_default_android_only};
-
 // Revamps how local search history is extracted and processed for generating
 // zero-prefix and prefix suggestions.
 extern const base::Feature kLocalHistorySuggestRevamp{
diff --git a/components/omnibox/common/omnibox_features.h b/components/omnibox/common/omnibox_features.h
index ea88c05..c23909b 100644
--- a/components/omnibox/common/omnibox_features.h
+++ b/components/omnibox/common/omnibox_features.h
@@ -38,12 +38,11 @@
 // Local history zero-prefix (aka zero-suggest) and prefix suggestions.
 extern const base::Feature kClobberTriggersContextualWebZeroSuggest;
 extern const base::Feature kClobberTriggersSRPZeroSuggest;
+extern const base::Feature kFocusTriggersContextualWebZeroSuggest;
+extern const base::Feature kFocusTriggersSRPZeroSuggest;
 extern const base::Feature kLocalHistorySuggestRevamp;
 extern const base::Feature kOmniboxLocalZeroSuggestAgeThreshold;
 extern const base::Feature kOmniboxTrendingZeroPrefixSuggestionsOnNTP;
-extern const base::Feature kOnFocusSuggestionsContextualWeb;
-extern const base::Feature kOnFocusSuggestionsContextualWebAllowSRP;
-extern const base::Feature kOnFocusSuggestionsContextualWebOnContent;
 extern const base::Feature kZeroSuggestPrefetching;
 // Related, kMaxZeroSuggestMatches.
 
diff --git a/components/optimization_guide/core/optimization_guide_features.cc b/components/optimization_guide/core/optimization_guide_features.cc
index 4bf3a8a..2af3b793 100644
--- a/components/optimization_guide/core/optimization_guide_features.cc
+++ b/components/optimization_guide/core/optimization_guide_features.cc
@@ -131,7 +131,7 @@
 // This feature flag does not allow for the entities model to load the name and
 // prefix filters.
 const base::Feature kPageEntitiesModelBypassFilters{
-    "PageEntitiesModelBypassFilters", base::FEATURE_DISABLED_BY_DEFAULT};
+    "PageEntitiesModelBypassFilters", enabled_by_default_desktop_only};
 
 // This feature flag enables resetting the entities model on shutdown.
 const base::Feature kPageEntitiesModelResetOnShutdown{
diff --git a/components/password_manager/core/browser/mock_webauthn_credentials_delegate.h b/components/password_manager/core/browser/mock_webauthn_credentials_delegate.h
index f9e1abf..138b03a 100644
--- a/components/password_manager/core/browser/mock_webauthn_credentials_delegate.h
+++ b/components/password_manager/core/browser/mock_webauthn_credentials_delegate.h
@@ -22,6 +22,7 @@
       const MockWebAuthnCredentialsDelegate&) = delete;
 
   MOCK_METHOD(bool, IsWebAuthnAutofillEnabled, (), (const, override));
+  MOCK_METHOD(void, LaunchWebAuthnFlow, (), (override));
   MOCK_METHOD(void,
               SelectWebAuthnCredential,
               (std::string backend_id),
@@ -38,4 +39,4 @@
 
 }  // namespace password_manager
 
-#endif  // COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_MOCK_WEBAUTHN_CREDENTIALS_DELEGATE_H_
\ No newline at end of file
+#endif  // COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_MOCK_WEBAUTHN_CREDENTIALS_DELEGATE_H_
diff --git a/components/password_manager/core/browser/password_autofill_manager.cc b/components/password_manager/core/browser/password_autofill_manager.cc
index 6bcfd28a..47557afe 100644
--- a/components/password_manager/core/browser/password_autofill_manager.cc
+++ b/components/password_manager/core/browser/password_autofill_manager.cc
@@ -243,6 +243,17 @@
   suggestions->push_back(std::move(suggestion));
 }
 
+#if !BUILDFLAG(IS_ANDROID)
+autofill::Suggestion CreateWebAuthnEntry() {
+  // TODO(crbug.com/1329958): i18n this string.
+  autofill::Suggestion suggestion(u"Sign in with another device…");
+  suggestion.icon = "fingerprint";
+  suggestion.frontend_id =
+      autofill::POPUP_ITEM_ID_WEBAUTHN_SIGN_IN_WITH_ANOTHER_DEVICE;
+  return suggestion;
+}
+#endif  // !BUILDFLAG(IS_ANDROID)
+
 autofill::Suggestion CreateGenerationEntry() {
   autofill::Suggestion suggestion(
       l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_GENERATE_PASSWORD));
@@ -391,7 +402,10 @@
       frontend_id ==
           autofill::POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_RE_SIGNIN ||
       frontend_id ==
-          autofill::POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_OPT_IN_AND_GENERATE)
+          autofill::
+              POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_OPT_IN_AND_GENERATE ||
+      frontend_id ==
+          autofill::POPUP_ITEM_ID_WEBAUTHN_SIGN_IN_WITH_ANOTHER_DEVICE)
     return;
   bool success =
       PreviewSuggestion(GetUsernameFromSuggestion(value), frontend_id);
@@ -471,6 +485,12 @@
         ->SelectWebAuthnCredential(absl::holds_alternative<std::string>(payload)
                                        ? absl::get<std::string>(payload)
                                        : std::string());
+  } else if (frontend_id ==
+             autofill::POPUP_ITEM_ID_WEBAUTHN_SIGN_IN_WITH_ANOTHER_DEVICE) {
+    metrics_util::LogPasswordDropdownItemSelected(
+        PasswordDropdownSelectedOption::kWebAuthnSignInWithAnotherDevice,
+        password_client_->IsIncognito());
+    password_client_->GetWebAuthnCredentialsDelegate()->LaunchWebAuthnFlow();
   } else {
     metrics_util::LogPasswordDropdownItemSelected(
         PasswordDropdownSelectedOption::kPassword,
@@ -685,6 +705,13 @@
                    &suggestions);
   }
 
+#if !BUILDFLAG(IS_ANDROID)
+  // Add "Sign in with another device" button.
+  if (show_webauthn_credentials && delegate->IsWebAuthnAutofillEnabled()) {
+    suggestions.push_back(CreateWebAuthnEntry());
+  }
+#endif  // !BUILDFLAG(IS_ANDROID)
+
   // Add password generation entry, if available.
   if (offers_generation) {
     suggestions.push_back(show_account_storage_optin
diff --git a/components/password_manager/core/browser/password_autofill_manager_unittest.cc b/components/password_manager/core/browser/password_autofill_manager_unittest.cc
index 97b44f1..7063775 100644
--- a/components/password_manager/core/browser/password_autofill_manager_unittest.cc
+++ b/components/password_manager/core/browser/password_autofill_manager_unittest.cc
@@ -868,7 +868,7 @@
   // Only the unlock button was available. After being clicked, it's in a
   // loading state which the DeleteFillData() call will end.
   Suggestion unlock_suggestion(
-      /*value=*/"Unlock passwords and fill", /*label=*/"", /*icon=*/"",
+      /*main_text=*/"Unlock passwords and fill", /*label=*/"", /*icon=*/"",
       /*frontend_id=*/
       autofill::POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_OPT_IN);
   unlock_suggestion.is_loading = Suggestion::IsLoading(true);
@@ -1862,6 +1862,9 @@
               SuggestionVectorIdsAre(ElementsAre(
                   autofill::POPUP_ITEM_ID_WEBAUTHN_CREDENTIAL,
                   autofill::POPUP_ITEM_ID_USERNAME_ENTRY,
+#if !BUILDFLAG(IS_ANDROID)
+                  autofill::POPUP_ITEM_ID_WEBAUTHN_SIGN_IN_WITH_ANOTHER_DEVICE,
+#endif  // !BUILDFLAG(IS_ANDROID)
                   autofill::POPUP_ITEM_ID_ALL_SAVED_PASSWORDS_ENTRY)));
   EXPECT_EQ(absl::get<std::string>(open_args.suggestions[0].payload), kId);
   EXPECT_EQ(open_args.suggestions[0].frontend_id,
@@ -1887,4 +1890,54 @@
       kName, autofill::POPUP_ITEM_ID_WEBAUTHN_CREDENTIAL, kId, /*position=*/1);
 }
 
+#if !BUILDFLAG(IS_ANDROID)
+TEST_F(PasswordAutofillManagerTest, ShowsWebAuthnSignInWithAnotherDevice) {
+  TestPasswordManagerClient client;
+  NiceMock<MockAutofillClient> autofill_client;
+  MockWebAuthnCredentialsDelegate webauthn_credentials_delegate;
+  InitializePasswordAutofillManager(&client, &autofill_client);
+
+  // Enable WebAuthn autofill.
+  std::vector<autofill::Suggestion> webauthn_credentials;
+  ON_CALL(webauthn_credentials_delegate, IsWebAuthnAutofillEnabled)
+      .WillByDefault(Return(true));
+  EXPECT_CALL(client, GetWebAuthnCredentialsDelegate)
+      .WillRepeatedly(Return(&webauthn_credentials_delegate));
+  EXPECT_CALL(webauthn_credentials_delegate, GetWebAuthnSuggestions)
+      .WillOnce(ReturnRef(webauthn_credentials));
+
+  // Show password suggestions including WebAuthn credentials.
+  autofill::AutofillClient::PopupOpenArgs open_args;
+  EXPECT_CALL(autofill_client, ShowAutofillPopup)
+      .WillOnce(testing::SaveArg<0>(&open_args));
+  gfx::RectF element_bounds;
+  password_autofill_manager_->OnShowPasswordSuggestions(
+      base::i18n::RIGHT_TO_LEFT, /*typed_username=*/std::u16string(),
+      autofill::ShowPasswordSuggestionsOptions::ACCEPTS_WEBAUTHN_CREDENTIALS,
+      element_bounds);
+  ASSERT_THAT(open_args.suggestions,
+              SuggestionVectorIdsAre(ElementsAre(
+                  autofill::POPUP_ITEM_ID_USERNAME_ENTRY,
+                  autofill::POPUP_ITEM_ID_WEBAUTHN_SIGN_IN_WITH_ANOTHER_DEVICE,
+                  autofill::POPUP_ITEM_ID_ALL_SAVED_PASSWORDS_ENTRY)));
+
+  // Check that the button shows the correct text.
+  // TODO(crbug.com/1329958): replace with a resource ID when i18n'd.
+  const std::u16string kSignInWithAnotherDeviceText =
+      u"Sign in with another device…";
+  EXPECT_EQ(open_args.suggestions[1].main_text.value,
+            kSignInWithAnotherDeviceText);
+
+  // Check that selecting the button reports back to the client.
+  EXPECT_CALL(webauthn_credentials_delegate, LaunchWebAuthnFlow());
+  EXPECT_CALL(
+      autofill_client,
+      HideAutofillPopup(autofill::PopupHidingReason::kAcceptSuggestion));
+  password_autofill_manager_->DidAcceptSuggestion(
+      kSignInWithAnotherDeviceText,
+      autofill::POPUP_ITEM_ID_WEBAUTHN_SIGN_IN_WITH_ANOTHER_DEVICE,
+      /*payload=*/std::string(), /*position=*/1);
+}
+#endif  // !BUILDFLAG(IS_ANDROID)
+
 }  // namespace password_manager
diff --git a/components/password_manager/core/browser/password_manager_metrics_util.h b/components/password_manager/core/browser/password_manager_metrics_util.h
index c97506f..6e600c0 100644
--- a/components/password_manager/core/browser/password_manager_metrics_util.h
+++ b/components/password_manager/core/browser/password_manager_metrics_util.h
@@ -403,7 +403,9 @@
   kResigninToUnlockAccountStore = 5,
   // User selected a WebAuthn credential.
   kWebAuthn = 6,
-  kMaxValue = kWebAuthn
+  // User selected the "Sign in with another device" button.
+  kWebAuthnSignInWithAnotherDevice = 7,
+  kMaxValue = kWebAuthnSignInWithAnotherDevice
 };
 
 // These values are persisted to logs. Entries should not be renumbered and
diff --git a/components/password_manager/core/browser/webauthn_credentials_delegate.h b/components/password_manager/core/browser/webauthn_credentials_delegate.h
index ebd27d6..c83028c 100644
--- a/components/password_manager/core/browser/webauthn_credentials_delegate.h
+++ b/components/password_manager/core/browser/webauthn_credentials_delegate.h
@@ -22,6 +22,10 @@
   // Returns true if integration between WebAuthn and Autofill is enabled.
   virtual bool IsWebAuthnAutofillEnabled() const = 0;
 
+  // Launches the normal WebAuthn flow that lets users use their phones or
+  // security keys to sign-in.
+  virtual void LaunchWebAuthnFlow() = 0;
+
   // Called when the user selects a WebAuthn credential from the autofill
   // suggestion list.
   virtual void SelectWebAuthnCredential(std::string backend_id) = 0;
diff --git a/components/renderer_context_menu/context_menu_content_type.cc b/components/renderer_context_menu/context_menu_content_type.cc
index e48fcf2..54afa8a 100644
--- a/components/renderer_context_menu/context_menu_content_type.cc
+++ b/components/renderer_context_menu/context_menu_content_type.cc
@@ -125,6 +125,9 @@
     case ITEM_GROUP_COPY:
       return !params_.is_editable && has_selection;
 
+    case ITEM_GROUP_PARTIAL_TRANSLATE:
+      return has_selection;
+
     case ITEM_GROUP_EXISTING_LINK_TO_TEXT:
       return params_.opened_from_highlight;
 
diff --git a/components/renderer_context_menu/context_menu_content_type.h b/components/renderer_context_menu/context_menu_content_type.h
index 45a3c1f..3e5e380f 100644
--- a/components/renderer_context_menu/context_menu_content_type.h
+++ b/components/renderer_context_menu/context_menu_content_type.h
@@ -41,6 +41,7 @@
     ITEM_GROUP_MEDIA_FILE,
     ITEM_GROUP_EDITABLE,
     ITEM_GROUP_COPY,
+    ITEM_GROUP_PARTIAL_TRANSLATE,
     ITEM_GROUP_SEARCH_PROVIDER,
     ITEM_GROUP_PRINT,
     ITEM_GROUP_ALL_EXTENSION,
diff --git a/components/safe_browsing/content/renderer/phishing_classifier/phishing_classifier.cc b/components/safe_browsing/content/renderer/phishing_classifier/phishing_classifier.cc
index c382b71..4f4e6f7 100644
--- a/components/safe_browsing/content/renderer/phishing_classifier/phishing_classifier.cc
+++ b/components/safe_browsing/content/renderer/phishing_classifier/phishing_classifier.cc
@@ -201,24 +201,12 @@
   TRACE_EVENT0("safe_browsing", "ExtractVisualFeatures");
 
   blink::WebLocalFrame* frame = render_frame_->GetWebFrame();
-  gfx::SizeF viewport_size = frame->View()->VisualViewportSize();
-  gfx::Rect bounds = ToEnclosingRect(gfx::RectF(viewport_size));
-
-  auto tracker = std::make_unique<paint_preview::PaintPreviewTracker>(
-      base::UnguessableToken::Create(), frame->GetEmbeddingToken(),
-      /*is_main_frame=*/true);
-  cc::PaintRecorder recorder;
-  cc::PaintCanvas* canvas =
-      recorder.beginRecording(bounds.width(), bounds.height());
-  canvas->SetPaintPreviewTracker(tracker.get());
-
-  if (!frame->CapturePaintPreview(bounds, canvas,
-                                  /*include_linked_destinations=*/false,
-                                  /*skip_accelerated_content=*/true)) {
+  sk_sp<cc::PaintRecord> paint_record = frame->GetPaintRecord();
+  if (!paint_record) {
     VisualExtractionFinished(/*success=*/false);
   }
-
-  sk_sp<cc::PaintRecord> paint_record = recorder.finishRecordingAsPicture();
+  gfx::SizeF viewport_size = frame->View()->VisualViewportSize();
+  gfx::Rect bounds = ToEnclosingRect(gfx::RectF(viewport_size));
 
   base::UmaHistogramTimes("SBClientPhishing.VisualFeatureTime",
                           base::TimeTicks::Now() - start_time);
diff --git a/components/search_engines/default_search_manager.cc b/components/search_engines/default_search_manager.cc
index d5dda45..a17d1d4 100644
--- a/components/search_engines/default_search_manager.cc
+++ b/components/search_engines/default_search_manager.cc
@@ -114,12 +114,10 @@
 }
 
 // static
-void DefaultSearchManager::AddPrefValueToMap(
-    std::unique_ptr<base::DictionaryValue> value,
-    PrefValueMap* pref_value_map) {
-  DCHECK(value);
+void DefaultSearchManager::AddPrefValueToMap(base::Value::Dict value,
+                                             PrefValueMap* pref_value_map) {
   pref_value_map->SetValue(kDefaultSearchProviderDataPrefName,
-                           base::Value::FromUniquePtrValue(std::move(value)));
+                           base::Value(std::move(value)));
 }
 
 // static
diff --git a/components/search_engines/default_search_manager.h b/components/search_engines/default_search_manager.h
index 425d5e5..738a115 100644
--- a/components/search_engines/default_search_manager.h
+++ b/components/search_engines/default_search_manager.h
@@ -9,12 +9,9 @@
 
 #include "base/callback.h"
 #include "base/memory/raw_ptr.h"
+#include "base/values.h"
 #include "components/prefs/pref_change_registrar.h"
 
-namespace base {
-class DictionaryValue;
-}
-
 namespace user_prefs {
 class PrefRegistrySyncable;
 }
@@ -98,7 +95,7 @@
   static void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry);
 
   // Save default search provider pref values into the map provided.
-  static void AddPrefValueToMap(std::unique_ptr<base::DictionaryValue> value,
+  static void AddPrefValueToMap(base::Value::Dict value,
                                 PrefValueMap* pref_value_map);
 
   // Testing code can call this with |disabled| set to true to cause
diff --git a/components/search_engines/default_search_policy_handler.cc b/components/search_engines/default_search_policy_handler.cc
index 77afa2b4..f3cb5634 100644
--- a/components/search_engines/default_search_policy_handler.cc
+++ b/components/search_engines/default_search_policy_handler.cc
@@ -9,6 +9,7 @@
 #include <utility>
 
 #include "base/strings/string_number_conversions.h"
+#include "base/values.h"
 #include "components/policy/core/browser/policy_error_map.h"
 #include "components/policy/core/common/policy_map.h"
 #include "components/policy/policy_constants.h"
@@ -26,35 +27,30 @@
 void SetListInPref(const PolicyMap& policies,
                    const char* policy_name,
                    const char* key,
-                   base::DictionaryValue* dict) {
-  DCHECK(dict);
+                   base::Value::Dict& dict) {
   const base::Value* policy_value =
       policies.GetValue(policy_name, base::Value::Type::LIST);
-  dict->Set(key, policy_value
-                     ? std::make_unique<base::Value>(policy_value->Clone())
-                     : std::make_unique<base::Value>(base::Value::Type::LIST));
+  dict.Set(key, policy_value ? policy_value->Clone()
+                             : base::Value(base::Value::Type::LIST));
 }
 
 // Extracts a string from a policy value and adds it to a pref dictionary.
 void SetStringInPref(const PolicyMap& policies,
                      const char* policy_name,
                      const char* key,
-                     base::DictionaryValue* dict) {
-  DCHECK(dict);
+                     base::Value::Dict& dict) {
   const base::Value* policy_value =
       policies.GetValue(policy_name, base::Value::Type::STRING);
-  dict->SetStringKey(key,
-                     policy_value ? policy_value->GetString() : std::string());
+  dict.Set(key, policy_value ? policy_value->GetString() : std::string());
 }
 
 void SetBooleanInPref(const PolicyMap& policies,
                       const char* policy_name,
                       const char* key,
-                      base::DictionaryValue* dict) {
-  DCHECK(dict);
+                      base::Value::Dict& dict) {
   const base::Value* policy_value =
       policies.GetValue(policy_name, base::Value::Type::BOOLEAN);
-  dict->SetBoolPath(key, policy_value && policy_value->GetBool());
+  dict.SetByDottedPath(key, policy_value && policy_value->GetBool());
 }
 
 }  // namespace
@@ -140,8 +136,8 @@
     return;
 
   if (DefaultSearchProviderIsDisabled(policies)) {
-    std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue);
-    dict->SetBoolean(DefaultSearchManager::kDisabledByPolicy, true);
+    base::Value::Dict dict;
+    dict.Set(DefaultSearchManager::kDisabledByPolicy, true);
     DefaultSearchManager::AddPrefValueToMap(std::move(dict), prefs);
     return;
   }
@@ -154,75 +150,76 @@
   if (!DefaultSearchURLIsValid(policies, &dummy, &url))
     return;
 
-  std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue);
+  base::Value::Dict dict;
 
   // Set pref values for policies affecting the default
   // search provider, which are listed in kDefaultSearchPolicyDataMap.
   // Set or remove pref accordingly when kDefaultSearchPolicyDataMap has a
   // change, then revise the number in the check below to be correct.
   SetBooleanInPref(policies, key::kDefaultSearchProviderEnabled,
-                   prefs::kDefaultSearchProviderEnabled, dict.get());
+                   prefs::kDefaultSearchProviderEnabled, dict);
   SetStringInPref(policies, key::kDefaultSearchProviderName,
-                  DefaultSearchManager::kShortName, dict.get());
+                  DefaultSearchManager::kShortName, dict);
   SetStringInPref(policies, key::kDefaultSearchProviderKeyword,
-                  DefaultSearchManager::kKeyword, dict.get());
+                  DefaultSearchManager::kKeyword, dict);
   SetStringInPref(policies, key::kDefaultSearchProviderSearchURL,
-                  DefaultSearchManager::kURL, dict.get());
+                  DefaultSearchManager::kURL, dict);
   SetStringInPref(policies, key::kDefaultSearchProviderSuggestURL,
-                  DefaultSearchManager::kSuggestionsURL, dict.get());
+                  DefaultSearchManager::kSuggestionsURL, dict);
   SetStringInPref(policies, key::kDefaultSearchProviderIconURL,
-                  DefaultSearchManager::kFaviconURL, dict.get());
+                  DefaultSearchManager::kFaviconURL, dict);
   SetListInPref(policies, key::kDefaultSearchProviderEncodings,
-                DefaultSearchManager::kInputEncodings, dict.get());
+                DefaultSearchManager::kInputEncodings, dict);
   SetListInPref(policies, key::kDefaultSearchProviderAlternateURLs,
-                DefaultSearchManager::kAlternateURLs, dict.get());
+                DefaultSearchManager::kAlternateURLs, dict);
   SetStringInPref(policies, key::kDefaultSearchProviderImageURL,
-                  DefaultSearchManager::kImageURL, dict.get());
+                  DefaultSearchManager::kImageURL, dict);
   SetStringInPref(policies, key::kDefaultSearchProviderNewTabURL,
-                  DefaultSearchManager::kNewTabURL, dict.get());
+                  DefaultSearchManager::kNewTabURL, dict);
   SetStringInPref(policies, key::kDefaultSearchProviderSearchURLPostParams,
-                  DefaultSearchManager::kSearchURLPostParams, dict.get());
+                  DefaultSearchManager::kSearchURLPostParams, dict);
   SetStringInPref(policies, key::kDefaultSearchProviderSuggestURLPostParams,
-                  DefaultSearchManager::kSuggestionsURLPostParams, dict.get());
+                  DefaultSearchManager::kSuggestionsURLPostParams, dict);
   SetStringInPref(policies, key::kDefaultSearchProviderImageURLPostParams,
-                  DefaultSearchManager::kImageURLPostParams, dict.get());
-  SetBooleanInPref(
-      policies, key::kDefaultSearchProviderContextMenuAccessAllowed,
-      prefs::kDefaultSearchProviderContextMenuAccessAllowed, dict.get());
+                  DefaultSearchManager::kImageURLPostParams, dict);
+  SetBooleanInPref(policies,
+                   key::kDefaultSearchProviderContextMenuAccessAllowed,
+                   prefs::kDefaultSearchProviderContextMenuAccessAllowed, dict);
 
   size_t policyCount = 14;
   CHECK_EQ(policyCount, std::size(kDefaultSearchPolicyDataMap));
 
   // Set the fields which are not specified by the policy to default values.
-  dict->SetString(DefaultSearchManager::kID,
-                  base::NumberToString(kInvalidTemplateURLID));
-  dict->SetInteger(DefaultSearchManager::kPrepopulateID, 0);
-  dict->SetInteger(DefaultSearchManager::kStarterPackId, 0);
-  dict->SetString(DefaultSearchManager::kSyncGUID, std::string());
-  dict->SetString(DefaultSearchManager::kOriginatingURL, std::string());
-  dict->SetBoolean(DefaultSearchManager::kSafeForAutoReplace, true);
-  dict->SetDouble(DefaultSearchManager::kDateCreated,
-                  base::Time::Now().ToInternalValue());
-  dict->SetDouble(DefaultSearchManager::kLastModified,
-                  base::Time::Now().ToInternalValue());
-  dict->SetInteger(DefaultSearchManager::kUsageCount, 0);
-  dict->SetBoolean(DefaultSearchManager::kCreatedByPolicy, true);
+  dict.Set(DefaultSearchManager::kID,
+           base::NumberToString(kInvalidTemplateURLID));
+  dict.Set(DefaultSearchManager::kPrepopulateID, 0);
+  dict.Set(DefaultSearchManager::kStarterPackId, 0);
+  dict.Set(DefaultSearchManager::kSyncGUID, std::string());
+  dict.Set(DefaultSearchManager::kOriginatingURL, std::string());
+  dict.Set(DefaultSearchManager::kSafeForAutoReplace, true);
+  dict.Set(DefaultSearchManager::kDateCreated,
+           static_cast<double>(base::Time::Now().ToInternalValue()));
+  dict.Set(DefaultSearchManager::kLastModified,
+           static_cast<double>(base::Time::Now().ToInternalValue()));
+  dict.Set(DefaultSearchManager::kUsageCount, 0);
+  dict.Set(DefaultSearchManager::kCreatedByPolicy, true);
 
   // For the name and keyword, default to the host if not specified.  If
   // there is no host (as is the case with file URLs of the form:
   // "file:///c:/..."), use "_" to guarantee that the keyword is non-empty.
-  std::string name, keyword;
-  dict->GetString(DefaultSearchManager::kKeyword, &keyword);
-  dict->GetString(DefaultSearchManager::kShortName, &name);
-  dict->GetString(DefaultSearchManager::kURL, &url);
+  std::string* keyword = dict.FindString(DefaultSearchManager::kKeyword);
+  std::string* name = dict.FindString(DefaultSearchManager::kShortName);
+  std::string* url_str = dict.FindString(DefaultSearchManager::kURL);
+  if (url_str)
+    url = *url_str;
 
   std::string host(GURL(url).host());
   if (host.empty())
     host = "_";
-  if (name.empty())
-    dict->SetString(DefaultSearchManager::kShortName, host);
-  if (keyword.empty())
-    dict->SetString(DefaultSearchManager::kKeyword, host);
+  if (!name || name->empty())
+    dict.Set(DefaultSearchManager::kShortName, host);
+  if (!keyword || keyword->empty())
+    dict.Set(DefaultSearchManager::kKeyword, host);
 
   DefaultSearchManager::AddPrefValueToMap(std::move(dict), prefs);
 }
diff --git a/components/segmentation_platform/internal/selection/segment_result_provider.cc b/components/segmentation_platform/internal/selection/segment_result_provider.cc
index 5fad0dc31..b7c5861f 100644
--- a/components/segmentation_platform/internal/selection/segment_result_provider.cc
+++ b/components/segmentation_platform/internal/selection/segment_result_provider.cc
@@ -271,6 +271,7 @@
   // The pointer is kept alive by the `request_state`.
   request->segment_info = segment_info;
   request->record_metrics_for_default = true;
+  request->input_context = request_state->options->input_context;
   request->callback =
       base::BindOnce(&SegmentResultProviderImpl::OnModelExecuted,
                      weak_ptr_factory_.GetWeakPtr(), std::move(request_state),
diff --git a/components/segmentation_platform/internal/selection/segment_selector_impl.cc b/components/segmentation_platform/internal/selection/segment_selector_impl.cc
index 8d9a850..dcfc51fb 100644
--- a/components/segmentation_platform/internal/selection/segment_selector_impl.cc
+++ b/components/segmentation_platform/internal/selection/segment_selector_impl.cc
@@ -233,6 +233,7 @@
       options->segment_id = needed_segment;
       options->segmentation_key = config_->segmentation_key;
       options->ignore_db_scores = config_->on_demand_execution;
+      options->input_context = input_context;
       options->callback =
           base::BindOnce(&SegmentSelectorImpl::OnGetResultForSegmentSelection,
                          weak_ptr_factory_.GetWeakPtr(), std::move(ranks),
diff --git a/components/translate/core/common/translate_util.cc b/components/translate/core/common/translate_util.cc
index 7ddaeed..54ef588 100644
--- a/components/translate/core/common/translate_util.cc
+++ b/components/translate/core/common/translate_util.cc
@@ -41,6 +41,9 @@
 #endif
 };
 
+const base::Feature kDesktopPartialTranslate{"DesktopPartialTranslate",
+                                             base::FEATURE_DISABLED_BY_DEFAULT};
+
 GURL GetTranslateSecurityOrigin() {
   std::string security_origin(kSecurityOrigin);
   base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
diff --git a/components/translate/core/common/translate_util.h b/components/translate/core/common/translate_util.h
index a179e8d..10eef51 100644
--- a/components/translate/core/common/translate_util.h
+++ b/components/translate/core/common/translate_util.h
@@ -17,6 +17,9 @@
 // Controls whether the TFLite-based language detection is enabled.
 extern const base::Feature kTFLiteLanguageDetectionEnabled;
 
+// Controls whether the Partial Translate function is available.
+extern const base::Feature kDesktopPartialTranslate;
+
 // Isolated world sets following security-origin by default.
 extern const char kSecurityOrigin[];
 
diff --git a/components/webrtc/net_address_utils.cc b/components/webrtc/net_address_utils.cc
index 3d1deaf..95d6578 100644
--- a/components/webrtc/net_address_utils.cc
+++ b/components/webrtc/net_address_utils.cc
@@ -59,15 +59,15 @@
 
 std::string SerializeP2PCandidate(const cricket::Candidate& candidate) {
   // TODO(sergeyu): Use SDP to format candidates?
-  base::DictionaryValue value;
-  value.SetString("ip", candidate.address().ipaddr().ToString());
-  value.SetInteger("port", candidate.address().port());
-  value.SetString("type", candidate.type());
-  value.SetString("protocol", candidate.protocol());
-  value.SetString("username", candidate.username());
-  value.SetString("password", candidate.password());
-  value.SetDouble("preference", candidate.preference());
-  value.SetInteger("generation", candidate.generation());
+  base::Value::Dict value;
+  value.Set("ip", candidate.address().ipaddr().ToString());
+  value.Set("port", candidate.address().port());
+  value.Set("type", candidate.type());
+  value.Set("protocol", candidate.protocol());
+  value.Set("username", candidate.username());
+  value.Set("password", candidate.password());
+  value.Set("preference", candidate.preference());
+  value.Set("generation", static_cast<int>(candidate.generation()));
 
   std::string result;
   base::JSONWriter::Write(value, &result);
@@ -76,41 +76,35 @@
 
 bool DeserializeP2PCandidate(const std::string& candidate_str,
                              cricket::Candidate* candidate) {
-  std::unique_ptr<base::Value> value(base::JSONReader::ReadDeprecated(
-      candidate_str, base::JSON_ALLOW_TRAILING_COMMAS));
-  if (!value.get() || !value->is_dict()) {
+  absl::optional<base::Value> value(
+      base::JSONReader::Read(candidate_str, base::JSON_ALLOW_TRAILING_COMMAS));
+  if (!value || !value->is_dict()) {
     return false;
   }
 
-  base::DictionaryValue* dic_value =
-      static_cast<base::DictionaryValue*>(value.get());
+  base::Value::Dict& dic_value = value->GetDict();
 
-  std::string ip;
-  int port = 0;
-  std::string type;
-  std::string protocol;
-  std::string username;
-  std::string password;
-  absl::optional<double> preference = dic_value->FindDoubleKey("preference");
-  int generation = 0;
+  std::string* ip = dic_value.FindString("ip");
+  absl::optional<int> port = dic_value.FindInt("port");
+  std::string* type = dic_value.FindString("type");
+  std::string* protocol = dic_value.FindString("protocol");
+  std::string* username = dic_value.FindString("username");
+  std::string* password = dic_value.FindString("password");
+  absl::optional<double> preference = dic_value.FindDouble("preference");
+  absl::optional<int> generation = dic_value.FindInt("generation");
 
-  if (!dic_value->GetString("ip", &ip) ||
-      !dic_value->GetInteger("port", &port) ||
-      !dic_value->GetString("type", &type) ||
-      !dic_value->GetString("protocol", &protocol) ||
-      !dic_value->GetString("username", &username) ||
-      !dic_value->GetString("password", &password) || !preference ||
-      !dic_value->GetInteger("generation", &generation)) {
+  if (!ip || !port || !type || !protocol || !username || !password ||
+      !preference || !generation) {
     return false;
   }
 
-  candidate->set_address(rtc::SocketAddress(ip, port));
-  candidate->set_type(type);
-  candidate->set_protocol(protocol);
-  candidate->set_username(username);
-  candidate->set_password(password);
+  candidate->set_address(rtc::SocketAddress(std::move(*ip), *port));
+  candidate->set_type(std::move(*type));
+  candidate->set_protocol(std::move(*protocol));
+  candidate->set_username(std::move(*username));
+  candidate->set_password(std::move(*password));
   candidate->set_preference(static_cast<float>(*preference));
-  candidate->set_generation(generation);
+  candidate->set_generation(*generation);
 
   return true;
 }
diff --git a/content/browser/accessibility/browser_accessibility_android.cc b/content/browser/accessibility/browser_accessibility_android.cc
index b6cab6b6..5b8e649 100644
--- a/content/browser/accessibility/browser_accessibility_android.cc
+++ b/content/browser/accessibility/browser_accessibility_android.cc
@@ -5,8 +5,8 @@
 #include "content/browser/accessibility/browser_accessibility_android.h"
 
 #include <algorithm>
-#include <unordered_map>
 
+#include "base/containers/flat_map.h"
 #include "base/cxx17_backports.h"
 #include "base/i18n/break_iterator.h"
 #include "base/lazy_instance.h"
@@ -81,7 +81,7 @@
       new BrowserAccessibilityAndroid(manager, node));
 }
 
-using UniqueIdMap = std::unordered_map<int32_t, BrowserAccessibilityAndroid*>;
+using UniqueIdMap = base::flat_map<int32_t, BrowserAccessibilityAndroid*>;
 // Map from each AXPlatformNode's unique id to its instance.
 base::LazyInstance<UniqueIdMap>::Leaky g_unique_id_map =
     LAZY_INSTANCE_INITIALIZER;
diff --git a/content/browser/accessibility/browser_accessibility_manager_android.cc b/content/browser/accessibility/browser_accessibility_manager_android.cc
index 8ddabdb..fd073a4 100644
--- a/content/browser/accessibility/browser_accessibility_manager_android.cc
+++ b/content/browser/accessibility/browser_accessibility_manager_android.cc
@@ -547,7 +547,7 @@
   // Reset content changed events counter every time we finish an atomic update.
   wcax->ResetContentChangedEventsCounter();
 
-  // Clear unordered_set of nodes cleared from the cache after atomic update.
+  // Clear set of nodes cleared from the cache after atomic update.
   nodes_already_cleared_.clear();
 
   if (root_changed) {
diff --git a/content/browser/accessibility/browser_accessibility_manager_android.h b/content/browser/accessibility/browser_accessibility_manager_android.h
index 41e1f11..e04df55 100644
--- a/content/browser/accessibility/browser_accessibility_manager_android.h
+++ b/content/browser/accessibility/browser_accessibility_manager_android.h
@@ -5,7 +5,6 @@
 #ifndef CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_MANAGER_ANDROID_H_
 #define CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_MANAGER_ANDROID_H_
 
-#include <unordered_set>
 #include <utility>
 
 #include "content/browser/accessibility/browser_accessibility_manager.h"
@@ -173,10 +172,9 @@
   // any_node_uses_touch_passthrough(), above, for details.
   bool touch_passthrough_enabled_ = false;
 
-  // An unordered_set of |unique_id| values for nodes cleared from the cache
+  // An set of |unique_id| values for nodes cleared from the cache
   // with each atomic update to prevent superfluous cache clear calls.
-  std::unordered_set<int32_t> nodes_already_cleared_ =
-      std::unordered_set<int32_t>();
+  std::set<int32_t> nodes_already_cleared_ = std::set<int32_t>();
 };
 
 }  // namespace content
diff --git a/content/browser/accessibility/web_contents_accessibility_android.cc b/content/browser/accessibility/web_contents_accessibility_android.cc
index 76f24d05..dd937d28 100644
--- a/content/browser/accessibility/web_contents_accessibility_android.cc
+++ b/content/browser/accessibility/web_contents_accessibility_android.cc
@@ -6,12 +6,12 @@
 
 #include <memory>
 #include <string>
-#include <unordered_map>
 #include <vector>
 
 #include "base/android/jni_android.h"
 #include "base/android/jni_array.h"
 #include "base/android/jni_string.h"
+#include "base/containers/flat_map.h"
 #include "base/cxx17_backports.h"
 #include "base/debug/crash_logging.h"
 #include "base/feature_list.h"
@@ -38,9 +38,8 @@
 namespace content {
 
 namespace {
-
 using SearchKeyToPredicateMap =
-    std::unordered_map<std::u16string, AccessibilityMatchPredicate>;
+    base::flat_map<std::u16string, AccessibilityMatchPredicate>;
 base::LazyInstance<SearchKeyToPredicateMap>::Leaky
     g_search_key_to_predicate_map = LAZY_INSTANCE_INITIALIZER;
 base::LazyInstance<std::u16string>::Leaky g_all_search_keys =
diff --git a/content/browser/browser_main_loop.cc b/content/browser/browser_main_loop.cc
index 6691ddf..403c7488 100644
--- a/content/browser/browser_main_loop.cc
+++ b/content/browser/browser_main_loop.cc
@@ -1038,6 +1038,11 @@
 }
 
 void BrowserMainLoop::PreShutdown() {
+  // Clear OnNextIdleCallback if it's still pending. Failure to do so can result
+  // in an OnFirstIdle phase incorrectly triggering during shutdown if an early
+  // exit paths results in a shutdown path that happens to RunLoop.
+  base::CurrentThread::Get()->RegisterOnNextIdleCallback(base::NullCallback());
+
   ui::Clipboard::OnPreShutdownForCurrentThread();
 }
 
diff --git a/content/browser/file_system/file_system_manager_impl.cc b/content/browser/file_system/file_system_manager_impl.cc
index bdd51338..244b9f8 100644
--- a/content/browser/file_system/file_system_manager_impl.cc
+++ b/content/browser/file_system/file_system_manager_impl.cc
@@ -105,7 +105,6 @@
     case storage::FileSystemType::kFileSystemTypeSyncableForInternalSync:
     case storage::FileSystemType::kFileSystemTypeLocalForPlatformApp:
     case storage::FileSystemType::kFileSystemTypeForTransientFile:
-    case storage::FileSystemType::kFileSystemTypePluginPrivate:
     case storage::FileSystemType::kFileSystemTypeProvided:
     case storage::FileSystemType::kFileSystemTypeDeviceMediaAsFileStorage:
     case storage::FileSystemType::kFileSystemTypeArcContent:
@@ -1280,12 +1279,6 @@
   if (!FileSystemURLIsValid(context_.get(), url))
     return base::File::FILE_ERROR_INVALID_URL;
 
-  // Deny access to files in PluginPrivate FileSystem from JavaScript.
-  // TODO(nhiroki): Move this filter somewhere else since this is not for
-  // validation.
-  if (url.type() == storage::kFileSystemTypePluginPrivate)
-    return base::File::FILE_ERROR_SECURITY;
-
   return absl::nullopt;
 }
 
diff --git a/content/browser/media/capture/web_contents_frame_tracker.cc b/content/browser/media/capture/web_contents_frame_tracker.cc
index 0b2daa6..69411bd 100644
--- a/content/browser/media/capture/web_contents_frame_tracker.cc
+++ b/content/browser/media/capture/web_contents_frame_tracker.cc
@@ -107,10 +107,16 @@
 }  // namespace
 
 WebContentsFrameTracker::WebContentsFrameTracker(
+    scoped_refptr<base::SequencedTaskRunner> device_task_runner,
     base::WeakPtr<WebContentsVideoCaptureDevice> device,
     MouseCursorOverlayController* cursor_controller)
     : device_(std::move(device)),
-      device_task_runner_(base::ThreadTaskRunnerHandle::Get()) {
+      device_task_runner_(std::move(device_task_runner)) {
+  // Verify on construction that this object is created on the UI thread.  After
+  // this, depend on the sequence checker to ensure consistent execution.
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
   DCHECK(device_task_runner_);
 
 #if !BUILDFLAG(IS_ANDROID)
@@ -120,7 +126,7 @@
 }
 
 WebContentsFrameTracker::~WebContentsFrameTracker() {
-  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   if (is_capturing_) {
     DidStopCapturingWebContents();
   }
@@ -128,7 +134,7 @@
 
 void WebContentsFrameTracker::WillStartCapturingWebContents(
     const gfx::Size& capture_size) {
-  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(!is_capturing_);
   if (!web_contents()) {
     return;
@@ -140,7 +146,7 @@
 }
 
 void WebContentsFrameTracker::DidStopCapturingWebContents() {
-  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   if (web_contents()) {
     SetCaptureScaleOverride(1.0f);
     DCHECK(is_capturing_);
@@ -152,6 +158,8 @@
 
 void WebContentsFrameTracker::SetCapturedContentSize(
     const gfx::Size& content_size) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
   if (!web_contents())
     return;
 
@@ -188,6 +196,8 @@
 // the first capturer's preferred size is set.
 gfx::Size WebContentsFrameTracker::CalculatePreferredSize(
     const gfx::Size& capture_size) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
   if (capture_size.IsEmpty()) {
     // NOTE: An empty preferred size will cause the WebContents to keep its
     // previous size preference.
@@ -229,6 +239,8 @@
 float WebContentsFrameTracker::CalculatePreferredScaleFactor(
     const gfx::Size& current_content_size,
     const gfx::Size& unscaled_current_content_size) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
   // A max factor above 2.0 would cause a quality degradation for local
   // rendering. The downscaling used by the compositor uses a linear filter
   // which only looks at 4 source pixels, so rendering more than 4 pixels per
@@ -305,6 +317,7 @@
 
 void WebContentsFrameTracker::RenderFrameCreated(
     RenderFrameHost* render_frame_host) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   OnPossibleTargetChange();
   if (capture_scale_override_ != 1.0f) {
     if (auto* view = render_frame_host->GetView()) {
@@ -317,12 +330,14 @@
 
 void WebContentsFrameTracker::RenderFrameDeleted(
     RenderFrameHost* render_frame_host) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   OnPossibleTargetChange();
 }
 
 void WebContentsFrameTracker::RenderFrameHostChanged(
     RenderFrameHost* old_host,
     RenderFrameHost* new_host) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   OnPossibleTargetChange();
   if (capture_scale_override_ != 1.0f) {
     // According to WebContentsObserver docs, old_host can be nullptr.
@@ -341,6 +356,7 @@
 }
 
 void WebContentsFrameTracker::WebContentsDestroyed() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   is_capturing_ = false;
   context_ = nullptr;
   Observe(nullptr);
@@ -348,12 +364,13 @@
 }
 
 void WebContentsFrameTracker::CaptureTargetChanged() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   OnPossibleTargetChange();
 }
 
 void WebContentsFrameTracker::SetWebContentsAndContextFromRoutingId(
     const GlobalRenderFrameHostId& id) {
-  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   Observe(WebContents::FromRenderFrameHost(RenderFrameHost::FromID(id)));
   if (web_contents()) {
     // If the routing ID was invalid, don't set up a context.
@@ -366,7 +383,7 @@
     const base::Token& crop_id,
     uint32_t crop_version,
     base::OnceCallback<void(media::mojom::CropRequestResult)> callback) {
-  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(callback);
 
   if (crop_version_ >= crop_version) {
@@ -407,13 +424,14 @@
 void WebContentsFrameTracker::SetWebContentsAndContextForTesting(
     WebContents* web_contents,
     std::unique_ptr<WebContentsFrameTracker::Context> context) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   Observe(web_contents);
   context_ = std::move(context);
   OnPossibleTargetChange();
 }
 
 void WebContentsFrameTracker::OnPossibleTargetChange() {
-  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   if (!web_contents()) {
     DCHECK(!context_);
     device_task_runner_->PostTask(
@@ -446,13 +464,14 @@
                        std::move(target), crop_version_));
   }
 
+  // Note: MouseCursorOverlayController runs on the UI thread. SetTargetView()
+  // must be called synchronously since the NativeView pointer is not valid
+  // across task switches, cf. https://crbug.com/818679
   SetTargetView(web_contents()->GetNativeView());
 }
 
-// Note: MouseCursorOverlayController runs on the UI thread. It's also
-// important that SetTargetView() be called in the current stack while
-// |view| is known to be a valid pointer. http://crbug.com/818679
 void WebContentsFrameTracker::SetTargetView(gfx::NativeView view) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   if (view == target_native_view_)
     return;
   target_native_view_ = view;
@@ -463,6 +482,7 @@
 
 void WebContentsFrameTracker::SetCaptureScaleOverride(float new_value) {
   DCHECK(context_);
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   if (new_value != capture_scale_override_) {
     capture_scale_override_ = new_value;
     context_->SetScaleOverrideForCapture(new_value);
diff --git a/content/browser/media/capture/web_contents_frame_tracker.h b/content/browser/media/capture/web_contents_frame_tracker.h
index b884460..4215a6a3 100644
--- a/content/browser/media/capture/web_contents_frame_tracker.h
+++ b/content/browser/media/capture/web_contents_frame_tracker.h
@@ -10,6 +10,7 @@
 #include "base/memory/raw_ptr.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/memory/weak_ptr.h"
+#include "base/sequence_checker.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "build/build_config.h"
 #include "components/viz/common/surfaces/frame_sink_id.h"
@@ -32,8 +33,7 @@
 // WebContentsVideoCaptureDevice |device| class any time the frame sink or
 // main render frame's view changes.
 class CONTENT_EXPORT WebContentsFrameTracker final
-    : public WebContentsObserver,
-      public base::SupportsWeakPtr<WebContentsFrameTracker> {
+    : public WebContentsObserver {
  public:
   // We generally retrieve certain properties by accessing fields on the
   // WebContents object, however these properties may come from a different
@@ -61,11 +61,15 @@
     virtual float GetScaleOverrideForCapture() const = 0;
   };
 
-  // NOTE on lifetime: |device| should outlive the WebContentsFrameTracker. The
-  // |device| will be exclusively accessed on the sequence that is used to
-  // construct |this| (which must not be the UI thread).
-  WebContentsFrameTracker(base::WeakPtr<WebContentsVideoCaptureDevice> device,
-                          MouseCursorOverlayController* cursor_controller);
+  // The |device| weak pointer will be used to post tasks back to the device via
+  // |device_task_runner|.
+  //
+  // See the cursor_controller_ member comments for cursor_controller lifetime
+  // documentation.
+  WebContentsFrameTracker(
+      scoped_refptr<base::SequencedTaskRunner> device_task_runner,
+      base::WeakPtr<WebContentsVideoCaptureDevice> device,
+      MouseCursorOverlayController* cursor_controller);
 
   WebContentsFrameTracker(WebContentsFrameTracker&&) = delete;
   WebContentsFrameTracker(const WebContentsFrameTracker&) = delete;
@@ -117,7 +121,9 @@
   // By including it in frame's metadata, Viz informs Blink what was the
   // latest invocation of cropTo() before a given frame was produced.
   //
-  // The callback reports success/failure.
+  // The callback reports success/failure. The callback may be called on an
+  // arbitrary sequence, so the caller is responsible for re-posting it
+  // to the desired target sequence as necessary.
   void Crop(const base::Token& crop_id,
             uint32_t crop_version,
             base::OnceCallback<void(media::mojom::CropRequestResult)> callback);
@@ -193,6 +199,8 @@
   // so the |current_content_size| passed into |CalculatePreferredScaleFactor|
   // may differ from this value.
   gfx::Size capture_size_;
+
+  SEQUENCE_CHECKER(sequence_checker_);
 };
 
 }  // namespace content
diff --git a/content/browser/media/capture/web_contents_frame_tracker_unittest.cc b/content/browser/media/capture/web_contents_frame_tracker_unittest.cc
index 0035784..f27f67d 100644
--- a/content/browser/media/capture/web_contents_frame_tracker_unittest.cc
+++ b/content/browser/media/capture/web_contents_frame_tracker_unittest.cc
@@ -81,7 +81,6 @@
 class MockCaptureDevice : public WebContentsVideoCaptureDevice,
                           public base::SupportsWeakPtr<MockCaptureDevice> {
  public:
-  using WebContentsVideoCaptureDevice::AsWeakPtr;
   MOCK_METHOD2(OnTargetChanged,
                void(const absl::optional<viz::VideoCaptureTarget>&, uint32_t));
   MOCK_METHOD0(OnTargetPermanentlyLost, void());
@@ -98,6 +97,12 @@
   void SetUp() override {
     RenderViewHostTestHarness::SetUp();
 
+    // The tests assume that they are running on the main thread (which is
+    // equivalent to the browser's UI thread) so that they can make calls on the
+    // tracker object synchronously.
+    ASSERT_TRUE(
+        content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
+
     // Views in the web context are incredibly fragile and prone to
     // non-deterministic test failures, so we use TestWebContents here.
     web_contents_ = TestWebContents::Create(browser_context(), nullptr);
@@ -106,19 +111,24 @@
     // All tests should call target changed as part of initialization.
     EXPECT_CALL(*device_, OnTargetChanged(_, _)).Times(1);
 
-    tracker_ = std::make_unique<WebContentsFrameTracker>(device_->AsWeakPtr(),
-                                                         controller());
-
+    // This PostTask technically isn't necessary since we're already on the main
+    // thread which is equivalent to the browser's UI thread, but it's a bit
+    // cleaner to do so in case we want to switch to a different threading model
+    // for the tests in the future.
     GetUIThreadTaskRunner({})->PostTask(
         FROM_HERE, base::BindOnce(&WebContentsFrameTrackerTest::SetUpOnUIThread,
-                                  base::Unretained(this)));
+                                  base::Unretained(this),
+                                  base::ThreadTaskRunnerHandle::Get()));
     RunAllTasksUntilIdle();
   }
 
-  void SetUpOnUIThread() {
+  void SetUpOnUIThread(
+      const scoped_refptr<base::SequencedTaskRunner> device_task_runner) {
     auto context = std::make_unique<SimpleContext>();
     raw_context_ = context.get();
     SetScreenSize(kSize1080p);
+    tracker_ = std::make_unique<WebContentsFrameTracker>(
+        device_task_runner, device_->AsWeakPtr(), controller());
     tracker_->SetWebContentsAndContextForTesting(web_contents_.get(),
                                                  std::move(context));
   }
@@ -147,17 +157,21 @@
   }
 
   void StartTrackerOnUIThread(const gfx::Size& capture_size) {
+    // Using base::Unretained for the tracker is presumed safe due to using
+    // RunAllTasksUntilIdle in TearDown.
     GetUIThreadTaskRunner({})->PostTask(
         FROM_HERE,
         base::BindOnce(&WebContentsFrameTracker::WillStartCapturingWebContents,
-                       tracker_->AsWeakPtr(), capture_size));
+                       base::Unretained(tracker_.get()), capture_size));
   }
 
   void StopTrackerOnUIThread() {
+    // Using base::Unretained for the tracker is presumed safe due to using
+    // RunAllTasksUntilIdle in TearDown.
     GetUIThreadTaskRunner({})->PostTask(
         FROM_HERE,
         base::BindOnce(&WebContentsFrameTracker::DidStopCapturingWebContents,
-                       tracker_->AsWeakPtr()));
+                       base::Unretained(tracker_.get())));
   }
 
   // The controller is ignored on Android, and must be initialized on all
diff --git a/content/browser/media/capture/web_contents_video_capture_device.cc b/content/browser/media/capture/web_contents_video_capture_device.cc
index 925b0026..9d53f01 100644
--- a/content/browser/media/capture/web_contents_video_capture_device.cc
+++ b/content/browser/media/capture/web_contents_video_capture_device.cc
@@ -18,18 +18,21 @@
 #include "content/public/browser/web_contents.h"
 #include "content/public/browser/web_contents_observer.h"
 #include "media/capture/video_capture_types.h"
+#include "mojo/public/cpp/bindings/callback_helpers.h"
 #include "ui/base/layout.h"
 
 namespace content {
 
 WebContentsVideoCaptureDevice::WebContentsVideoCaptureDevice(
-    const GlobalRenderFrameHostId& id)
-    : tracker_(new WebContentsFrameTracker(AsWeakPtr(), cursor_controller())) {
-  GetUIThreadTaskRunner({})->PostTask(
-      FROM_HERE,
-      base::BindOnce(
-          &WebContentsFrameTracker::SetWebContentsAndContextFromRoutingId,
-          tracker_->AsWeakPtr(), id));
+    const GlobalRenderFrameHostId& id) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  tracker_ = base::SequenceBound<WebContentsFrameTracker>(
+      GetUIThreadTaskRunner({}), base::ThreadTaskRunnerHandle::Get(),
+      weak_ptr_factory_.GetWeakPtr(), cursor_controller());
+  tracker_
+      .AsyncCall(
+          &WebContentsFrameTracker::SetWebContentsAndContextFromRoutingId)
+      .WithArgs(id);
 }
 
 WebContentsVideoCaptureDevice::~WebContentsVideoCaptureDevice() = default;
@@ -51,22 +54,14 @@
     const base::Token& crop_id,
     uint32_t crop_version,
     base::OnceCallback<void(media::mojom::CropRequestResult)> callback) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(callback);
 
-  GetUIThreadTaskRunner({})->PostTask(
-      FROM_HERE,
-      base::BindOnce(
-          [](const base::Token& crop_id, uint32_t crop_version,
-             base::OnceCallback<void(media::mojom::CropRequestResult)> callback,
-             base::WeakPtr<WebContentsFrameTracker> tracker) {
-            if (!tracker) {
-              std::move(callback).Run(
-                  media::mojom::CropRequestResult::kErrorGeneric);
-              return;
-            }
-            tracker->Crop(crop_id, crop_version, std::move(callback));
-          },
-          crop_id, crop_version, std::move(callback), tracker_->AsWeakPtr()));
+  tracker_.AsyncCall(&WebContentsFrameTracker::Crop)
+      .WithArgs(crop_id, crop_version,
+                mojo::WrapCallbackWithDefaultInvokeIfNotRun(
+                    std::move(callback),
+                    media::mojom::CropRequestResult::kErrorGeneric));
 }
 
 void WebContentsVideoCaptureDevice::OnFrameCaptured(
@@ -75,16 +70,15 @@
     const gfx::Rect& content_rect,
     mojo::PendingRemote<viz::mojom::FrameSinkVideoConsumerFrameCallbacks>
         callbacks) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   if (info->metadata.source_size.has_value()) {
     const gfx::Size new_size = *info->metadata.source_size;
     // Only update the captured content size when the size changes. See also
     // the comment in WebContentsFrameTracker::SetCapturedContentSize which
     // expects that behavior.
     if (new_size != content_size_) {
-      GetUIThreadTaskRunner({})->PostTask(
-          FROM_HERE,
-          base::BindOnce(&WebContentsFrameTracker::SetCapturedContentSize,
-                         tracker_->AsWeakPtr(), new_size));
+      tracker_.AsyncCall(&WebContentsFrameTracker::SetCapturedContentSize)
+          .WithArgs(new_size);
       content_size_ = new_size;
     }
   }
@@ -96,18 +90,14 @@
 WebContentsVideoCaptureDevice::WebContentsVideoCaptureDevice() = default;
 
 void WebContentsVideoCaptureDevice::WillStart() {
-  GetUIThreadTaskRunner({})->PostTask(
-      FROM_HERE,
-      base::BindOnce(&WebContentsFrameTracker::WillStartCapturingWebContents,
-                     tracker_->AsWeakPtr(),
-                     capture_params().SuggestConstraints().max_frame_size));
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  tracker_.AsyncCall(&WebContentsFrameTracker::WillStartCapturingWebContents)
+      .WithArgs(capture_params().SuggestConstraints().max_frame_size);
 }
 
 void WebContentsVideoCaptureDevice::DidStop() {
-  GetUIThreadTaskRunner({})->PostTask(
-      FROM_HERE,
-      base::BindOnce(&WebContentsFrameTracker::DidStopCapturingWebContents,
-                     tracker_->AsWeakPtr()));
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  tracker_.AsyncCall(&WebContentsFrameTracker::DidStopCapturingWebContents);
 
   // Currently, the video capture device is effectively a single-use object, so
   // resetting capture_size_ isn't strictly necessary, but it helps ensure that
diff --git a/content/browser/media/capture/web_contents_video_capture_device.h b/content/browser/media/capture/web_contents_video_capture_device.h
index 9d7a5c4..f269104d 100644
--- a/content/browser/media/capture/web_contents_video_capture_device.h
+++ b/content/browser/media/capture/web_contents_video_capture_device.h
@@ -9,6 +9,8 @@
 #include <string>
 
 #include "base/memory/weak_ptr.h"
+#include "base/sequence_checker.h"
+#include "base/threading/sequence_bound.h"
 #include "content/browser/media/capture/frame_sink_video_capture_device.h"
 #include "content/browser/media/capture/web_contents_frame_tracker.h"
 #include "content/common/content_export.h"
@@ -28,8 +30,7 @@
 // RenderFrameHost tree mutates (e.g., due to page navigations, crashes, or
 // reloads), capture will continue without interruption.
 class CONTENT_EXPORT WebContentsVideoCaptureDevice
-    : public FrameSinkVideoCaptureDevice,
-      public base::SupportsWeakPtr<WebContentsVideoCaptureDevice> {
+    : public FrameSinkVideoCaptureDevice {
  public:
   explicit WebContentsVideoCaptureDevice(const GlobalRenderFrameHostId& id);
 
@@ -58,7 +59,7 @@
       media::mojom::VideoFrameInfoPtr info,
       const gfx::Rect& content_rect,
       mojo::PendingRemote<viz::mojom::FrameSinkVideoConsumerFrameCallbacks>
-          callbacks) override;
+          callbacks) final;
 
   // For testing, we need the ability to create a device without its tracker.
  protected:
@@ -75,8 +76,13 @@
   // A helper that runs on the UI thread to monitor changes to the
   // RenderFrameHost tree during the lifetime of a WebContents instance, and
   // posts notifications back to update the target frame sink.
-  std::unique_ptr<WebContentsFrameTracker, BrowserThread::DeleteOnUIThread>
-      tracker_;
+  base::SequenceBound<WebContentsFrameTracker> tracker_;
+
+  SEQUENCE_CHECKER(sequence_checker_);
+
+  // Used to dispense WeakPtrs for the WebContentsFrameTracker to use to post
+  // tasks to the VideoCaptureDevice.
+  base::WeakPtrFactory<WebContentsVideoCaptureDevice> weak_ptr_factory_{this};
 };
 
 }  // namespace content
diff --git a/content/browser/renderer_host/media/in_process_launched_video_capture_device.cc b/content/browser/renderer_host/media/in_process_launched_video_capture_device.cc
index a2a61b9fd..0350955 100644
--- a/content/browser/renderer_host/media/in_process_launched_video_capture_device.cc
+++ b/content/browser/renderer_host/media/in_process_launched_video_capture_device.cc
@@ -124,6 +124,9 @@
   // Unretained() is safe to use here because |device| would be null if it
   // was scheduled for shutdown and destruction, and because this task is
   // guaranteed to run before the task that destroys the |device|.
+  //
+  // Explicitly bind the callback to the I/O thread since the VideoCaptureDevice
+  // Crop method runs the callback on an unspecified thread.
   device_task_runner_->PostTask(
       FROM_HERE,
       base::BindOnce(&media::VideoCaptureDevice::Crop,
diff --git a/content/browser/renderer_host/render_widget_host_view_android.cc b/content/browser/renderer_host/render_widget_host_view_android.cc
index 7f7b9c34..15e04f9 100644
--- a/content/browser/renderer_host/render_widget_host_view_android.cc
+++ b/content/browser/renderer_host/render_widget_host_view_android.cc
@@ -340,13 +340,22 @@
   RenderFrameHostImpl* frame_host = host()->frame_tree()->GetMainFrame();
   if (!frame_host || !frame_host->GetPage().virtual_keyboard_overlays_content())
     return;
-  gfx::Rect keyboard_rect_copy = keyboard_rect;
-  if (!keyboard_rect_copy.IsEmpty()) {
+  gfx::Rect keyboard_rect_with_scale;
+  if (!keyboard_rect.IsEmpty()) {
+    // This is necessary because the receiver of this rect in the renderer
+    // expects the rect to be in device-independnet pixels, but |keyboard_rect|
+    // is in device pixels. See
+    // LocalFrameMojoHandler::NotifyVirtualKeyboardOverlayRect.
+    // To trigger this code, follow the steps in
+    // .../external/wpt/virtual-keyboard/virtual-keyboard-css-env-manual.html
+    float scale = 1 / view_.GetDipScale();
+    keyboard_rect_with_scale = ScaleToEnclosedRect(keyboard_rect, scale);
     // Intersect the keyboard rect with the `this` bounds which will be sent
     // to the renderer.
-    keyboard_rect_copy.Intersect(GetViewBounds());
+    keyboard_rect_with_scale.Intersect(GetViewBounds());
   }
-  frame_host->GetPage().NotifyVirtualKeyboardOverlayRect(keyboard_rect_copy);
+  frame_host->GetPage().NotifyVirtualKeyboardOverlayRect(
+      keyboard_rect_with_scale);
 }
 
 bool RenderWidgetHostViewAndroid::ShouldVirtualKeyboardOverlayContent() {
diff --git a/content/browser/storage_partition_impl.cc b/content/browser/storage_partition_impl.cc
index 9908df8..6930b7f 100644
--- a/content/browser/storage_partition_impl.cc
+++ b/content/browser/storage_partition_impl.cc
@@ -765,7 +765,9 @@
 
   // The Plugin Private File System has been deprecated. Delete all data at
   // %profile/File System/Plugins.
-  auto plugin_path = filesystem_context->plugin_private_backend()->base_path();
+  auto plugin_path = filesystem_context->partition_path()
+                         .Append(storage::kFileSystemDirectory)
+                         .Append(FILE_PATH_LITERAL("Plugins"));
 
   filesystem_context->default_file_task_runner()->PostTaskAndReply(
       FROM_HERE,
diff --git a/content/child/runtime_features.cc b/content/child/runtime_features.cc
index 549fedc..7915f82 100644
--- a/content/child/runtime_features.cc
+++ b/content/child/runtime_features.cc
@@ -262,7 +262,6 @@
     {wf::EnablePushSubscriptionChangeEvent,
      features::kPushSubscriptionChangeEvent},
     {wf::EnableRestrictGamepadAccess, features::kRestrictGamepadAccess},
-    {wf::EnableScrollUnification, features::kScrollUnification},
     {wf::EnableSecurePaymentConfirmation, features::kSecurePaymentConfirmation},
     {wf::EnableSecurePaymentConfirmationDebug,
      features::kSecurePaymentConfirmationDebug},
diff --git a/content/public/browser/page_user_data.h b/content/public/browser/page_user_data.h
index 54204f1e..44a713e 100644
--- a/content/public/browser/page_user_data.h
+++ b/content/public/browser/page_user_data.h
@@ -53,6 +53,8 @@
     }
   }
 
+  // TODO(crbug.com/1335845): Due to a unresolved bug, this can return nullptr
+  // even after CreateForPage() or GetOrCreateForPage() has been called.
   static T* GetForPage(Page& page) {
     return static_cast<T*>(page.GetUserData(UserDataKey()));
   }
diff --git a/content/renderer/accessibility/ax_image_annotator.h b/content/renderer/accessibility/ax_image_annotator.h
index e661949..5bab8f1 100644
--- a/content/renderer/accessibility/ax_image_annotator.h
+++ b/content/renderer/accessibility/ax_image_annotator.h
@@ -6,7 +6,6 @@
 #define CONTENT_RENDERER_ACCESSIBILITY_AX_IMAGE_ANNOTATOR_H_
 
 #include <string>
-#include <unordered_map>
 
 #include "base/bind.h"
 #include "base/memory/weak_ptr.h"
@@ -129,7 +128,7 @@
   // Keeps track of the image data and the automatic annotations for each image.
   //
   // The key is retrieved using WebAXObject::AxID().
-  std::unordered_map<int, ImageInfo> image_annotations_;
+  std::map<int, ImageInfo> image_annotations_;
 
   // This member needs to be last because it should destructed first.
   base::WeakPtrFactory<AXImageAnnotator> weak_factory_{this};
diff --git a/content/renderer/accessibility/blink_ax_tree_source.h b/content/renderer/accessibility/blink_ax_tree_source.h
index 82a6cb4..dd1e7a1 100644
--- a/content/renderer/accessibility/blink_ax_tree_source.h
+++ b/content/renderer/accessibility/blink_ax_tree_source.h
@@ -9,7 +9,6 @@
 
 #include <set>
 #include <string>
-#include <unordered_map>
 
 #include "content/common/content_export.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
@@ -201,7 +200,7 @@
   mutable absl::optional<int32_t> first_unlabeled_image_id_ = absl::nullopt;
 
   // Current bounding box of every object, so we can detect when it moves.
-  mutable std::unordered_map<int, ui::AXRelativeBounds> cached_bounding_boxes_;
+  mutable std::map<int, ui::AXRelativeBounds> cached_bounding_boxes_;
 
   // These are updated when calling |Freeze|.
   bool frozen_ = false;
diff --git a/content/test/gpu/gpu_tests/test_expectations/context_lost_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/context_lost_expectations.txt
index 5723d72..4ebf136 100644
--- a/content/test/gpu/gpu_tests/test_expectations/context_lost_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/context_lost_expectations.txt
@@ -173,6 +173,10 @@
 # Failing on android N5
 crbug.com/1340755 [ android android-nexus-5 no-passthrough ] ContextLost_WebGL2UnpackImageHeight [ Failure ]
 
+# Flaking on Linux FYI Release (AMD RX 5500 XT)
+crbug.com/1340081 [ linux release amd-0x7340 ] ContextLost_WebGLContextLostFromSelectElement [ RetryOnFailure ]
+
+
 #######################################################################
 # Automated Entries After This Point - Do Not Manually Add Below Here #
 #######################################################################
diff --git a/content/test/gpu/gpu_tests/test_expectations/gpu_process_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/gpu_process_expectations.txt
index 17f6cf6..50e2c36b 100644
--- a/content/test/gpu/gpu_tests/test_expectations/gpu_process_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/gpu_process_expectations.txt
@@ -129,6 +129,12 @@
 
 crbug.com/1321145 [ fuchsia fuchsia-board-qemu-x64 ] * [ Failure ]
 
+# Flaking on Linux FYI Release (AMD RX 5500 XT)
+crbug.com/1340081 [ linux release amd-0x7340 ] GpuProcess_canvas2d [ RetryOnFailure ]
+crbug.com/1340081 [ linux release amd-0x7340 ] GpuProcess_css3d [ RetryOnFailure ]
+
+
+
 #######################################################################
 # Automated Entries After This Point - Do Not Manually Add Below Here #
 #######################################################################
diff --git a/docs/vscode.md b/docs/vscode.md
index 2d0fb14..395a21b 100644
--- a/docs/vscode.md
+++ b/docs/vscode.md
@@ -60,6 +60,8 @@
 Code does not require project or solution files. However, it does store
 workspace settings in a `.vscode` folder in your base directory.
 
+If you installed Code Insiders, the binary name is `code-insiders` instead.
+
 ### Fixes for Known Issues
 
 #### Git on Windows
@@ -72,9 +74,15 @@
 ```json
 {
   "git.path": "C:\\src\\depot_tools\\git.bat"
+
+  // more settings here...
 }
 ```
 
+Tip: you can jump to the settings JSON file by using `Ctrl+Shift+P` and using
+the "Preferences: Open User Settings (JSON)" verb (for whatever reason, setting
+`git.path` as a folder setting does not appear to work).
+
 ### Useful Extensions
 
 Up to now, you have a basic version of VS Code without much language support.
@@ -142,7 +150,8 @@
 ### Usage Tips
 *   `Ctrl+P` opens a search box to find and open a file.
 *   `F1` or `Ctrl+Shift+P` opens a search box to find a command (e.g. Tasks: Run
-    Task).
+    Task). Note: if you want to run one of the [Predefined tasks in
+    tasks.json](#Tasks), it is faster to just use `Ctrl+P` &gt; "task <n>".
 *   `Ctrl+K, Ctrl+S` opens the key bindings editor.
 *   ``Ctrl+` `` toggles the built-in terminal.
 *   `Ctrl+Shift+M` toggles the problems view (linter warnings, compile errors
@@ -232,7 +241,7 @@
 introduction to VS Code customization.
 
 ### Workspace Settings
-Open the file [//tools/vscode/settings.json5](/tools/vscode/settings.json5),
+Open the file [//tools/vscode/settings.json](/tools/vscode/settings.json),
 and check out the default settings there. Feel free to commit added or removed
 settings to enable better team development, or change settings locally to suit
 personal preference. Remember to replace `<full_path_to_your_home>`! To use
@@ -240,7 +249,7 @@
 at the src directory:
 ```
 $ mkdir .vscode/
-$ cp tools/vscode/settings.json5 .vscode/settings.json
+$ cp tools/vscode/settings.json .vscode
 ```
 
 Note: these settings assume that the workspace folder (the root folder displayed
@@ -250,24 +259,55 @@
 ### Tasks
 Next, we'll tell VS Code how to compile our code, run tests, and to read
 warnings and errors from the build output. Open the file
-[//tools/vscode/tasks.json5](/tools/vscode/tasks.json5). This will provide tasks
+[//tools/vscode/tasks.json](/tools/vscode/tasks.json). This will provide tasks
 to do basic things. You might have to adjust the commands to your situation and
 needs. To use these settings wholesale, enter the following command into your
 terminal:
 ```
-$ cp tools/vscode/tasks.json5 .vscode/tasks.json
+$ cp tools/vscode/tasks.json .vscode
 ```
 
+Before running most of the tasks, you'll need to set `chrome.outputDir`. You can
+do this by typing `Ctrl+Shift+P` &gt; "Preferences: Open Folder Settings (JSON)"
+and adding something like:
+
+```json
+{
+  "chrome.outputDir": "C:\\src\\chrome\\src\\out\\release"
+
+  // more settings here...
+}
+
+```
+
+Now you can run tasks by using `Ctrl+P` and typing "task " and then a number
+of your choice. If you select one of the build tasks, the build output will
+display in the terminal pane. Jump through build problems quickly using F8 /
+Shift-F8. See [task names](#task-names) for more info on running tasks.
+
+If you have intellisense enabled but do not have include paths set up correctly,
+jumping through problems will also try to navigate through all the include files
+it cannot locate and add a lot of noise. You can fix your include path or simply
+set intellisense to "tag parser" mode by doing the following:
+
+1. Open Preferences (`Ctrl+Shift+P` &gt; "Preferences: Open User Settings").
+2. Type "intellisense engine" in the settings search box.
+3. Select "Tag Parser" as the provider.
+
+Note: on a Chromebook, use 🔍+<8th button in the top row that's not ESC>. In
+most cases, this is the top row button that is the closest to be directly above
+the 8 key.
+
 ### Launch Commands
 Launch commands are the equivalent of `F5` in Visual Studio: They launch some
 program or a debugger. Optionally, they can run some task defined in
 `tasks.json`. Launch commands can be run from the debug view (`Ctrl+Shift+D`).
-Open the file at [//tools/vscode/launch.json5](/tools/vscode/launch.json5) and
+Open the file at [//tools/vscode/launch.json](/tools/vscode/launch.json) and
 adjust the example launch commands to your situation and needs (e.g., the value
 of "type" needs adjustment for Windows). To use these settings wholesale, enter
 the following command into your terminal:
 ```
-$ cp tools/vscode/launch.json5 .vscode/launch.json
+$ cp tools/vscode/launch.json .vscode
 ```
 
 ### Key Bindings
@@ -290,11 +330,11 @@
 [in the marketplace](https://marketplace.visualstudio.com/search?target=vscode&category=Keymaps).
 
 Some key bindings that are likely to be useful for you are available at
-[//tools/vscode/keybindings.json5](/tools/vscode/keybindings.json5). Please
+[//tools/vscode/keybindings.json](/tools/vscode/keybindings.json). Please
 take a look and adjust them to your situation and needs. To use these settings
 wholesale, enter the following command into your terminal:
 ```
-$ cp tools/vscode/keybindings.json5 .vscode/keybindings.json
+$ cp tools/vscode/keybindings.json .vscode
 ```
 
 ### Remote
@@ -342,17 +382,18 @@
 ### Snippets
 
 There are some useful snippets provided in
-[//tools/vscode/cpp.json5](/tools/vscode/cpp.json5).
+[//tools/vscode/cpp.json](/tools/vscode/cpp.json).
 
 You can either install them in your user profile (path may vary depending on the
 platform):
 ```
-$ cp tools/vscode/cpp.json5 ~/.config/Code/User/snippets/cpp.json
+$ mkdir -p ~/.config/Code/User/snippets
+$ cp tools/vscode/cpp.json ~/.config/Code/User/snippets
 ```
 
 Or install them as project snippets:
 ```
-$ cp tools/vscode/cpp.json5 .vscode/cpp.code-snippets
+$ cp tools/vscode/cpp.json .vscode/cpp.code-snippets
 ```
 
 ### Tips
diff --git a/extensions/renderer/bindings/api_binding.cc b/extensions/renderer/bindings/api_binding.cc
index 737ba8f..9c8f3660 100644
--- a/extensions/renderer/bindings/api_binding.cc
+++ b/extensions/renderer/bindings/api_binding.cc
@@ -615,6 +615,7 @@
 
   bool invalid_invocation = false;
   v8::Local<v8::Function> custom_callback;
+  binding::ResultModifierFunction result_modifier;
   bool updated_args = false;
   int old_request_id = request_handler_->last_sent_request_id();
   {
@@ -656,6 +657,7 @@
         break;  // Handle in the default manner.
     }
     custom_callback = hooks_result.custom_callback;
+    result_modifier = std::move(hooks_result.result_modifier);
   }
 
   if (invalid_invocation) {
@@ -700,7 +702,8 @@
 
   v8::Local<v8::Promise> promise = request_handler_->StartRequest(
       context, name, std::move(parse_result.arguments_list),
-      parse_result.async_type, parse_result.callback, custom_callback);
+      parse_result.async_type, parse_result.callback, custom_callback,
+      std::move(result_modifier));
   if (!promise.IsEmpty())
     arguments->Return(promise);
 }
diff --git a/extensions/renderer/bindings/api_binding_hooks.cc b/extensions/renderer/bindings/api_binding_hooks.cc
index 263605a..b806dd04 100644
--- a/extensions/renderer/bindings/api_binding_hooks.cc
+++ b/extensions/renderer/bindings/api_binding_hooks.cc
@@ -247,12 +247,14 @@
 // success and adds another handler function to the end of |arguments| for
 // resolving in the case of a failure. Also adds the associated promise to the
 // return on |result| if this is for a promise based request.
-void AddSuccessAndFailureCallbacks(v8::Local<v8::Context> context,
-                                   binding::AsyncResponseType async_type,
-                                   APIRequestHandler& request_handler,
-                                   base::WeakPtr<APIBindingHooks> weak_ptr,
-                                   std::vector<v8::Local<v8::Value>>* arguments,
-                                   APIBindingHooks::RequestResult& result) {
+void AddSuccessAndFailureCallbacks(
+    v8::Local<v8::Context> context,
+    binding::AsyncResponseType async_type,
+    APIRequestHandler& request_handler,
+    binding::ResultModifierFunction result_modifier,
+    base::WeakPtr<APIBindingHooks> weak_ptr,
+    std::vector<v8::Local<v8::Value>>* arguments,
+    APIBindingHooks::RequestResult& result) {
   DCHECK(!arguments->empty());
 
   // Since ParseArgumentsToV8 fills missing optional arguments with null, the
@@ -270,7 +272,8 @@
   }
 
   APIRequestHandler::RequestDetails request_details =
-      request_handler.AddPendingRequest(context, async_type, response_callback);
+      request_handler.AddPendingRequest(context, async_type, response_callback,
+                                        std::move(result_modifier));
   DCHECK_EQ(async_type == binding::AsyncResponseType::kPromise,
             !request_details.promise.IsEmpty());
   result.return_value = request_details.promise;
@@ -320,11 +323,17 @@
     ResultCode code,
     v8::Local<v8::Function> custom_callback)
     : code(code), custom_callback(custom_callback) {}
+APIBindingHooks::RequestResult::RequestResult(
+    ResultCode code,
+    v8::Local<v8::Function> custom_callback,
+    binding::ResultModifierFunction result_modifier)
+    : code(code),
+      custom_callback(custom_callback),
+      result_modifier(std::move(result_modifier)) {}
 APIBindingHooks::RequestResult::RequestResult(std::string invocation_error)
     : code(INVALID_INVOCATION), error(std::move(invocation_error)) {}
-APIBindingHooks::RequestResult::~RequestResult() {}
-APIBindingHooks::RequestResult::RequestResult(const RequestResult& other) =
-    default;
+APIBindingHooks::RequestResult::~RequestResult() = default;
+APIBindingHooks::RequestResult::RequestResult(RequestResult&& other) = default;
 
 APIBindingHooks::APIBindingHooks(const std::string& api_name,
                                  APIRequestHandler* request_handler)
@@ -337,6 +346,7 @@
     const APISignature* signature,
     std::vector<v8::Local<v8::Value>>* arguments,
     const APITypeReferenceMap& type_refs) {
+  binding::ResultModifierFunction result_modifier;
   // Easy case: a native custom hook.
   if (delegate_) {
     RequestResult result = delegate_->HandleRequest(
@@ -346,14 +356,19 @@
         !result.custom_callback.IsEmpty()) {
       return result;
     }
+    // If the native hooks didn't handle the call but did set a result modifier,
+    // grab it to be able to use it along with the custom hooks below.
+    result_modifier = std::move(result.result_modifier);
   }
 
   // Harder case: looking up a custom hook registered on the context (since
   // these are JS, each context has a separate instance).
   v8::Local<v8::Object> hook_interface_object =
       GetJSHookInterfaceObject(api_name_, context, false);
-  if (hook_interface_object.IsEmpty())
-    return RequestResult(RequestResult::NOT_HANDLED);
+  if (hook_interface_object.IsEmpty()) {
+    return RequestResult(RequestResult::NOT_HANDLED, v8::Local<v8::Function>(),
+                         std::move(result_modifier));
+  }
 
   v8::Isolate* isolate = context->GetIsolate();
 
@@ -388,8 +403,10 @@
 
   // If both the post validation hook and the handle request hook are empty,
   // we're done...
-  if (post_validate_hook.IsEmpty() && handle_request.IsEmpty())
-    return RequestResult(RequestResult::NOT_HANDLED, custom_callback);
+  if (post_validate_hook.IsEmpty() && handle_request.IsEmpty()) {
+    return RequestResult(RequestResult::NOT_HANDLED, custom_callback,
+                         std::move(result_modifier));
+  }
 
   // ... otherwise, we have to validate the arguments.
   APISignature::V8ParseResult parse_result =
@@ -424,15 +441,16 @@
     RequestResult::ResultCode result = updated_args
                                            ? RequestResult::ARGUMENTS_UPDATED
                                            : RequestResult::NOT_HANDLED;
-    return RequestResult(result, custom_callback);
+    return RequestResult(result, custom_callback, std::move(result_modifier));
   }
 
   RequestResult result(RequestResult::HANDLED, custom_callback);
 
   if (signature->has_async_return()) {
     AddSuccessAndFailureCallbacks(context, parse_result.async_type,
-                                  *request_handler_, weak_factory_.GetWeakPtr(),
-                                  arguments, result);
+                                  *request_handler_, std::move(result_modifier),
+                                  weak_factory_.GetWeakPtr(), arguments,
+                                  result);
   }
 
   // Safe to use synchronous JS since it's in direct response to JS calling
diff --git a/extensions/renderer/bindings/api_binding_hooks.h b/extensions/renderer/bindings/api_binding_hooks.h
index 0aeffcb..9359818 100644
--- a/extensions/renderer/bindings/api_binding_hooks.h
+++ b/extensions/renderer/bindings/api_binding_hooks.h
@@ -9,6 +9,7 @@
 #include <string>
 
 #include "base/memory/weak_ptr.h"
+#include "extensions/renderer/bindings/api_binding_types.h"
 #include "v8/include/v8.h"
 
 namespace gin {
@@ -45,12 +46,16 @@
 
     explicit RequestResult(ResultCode code);
     RequestResult(ResultCode code, v8::Local<v8::Function> custom_callback);
-    RequestResult(std::string invocation_error);
-    RequestResult(const RequestResult& other);
+    RequestResult(ResultCode code,
+                  v8::Local<v8::Function> custom_callback,
+                  binding::ResultModifierFunction result_modifier);
+    explicit RequestResult(std::string invocation_error);
+    RequestResult(RequestResult&& other);
     ~RequestResult();
 
     ResultCode code;
     v8::Local<v8::Function> custom_callback;
+    binding::ResultModifierFunction result_modifier;
     v8::Local<v8::Value> return_value;  // Only valid if code == HANDLED.
     std::string error;
   };
diff --git a/extensions/renderer/bindings/api_binding_js_util.cc b/extensions/renderer/bindings/api_binding_js_util.cc
index 7391858..63862962 100644
--- a/extensions/renderer/bindings/api_binding_js_util.cc
+++ b/extensions/renderer/bindings/api_binding_js_util.cc
@@ -110,7 +110,8 @@
 
   request_handler_->StartRequest(
       context, name, std::move(parse_result.arguments_list),
-      parse_result.async_type, parse_result.callback, custom_callback);
+      parse_result.async_type, parse_result.callback, custom_callback,
+      binding::ResultModifierFunction());
 }
 
 void APIBindingJSUtil::RegisterEventArgumentMassager(
diff --git a/extensions/renderer/bindings/api_binding_types.h b/extensions/renderer/bindings/api_binding_types.h
index b07e2e9..6aec729 100644
--- a/extensions/renderer/bindings/api_binding_types.h
+++ b/extensions/renderer/bindings/api_binding_types.h
@@ -61,6 +61,13 @@
 using AddConsoleError = base::RepeatingCallback<void(v8::Local<v8::Context>,
                                                      const std::string& error)>;
 
+using V8ArgumentList = std::vector<v8::Local<v8::Value>>;
+
+using ResultModifierFunction =
+    base::OnceCallback<V8ArgumentList(const V8ArgumentList&,
+                                      v8::Local<v8::Context>,
+                                      AsyncResponseType)>;
+
 }  // namespace binding
 }  // namespace extensions
 
diff --git a/extensions/renderer/bindings/api_binding_unittest.cc b/extensions/renderer/bindings/api_binding_unittest.cc
index dd872de..f04ca5b 100644
--- a/extensions/renderer/bindings/api_binding_unittest.cc
+++ b/extensions/renderer/bindings/api_binding_unittest.cc
@@ -18,6 +18,7 @@
 #include "extensions/renderer/bindings/api_binding_hooks_test_delegate.h"
 #include "extensions/renderer/bindings/api_binding_test.h"
 #include "extensions/renderer/bindings/api_binding_test_util.h"
+#include "extensions/renderer/bindings/api_binding_types.h"
 #include "extensions/renderer/bindings/api_binding_util.h"
 #include "extensions/renderer/bindings/api_event_handler.h"
 #include "extensions/renderer/bindings/api_invocation_errors.h"
@@ -1823,11 +1824,11 @@
          v8::Local<v8::Context> context,
          std::vector<v8::Local<v8::Value>>* arguments,
          const APITypeReferenceMap& map) {
-        handler->StartRequest(context, "test.handleAndSendRequest",
-                              std::make_unique<base::ListValue>(),
-                              binding::AsyncResponseType::kNone,
-                              v8::Local<v8::Function>(),
-                              v8::Local<v8::Function>());
+        handler->StartRequest(
+            context, "test.handleAndSendRequest",
+            std::make_unique<base::ListValue>(),
+            binding::AsyncResponseType::kNone, v8::Local<v8::Function>(),
+            v8::Local<v8::Function>(), binding::ResultModifierFunction());
         return RequestResult(RequestResult::HANDLED);
       };
   hooks->AddHandler(
@@ -1979,6 +1980,195 @@
                                                 "calledCustomCallback"));
 }
 
+// Test native hooks that don't handle the result, but add a result modifier.
+TEST_F(APIBindingUnittest, TestHooksWithResultModifier) {
+  SetFunctions(kFunctionsWithPromiseSignatures);
+
+  bool context_allows_promises = true;
+  SetPromiseAvailabilityFlag(&context_allows_promises);
+
+  // Register a hook for the test.supportsPromises method with a result modifier
+  // that changes the result when the async response type is callback based.
+  auto hooks = std::make_unique<APIBindingHooksTestDelegate>();
+  auto result_modifier =
+      [](const std::vector<v8::Local<v8::Value>>& result_args,
+         v8::Local<v8::Context> context,
+         binding::AsyncResponseType async_type) {
+        if (async_type == binding::AsyncResponseType::kCallback) {
+          // For callback based calls change the result to a vector with
+          // multiple arguments by appending "bar" to the end.
+          std::vector<v8::Local<v8::Value>> new_args{
+              result_args[0], gin::StringToV8(context->GetIsolate(), "bar")};
+          return new_args;
+        }
+        return result_args;
+      };
+
+  auto hook_with_result_modifier =
+      [&result_modifier](const APISignature* signature,
+                         v8::Local<v8::Context> context,
+                         std::vector<v8::Local<v8::Value>>* arguments,
+                         const APITypeReferenceMap& ref_map) {
+        APIBindingHooks::RequestResult result(
+            APIBindingHooks::RequestResult::NOT_HANDLED,
+            v8::Local<v8::Function>(), base::BindOnce(result_modifier));
+        return result;
+      };
+  hooks->AddHandler("test.supportsPromises",
+                    base::BindLambdaForTesting(hook_with_result_modifier));
+  SetHooksDelegate(std::move(hooks));
+
+  InitializeBinding();
+
+  v8::HandleScope handle_scope(isolate());
+  v8::Local<v8::Context> context = MainContext();
+  v8::Local<v8::Object> binding_object = binding()->CreateInstance(context);
+
+  // A promise-based call should remain unmodified and return as normal.
+  {
+    v8::Local<v8::Function> promise_api_call = FunctionFromString(
+        context, "(function(api) { return api.supportsPromises(1); });");
+    v8::Local<v8::Value> args[] = {binding_object};
+    v8::Local<v8::Value> api_result =
+        RunFunctionOnGlobal(promise_api_call, context, std::size(args), args);
+
+    v8::Local<v8::Promise> promise;
+    ASSERT_TRUE(GetValueAs(api_result, &promise));
+    EXPECT_EQ(v8::Promise::kPending, promise->State());
+
+    ASSERT_TRUE(last_request());
+    request_handler()->CompleteRequest(last_request()->request_id,
+                                       ListValueFromString(R"(["foo"])"),
+                                       std::string());
+
+    EXPECT_EQ(v8::Promise::kFulfilled, promise->State());
+    EXPECT_EQ(R"("foo")", V8ToString(promise->Result(), context));
+  }
+
+  // A callback-based call will be modified by the hook and return with multiple
+  // parameters.
+  {
+    constexpr char kFunctionCall[] =
+        R"((function(api) {
+             api.supportsPromises(2, (normalResult, addedResult) => {
+               this.argument1 = normalResult;
+               this.argument2 = addedResult;
+             });
+           }))";
+    v8::Local<v8::Function> callback_api_call =
+        FunctionFromString(context, kFunctionCall);
+    v8::Local<v8::Value> args[] = {binding_object};
+    RunFunctionOnGlobal(callback_api_call, context, std::size(args), args);
+
+    ASSERT_TRUE(last_request());
+    request_handler()->CompleteRequest(last_request()->request_id,
+                                       ListValueFromString(R"(["foo"])"),
+                                       std::string());
+
+    EXPECT_EQ(R"("foo")", GetStringPropertyFromObject(context->Global(),
+                                                      context, "argument1"));
+    EXPECT_EQ(R"("bar")", GetStringPropertyFromObject(context->Global(),
+                                                      context, "argument2"));
+  }
+}
+
+// Test native hooks that add a result modifier are compatible with JS hooks
+// which handle the request.
+TEST_F(APIBindingUnittest, TestHooksWithResultModifierAndJSHook) {
+  bool context_allows_promises = true;
+  SetPromiseAvailabilityFlag(&context_allows_promises);
+
+  // Register a JS hook for supportsPromises.
+  const char kRegisterHook[] = R"(
+      (function(hooks) {
+        hooks.setHandleRequest('supportsPromises', (firstArg, callback) => {
+          // Call the callback, appending "-foo" to the argument passed in.
+          callback(firstArg + '-foo');
+        });
+      }))";
+
+  InitializeJSHooks(kRegisterHook);
+  SetFunctions(kFunctionsWithPromiseSignatures);
+
+  // Register a native hook for test.supportsPromises with a result modifier
+  // that changes the result when the async response type is callback based.
+  auto hooks = std::make_unique<APIBindingHooksTestDelegate>();
+  auto result_modifier =
+      [](const std::vector<v8::Local<v8::Value>>& result_args,
+         v8::Local<v8::Context> context,
+         binding::AsyncResponseType async_type) {
+        if (async_type == binding::AsyncResponseType::kCallback) {
+          // For callback based calls change the result to a vector with
+          // multiple arguments by appending "bar" to the end.
+          std::vector<v8::Local<v8::Value>> new_args{
+              result_args[0], gin::StringToV8(context->GetIsolate(), "bar")};
+          return new_args;
+        }
+        return result_args;
+      };
+
+  auto hook_with_result_modifier =
+      [&result_modifier](const APISignature* signature,
+                         v8::Local<v8::Context> context,
+                         std::vector<v8::Local<v8::Value>>* arguments,
+                         const APITypeReferenceMap& ref_map) {
+        APIBindingHooks::RequestResult result(
+            APIBindingHooks::RequestResult::NOT_HANDLED,
+            v8::Local<v8::Function>(), base::BindOnce(result_modifier));
+        return result;
+      };
+  // Normally handlers are bound using base::BindRepeating, but to bind a lambda
+  // with a capture we have to use BindLambdaForTesting.
+  hooks->AddHandler("test.supportsPromises",
+                    base::BindLambdaForTesting(hook_with_result_modifier));
+  SetHooksDelegate(std::move(hooks));
+
+  InitializeBinding();
+
+  v8::HandleScope handle_scope(isolate());
+  v8::Local<v8::Context> context = MainContext();
+  v8::Local<v8::Object> binding_object = binding()->CreateInstance(context);
+
+  // A promise-based call should just be modified by the JS hook..
+  {
+    v8::Local<v8::Function> promise_api_call = FunctionFromString(
+        context, "(function(api) { return api.supportsPromises(1); });");
+    v8::Local<v8::Value> args[] = {binding_object};
+    v8::Local<v8::Value> api_result =
+        RunFunctionOnGlobal(promise_api_call, context, std::size(args), args);
+
+    // Since the JS callback completes the request right away, the promise
+    // should already be fulfilled without us needing to manually complete the
+    // request.
+    v8::Local<v8::Promise> promise;
+    ASSERT_TRUE(GetValueAs(api_result, &promise));
+    EXPECT_EQ(v8::Promise::kFulfilled, promise->State());
+    EXPECT_EQ(R"("1-foo")", V8ToString(promise->Result(), context));
+  }
+
+  // A callback-based call will be modified by the native hook to return with
+  // multiple parameters, as well as having the first parameter modified by the
+  // JS hook.
+  {
+    constexpr char kFunctionCall[] =
+        R"((function(api) {
+             api.supportsPromises(2, (normalResult, addedResult) => {
+               this.argument1 = normalResult;
+               this.argument2 = addedResult;
+             });
+           }))";
+    v8::Local<v8::Function> promise_api_call =
+        FunctionFromString(context, kFunctionCall);
+    v8::Local<v8::Value> args[] = {binding_object};
+    RunFunctionOnGlobal(promise_api_call, context, std::size(args), args);
+
+    EXPECT_EQ(R"("2-foo")", GetStringPropertyFromObject(context->Global(),
+                                                        context, "argument1"));
+    EXPECT_EQ(R"("bar")", GetStringPropertyFromObject(context->Global(),
+                                                      context, "argument2"));
+  }
+}
+
 TEST_F(APIBindingUnittest, AccessAPIMethodsAndEventsAfterInvalidation) {
   SetEvents(R"([{"name": "onFoo"}])");
   InitializeBinding();
diff --git a/extensions/renderer/bindings/api_request_handler.cc b/extensions/renderer/bindings/api_request_handler.cc
index baebd423..b76a12b 100644
--- a/extensions/renderer/bindings/api_request_handler.cc
+++ b/extensions/renderer/bindings/api_request_handler.cc
@@ -84,11 +84,13 @@
   AsyncResultHandler(v8::Isolate* isolate,
                      v8::Local<v8::Function> callback,
                      v8::Local<v8::Function> custom_callback,
+                     binding::ResultModifierFunction result_modifier,
                      ExceptionHandler* exception_handler);
   // A promise-based result handler.
   AsyncResultHandler(v8::Isolate* isolate,
                      v8::Local<v8::Promise::Resolver> promise_resolver,
-                     v8::Local<v8::Function> custom_callback);
+                     v8::Local<v8::Function> custom_callback,
+                     binding::ResultModifierFunction result_modifier);
 
   AsyncResultHandler(const AsyncResultHandler&) = delete;
   AsyncResultHandler& operator=(const AsyncResultHandler&) = delete;
@@ -135,6 +137,9 @@
       const std::vector<v8::Local<v8::Value>>& response_args,
       const std::string& error);
 
+  // The type of asynchronous response this handler is for.
+  const binding::AsyncResponseType async_type_;
+
   // Callback-based handlers. Mutually exclusive with promise-based handlers.
   v8::Global<v8::Function> extension_callback_;
 
@@ -150,14 +155,20 @@
 
   // Custom callback handlers.
   v8::Global<v8::Function> custom_callback_;
+
+  // A OnceCallback that can be used to modify the return arguments.
+  binding::ResultModifierFunction result_modifier_;
 };
 
 APIRequestHandler::AsyncResultHandler::AsyncResultHandler(
     v8::Isolate* isolate,
     v8::Local<v8::Function> extension_callback,
     v8::Local<v8::Function> custom_callback,
+    binding::ResultModifierFunction result_modifier,
     ExceptionHandler* exception_handler)
-    : exception_handler_(exception_handler) {
+    : async_type_(binding::AsyncResponseType::kCallback),
+      exception_handler_(exception_handler),
+      result_modifier_(std::move(result_modifier)) {
   DCHECK(!extension_callback.IsEmpty() || !custom_callback.IsEmpty());
   DCHECK(exception_handler_);
   if (!extension_callback.IsEmpty())
@@ -169,7 +180,10 @@
 APIRequestHandler::AsyncResultHandler::AsyncResultHandler(
     v8::Isolate* isolate,
     v8::Local<v8::Promise::Resolver> promise_resolver,
-    v8::Local<v8::Function> custom_callback) {
+    v8::Local<v8::Function> custom_callback,
+    binding::ResultModifierFunction result_modifier)
+    : async_type_(binding::AsyncResponseType::kPromise),
+      result_modifier_(std::move(result_modifier)) {
   // NOTE(devlin): We'll need to handle an empty promise resolver if
   // v8::Promise::Resolver::New() isn't guaranteed.
   DCHECK(!promise_resolver.IsEmpty());
@@ -194,19 +208,24 @@
     last_error->SetError(context, error);
   }
 
+  const std::vector<v8::Local<v8::Value>> args =
+      result_modifier_.is_null()
+          ? response_args
+          : std::move(result_modifier_)
+                .Run(response_args, context, async_type_);
+
   if (has_custom_callback()) {
     // Custom callback case; the custom callback will invoke a curried-in
     // callback, which will trigger the response in the extension (either
     // promise or callback).
-    CallCustomCallback(context, response_args, error);
+    CallCustomCallback(context, args, error);
   } else if (!promise_resolver_.IsEmpty()) {  // Promise-based request.
     DCHECK(extension_callback_.IsEmpty());
-    ResolvePromise(context, response_args, error,
-                   promise_resolver_.Get(isolate));
-  } else {  // Callback case.
+    ResolvePromise(context, args, error, promise_resolver_.Get(isolate));
+  } else {  // Callback-based request.
     DCHECK(!extension_callback_.IsEmpty());
     DCHECK(exception_handler_);
-    CallExtensionCallback(context, std::move(response_args),
+    CallExtensionCallback(context, std::move(args),
                           extension_callback_.Get(isolate), exception_handler_);
   }
 
@@ -396,12 +415,14 @@
     std::unique_ptr<base::Value> arguments_list,
     binding::AsyncResponseType async_type,
     v8::Local<v8::Function> callback,
-    v8::Local<v8::Function> custom_callback) {
+    v8::Local<v8::Function> custom_callback,
+    binding::ResultModifierFunction result_modifier) {
   v8::Isolate* isolate = context->GetIsolate();
 
   v8::Local<v8::Promise> promise;
-  std::unique_ptr<AsyncResultHandler> async_handler = GetAsyncResultHandler(
-      context, async_type, callback, custom_callback, &promise);
+  std::unique_ptr<AsyncResultHandler> async_handler =
+      GetAsyncResultHandler(context, async_type, callback, custom_callback,
+                            std::move(result_modifier), &promise);
   DCHECK_EQ(async_type == binding::AsyncResponseType::kPromise,
             !promise.IsEmpty());
 
@@ -447,11 +468,13 @@
 APIRequestHandler::RequestDetails APIRequestHandler::AddPendingRequest(
     v8::Local<v8::Context> context,
     binding::AsyncResponseType async_type,
-    v8::Local<v8::Function> callback) {
+    v8::Local<v8::Function> callback,
+    binding::ResultModifierFunction result_modifier) {
   v8::Isolate* isolate = context->GetIsolate();
   v8::Local<v8::Promise> promise;
   std::unique_ptr<AsyncResultHandler> async_handler = GetAsyncResultHandler(
-      context, async_type, callback, v8::Local<v8::Function>(), &promise);
+      context, async_type, callback, v8::Local<v8::Function>(),
+      std::move(result_modifier), &promise);
   DCHECK_EQ(async_type == binding::AsyncResponseType::kPromise,
             !promise.IsEmpty());
 
@@ -515,6 +538,7 @@
     binding::AsyncResponseType async_type,
     v8::Local<v8::Function> extension_callback,
     v8::Local<v8::Function> custom_callback,
+    binding::ResultModifierFunction result_modifier,
     v8::Local<v8::Promise>* promise_out) {
   v8::Isolate* isolate = context->GetIsolate();
 
@@ -525,12 +549,13 @@
            "started with a callback being passed in.";
     v8::Local<v8::Promise::Resolver> resolver =
         v8::Promise::Resolver::New(context).ToLocalChecked();
-    async_handler = std::make_unique<AsyncResultHandler>(isolate, resolver,
-                                                         custom_callback);
+    async_handler = std::make_unique<AsyncResultHandler>(
+        isolate, resolver, custom_callback, std::move(result_modifier));
     *promise_out = resolver->GetPromise();
   } else if (!custom_callback.IsEmpty() || !extension_callback.IsEmpty()) {
     async_handler = std::make_unique<AsyncResultHandler>(
-        isolate, extension_callback, custom_callback, exception_handler_);
+        isolate, extension_callback, custom_callback,
+        std::move(result_modifier), exception_handler_);
   }
   return async_handler;
 }
diff --git a/extensions/renderer/bindings/api_request_handler.h b/extensions/renderer/bindings/api_request_handler.h
index c166181d..46f051b 100644
--- a/extensions/renderer/bindings/api_request_handler.h
+++ b/extensions/renderer/bindings/api_request_handler.h
@@ -77,7 +77,8 @@
       std::unique_ptr<base::Value> arguments_list,
       binding::AsyncResponseType async_type,
       v8::Local<v8::Function> callback,
-      v8::Local<v8::Function> custom_callback);
+      v8::Local<v8::Function> custom_callback,
+      binding::ResultModifierFunction result_modifier);
 
   // Adds a pending request for the request handler to manage (and complete via
   // CompleteRequest). This is used by renderer-side implementations that
@@ -85,9 +86,11 @@
   // classes don't have to worry about context invalidation. Returns the details
   // of the newly-added request.
   // Note: Unlike StartRequest(), this will not track user gesture state.
-  RequestDetails AddPendingRequest(v8::Local<v8::Context> context,
-                                   binding::AsyncResponseType async_type,
-                                   v8::Local<v8::Function> callback);
+  RequestDetails AddPendingRequest(
+      v8::Local<v8::Context> context,
+      binding::AsyncResponseType async_type,
+      v8::Local<v8::Function> callback,
+      binding::ResultModifierFunction result_modifier);
 
   // Responds to the request with the given |request_id|, calling the callback
   // with the given |response| arguments.
@@ -152,6 +155,7 @@
       binding::AsyncResponseType async_type,
       v8::Local<v8::Function> callback,
       v8::Local<v8::Function> custom_callback,
+      binding::ResultModifierFunction result_modifier,
       v8::Local<v8::Promise>* promise_out);
 
   // Common implementation for completing a request.
diff --git a/extensions/renderer/bindings/api_request_handler_unittest.cc b/extensions/renderer/bindings/api_request_handler_unittest.cc
index 08abfd0..a291751d 100644
--- a/extensions/renderer/bindings/api_request_handler_unittest.cc
+++ b/extensions/renderer/bindings/api_request_handler_unittest.cc
@@ -14,6 +14,7 @@
 #include "extensions/renderer/bindings/exception_handler.h"
 #include "extensions/renderer/bindings/test_interaction_provider.h"
 #include "extensions/renderer/bindings/test_js_runner.h"
+#include "extensions/renderer/v8_helpers.h"
 #include "gin/converter.h"
 #include "gin/function_template.h"
 #include "gin/public/context_holder.h"
@@ -102,10 +103,10 @@
   v8::Local<v8::Function> function = FunctionFromString(context, kEchoArgs);
   ASSERT_FALSE(function.IsEmpty());
 
-  request_handler->StartRequest(context, kMethod,
-                                std::make_unique<base::ListValue>(),
-                                binding::AsyncResponseType::kCallback, function,
-                                v8::Local<v8::Function>());
+  request_handler->StartRequest(
+      context, kMethod, std::make_unique<base::ListValue>(),
+      binding::AsyncResponseType::kCallback, function,
+      v8::Local<v8::Function>(), binding::ResultModifierFunction());
   int request_id = request_handler->last_sent_request_id();
   EXPECT_THAT(request_handler->GetPendingRequestIdsForTesting(),
               testing::UnorderedElementsAre(request_id));
@@ -123,7 +124,7 @@
   request_handler->StartRequest(
       context, kMethod, std::make_unique<base::ListValue>(),
       binding::AsyncResponseType::kNone, v8::Local<v8::Function>(),
-      v8::Local<v8::Function>());
+      v8::Local<v8::Function>(), binding::ResultModifierFunction());
   request_id = request_handler->last_sent_request_id();
   EXPECT_NE(-1, request_id);
   request_handler->CompleteRequest(request_id, base::Value::List(),
@@ -140,10 +141,10 @@
   v8::Local<v8::Function> function = FunctionFromString(context, kEchoArgs);
   ASSERT_FALSE(function.IsEmpty());
 
-  request_handler->StartRequest(context, kMethod,
-                                std::make_unique<base::ListValue>(),
-                                binding::AsyncResponseType::kCallback, function,
-                                v8::Local<v8::Function>());
+  request_handler->StartRequest(
+      context, kMethod, std::make_unique<base::ListValue>(),
+      binding::AsyncResponseType::kCallback, function,
+      v8::Local<v8::Function>(), binding::ResultModifierFunction());
   int request_id = request_handler->last_sent_request_id();
   EXPECT_THAT(request_handler->GetPendingRequestIdsForTesting(),
               testing::UnorderedElementsAre(request_id));
@@ -176,15 +177,15 @@
   v8::Local<v8::Function> function_b = FunctionFromString(
       context_b, "(function(res) { this.result = res + 'beta'; })");
 
-  request_handler->StartRequest(context_a, kMethod,
-                                std::make_unique<base::ListValue>(),
-                                binding::AsyncResponseType::kCallback,
-                                function_a, v8::Local<v8::Function>());
+  request_handler->StartRequest(
+      context_a, kMethod, std::make_unique<base::ListValue>(),
+      binding::AsyncResponseType::kCallback, function_a,
+      v8::Local<v8::Function>(), binding::ResultModifierFunction());
   int request_a = request_handler->last_sent_request_id();
-  request_handler->StartRequest(context_b, kMethod,
-                                std::make_unique<base::ListValue>(),
-                                binding::AsyncResponseType::kCallback,
-                                function_b, v8::Local<v8::Function>());
+  request_handler->StartRequest(
+      context_b, kMethod, std::make_unique<base::ListValue>(),
+      binding::AsyncResponseType::kCallback, function_b,
+      v8::Local<v8::Function>(), binding::ResultModifierFunction());
   int request_b = request_handler->last_sent_request_id();
 
   EXPECT_THAT(request_handler->GetPendingRequestIdsForTesting(),
@@ -224,7 +225,8 @@
 
   request_handler->StartRequest(
       context, "method", std::make_unique<base::ListValue>(),
-      binding::AsyncResponseType::kCallback, callback, custom_callback);
+      binding::AsyncResponseType::kCallback, callback, custom_callback,
+      binding::ResultModifierFunction());
   int request_id = request_handler->last_sent_request_id();
   EXPECT_THAT(request_handler->GetPendingRequestIdsForTesting(),
               testing::UnorderedElementsAre(request_id));
@@ -294,10 +296,10 @@
   ASSERT_FALSE(callback_throwing_error.IsEmpty());
   ASSERT_FALSE(custom_callback.IsEmpty());
 
-  request_handler.StartRequest(context, "method",
-                               std::make_unique<base::ListValue>(),
-                               binding::AsyncResponseType::kCallback,
-                               callback_throwing_error, custom_callback);
+  request_handler.StartRequest(
+      context, "method", std::make_unique<base::ListValue>(),
+      binding::AsyncResponseType::kCallback, callback_throwing_error,
+      custom_callback, binding::ResultModifierFunction());
   int request_id = request_handler.last_sent_request_id();
   EXPECT_THAT(request_handler.GetPendingRequestIdsForTesting(),
               testing::UnorderedElementsAre(request_id));
@@ -340,7 +342,7 @@
   v8::Local<v8::Promise> promise = request_handler->StartRequest(
       context, "method", std::make_unique<base::ListValue>(),
       binding::AsyncResponseType::kPromise, v8::Local<v8::Function>(),
-      custom_callback);
+      custom_callback, binding::ResultModifierFunction());
   ASSERT_FALSE(promise.IsEmpty());
 
   int request_id = request_handler->last_sent_request_id();
@@ -390,7 +392,8 @@
   v8::Local<v8::Function> empty_callback;
   request_handler->StartRequest(
       context, "method", std::make_unique<base::ListValue>(),
-      binding::AsyncResponseType::kNone, empty_callback, custom_callback);
+      binding::AsyncResponseType::kNone, empty_callback, custom_callback,
+      binding::ResultModifierFunction());
   int request_id = request_handler->last_sent_request_id();
   EXPECT_THAT(request_handler->GetPendingRequestIdsForTesting(),
               testing::UnorderedElementsAre(request_id));
@@ -410,6 +413,58 @@
   EXPECT_TRUE(request_handler->GetPendingRequestIdsForTesting().empty());
 }
 
+TEST_F(APIRequestHandlerTest, ResultModifier) {
+  v8::HandleScope handle_scope(isolate());
+  v8::Local<v8::Context> context = MainContext();
+
+  binding::ResultModifierFunction result_modifier =
+      base::BindOnce([](const std::vector<v8::Local<v8::Value>>& result_args,
+                        v8::Local<v8::Context> context,
+                        binding::AsyncResponseType async_type) {
+        EXPECT_EQ(1u, result_args.size());
+        EXPECT_TRUE(result_args[0]->IsObject());
+        v8::Local<v8::Object> result_obj = result_args[0].As<v8::Object>();
+
+        v8::Local<v8::Value> prop_1;
+        bool success =
+            v8_helpers::GetProperty(context, result_obj, "prop1", &prop_1);
+        DCHECK(success);
+        v8::Local<v8::Value> prop_2;
+        success =
+            v8_helpers::GetProperty(context, result_obj, "prop2", &prop_2);
+        DCHECK(success);
+
+        std::vector<v8::Local<v8::Value>> new_args{prop_1, prop_2};
+        return new_args;
+      });
+
+  std::unique_ptr<APIRequestHandler> request_handler = CreateRequestHandler();
+
+  v8::Local<v8::Function> callback = FunctionFromString(
+      context, "(function(arg1, arg2) {this.arg1 = arg1; this.arg2 = arg2});");
+  ASSERT_FALSE(callback.IsEmpty());
+
+  request_handler->StartRequest(
+      context, "method", std::make_unique<base::ListValue>(),
+      binding::AsyncResponseType::kCallback, callback,
+      v8::Local<v8::Function>(), std::move(result_modifier));
+  int request_id = request_handler->last_sent_request_id();
+  EXPECT_THAT(request_handler->GetPendingRequestIdsForTesting(),
+              testing::UnorderedElementsAre(request_id));
+
+  request_handler->CompleteRequest(
+      request_id, ListValueFromString("[{'prop1':'foo', 'prop2':'bar'}]"),
+      std::string());
+  EXPECT_TRUE(did_run_js());
+
+  EXPECT_TRUE(request_handler->GetPendingRequestIdsForTesting().empty());
+
+  EXPECT_EQ(R"("foo")",
+            GetStringPropertyFromObject(context->Global(), context, "arg1"));
+  EXPECT_EQ(R"("bar")",
+            GetStringPropertyFromObject(context->Global(), context, "arg2"));
+}
+
 // Test user gestures being curried around for API requests.
 TEST_F(APIRequestHandlerTest, UserGestureTest) {
   v8::HandleScope handle_scope(isolate());
@@ -430,10 +485,10 @@
       function_template->GetFunction(context).ToLocalChecked();
 
   // Try first without a user gesture.
-  request_handler->StartRequest(context, kMethod,
-                                std::make_unique<base::ListValue>(),
-                                binding::AsyncResponseType::kCallback,
-                                v8_callback, v8::Local<v8::Function>());
+  request_handler->StartRequest(
+      context, kMethod, std::make_unique<base::ListValue>(),
+      binding::AsyncResponseType::kCallback, v8_callback,
+      v8::Local<v8::Function>(), binding::ResultModifierFunction());
   int request_id = request_handler->last_sent_request_id();
   request_handler->CompleteRequest(request_id, ListValueFromString("[]"),
                                    std::string());
@@ -452,10 +507,10 @@
 
   EXPECT_TRUE(interaction_provider()->HasActiveInteraction(context));
 
-  request_handler->StartRequest(context, kMethod,
-                                std::make_unique<base::ListValue>(),
-                                binding::AsyncResponseType::kCallback,
-                                v8_callback, v8::Local<v8::Function>());
+  request_handler->StartRequest(
+      context, kMethod, std::make_unique<base::ListValue>(),
+      binding::AsyncResponseType::kCallback, v8_callback,
+      v8::Local<v8::Function>(), binding::ResultModifierFunction());
   request_id = request_handler->last_sent_request_id();
   request_handler->CompleteRequest(request_id, ListValueFromString("[]"),
                                    std::string());
@@ -501,10 +556,10 @@
     // console or exposed to the callback.
     v8::Local<v8::Function> callback =
         FunctionFromString(context, kReportExposedLastError);
-    request_handler.StartRequest(context, kMethod,
-                                 std::make_unique<base::ListValue>(),
-                                 binding::AsyncResponseType::kCallback,
-                                 callback, v8::Local<v8::Function>());
+    request_handler.StartRequest(
+        context, kMethod, std::make_unique<base::ListValue>(),
+        binding::AsyncResponseType::kCallback, callback,
+        v8::Local<v8::Function>(), binding::ResultModifierFunction());
     int request_id = request_handler.last_sent_request_id();
     request_handler.CompleteRequest(request_id, base::Value::List(),
                                     std::string());
@@ -519,10 +574,10 @@
     // exposed to the callback).
     v8::Local<v8::Function> callback =
         FunctionFromString(context, kReportExposedLastError);
-    request_handler.StartRequest(context, kMethod,
-                                 std::make_unique<base::ListValue>(),
-                                 binding::AsyncResponseType::kCallback,
-                                 callback, v8::Local<v8::Function>());
+    request_handler.StartRequest(
+        context, kMethod, std::make_unique<base::ListValue>(),
+        binding::AsyncResponseType::kCallback, callback,
+        v8::Local<v8::Function>(), binding::ResultModifierFunction());
     int request_id = request_handler.last_sent_request_id();
     request_handler.CompleteRequest(request_id, base::Value::List(),
                                     "some error");
@@ -536,10 +591,10 @@
     // callback. The error should be logged.
     v8::Local<v8::Function> callback =
         FunctionFromString(context, "(function() {})");
-    request_handler.StartRequest(context, kMethod,
-                                 std::make_unique<base::ListValue>(),
-                                 binding::AsyncResponseType::kCallback,
-                                 callback, v8::Local<v8::Function>());
+    request_handler.StartRequest(
+        context, kMethod, std::make_unique<base::ListValue>(),
+        binding::AsyncResponseType::kCallback, callback,
+        v8::Local<v8::Function>(), binding::ResultModifierFunction());
     int request_id = request_handler.last_sent_request_id();
     request_handler.CompleteRequest(request_id, base::Value::List(),
                                     "some error");
@@ -553,10 +608,10 @@
     // and no author-script-provided callback. The error should be logged.
     v8::Local<v8::Function> custom_callback =
         FunctionFromString(context, "(function() {})");
-    request_handler.StartRequest(context, kMethod,
-                                 std::make_unique<base::ListValue>(),
-                                 binding::AsyncResponseType::kNone,
-                                 v8::Local<v8::Function>(), custom_callback);
+    request_handler.StartRequest(
+        context, kMethod, std::make_unique<base::ListValue>(),
+        binding::AsyncResponseType::kNone, v8::Local<v8::Function>(),
+        custom_callback, binding::ResultModifierFunction());
     int request_id = request_handler.last_sent_request_id();
     request_handler.CompleteRequest(request_id, base::Value::List(),
                                     "some error");
@@ -571,7 +626,7 @@
     request_handler.StartRequest(
         context, kMethod, std::make_unique<base::ListValue>(),
         binding::AsyncResponseType::kNone, v8::Local<v8::Function>(),
-        v8::Local<v8::Function>());
+        v8::Local<v8::Function>(), binding::ResultModifierFunction());
     int request_id = request_handler.last_sent_request_id();
     request_handler.CompleteRequest(request_id, base::Value::List(),
                                     "some error");
@@ -602,7 +657,8 @@
   ASSERT_FALSE(function.IsEmpty());
 
   auto details = request_handler.AddPendingRequest(
-      context, binding::AsyncResponseType::kCallback, function);
+      context, binding::AsyncResponseType::kCallback, function,
+      binding::ResultModifierFunction());
   int request_id = details.request_id;
   EXPECT_TRUE(details.promise.IsEmpty());
   EXPECT_THAT(request_handler.GetPendingRequestIdsForTesting(),
@@ -641,7 +697,8 @@
   EXPECT_TRUE(request_handler.GetPendingRequestIdsForTesting().empty());
 
   auto details = request_handler.AddPendingRequest(
-      context, binding::AsyncResponseType::kPromise, v8::Local<v8::Function>());
+      context, binding::AsyncResponseType::kPromise, v8::Local<v8::Function>(),
+      binding::ResultModifierFunction());
   int request_id = details.request_id;
   v8::Local<v8::Promise> promise = details.promise;
   EXPECT_THAT(request_handler.GetPendingRequestIdsForTesting(),
@@ -663,6 +720,48 @@
   EXPECT_FALSE(dispatched_request);
 }
 
+TEST_F(APIRequestHandlerTest, AddPendingRequestWithResultModifier) {
+  v8::HandleScope handle_scope(isolate());
+  v8::Local<v8::Context> context = MainContext();
+  binding::ResultModifierFunction result_modifier =
+      base::BindOnce([](const std::vector<v8::Local<v8::Value>>& result_args,
+                        v8::Local<v8::Context> context,
+                        binding::AsyncResponseType async_type) {
+        DCHECK_EQ(1u, result_args.size());
+        DCHECK(result_args[0]->IsObject());
+        v8::Local<v8::Object> result_obj = result_args[0].As<v8::Object>();
+
+        v8::Local<v8::Value> prop_1;
+        bool success =
+            v8_helpers::GetProperty(context, result_obj, "prop1", &prop_1);
+        DCHECK(success);
+        v8::Local<v8::Value> prop_2;
+        success =
+            v8_helpers::GetProperty(context, result_obj, "prop2", &prop_2);
+        DCHECK(success);
+
+        std::vector<v8::Local<v8::Value>> new_args{prop_1, prop_2};
+        return new_args;
+      });
+
+  std::unique_ptr<APIRequestHandler> request_handler = CreateRequestHandler();
+
+  v8::Local<v8::Function> function = FunctionFromString(context, kEchoArgs);
+  ASSERT_FALSE(function.IsEmpty());
+
+  auto details = request_handler->AddPendingRequest(
+      context, binding::AsyncResponseType::kCallback, function,
+      std::move(result_modifier));
+  int request_id = details.request_id;
+  EXPECT_TRUE(details.promise.IsEmpty());
+
+  const char kArguments[] = "[{'prop1':'bar', 'prop2':'baz'}]";
+  request_handler->CompleteRequest(request_id, ListValueFromString(kArguments),
+                                   std::string());
+  EXPECT_EQ(R"(["bar","baz"])",
+            GetStringPropertyFromObject(context->Global(), context, "result"));
+}
+
 // Tests that throwing an exception in a callback is properly handled.
 TEST_F(APIRequestHandlerTest, ThrowExceptionInCallback) {
   v8::HandleScope handle_scope(isolate());
@@ -685,7 +784,8 @@
   v8::Local<v8::Function> callback_throwing_error =
       FunctionFromString(context, "(function() { throw new Error('hello'); })");
   auto details = request_handler.AddPendingRequest(
-      context, binding::AsyncResponseType::kCallback, callback_throwing_error);
+      context, binding::AsyncResponseType::kCallback, callback_throwing_error,
+      binding::ResultModifierFunction());
   int request_id = details.request_id;
   EXPECT_TRUE(details.promise.IsEmpty());
 
@@ -715,7 +815,7 @@
   v8::Local<v8::Promise> promise = request_handler->StartRequest(
       context, kMethod, std::make_unique<base::ListValue>(),
       binding::AsyncResponseType::kPromise, v8::Local<v8::Function>(),
-      v8::Local<v8::Function>());
+      v8::Local<v8::Function>(), binding::ResultModifierFunction());
   ASSERT_FALSE(promise.IsEmpty());
 
   int request_id = request_handler->last_sent_request_id();
@@ -745,7 +845,7 @@
   v8::Local<v8::Promise> promise = request_handler->StartRequest(
       context, kMethod, std::make_unique<base::ListValue>(),
       binding::AsyncResponseType::kPromise, v8::Local<v8::Function>(),
-      v8::Local<v8::Function>());
+      v8::Local<v8::Function>(), binding::ResultModifierFunction());
   ASSERT_FALSE(promise.IsEmpty());
 
   int request_id = request_handler->last_sent_request_id();
diff --git a/extensions/renderer/bindings/declarative_event.cc b/extensions/renderer/bindings/declarative_event.cc
index fdfc217..419e40fd 100644
--- a/extensions/renderer/bindings/declarative_event.cc
+++ b/extensions/renderer/bindings/declarative_event.cc
@@ -201,10 +201,10 @@
   // We don't currently support promise based requests through DeclarativeEvent.
   DCHECK_NE(binding::AsyncResponseType::kPromise, parse_result.async_type);
 
-  request_handler_->StartRequest(context, request_name,
-                                 std::move(parse_result.arguments_list),
-                                 parse_result.async_type, parse_result.callback,
-                                 v8::Local<v8::Function>());
+  request_handler_->StartRequest(
+      context, request_name, std::move(parse_result.arguments_list),
+      parse_result.async_type, parse_result.callback, v8::Local<v8::Function>(),
+      binding::ResultModifierFunction());
 }
 
 }  // namespace extensions
diff --git a/extensions/renderer/chrome_setting.cc b/extensions/renderer/chrome_setting.cc
index 76369b78..28242e4 100644
--- a/extensions/renderer/chrome_setting.cc
+++ b/extensions/renderer/chrome_setting.cc
@@ -182,8 +182,8 @@
 
   v8::Local<v8::Promise> promise = request_handler_->StartRequest(
       context, full_name, std::move(parse_result.arguments_list),
-      parse_result.async_type, parse_result.callback,
-      v8::Local<v8::Function>());
+      parse_result.async_type, parse_result.callback, v8::Local<v8::Function>(),
+      binding::ResultModifierFunction());
   if (!promise.IsEmpty())
     arguments->Return(promise);
 }
diff --git a/extensions/renderer/content_setting.cc b/extensions/renderer/content_setting.cc
index aec730a1..743311b1 100644
--- a/extensions/renderer/content_setting.cc
+++ b/extensions/renderer/content_setting.cc
@@ -217,7 +217,8 @@
   v8::Local<v8::Promise> promise = request_handler_->StartRequest(
       context, "contentSettings." + method_name,
       std::move(parse_result.arguments_list), parse_result.async_type,
-      parse_result.callback, v8::Local<v8::Function>());
+      parse_result.callback, v8::Local<v8::Function>(),
+      binding::ResultModifierFunction());
   if (!promise.IsEmpty())
     arguments->Return(promise);
 }
diff --git a/extensions/renderer/one_time_message_handler.cc b/extensions/renderer/one_time_message_handler.cc
index 939d4eb7..9f5a422 100644
--- a/extensions/renderer/one_time_message_handler.cc
+++ b/extensions/renderer/one_time_message_handler.cc
@@ -201,7 +201,8 @@
 
     APIRequestHandler::RequestDetails details =
         bindings_system_->api_system()->request_handler()->AddPendingRequest(
-            script_context->v8_context(), async_type, response_callback);
+            script_context->v8_context(), async_type, response_callback,
+            binding::ResultModifierFunction());
     OneTimeOpener& port = data->openers[new_port_id];
     port.request_id = details.request_id;
     port.routing_id = routing_id;
diff --git a/extensions/renderer/storage_area.cc b/extensions/renderer/storage_area.cc
index 4e96257f..d6151a3 100644
--- a/extensions/renderer/storage_area.cc
+++ b/extensions/renderer/storage_area.cc
@@ -308,8 +308,8 @@
 
   v8::Local<v8::Promise> promise = request_handler_->StartRequest(
       context, full_method_name, std::move(parse_result.arguments_list),
-      parse_result.async_type, parse_result.callback,
-      v8::Local<v8::Function>());
+      parse_result.async_type, parse_result.callback, v8::Local<v8::Function>(),
+      binding::ResultModifierFunction());
 
   if (!promise.IsEmpty())
     arguments->Return(promise);
diff --git a/gpu/config/gpu_finch_features.cc b/gpu/config/gpu_finch_features.cc
index eb8495b..da460e6 100644
--- a/gpu/config/gpu_finch_features.cc
+++ b/gpu/config/gpu_finch_features.cc
@@ -261,6 +261,11 @@
 // crbug.com/1294648
 const base::FeatureParam<std::string> kDrDcBlockListByDevice{
     &kEnableDrDc, "BlockListByDevice", "LF9810_2GB"};
+
+// crbug.com/1340059, crbug.com/1340064
+const base::FeatureParam<std::string> kDrDcBlockListByModel{
+    &kEnableDrDc, "BlockListByModel",
+    "SM-J400M|SM-J415F|ONEPLUS A3003|OCTAStream*"};
 #endif  // BUILDFLAG(IS_ANDROID)
 
 // Enable SkiaRenderer Dawn graphics backend. On Windows this will use D3D12,
@@ -368,10 +373,10 @@
   const auto* build_info = base::android::BuildInfo::GetInstance();
   if (IsDeviceBlocked(build_info->device(), kDrDcBlockListByDevice.Get()))
     return false;
-
+  if (IsDeviceBlocked(build_info->model(), kDrDcBlockListByModel.Get()))
+    return false;
   if (!base::FeatureList::IsEnabled(kEnableDrDc))
     return false;
-
   return IsUsingVulkan() ? base::FeatureList::IsEnabled(kEnableDrDcVulkan)
                          : true;
 #else
diff --git a/headless/lib/browser/devtools_api/client_api_generator_unittest.py b/headless/lib/browser/devtools_api/client_api_generator_unittest.py
index 9e1ca65..cd49df0 100755
--- a/headless/lib/browser/devtools_api/client_api_generator_unittest.py
+++ b/headless/lib/browser/devtools_api/client_api_generator_unittest.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
@@ -15,7 +15,7 @@
 
   def test_ArgumentParsing(self):
     with tempfile.NamedTemporaryFile() as f:
-      f.write('{"foo": true}')
+      f.write(b'{"foo": true}')
       f.flush()
       json_api, output_dir = client_api_generator.ParseArguments([
           '--protocol', f.name, '--output_dir', 'out'])
diff --git a/infra/config/dev/subprojects/chromium/ci.star b/infra/config/dev/subprojects/chromium/ci.star
index 2122b4c..0007a7c 100644
--- a/infra/config/dev/subprojects/chromium/ci.star
+++ b/infra/config/dev/subprojects/chromium/ci.star
@@ -92,7 +92,7 @@
 ci_builder(
     name = "mac-arm-rel-swarming",
     cpu = cpu.ARM64,
-    os = os.MAC_11,
+    os = os.MAC_DEFAULT,
 )
 
 ci_builder(
diff --git a/infra/config/generated/luci/cr-buildbucket-dev.cfg b/infra/config/generated/luci/cr-buildbucket-dev.cfg
index ce6fa08..e77cc06 100644
--- a/infra/config/generated/luci/cr-buildbucket-dev.cfg
+++ b/infra/config/generated/luci/cr-buildbucket-dev.cfg
@@ -191,7 +191,7 @@
       name: "mac-arm-rel-swarming"
       swarming_host: "chromium-swarm-dev.appspot.com"
       dimensions: "cpu:arm64"
-      dimensions: "os:Mac-11"
+      dimensions: "os:Mac-12"
       exe {
         cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
         cipd_version: "refs/heads/main"
@@ -244,7 +244,7 @@
       name: "mac-rel-swarming"
       swarming_host: "chromium-swarm-dev.appspot.com"
       dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-11|Mac-12"
+      dimensions: "os:Mac-12"
       exe {
         cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
         cipd_version: "refs/heads/main"
diff --git a/infra/config/generated/luci/cr-buildbucket.cfg b/infra/config/generated/luci/cr-buildbucket.cfg
index fd4a6c8..f145f8a 100644
--- a/infra/config/generated/luci/cr-buildbucket.cfg
+++ b/infra/config/generated/luci/cr-buildbucket.cfg
@@ -110,7 +110,7 @@
       dimensions: "builderless:1"
       dimensions: "cpu:x86-64"
       dimensions: "free_space:standard"
-      dimensions: "os:Mac-11|Mac-12"
+      dimensions: "os:Mac-12"
       dimensions: "pool:luci.chromium.ci"
       exe {
         cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
@@ -3363,7 +3363,7 @@
       dimensions: "builderless:1"
       dimensions: "cpu:x86-64"
       dimensions: "free_space:standard"
-      dimensions: "os:Mac-11|Mac-12"
+      dimensions: "os:Mac-12"
       dimensions: "pool:luci.chromium.ci"
       exe {
         cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
@@ -3440,7 +3440,7 @@
       dimensions: "builderless:1"
       dimensions: "cpu:x86-64"
       dimensions: "free_space:standard"
-      dimensions: "os:Mac-11|Mac-12"
+      dimensions: "os:Mac-12"
       dimensions: "pool:luci.chromium.ci"
       exe {
         cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
@@ -3517,7 +3517,7 @@
       dimensions: "builderless:1"
       dimensions: "cpu:x86-64"
       dimensions: "free_space:standard"
-      dimensions: "os:Mac-11|Mac-12"
+      dimensions: "os:Mac-12"
       dimensions: "pool:luci.chromium.ci"
       exe {
         cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
@@ -4074,7 +4074,7 @@
       dimensions: "builderless:1"
       dimensions: "cpu:x86-64"
       dimensions: "free_space:standard"
-      dimensions: "os:Mac-11|Mac-12"
+      dimensions: "os:Mac-12"
       dimensions: "pool:luci.chromium.ci"
       exe {
         cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
@@ -4156,7 +4156,7 @@
       dimensions: "builderless:1"
       dimensions: "cpu:x86-64"
       dimensions: "free_space:standard"
-      dimensions: "os:Mac-11|Mac-12"
+      dimensions: "os:Mac-12"
       dimensions: "pool:luci.chromium.ci"
       exe {
         cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
@@ -9938,7 +9938,7 @@
       dimensions: "builder:Libfuzzer Upload Mac ASan"
       dimensions: "cores:24"
       dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-11|Mac-12"
+      dimensions: "os:Mac-12"
       dimensions: "pool:luci.chromium.ci"
       exe {
         cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
@@ -13367,7 +13367,7 @@
       dimensions: "builder:Mac ASAN Release"
       dimensions: "cores:4"
       dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-11|Mac-12"
+      dimensions: "os:Mac-12"
       dimensions: "pool:luci.chromium.ci"
       exe {
         cipd_package: "infra/chromium/bootstrapper/${platform}"
@@ -13445,7 +13445,7 @@
       dimensions: "builder:Mac ASAN Release Media"
       dimensions: "cores:4"
       dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-11|Mac-12"
+      dimensions: "os:Mac-12"
       dimensions: "pool:luci.chromium.ci"
       exe {
         cipd_package: "infra/chromium/bootstrapper/${platform}"
@@ -13522,7 +13522,7 @@
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builder:Mac ASan 64 Builder"
       dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-11|Mac-12"
+      dimensions: "os:Mac-12"
       dimensions: "pool:luci.chromium.ci"
       exe {
         cipd_package: "infra/chromium/bootstrapper/${platform}"
@@ -13603,7 +13603,7 @@
       dimensions: "builder:Mac ASan 64 Tests (1)"
       dimensions: "cores:12"
       dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-11|Mac-12"
+      dimensions: "os:Mac-12"
       dimensions: "pool:luci.chromium.ci"
       exe {
         cipd_package: "infra/chromium/bootstrapper/${platform}"
@@ -13683,7 +13683,7 @@
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builder:Mac Builder"
       dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-11|Mac-12"
+      dimensions: "os:Mac-12"
       dimensions: "pool:luci.chromium.ci"
       exe {
         cipd_package: "infra/chromium/bootstrapper/${platform}"
@@ -13844,7 +13844,7 @@
       dimensions: "builder:Mac Builder (py2 less)"
       dimensions: "cores:4"
       dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-11|Mac-12"
+      dimensions: "os:Mac-12"
       dimensions: "pool:luci.chromium.ci"
       exe {
         cipd_package: "infra/chromium/bootstrapper/${platform}"
@@ -13927,7 +13927,7 @@
       dimensions: "builderless:1"
       dimensions: "cpu:x86-64"
       dimensions: "free_space:standard"
-      dimensions: "os:Mac-11|Mac-12"
+      dimensions: "os:Mac-12"
       dimensions: "pool:luci.chromium.ci"
       exe {
         cipd_package: "infra/chromium/bootstrapper/${platform}"
@@ -14006,7 +14006,7 @@
       dimensions: "builderless:1"
       dimensions: "cpu:x86-64"
       dimensions: "free_space:standard"
-      dimensions: "os:Mac-11|Mac-12"
+      dimensions: "os:Mac-12"
       dimensions: "pool:luci.chromium.ci"
       exe {
         cipd_package: "infra/chromium/bootstrapper/${platform}"
@@ -15651,7 +15651,7 @@
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builder:Mac deterministic"
       dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-11|Mac-12"
+      dimensions: "os:Mac-12"
       dimensions: "pool:luci.chromium.ci"
       exe {
         cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
@@ -15720,7 +15720,7 @@
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builder:Mac deterministic (dbg)"
       dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-11|Mac-12"
+      dimensions: "os:Mac-12"
       dimensions: "pool:luci.chromium.ci"
       exe {
         cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
@@ -18663,7 +18663,7 @@
       dimensions: "builderless:1"
       dimensions: "cpu:x86-64"
       dimensions: "free_space:standard"
-      dimensions: "os:Mac-11|Mac-12"
+      dimensions: "os:Mac-12"
       dimensions: "pool:luci.chromium.ci"
       dimensions: "ssd:1"
       exe {
@@ -18745,7 +18745,7 @@
       dimensions: "builderless:1"
       dimensions: "cpu:x86-64"
       dimensions: "free_space:standard"
-      dimensions: "os:Mac-11|Mac-12"
+      dimensions: "os:Mac-12"
       dimensions: "pool:luci.chromium.ci"
       dimensions: "ssd:1"
       exe {
@@ -18827,7 +18827,7 @@
       dimensions: "builderless:1"
       dimensions: "cpu:x86-64"
       dimensions: "free_space:standard"
-      dimensions: "os:Mac-11|Mac-12"
+      dimensions: "os:Mac-12"
       dimensions: "pool:luci.chromium.ci"
       dimensions: "ssd:1"
       exe {
@@ -18909,7 +18909,7 @@
       dimensions: "builderless:1"
       dimensions: "cpu:x86-64"
       dimensions: "free_space:standard"
-      dimensions: "os:Mac-11|Mac-12"
+      dimensions: "os:Mac-12"
       dimensions: "pool:luci.chromium.ci"
       dimensions: "ssd:1"
       exe {
@@ -31271,7 +31271,7 @@
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builder:ios-catalyst"
       dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-11|Mac-12"
+      dimensions: "os:Mac-12"
       dimensions: "pool:luci.chromium.ci"
       exe {
         cipd_package: "infra/chromium/bootstrapper/${platform}"
@@ -31356,7 +31356,7 @@
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builder:ios-device"
       dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-11|Mac-12"
+      dimensions: "os:Mac-12"
       dimensions: "pool:luci.chromium.ci"
       exe {
         cipd_package: "infra/chromium/bootstrapper/${platform}"
@@ -31442,7 +31442,7 @@
       dimensions: "builderless:1"
       dimensions: "cpu:x86-64"
       dimensions: "free_space:standard"
-      dimensions: "os:Mac-11|Mac-12"
+      dimensions: "os:Mac-12"
       dimensions: "pool:luci.chromium.ci"
       exe {
         cipd_package: "infra/chromium/bootstrapper/${platform}"
@@ -31766,7 +31766,7 @@
       dimensions: "builderless:1"
       dimensions: "cpu:x86-64"
       dimensions: "free_space:standard"
-      dimensions: "os:Mac-11|Mac-12"
+      dimensions: "os:Mac-12"
       dimensions: "pool:luci.chromium.ci"
       exe {
         cipd_package: "infra/chromium/bootstrapper/${platform}"
@@ -31848,7 +31848,7 @@
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builder:ios-simulator"
       dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-11|Mac-12"
+      dimensions: "os:Mac-12"
       dimensions: "pool:luci.chromium.ci"
       exe {
         cipd_package: "infra/chromium/bootstrapper/${platform}"
@@ -31933,7 +31933,7 @@
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builder:ios-simulator-code-coverage"
       dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-11"
+      dimensions: "os:Mac-12"
       dimensions: "pool:luci.chromium.ci"
       dimensions: "ssd:1"
       exe {
@@ -32095,7 +32095,7 @@
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builder:ios-simulator-full-configs"
       dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-11|Mac-12"
+      dimensions: "os:Mac-12"
       dimensions: "pool:luci.chromium.ci"
       exe {
         cipd_package: "infra/chromium/bootstrapper/${platform}"
@@ -32260,7 +32260,7 @@
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builder:ios-simulator-noncq"
       dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-11|Mac-12"
+      dimensions: "os:Mac-12"
       dimensions: "pool:luci.chromium.ci"
       exe {
         cipd_package: "infra/chromium/bootstrapper/${platform}"
@@ -32668,7 +32668,7 @@
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builder:ios16-beta-simulator"
       dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-11|Mac-12"
+      dimensions: "os:Mac-12"
       dimensions: "pool:luci.chromium.ci"
       exe {
         cipd_package: "infra/chromium/bootstrapper/${platform}"
@@ -32749,7 +32749,7 @@
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builder:ios16-sdk-simulator"
       dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-11|Mac-12"
+      dimensions: "os:Mac-12"
       dimensions: "pool:luci.chromium.ci"
       exe {
         cipd_package: "infra/chromium/bootstrapper/${platform}"
@@ -38077,7 +38077,7 @@
       dimensions: "builder:mac-archive-dbg"
       dimensions: "cores:4"
       dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-11|Mac-12"
+      dimensions: "os:Mac-12"
       dimensions: "pool:luci.chromium.ci"
       exe {
         cipd_package: "infra/chromium/bootstrapper/${platform}"
@@ -38158,7 +38158,7 @@
       dimensions: "builder:mac-archive-rel"
       dimensions: "cores:12"
       dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-11|Mac-12"
+      dimensions: "os:Mac-12"
       dimensions: "pool:luci.chromium.ci"
       exe {
         cipd_package: "infra/chromium/bootstrapper/${platform}"
@@ -38239,7 +38239,7 @@
       dimensions: "builder:mac-arm64-archive-dbg"
       dimensions: "cores:12"
       dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-11|Mac-12"
+      dimensions: "os:Mac-12"
       dimensions: "pool:luci.chromium.ci"
       exe {
         cipd_package: "infra/chromium/bootstrapper/${platform}"
@@ -38320,7 +38320,7 @@
       dimensions: "builder:mac-arm64-archive-rel"
       dimensions: "cores:12"
       dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-11|Mac-12"
+      dimensions: "os:Mac-12"
       dimensions: "pool:luci.chromium.ci"
       exe {
         cipd_package: "infra/chromium/bootstrapper/${platform}"
@@ -38400,7 +38400,7 @@
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builder:mac-arm64-on-arm64-rel"
       dimensions: "cpu:arm64"
-      dimensions: "os:Mac-11|Mac-12"
+      dimensions: "os:Mac-12"
       dimensions: "pool:luci.chromium.ci"
       exe {
         cipd_package: "infra/chromium/bootstrapper/${platform}"
@@ -38558,7 +38558,7 @@
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builder:mac-arm64-rel"
       dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-11|Mac-12"
+      dimensions: "os:Mac-12"
       dimensions: "pool:luci.chromium.ci"
       exe {
         cipd_package: "infra/chromium/bootstrapper/${platform}"
@@ -38878,7 +38878,7 @@
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builder:mac-fieldtrial-rel"
       dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-11|Mac-12"
+      dimensions: "os:Mac-12"
       dimensions: "pool:luci.chromium.ci"
       exe {
         cipd_package: "infra/chromium/bootstrapper/${platform}"
@@ -38956,7 +38956,7 @@
       dimensions: "builder:mac-hermetic-upgrade-rel"
       dimensions: "cores:12"
       dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-11|Mac-12"
+      dimensions: "os:Mac-12"
       dimensions: "pool:luci.chromium.ci"
       exe {
         cipd_package: "infra/chromium/bootstrapper/${platform}"
@@ -39743,7 +39743,7 @@
       dimensions: "builderless:1"
       dimensions: "cpu:x86-64"
       dimensions: "free_space:standard"
-      dimensions: "os:Mac-11|Mac-12"
+      dimensions: "os:Mac-12"
       dimensions: "pool:luci.chromium.ci"
       exe {
         cipd_package: "infra/chromium/bootstrapper/${platform}"
@@ -44801,7 +44801,7 @@
       dimensions: "builder:Chromium Mac Goma RBE Staging"
       dimensions: "cores:4"
       dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-11|Mac-12"
+      dimensions: "os:Mac-12"
       dimensions: "pool:luci.chromium.ci"
       exe {
         cipd_package: "infra/chromium/bootstrapper/${platform}"
@@ -44850,7 +44850,7 @@
       dimensions: "builder:Chromium Mac Goma RBE Staging (clobber)"
       dimensions: "cores:4"
       dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-11|Mac-12"
+      dimensions: "os:Mac-12"
       dimensions: "pool:luci.chromium.ci"
       exe {
         cipd_package: "infra/chromium/bootstrapper/${platform}"
@@ -44899,7 +44899,7 @@
       dimensions: "builder:Chromium Mac Goma RBE Staging (dbg)"
       dimensions: "cores:4"
       dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-11|Mac-12"
+      dimensions: "os:Mac-12"
       dimensions: "pool:luci.chromium.ci"
       exe {
         cipd_package: "infra/chromium/bootstrapper/${platform}"
@@ -44948,7 +44948,7 @@
       dimensions: "builder:Chromium Mac Goma RBE ToT"
       dimensions: "cores:4"
       dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-11|Mac-12"
+      dimensions: "os:Mac-12"
       dimensions: "pool:luci.chromium.ci"
       exe {
         cipd_package: "infra/chromium/bootstrapper/${platform}"
@@ -45291,7 +45291,7 @@
       dimensions: "builder:Chromium iOS Goma RBE ToT"
       dimensions: "cores:4"
       dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-11|Mac-12"
+      dimensions: "os:Mac-12"
       dimensions: "pool:luci.chromium.ci"
       exe {
         cipd_package: "infra/chromium/bootstrapper/${platform}"
@@ -45446,7 +45446,7 @@
       dimensions: "builder:Mac Builder (dbg) Goma RBE Canary (clobber)"
       dimensions: "cores:4"
       dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-11|Mac-12"
+      dimensions: "os:Mac-12"
       dimensions: "pool:luci.chromium.ci"
       exe {
         cipd_package: "infra/chromium/bootstrapper/${platform}"
@@ -45495,7 +45495,7 @@
       dimensions: "builder:Mac Builder (dbg) Goma RBE Latest Client (clobber)"
       dimensions: "cores:4"
       dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-11|Mac-12"
+      dimensions: "os:Mac-12"
       dimensions: "pool:luci.chromium.ci"
       exe {
         cipd_package: "infra/chromium/bootstrapper/${platform}"
@@ -45543,7 +45543,7 @@
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builder:Mac M1 Builder (dbg) Goma RBE Canary (clobber)"
       dimensions: "cpu:arm64"
-      dimensions: "os:Mac-11|Mac-12"
+      dimensions: "os:Mac-12"
       dimensions: "pool:luci.chromium.ci"
       exe {
         cipd_package: "infra/chromium/bootstrapper/${platform}"
@@ -46436,7 +46436,7 @@
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builder:ios-device-goma-rbe-canary-clobber"
       dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-11|Mac-12"
+      dimensions: "os:Mac-12"
       dimensions: "pool:luci.chromium.ci"
       exe {
         cipd_package: "infra/chromium/bootstrapper/${platform}"
@@ -46488,7 +46488,7 @@
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builder:ios-device-goma-rbe-latest-clobber"
       dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-11|Mac-12"
+      dimensions: "os:Mac-12"
       dimensions: "pool:luci.chromium.ci"
       exe {
         cipd_package: "infra/chromium/bootstrapper/${platform}"
@@ -46745,7 +46745,7 @@
       dimensions: "builder:mac-archive-rel-goma-rbe-canary"
       dimensions: "cores:4"
       dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-11|Mac-12"
+      dimensions: "os:Mac-12"
       dimensions: "pool:luci.chromium.ci"
       exe {
         cipd_package: "infra/chromium/bootstrapper/${platform}"
@@ -46794,7 +46794,7 @@
       dimensions: "builder:mac-archive-rel-goma-rbe-latest"
       dimensions: "cores:4"
       dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-11|Mac-12"
+      dimensions: "os:Mac-12"
       dimensions: "pool:luci.chromium.ci"
       exe {
         cipd_package: "infra/chromium/bootstrapper/${platform}"
@@ -47081,7 +47081,7 @@
       dimensions: "builderless:1"
       dimensions: "cpu:x86-64"
       dimensions: "free_space:standard"
-      dimensions: "os:Mac-11|Mac-12"
+      dimensions: "os:Mac-12"
       dimensions: "pool:luci.chromium.ci"
       exe {
         cipd_package: "infra/chromium/bootstrapper/${platform}"
@@ -47159,7 +47159,7 @@
       dimensions: "builderless:1"
       dimensions: "cpu:x86-64"
       dimensions: "free_space:standard"
-      dimensions: "os:Mac-11|Mac-12"
+      dimensions: "os:Mac-12"
       dimensions: "pool:luci.chromium.ci"
       exe {
         cipd_package: "infra/chromium/bootstrapper/${platform}"
@@ -47637,7 +47637,7 @@
       dimensions: "builderless:1"
       dimensions: "cpu:x86-64"
       dimensions: "free_space:standard"
-      dimensions: "os:Mac-11|Mac-12"
+      dimensions: "os:Mac-12"
       dimensions: "pool:luci.chromium.ci"
       exe {
         cipd_package: "infra/chromium/bootstrapper/${platform}"
@@ -47719,7 +47719,7 @@
       dimensions: "builderless:1"
       dimensions: "cpu:x86-64"
       dimensions: "free_space:standard"
-      dimensions: "os:Mac-11|Mac-12"
+      dimensions: "os:Mac-12"
       dimensions: "pool:luci.chromium.ci"
       exe {
         cipd_package: "infra/chromium/bootstrapper/${platform}"
@@ -47801,7 +47801,7 @@
       dimensions: "builderless:1"
       dimensions: "cpu:x86-64"
       dimensions: "free_space:standard"
-      dimensions: "os:Mac-11|Mac-12"
+      dimensions: "os:Mac-12"
       dimensions: "pool:luci.chromium.ci"
       exe {
         cipd_package: "infra/chromium/bootstrapper/${platform}"
@@ -47879,7 +47879,7 @@
       dimensions: "builderless:1"
       dimensions: "cpu:x86-64"
       dimensions: "free_space:standard"
-      dimensions: "os:Mac-11|Mac-12"
+      dimensions: "os:Mac-12"
       dimensions: "pool:luci.chromium.ci"
       exe {
         cipd_package: "infra/chromium/bootstrapper/${platform}"
@@ -48102,7 +48102,7 @@
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builderless:1"
       dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-11|Mac-12"
+      dimensions: "os:Mac-12"
       dimensions: "pool:luci.chromium.try"
       exe {
         cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
@@ -62416,7 +62416,7 @@
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builder:ios-asan"
       dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-11|Mac-12"
+      dimensions: "os:Mac-12"
       dimensions: "pool:luci.chromium.try"
       exe {
         cipd_package: "infra/chromium/bootstrapper/${platform}"
@@ -62508,7 +62508,7 @@
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builder:ios-catalyst"
       dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-11|Mac-12"
+      dimensions: "os:Mac-12"
       dimensions: "pool:luci.chromium.try"
       exe {
         cipd_package: "infra/chromium/bootstrapper/${platform}"
@@ -62600,7 +62600,7 @@
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builder:ios-clang-tidy-rel"
       dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-11|Mac-12"
+      dimensions: "os:Mac-12"
       dimensions: "pool:luci.chromium.try"
       exe {
         cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
@@ -62686,7 +62686,7 @@
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builder:ios-device"
       dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-11|Mac-12"
+      dimensions: "os:Mac-12"
       dimensions: "pool:luci.chromium.try"
       exe {
         cipd_package: "infra/chromium/bootstrapper/${platform}"
@@ -62778,7 +62778,7 @@
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builderless:1"
       dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-11|Mac-12"
+      dimensions: "os:Mac-12"
       dimensions: "pool:luci.chromium.try"
       exe {
         cipd_package: "infra/chromium/bootstrapper/${platform}"
@@ -62870,7 +62870,7 @@
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builder:ios-m1-simulator"
       dimensions: "cpu:arm64"
-      dimensions: "os:Mac-11|Mac-12"
+      dimensions: "os:Mac-12"
       dimensions: "pool:luci.chromium.try"
       exe {
         cipd_package: "infra/chromium/bootstrapper/${platform}"
@@ -62962,7 +62962,7 @@
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builder:ios-m1-simulator-cronet"
       dimensions: "cpu:arm64"
-      dimensions: "os:Mac-11|Mac-12"
+      dimensions: "os:Mac-12"
       dimensions: "pool:luci.chromium.try"
       exe {
         cipd_package: "infra/chromium/bootstrapper/${platform}"
@@ -63054,7 +63054,7 @@
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builder:ios-simulator"
       dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-11|Mac-12"
+      dimensions: "os:Mac-12"
       dimensions: "pool:luci.chromium.try"
       exe {
         cipd_package: "infra/chromium/bootstrapper/${platform}"
@@ -63146,7 +63146,7 @@
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builder:ios-simulator-cronet"
       dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-11|Mac-12"
+      dimensions: "os:Mac-12"
       dimensions: "pool:luci.chromium.try"
       exe {
         cipd_package: "infra/chromium/bootstrapper/${platform}"
@@ -63238,7 +63238,7 @@
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builder:ios-simulator-full-configs"
       dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-11|Mac-12"
+      dimensions: "os:Mac-12"
       dimensions: "pool:luci.chromium.try"
       exe {
         cipd_package: "infra/chromium/bootstrapper/${platform}"
@@ -63330,7 +63330,7 @@
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builder:ios-simulator-inverse-fieldtrials-fyi"
       dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-11|Mac-12"
+      dimensions: "os:Mac-12"
       dimensions: "pool:luci.chromium.try"
       exe {
         cipd_package: "infra/chromium/bootstrapper/${platform}"
@@ -63422,7 +63422,7 @@
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builder:ios-simulator-multi-window"
       dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-11|Mac-12"
+      dimensions: "os:Mac-12"
       dimensions: "pool:luci.chromium.try"
       exe {
         cipd_package: "infra/chromium/bootstrapper/${platform}"
@@ -63514,7 +63514,7 @@
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builder:ios-simulator-noncq"
       dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-11|Mac-12"
+      dimensions: "os:Mac-12"
       dimensions: "pool:luci.chromium.try"
       exe {
         cipd_package: "infra/chromium/bootstrapper/${platform}"
@@ -63606,7 +63606,7 @@
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builder:ios-simulator-rts"
       dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-11|Mac-12"
+      dimensions: "os:Mac-12"
       dimensions: "pool:luci.chromium.try"
       exe {
         cipd_package: "infra/chromium/bootstrapper/${platform}"
@@ -63698,7 +63698,7 @@
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builder:ios15-beta-simulator"
       dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-11|Mac-12"
+      dimensions: "os:Mac-12"
       dimensions: "pool:luci.chromium.try"
       exe {
         cipd_package: "infra/chromium/bootstrapper/${platform}"
@@ -63882,7 +63882,7 @@
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builder:ios16-beta-simulator"
       dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-11|Mac-12"
+      dimensions: "os:Mac-12"
       dimensions: "pool:luci.chromium.try"
       exe {
         cipd_package: "infra/chromium/bootstrapper/${platform}"
@@ -63974,7 +63974,7 @@
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builder:ios16-sdk-simulator"
       dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-11|Mac-12"
+      dimensions: "os:Mac-12"
       dimensions: "pool:luci.chromium.try"
       exe {
         cipd_package: "infra/chromium/bootstrapper/${platform}"
@@ -71922,7 +71922,7 @@
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builder:mac-arm64-on-arm64-rel"
       dimensions: "cpu:arm64"
-      dimensions: "os:Mac-11|Mac-12"
+      dimensions: "os:Mac-12"
       dimensions: "pool:luci.chromium.try"
       dimensions: "ssd:1"
       exe {
@@ -72100,7 +72100,7 @@
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builderless:1"
       dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-11|Mac-12"
+      dimensions: "os:Mac-12"
       dimensions: "pool:luci.chromium.try"
       dimensions: "ssd:1"
       exe {
@@ -72275,7 +72275,7 @@
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builderless:1"
       dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-11|Mac-12"
+      dimensions: "os:Mac-12"
       dimensions: "pool:luci.chromium.try"
       dimensions: "ssd:1"
       exe {
@@ -72364,7 +72364,7 @@
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builderless:1"
       dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-11|Mac-12"
+      dimensions: "os:Mac-12"
       dimensions: "pool:luci.chromium.try"
       dimensions: "ssd:1"
       exe {
@@ -72541,7 +72541,7 @@
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builderless:1"
       dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-11|Mac-12"
+      dimensions: "os:Mac-12"
       dimensions: "pool:luci.chromium.try"
       dimensions: "ssd:1"
       exe {
@@ -72724,7 +72724,7 @@
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builder:mac-rel-compilator"
       dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-11|Mac-12"
+      dimensions: "os:Mac-12"
       dimensions: "pool:luci.chromium.try"
       dimensions: "ssd:1"
       exe {
@@ -74590,7 +74590,7 @@
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builderless:1"
       dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-11|Mac-12"
+      dimensions: "os:Mac-12"
       dimensions: "pool:luci.chromium.try"
       dimensions: "ssd:1"
       exe {
diff --git a/infra/config/lib/builders.star b/infra/config/lib/builders.star
index 6e9d6dc..009faf5 100644
--- a/infra/config/lib/builders.star
+++ b/infra/config/lib/builders.star
@@ -73,8 +73,7 @@
     MAC_10_15 = os_enum("Mac-10.15", os_category.MAC),
     MAC_11 = os_enum("Mac-11", os_category.MAC),
     MAC_12 = os_enum("Mac-12", os_category.MAC),
-    # TODO(crbug.com/1323966) Remove Mac11 once builders have been migrated to Mac12
-    MAC_DEFAULT = os_enum("Mac-11|Mac-12", os_category.MAC),
+    MAC_DEFAULT = os_enum("Mac-12", os_category.MAC),
     MAC_ANY = os_enum("Mac", os_category.MAC),
     MAC_BETA = os_enum("Mac-12", os_category.MAC),
     WINDOWS_7 = os_enum("Windows-7", os_category.WINDOWS),
diff --git a/infra/config/subprojects/chromium/ci/chromium.fyi.star b/infra/config/subprojects/chromium/ci/chromium.fyi.star
index 407eb89..711eee1 100644
--- a/infra/config/subprojects/chromium/ci/chromium.fyi.star
+++ b/infra/config/subprojects/chromium/ci/chromium.fyi.star
@@ -75,7 +75,7 @@
 def fyi_ios_builder(*, name, **kwargs):
     kwargs.setdefault("cores", None)
     if kwargs.get("builderless", False):
-        kwargs.setdefault("os", os.MAC_11)
+        kwargs.setdefault("os", os.MAC_DEFAULT)
     kwargs.setdefault("xcode", xcode.x13main)
     return ci.builder(name = name, **kwargs)
 
@@ -1411,7 +1411,7 @@
         short_name = "ios",
     ),
     cores = None,
-    os = os.MAC_11,
+    os = os.MAC_DEFAULT,
     use_clang_coverage = True,
     coverage_exclude_sources = "ios_test_files_and_test_utils",
     coverage_test_types = ["overall", "unit"],
diff --git a/ios/chrome/browser/BUILD.gn b/ios/chrome/browser/BUILD.gn
index e69dcd5..d1af449 100644
--- a/ios/chrome/browser/BUILD.gn
+++ b/ios/chrome/browser/BUILD.gn
@@ -248,6 +248,7 @@
     "//ios/chrome/browser/prefs",
     "//ios/chrome/browser/prefs:browser_prefs",
     "//ios/chrome/browser/safe_browsing",
+    "//ios/chrome/browser/segmentation_platform",
     "//ios/chrome/browser/send_tab_to_self",
     "//ios/chrome/browser/signin",
     "//ios/chrome/browser/translate",
diff --git a/ios/chrome/browser/application_context.h b/ios/chrome/browser/application_context.h
index c05c6ac..99aed09 100644
--- a/ios/chrome/browser/application_context.h
+++ b/ios/chrome/browser/application_context.h
@@ -56,6 +56,10 @@
 class NetworkTimeTracker;
 }
 
+namespace segmentation_platform {
+class OTRWebStateObserver;
+}
+
 namespace ukm {
 class UkmRecorder;
 }
@@ -166,6 +170,10 @@
   // Returns the SingleSignOnService instance used by this application.
   virtual id<SingleSignOnService> GetSSOService() = 0;
 
+  // Returns the application's OTRWebStateObserver for segmentation platform.
+  virtual segmentation_platform::OTRWebStateObserver*
+  GetSegmentationOTRWebStateObserver() = 0;
+
  protected:
   // Sets the global ApplicationContext instance.
   static void SetApplicationContext(ApplicationContext* context);
diff --git a/ios/chrome/browser/application_context_impl.h b/ios/chrome/browser/application_context_impl.h
index bd673d4..1fc7ca9 100644
--- a/ios/chrome/browser/application_context_impl.h
+++ b/ios/chrome/browser/application_context_impl.h
@@ -81,6 +81,8 @@
   breadcrumbs::BreadcrumbPersistentStorageManager*
   GetBreadcrumbPersistentStorageManager() override;
   id<SingleSignOnService> GetSSOService() override;
+  segmentation_platform::OTRWebStateObserver*
+  GetSegmentationOTRWebStateObserver() override;
 
  private:
   // Sets the locale used by the application.
@@ -125,6 +127,9 @@
   scoped_refptr<SafeBrowsingService> safe_browsing_service_;
 
   __strong id<SingleSignOnService> single_sign_on_service_ = nil;
+
+  std::unique_ptr<segmentation_platform::OTRWebStateObserver>
+      segmentation_otr_web_state_observer_;
 };
 
 #endif  // IOS_CHROME_BROWSER_APPLICATION_CONTEXT_IMPL_H_
diff --git a/ios/chrome/browser/application_context_impl.mm b/ios/chrome/browser/application_context_impl.mm
index a0f39248..743e27a 100644
--- a/ios/chrome/browser/application_context_impl.mm
+++ b/ios/chrome/browser/application_context_impl.mm
@@ -58,6 +58,7 @@
 #include "ios/chrome/browser/pref_names.h"
 #include "ios/chrome/browser/prefs/browser_prefs.h"
 #include "ios/chrome/browser/prefs/ios_chrome_pref_service_factory.h"
+#include "ios/chrome/browser/segmentation_platform/otr_web_state_observer.h"
 #include "ios/chrome/browser/update_client/ios_chrome_update_query_params_delegate.h"
 #include "ios/chrome/common/channel_info.h"
 #include "ios/components/security_interstitials/safe_browsing/safe_browsing_service_impl.h"
@@ -469,6 +470,16 @@
   return single_sign_on_service_;
 }
 
+segmentation_platform::OTRWebStateObserver*
+ApplicationContextImpl::GetSegmentationOTRWebStateObserver() {
+  if (!segmentation_otr_web_state_observer_) {
+    segmentation_otr_web_state_observer_ =
+        std::make_unique<segmentation_platform::OTRWebStateObserver>(
+            GetChromeBrowserStateManager());
+  }
+  return segmentation_otr_web_state_observer_.get();
+}
+
 void ApplicationContextImpl::SetApplicationLocale(const std::string& locale) {
   DCHECK(thread_checker_.CalledOnValidThread());
   application_locale_ = locale;
diff --git a/ios/chrome/browser/segmentation_platform/BUILD.gn b/ios/chrome/browser/segmentation_platform/BUILD.gn
index d8bdaad..20b16a65 100644
--- a/ios/chrome/browser/segmentation_platform/BUILD.gn
+++ b/ios/chrome/browser/segmentation_platform/BUILD.gn
@@ -8,6 +8,8 @@
   sources = [
     "model_provider_factory_impl.h",
     "model_provider_factory_impl.mm",
+    "otr_web_state_observer.h",
+    "otr_web_state_observer.mm",
     "segmentation_platform_service_factory.h",
     "segmentation_platform_service_factory.mm",
   ]
@@ -28,7 +30,9 @@
     "//ios/chrome/browser:chrome_paths",
     "//ios/chrome/browser/browser_state",
     "//ios/chrome/browser/history",
+    "//ios/chrome/browser/main:public",
     "//ios/chrome/browser/optimization_guide",
+    "//ios/chrome/browser/web_state_list",
   ]
   if (build_with_tflite_lib) {
     deps += [ "//components/segmentation_platform/internal:optimization_guide_segmentation_handler" ]
@@ -40,7 +44,10 @@
 source_set("unit_tests") {
   configs += [ "//build/config/compiler:enable_arc" ]
   testonly = true
-  sources = [ "segmentation_platform_service_factory_unittest.mm" ]
+  sources = [
+    "otr_web_state_observer_unittest.mm",
+    "segmentation_platform_service_factory_unittest.mm",
+  ]
   deps = [
     ":segmentation_platform",
     "//base/test:test_support",
@@ -50,8 +57,10 @@
     "//components/segmentation_platform/public",
     "//components/segmentation_platform/public/proto",
     "//ios/chrome/browser/browser_state:test_support",
+    "//ios/chrome/browser/main:public",
     "//ios/chrome/browser/main:test_support",
     "//ios/chrome/browser/prefs:browser_prefs",
+    "//ios/chrome/browser/web_state_list",
     "//ios/chrome/test:test_support",
     "//ios/web/public/test",
     "//ios/web/public/test/fakes",
diff --git a/ios/chrome/browser/segmentation_platform/otr_web_state_observer.h b/ios/chrome/browser/segmentation_platform/otr_web_state_observer.h
new file mode 100644
index 0000000..ab27ee95
--- /dev/null
+++ b/ios/chrome/browser/segmentation_platform/otr_web_state_observer.h
@@ -0,0 +1,80 @@
+// Copyright 2022 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_SEGMENTATION_PLATFORM_OTR_WEB_STATE_OBSERVER_H_
+#define IOS_CHROME_BROWSER_SEGMENTATION_PLATFORM_OTR_WEB_STATE_OBSERVER_H_
+
+#include "base/containers/flat_map.h"
+#include "base/files/file_path.h"
+#include "base/memory/raw_ptr.h"
+#include "base/observer_list.h"
+#include "ios/chrome/browser/browser_state/browser_state_info_cache_observer.h"
+
+class AllWebStateListObservationRegistrar;
+
+namespace ios {
+class ChromeBrowserStateManager;
+}
+
+namespace segmentation_platform {
+
+// Keeps track of all the OTR WebState(s) across all browsers in an application.
+class OTRWebStateObserver : public BrowserStateInfoCacheObserver {
+ public:
+  // Observer interface to listen to changes in number of OTR WebState.
+  class ObserverClient : public base::CheckedObserver {
+   public:
+    ObserverClient() = default;
+
+    // Called every time the number of OTR WebState changes in any of the
+    // browsers in the application.
+    virtual void OnOTRWebStateCountChanged(bool otr_state_exists) = 0;
+  };
+
+  explicit OTRWebStateObserver(
+      ios::ChromeBrowserStateManager* browser_state_manager);
+  ~OTRWebStateObserver() override;
+
+  OTRWebStateObserver(OTRWebStateObserver&) = delete;
+  OTRWebStateObserver& operator=(OTRWebStateObserver&) = delete;
+
+  // BrowserStateInfoCacheObserver:
+  void OnBrowserStateAdded(const base::FilePath& path) override;
+  void OnBrowserStateWasRemoved(const base::FilePath& path) override;
+
+  // Add/Remove observers.
+  void AddObserver(ObserverClient* client);
+  void RemoveObserver(ObserverClient* client);
+
+ private:
+  class WebStateObserver;
+
+  // Stores data about a ChromeBrowserState.
+  struct BrowserStateData {
+    BrowserStateData();
+    ~BrowserStateData();
+
+    // Observer for all WebState(s) in the state.
+    std::unique_ptr<AllWebStateListObservationRegistrar>
+        all_web_state_observation;
+    // Count of number of OTR WebState(s) in the BrowserState.
+    int otr_web_state_count = 0;
+  };
+
+  void OnWebStateListChanged(const base::FilePath& browser_state_path,
+                             int otr_web_state_count);
+
+  // Counts OTR WebState(s) across all the BrowserState(s) and returns true if
+  // any OTR WebState exists.
+  bool HasAnyOtrWebState() const;
+
+  base::ObserverList<ObserverClient> observer_clients_;
+  const raw_ptr<ios::ChromeBrowserStateManager> browser_state_manager_;
+  base::flat_map<base::FilePath, std::unique_ptr<BrowserStateData>>
+      browser_state_data_;
+};
+
+}  // namespace segmentation_platform
+
+#endif  // IOS_CHROME_BROWSER_SEGMENTATION_PLATFORM_OTR_WEB_STATE_OBSERVER_H_
diff --git a/ios/chrome/browser/segmentation_platform/otr_web_state_observer.mm b/ios/chrome/browser/segmentation_platform/otr_web_state_observer.mm
new file mode 100644
index 0000000..0929be58
--- /dev/null
+++ b/ios/chrome/browser/segmentation_platform/otr_web_state_observer.mm
@@ -0,0 +1,155 @@
+// Copyright 2022 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 "ios/chrome/browser/segmentation_platform/otr_web_state_observer.h"
+
+#include "ios/chrome/browser/browser_state/browser_state_info_cache.h"
+#include "ios/chrome/browser/browser_state/chrome_browser_state.h"
+#include "ios/chrome/browser/browser_state/chrome_browser_state_manager.h"
+#include "ios/chrome/browser/main/all_web_state_list_observation_registrar.h"
+#include "ios/chrome/browser/main/browser.h"
+#include "ios/chrome/browser/main/browser_list.h"
+#include "ios/chrome/browser/main/browser_list_factory.h"
+#include "ios/chrome/browser/web_state_list/web_state_list_observer.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+namespace segmentation_platform {
+
+class OTRWebStateObserver::WebStateObserver : public WebStateListObserver {
+ public:
+  WebStateObserver(const base::FilePath& browser_state_path,
+                   OTRWebStateObserver* states_observer,
+                   BrowserList* browser_list)
+      : browser_state_path_(browser_state_path),
+        states_observer_(states_observer),
+        browser_list_(browser_list) {
+    // Ensure the count is updated at creation time.
+    UpdateOtrWebStateCount();
+  }
+
+  // WebStateListObserver
+  void WebStateInsertedAt(WebStateList* web_state_list,
+                          web::WebState* web_state,
+                          int index,
+                          bool activating) override;
+  void WebStateDetachedAt(WebStateList* web_state_list,
+                          web::WebState* web_state,
+                          int index) override;
+  void BatchOperationEnded(WebStateList* web_state_list) override;
+
+ private:
+  void UpdateOtrWebStateCount();
+
+  const base::FilePath browser_state_path_;
+
+  const raw_ptr<OTRWebStateObserver> states_observer_;
+
+  // BrowserList should be valid as WebStateList notifications are running.
+  const raw_ptr<BrowserList> browser_list_;
+};
+
+void OTRWebStateObserver::WebStateObserver::WebStateInsertedAt(
+    WebStateList* web_state_list,
+    web::WebState* web_state,
+    int index,
+    bool activating) {
+  UpdateOtrWebStateCount();
+}
+
+void OTRWebStateObserver::WebStateObserver::WebStateDetachedAt(
+    WebStateList* web_state_list,
+    web::WebState* web_state,
+    int index) {
+  UpdateOtrWebStateCount();
+}
+
+void OTRWebStateObserver::WebStateObserver::BatchOperationEnded(
+    WebStateList* web_state_list) {
+  UpdateOtrWebStateCount();
+}
+
+void OTRWebStateObserver::WebStateObserver::UpdateOtrWebStateCount() {
+  const std::set<Browser*>& browsers = browser_list_->AllIncognitoBrowsers();
+  int otr_state_count = 0;
+  for (Browser* browser : browsers) {
+    WebStateList* web_state_list = browser->GetWebStateList();
+    if (web_state_list) {
+      otr_state_count += web_state_list->count();
+    }
+  }
+  states_observer_->OnWebStateListChanged(browser_state_path_, otr_state_count);
+}
+
+OTRWebStateObserver::BrowserStateData::BrowserStateData() = default;
+OTRWebStateObserver::BrowserStateData::~BrowserStateData() = default;
+
+OTRWebStateObserver::OTRWebStateObserver(
+    ios::ChromeBrowserStateManager* browser_state_manager)
+    : browser_state_manager_(browser_state_manager) {
+  browser_state_manager_->GetBrowserStateInfoCache()->AddObserver(this);
+  for (ChromeBrowserState* state :
+       browser_state_manager_->GetLoadedBrowserStates()) {
+    OnBrowserStateAdded(state->GetStatePath());
+  }
+}
+
+OTRWebStateObserver::~OTRWebStateObserver() {
+  browser_state_manager_->GetBrowserStateInfoCache()->RemoveObserver(this);
+}
+
+void OTRWebStateObserver::OnBrowserStateAdded(const base::FilePath& path) {
+  auto* browser_state = browser_state_manager_->GetBrowserState(path);
+  BrowserList* browser_list =
+      BrowserListFactory::GetForBrowserState(browser_state);
+  DCHECK(browser_list);
+
+  auto it = browser_state_data_.emplace(
+      std::make_pair(path, std::make_unique<BrowserStateData>()));
+  DCHECK(it.second);
+  BrowserStateData& data = *it.first->second;
+  data.all_web_state_observation =
+      std::make_unique<AllWebStateListObservationRegistrar>(
+          browser_state,
+          std::make_unique<WebStateObserver>(path, this, browser_list),
+          AllWebStateListObservationRegistrar::INCOGNITO);
+}
+
+void OTRWebStateObserver::OnBrowserStateWasRemoved(const base::FilePath& path) {
+  browser_state_data_.erase(path);
+}
+
+void OTRWebStateObserver::AddObserver(ObserverClient* client) {
+  observer_clients_.AddObserver(client);
+  // Notify if OTR state was created before registration.
+  client->OnOTRWebStateCountChanged(HasAnyOtrWebState());
+}
+
+void OTRWebStateObserver::RemoveObserver(ObserverClient* client) {
+  observer_clients_.RemoveObserver(client);
+}
+
+void OTRWebStateObserver::OnWebStateListChanged(
+    const base::FilePath& browser_state_path,
+    int otr_web_state_count) {
+  auto& data = browser_state_data_[browser_state_path];
+  data->otr_web_state_count = otr_web_state_count;
+
+  const bool has_otr_state = HasAnyOtrWebState();
+  for (ObserverClient& obs : observer_clients_) {
+    obs.OnOTRWebStateCountChanged(has_otr_state);
+  }
+}
+
+bool OTRWebStateObserver::HasAnyOtrWebState() const {
+  bool has_otr_state = false;
+  for (const auto& data : browser_state_data_) {
+    has_otr_state = has_otr_state || (data.second->otr_web_state_count > 0);
+  }
+  return has_otr_state;
+}
+
+}  // namespace segmentation_platform
diff --git a/ios/chrome/browser/segmentation_platform/otr_web_state_observer_unittest.mm b/ios/chrome/browser/segmentation_platform/otr_web_state_observer_unittest.mm
new file mode 100644
index 0000000..c0c4485
--- /dev/null
+++ b/ios/chrome/browser/segmentation_platform/otr_web_state_observer_unittest.mm
@@ -0,0 +1,148 @@
+// Copyright 2022 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 "ios/chrome/browser/segmentation_platform/otr_web_state_observer.h"
+
+#include "base/scoped_observation.h"
+#include "ios/chrome/browser/browser_state/test_chrome_browser_state.h"
+#include "ios/chrome/browser/browser_state/test_chrome_browser_state_manager.h"
+#include "ios/chrome/browser/main/browser_list.h"
+#include "ios/chrome/browser/main/browser_list_factory.h"
+#include "ios/chrome/browser/main/test_browser.h"
+#include "ios/chrome/browser/web_state_list/web_state_list.h"
+#include "ios/chrome/browser/web_state_list/web_state_opener.h"
+#include "ios/web/public/test/web_task_environment.h"
+#include "ios/web/public/web_state.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/platform_test.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+namespace segmentation_platform {
+
+namespace {
+
+class MockObserverClient : public OTRWebStateObserver::ObserverClient {
+ public:
+  ~MockObserverClient() override = default;
+
+  MOCK_METHOD(void, OnOTRWebStateCountChanged, (bool));
+
+  void Observe(OTRWebStateObserver* web_state_observer) {
+    observation_.Observe(web_state_observer);
+  }
+
+ private:
+  base::ScopedObservation<OTRWebStateObserver,
+                          OTRWebStateObserver::ObserverClient>
+      observation_{this};
+};
+
+}  // namespace
+
+class OTRWebStateObserverTest : public PlatformTest {
+ public:
+  OTRWebStateObserverTest() {}
+  ~OTRWebStateObserverTest() override = default;
+
+  void SetUp() override {
+    PlatformTest::SetUp();
+
+    auto state = TestChromeBrowserState::Builder().Build();
+    browser_state_ = state.get();
+    browser_state_manager_ =
+        std::make_unique<TestChromeBrowserStateManager>(std::move(state));
+    observer_ =
+        std::make_unique<OTRWebStateObserver>(browser_state_manager_.get());
+
+    BrowserList* browser_list =
+        BrowserListFactory::GetForBrowserState(browser_state_);
+    browser_ = std::make_unique<TestBrowser>(browser_state_);
+    browser_list->AddBrowser(browser_.get());
+
+    otr_browser_state_ = browser_state_->GetOffTheRecordChromeBrowserState();
+    otr_browser_ = std::make_unique<TestBrowser>(otr_browser_state_);
+    BrowserList* otr_browser_list =
+        BrowserListFactory::GetForBrowserState(otr_browser_state_);
+    otr_browser_list->AddIncognitoBrowser(otr_browser_.get());
+  }
+
+  void TearDown() override {
+    PlatformTest::TearDown();
+
+    otr_browser_.reset();
+    browser_.reset();
+    observer_.reset();
+    browser_state_manager_.reset();
+  }
+
+  void AddWebState(int index) {
+    web::WebState::CreateParams create_params(browser_state_);
+    auto web_state = web::WebState::Create(create_params);
+
+    browser_->GetWebStateList()->InsertWebState(index, std::move(web_state),
+                                                WebStateList::INSERT_NO_FLAGS,
+                                                WebStateOpener());
+  }
+
+  void AddOtrWebState(int index) {
+    web::WebState::CreateParams create_params(otr_browser_state_);
+    auto web_state = web::WebState::Create(create_params);
+
+    otr_browser_->GetWebStateList()->InsertWebState(
+        index, std::move(web_state), WebStateList::INSERT_NO_FLAGS,
+        WebStateOpener());
+  }
+
+ protected:
+  web::WebTaskEnvironment task_environment_;
+
+  std::unique_ptr<TestChromeBrowserStateManager> browser_state_manager_;
+  raw_ptr<TestChromeBrowserState> browser_state_;
+  raw_ptr<ChromeBrowserState> otr_browser_state_;
+  std::unique_ptr<OTRWebStateObserver> observer_;
+  std::unique_ptr<TestBrowser> browser_;
+  std::unique_ptr<TestBrowser> otr_browser_;
+};
+
+TEST_F(OTRWebStateObserverTest, CreationNotifiesNoOTR) {
+  MockObserverClient observer_client;
+  EXPECT_CALL(observer_client, OnOTRWebStateCountChanged(false));
+  observer_client.Observe(observer_.get());
+}
+
+TEST_F(OTRWebStateObserverTest, CreateWithOTR) {
+  AddOtrWebState(0);
+
+  MockObserverClient observer_client;
+  EXPECT_CALL(observer_client, OnOTRWebStateCountChanged(true));
+  observer_client.Observe(observer_.get());
+
+  EXPECT_CALL(observer_client, OnOTRWebStateCountChanged(true));
+  AddOtrWebState(1);
+
+  EXPECT_CALL(observer_client, OnOTRWebStateCountChanged(true));
+  AddWebState(0);
+  otr_browser_->GetWebStateList()->CloseWebStateAt(
+      1, WebStateList::CLOSE_NO_FLAGS);
+
+  EXPECT_CALL(observer_client, OnOTRWebStateCountChanged(false));
+  otr_browser_->GetWebStateList()->CloseWebStateAt(
+      0, WebStateList::CLOSE_NO_FLAGS);
+}
+
+TEST_F(OTRWebStateObserverTest, AddOtrAfterCreation) {
+  MockObserverClient observer_client;
+  EXPECT_CALL(observer_client, OnOTRWebStateCountChanged(false));
+  observer_client.Observe(observer_.get());
+
+  EXPECT_CALL(observer_client, OnOTRWebStateCountChanged(true));
+  AddWebState(0);
+  AddOtrWebState(0);
+}
+
+}  // namespace segmentation_platform
diff --git a/ios/chrome/browser/segmentation_platform/segmentation_platform_service_factory.mm b/ios/chrome/browser/segmentation_platform/segmentation_platform_service_factory.mm
index 5959a99..4c292b0a 100644
--- a/ios/chrome/browser/segmentation_platform/segmentation_platform_service_factory.mm
+++ b/ios/chrome/browser/segmentation_platform/segmentation_platform_service_factory.mm
@@ -5,6 +5,8 @@
 #include "ios/chrome/browser/segmentation_platform/segmentation_platform_service_factory.h"
 
 #include "base/feature_list.h"
+#include "base/scoped_observation.h"
+#include "base/supports_user_data.h"
 #include "base/task/sequenced_task_runner.h"
 #include "base/task/task_traits.h"
 #include "base/task/thread_pool.h"
@@ -15,12 +17,14 @@
 #include "components/segmentation_platform/internal/segmentation_platform_service_impl.h"
 #include "components/segmentation_platform/internal/ukm_data_manager.h"
 #include "components/segmentation_platform/public/features.h"
+#include "ios/chrome/browser/application_context.h"
 #include "ios/chrome/browser/browser_state/browser_state_otr_helper.h"
 #include "ios/chrome/browser/browser_state/chrome_browser_state.h"
 #include "ios/chrome/browser/history/history_service_factory.h"
 #include "ios/chrome/browser/optimization_guide/optimization_guide_service.h"
 #include "ios/chrome/browser/optimization_guide/optimization_guide_service_factory.h"
 #include "ios/chrome/browser/segmentation_platform/model_provider_factory_impl.h"
+#include "ios/chrome/browser/segmentation_platform/otr_web_state_observer.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
@@ -32,11 +36,37 @@
 const base::FilePath::CharType kSegmentationPlatformStorageDirName[] =
     FILE_PATH_LITERAL("Segmentation Platform");
 
+const char kSegmentationPlatformProfileObserverKey[] =
+    "segmentation_platform_profile_observer";
+
 UkmDataManager* GetUkmDataManager() {
   static base::NoDestructor<DummyUkmDataManager> instance;
   return instance.get();
 }
 
+// Observes existance of Incognito tabs in the application.
+class IncognitoObserver : public OTRWebStateObserver::ObserverClient,
+                          public base::SupportsUserData::Data {
+ public:
+  IncognitoObserver(SegmentationPlatformService* service,
+                    OTRWebStateObserver* otr_observer)
+      : service_(service) {
+    observation_.Observe(otr_observer);
+  }
+
+  // OTRWebStateObserver::ObserverClient:
+  void OnOTRWebStateCountChanged(bool otr_state_exists) override {
+    const bool enable_metrics = !otr_state_exists;
+    service_->EnableMetrics(enable_metrics);
+  }
+
+ private:
+  base::ScopedObservation<OTRWebStateObserver,
+                          OTRWebStateObserver::ObserverClient>
+      observation_{this};
+  const raw_ptr<SegmentationPlatformService> service_;
+};
+
 std::unique_ptr<KeyedService> BuildSegmentationPlatformService(
     web::BrowserState* context) {
   DCHECK(!context->IsOffTheRecord());
@@ -67,9 +97,18 @@
   // TODO(crbug.com/1333641): params->field_trial_register should be
   // initialized.
 
-  // TODO(crbug.com/1333641): The factory should call EnableMetrics() based on
-  // incognito profile creation.
-  return std::make_unique<SegmentationPlatformServiceImpl>(std::move(params));
+  auto service =
+      std::make_unique<SegmentationPlatformServiceImpl>(std::move(params));
+
+  auto* otr_observer =
+      GetApplicationContext()->GetSegmentationOTRWebStateObserver();
+  // Can be null in tests.
+  if (otr_observer) {
+    service->SetUserData(
+        kSegmentationPlatformProfileObserverKey,
+        std::make_unique<IncognitoObserver>(service.get(), otr_observer));
+  }
+  return service;
 }
 
 }  // namespace
diff --git a/ios/chrome/browser/ui/content_suggestions/cells/content_suggestions_module_container.mm b/ios/chrome/browser/ui/content_suggestions/cells/content_suggestions_module_container.mm
index 369f166..5b19706 100644
--- a/ios/chrome/browser/ui/content_suggestions/cells/content_suggestions_module_container.mm
+++ b/ios/chrome/browser/ui/content_suggestions/cells/content_suggestions_module_container.mm
@@ -20,10 +20,10 @@
 const float kContentHorizontalInset = 16.0f;
 
 // The vertical spacing between the title and the content of the module.
-const float kContentTitleVerticalSpacing = 10.0f;
+const float kContentTitleVerticalSpacing = 12.0f;
 
 // The top inset of the title label to this container.
-const float kTitleTopInset = 11.0f;
+const float kTitleTopInset = 14.0f;
 
 // The minimum width of the title label.
 const float kTitleMinimumWidth = 99.0f;
@@ -35,7 +35,7 @@
 const float kPlaceholderTitleCornerRadius = 2.0f;
 
 // The corner radius of this container.
-const float kCornerRadius = 10;
+const float kCornerRadius = 16;
 
 // The shadow radius of this container.
 const float kShadowRadius = 60;
@@ -69,7 +69,8 @@
     _type = type;
 
     self.layer.cornerRadius = kCornerRadius;
-    self.backgroundColor = [UIColor colorNamed:kBackgroundColor];
+    self.backgroundColor =
+        [UIColor colorNamed:kGroupedSecondaryBackgroundColor];
     self.layer.shadowColor = [UIColor blackColor].CGColor;
     self.layer.shadowOffset = kShadowOffset;
     self.layer.shadowRadius = kShadowRadius;
diff --git a/ios/chrome/browser/ui/content_suggestions/cells/content_suggestions_most_visited_tile_view.mm b/ios/chrome/browser/ui/content_suggestions/cells/content_suggestions_most_visited_tile_view.mm
index d037198..57104cb 100644
--- a/ios/chrome/browser/ui/content_suggestions/cells/content_suggestions_most_visited_tile_view.mm
+++ b/ios/chrome/browser/ui/content_suggestions/cells/content_suggestions_most_visited_tile_view.mm
@@ -80,6 +80,19 @@
                                                    fromView:self];
 }
 
+- (UITargetedPreview*)contextMenuInteraction:
+                          (UIContextMenuInteraction*)interaction
+    previewForHighlightingMenuWithConfiguration:
+        (UIContextMenuConfiguration*)configuration {
+  // This ensures that the background of the context menu matches the background
+  // behind the tile.
+  UIPreviewParameters* previewParameters = [[UIPreviewParameters alloc] init];
+  previewParameters.backgroundColor =
+      [UIColor colorNamed:kGroupedSecondaryBackgroundColor];
+  return [[UITargetedPreview alloc] initWithView:self
+                                      parameters:previewParameters];
+}
+
 #pragma mark - AccessibilityCustomAction
 
 // Custom action for a cell configured with this item.
diff --git a/ios/chrome/browser/ui/first_run/BUILD.gn b/ios/chrome/browser/ui/first_run/BUILD.gn
index 1dfb408..5da92d3 100644
--- a/ios/chrome/browser/ui/first_run/BUILD.gn
+++ b/ios/chrome/browser/ui/first_run/BUILD.gn
@@ -248,7 +248,6 @@
     "//ios/chrome/browser/ui/settings/google_services:constants",
     "//ios/chrome/common:string_util",
     "//ios/chrome/common/ui/promo_style:constants",
-    "//ios/chrome/common/ui/table_view:cells_constants",
     "//ios/chrome/test/earl_grey:eg_test_support+eg2",
     "//ios/chrome/test/earl_grey:switches",
     "//ios/public/provider/chrome/browser/signin:constants",
diff --git a/ios/chrome/browser/ui/first_run/first_run_two_steps_egtest.mm b/ios/chrome/browser/ui/first_run/first_run_two_steps_egtest.mm
index c8f3fda..867cc59 100644
--- a/ios/chrome/browser/ui/first_run/first_run_two_steps_egtest.mm
+++ b/ios/chrome/browser/ui/first_run/first_run_two_steps_egtest.mm
@@ -3,17 +3,9 @@
 // found in the LICENSE file.
 
 #import "components/signin/ios/browser/features.h"
-#import "ios/chrome/browser/ui/authentication/signin_earl_grey.h"
-#import "ios/chrome/browser/ui/first_run/first_run_app_interface.h"
 #import "ios/chrome/browser/ui/first_run/first_run_constants.h"
 #import "ios/chrome/browser/ui/first_run/fre_field_trial.h"
-#import "ios/chrome/browser/ui/settings/google_services/google_services_settings_constants.h"
 #import "ios/chrome/browser/ui/ui_feature_flags.h"
-#import "ios/chrome/common/ui/promo_style/constants.h"
-#import "ios/chrome/common/ui/table_view/table_view_cells_constants.h"
-#import "ios/chrome/test/earl_grey/chrome_actions.h"
-#import "ios/chrome/test/earl_grey/chrome_earl_grey_ui.h"
-#import "ios/chrome/test/earl_grey/chrome_matchers.h"
 #import "ios/chrome/test/earl_grey/chrome_test_case.h"
 #import "ios/chrome/test/earl_grey/test_switches.h"
 #import "ios/testing/earl_grey/app_launch_configuration.h"
@@ -23,21 +15,6 @@
 #error "This file requires ARC support."
 #endif
 
-namespace {
-
-// Returns matcher for the secondary action button.
-id<GREYMatcher> PromoStyleSecondaryActionButtonMatcher() {
-  return grey_accessibilityID(
-      kPromoStyleSecondaryActionAccessibilityIdentifier);
-}
-
-// Returns matcher for UMA manage link.
-id<GREYMatcher> ManageUMALinkMatcher() {
-  return grey_accessibilityLabel(@"Manage");
-}
-
-}  // namespace
-
 // Test first run stages
 @interface FirstRunTwoStepsTestCase : ChromeTestCase
 
@@ -80,53 +57,10 @@
 
 #pragma mark Tests
 
-// Tests FRE with UMA default value and without sign-in.
-- (void)testWithUMACheckedAndNoSignin {
+// First test.
+// TODO(crbug.com/1290848): Need to create EGTests for 2 steps FRE.
+- (void)testWelcomeScreenUI {
   [self verifyWelcomeScreenIsDisplayed];
-  // Skip sign-in.
-  id<GREYMatcher> secondaryButtonMatcher =
-      PromoStyleSecondaryActionButtonMatcher();
-  [self scrollToElementAndAssertVisibility:secondaryButtonMatcher];
-  [[EarlGrey selectElementWithMatcher:PromoStyleSecondaryActionButtonMatcher()]
-      performAction:grey_tap()];
-  // Check that UMA is ON.
-  [self checkUMACheckboxValue:YES];
-  // Check signed out.
-  [SigninEarlGrey verifySignedOut];
-}
-
-// Tests FRE with UMA off and without sign-in.
-- (void)testWithUMAUncheckedAndNoSignin {
-  [self verifyWelcomeScreenIsDisplayed];
-  // Scroll to and open the UMA dialog.
-  id<GREYMatcher> manageUMALinkMatcher = ManageUMALinkMatcher();
-  [self scrollToElementAndAssertVisibility:manageUMALinkMatcher];
-  [[EarlGrey selectElementWithMatcher:manageUMALinkMatcher]
-      performAction:grey_tap()];
-  // Turn off UMA.
-  [[EarlGrey
-      selectElementWithMatcher:chrome_test_util::TableViewSwitchCell(
-                                   kImproveChromeItemAccessibilityIdentifier,
-                                   /*is_toggled_on=*/YES,
-                                   /*enabled=*/YES)]
-      performAction:chrome_test_util::TurnTableViewSwitchOn(NO)];
-  // Close UMA dialog.
-  [[EarlGrey
-      selectElementWithMatcher:chrome_test_util::NavigationBarDoneButton()]
-      performAction:grey_tap()];
-  // Skip sign-in.
-  id<GREYMatcher> secondaryButtonMatcher =
-      PromoStyleSecondaryActionButtonMatcher();
-  [self scrollToElementAndAssertVisibility:secondaryButtonMatcher];
-  [[EarlGrey selectElementWithMatcher:PromoStyleSecondaryActionButtonMatcher()]
-      performAction:grey_tap()];
-  GREYAssertFalse(
-      [FirstRunAppInterface isUMACollectionEnabled],
-      @"kMetricsReportingEnabled pref was unexpectedly true by default.");
-  // Check that UMA is OFF.
-  [self checkUMACheckboxValue:NO];
-  // Check signed out.
-  [SigninEarlGrey verifySignedOut];
 }
 
 #pragma mark Helper
@@ -139,33 +73,4 @@
       assertWithMatcher:grey_notNil()];
 }
 
-// Opens the Google services settings.
-- (void)checkUMACheckboxValue:(BOOL)UMACheckboxValue {
-  [ChromeEarlGreyUI openSettingsMenu];
-  [ChromeEarlGreyUI
-      tapSettingsMenuButton:chrome_test_util::GoogleServicesSettingsButton()];
-  id<GREYMatcher> scrollViewMatcher =
-      grey_accessibilityID(kGoogleServicesSettingsViewIdentifier);
-  [[EarlGrey selectElementWithMatcher:scrollViewMatcher]
-      assertWithMatcher:grey_notNil()];
-  [[EarlGrey
-      selectElementWithMatcher:chrome_test_util::TableViewSwitchCell(
-                                   kImproveChromeItemAccessibilityIdentifier,
-                                   /*is_toggled_on=*/UMACheckboxValue,
-                                   /*enabled=*/YES)]
-      assertWithMatcher:grey_sufficientlyVisible()];
-}
-
-// Scrolls down to |elementMatcher| in the scrollable content of the first run
-// screen.
-- (void)scrollToElementAndAssertVisibility:(id<GREYMatcher>)elementMatcher {
-  id<GREYMatcher> scrollView =
-      grey_accessibilityID(kPromoStyleScrollViewAccessibilityIdentifier);
-  [[[EarlGrey
-      selectElementWithMatcher:grey_allOf(elementMatcher,
-                                          grey_sufficientlyVisible(), nil)]
-         usingSearchAction:grey_scrollInDirection(kGREYDirectionDown, 50)
-      onElementWithMatcher:scrollView] assertWithMatcher:grey_notNil()];
-}
-
 @end
diff --git a/ios/chrome/browser/ui/first_run/uma/uma_table_view_controller.mm b/ios/chrome/browser/ui/first_run/uma/uma_table_view_controller.mm
index 53a3fed..81ffa3e 100644
--- a/ios/chrome/browser/ui/first_run/uma/uma_table_view_controller.mm
+++ b/ios/chrome/browser/ui/first_run/uma/uma_table_view_controller.mm
@@ -135,8 +135,6 @@
   switchItem.on = self.UMAReportingUserChoice;
   switchItem.text =
       l10n_util::GetNSString(IDS_IOS_FIRST_RUN_UMA_DIALOG_CHECKBOX);
-  switchItem.accessibilityIdentifier =
-      kImproveChromeItemAccessibilityIdentifier;
   [model addItem:switchItem toSectionWithIdentifier:UMAMainSectionIdentifier];
 
   // Adds the footer.
diff --git a/ios/chrome/browser/ui/settings/google_services/google_services_settings_mediator.mm b/ios/chrome/browser/ui/settings/google_services/google_services_settings_mediator.mm
index 2efac12..baa759f 100644
--- a/ios/chrome/browser/ui/settings/google_services/google_services_settings_mediator.mm
+++ b/ios/chrome/browser/ui/settings/google_services/google_services_settings_mediator.mm
@@ -40,7 +40,6 @@
 #import "ios/chrome/browser/ui/ui_feature_flags.h"
 #import "ios/chrome/browser/ui/util/uikit_ui_util.h"
 #import "ios/chrome/common/ui/colors/semantic_color_names.h"
-#import "ios/chrome/common/ui/table_view/table_view_cells_constants.h"
 #include "ios/chrome/grit/ios_chromium_strings.h"
 #include "ios/chrome/grit/ios_strings.h"
 #import "ios/public/provider/chrome/browser/signin/chrome_identity.h"
@@ -420,8 +419,6 @@
                         IDS_IOS_GOOGLE_SERVICES_SETTINGS_IMPROVE_CHROME_TEXT
                   detailStringID:
                       IDS_IOS_GOOGLE_SERVICES_SETTINGS_IMPROVE_CHROME_DETAIL];
-      improveChromeItem.accessibilityIdentifier =
-          kImproveChromeItemAccessibilityIdentifier;
       [items addObject:improveChromeItem];
     }
     if (self.userPrefService->IsManagedPreference(
diff --git a/ios/chrome/common/ui/table_view/table_view_cells_constants.h b/ios/chrome/common/ui/table_view/table_view_cells_constants.h
index d01d315..69f5507 100644
--- a/ios/chrome/common/ui/table_view/table_view_cells_constants.h
+++ b/ios/chrome/common/ui/table_view/table_view_cells_constants.h
@@ -73,8 +73,4 @@
 // Returns a padding according to the width of the current device.
 extern CGFloat HorizontalPadding();
 
-// Accessibility identifier for UMA checkbox in the FRE and in Google services
-// settings.
-extern NSString* const kImproveChromeItemAccessibilityIdentifier;
-
 #endif  // IOS_CHROME_COMMON_UI_TABLE_VIEW_TABLE_VIEW_CELLS_CONSTANTS_H_
diff --git a/ios/chrome/common/ui/table_view/table_view_cells_constants.mm b/ios/chrome/common/ui/table_view/table_view_cells_constants.mm
index 6961ea7d..252301d 100644
--- a/ios/chrome/common/ui/table_view/table_view_cells_constants.mm
+++ b/ios/chrome/common/ui/table_view/table_view_cells_constants.mm
@@ -35,9 +35,6 @@
 NSString* const kTableViewURLCellFaviconBadgeViewID =
     @"TableViewURLCellFaviconBadgeView";
 
-NSString* const kImproveChromeItemAccessibilityIdentifier =
-    @"ImproveChromeItemAccessibilityIdentifier";
-
 CGFloat HorizontalPadding() {
   if (!IsSmallDevice())
     return 0;
diff --git a/ios/chrome/test/testing_application_context.h b/ios/chrome/test/testing_application_context.h
index 7afd4ce1..474ae2b 100644
--- a/ios/chrome/test/testing_application_context.h
+++ b/ios/chrome/test/testing_application_context.h
@@ -69,6 +69,8 @@
   breadcrumbs::BreadcrumbPersistentStorageManager*
   GetBreadcrumbPersistentStorageManager() override;
   id<SingleSignOnService> GetSSOService() override;
+  segmentation_platform::OTRWebStateObserver*
+  GetSegmentationOTRWebStateObserver() override;
 
  private:
   base::ThreadChecker thread_checker_;
diff --git a/ios/chrome/test/testing_application_context.mm b/ios/chrome/test/testing_application_context.mm
index 9b5e7755..4c28430c 100644
--- a/ios/chrome/test/testing_application_context.mm
+++ b/ios/chrome/test/testing_application_context.mm
@@ -224,3 +224,9 @@
   }
   return single_sign_on_service_;
 }
+
+segmentation_platform::OTRWebStateObserver*
+TestingApplicationContext::GetSegmentationOTRWebStateObserver() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  return nullptr;
+}
diff --git a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios.zip.sha1
index 023f6bf..e57d898 100644
--- a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-71f147a54290876aafe378ae8741128f09416cad
\ No newline at end of file
+d6b1eb771ac0ce46bf579c38984da931fa0d005b
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator.zip.sha1
index 986db64..71fa1cb7 100644
--- a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-08febb77238e1beb7446f7dfb3676e536cd09a1e
\ No newline at end of file
+2f9790030838889f05609e3709afd13c3a658973
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios.zip.sha1
index a162fe0..61ee0f7 100644
--- a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-f371cd8e92ec8927835b186d676da21c4e858342
\ No newline at end of file
+ad75f0ce710965c4dd4d5077b80dc0637ca707e5
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator.zip.sha1
index d6d3f50..3bd95f85 100644
--- a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-daee7e794b1064a629d84b544d27b2a4d0aef6d7
\ No newline at end of file
+7755450dc8a3ca0ede999d2e50aa04eb96e2e8fc
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.ios.zip.sha1
index 110b589..5e24222 100644
--- a/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-4924611f57a67b154d8fa33033d4e8247c21b9e5
\ No newline at end of file
+fc24f4114ddabf325b5350fde2532278bef88ddd
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.iossimulator.zip.sha1
index b89a6e6..128507b 100644
--- a/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-915eb1d3129f54050383264d367b0f645214f5fb
\ No newline at end of file
+a5833abcca3a3e57b42c501994897c2457f5e0c7
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios.zip.sha1
index 17a043b..a4b64ed 100644
--- a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-a3e08fd5d329abc4a3dafb74502a025a10c4a470
\ No newline at end of file
+1a8e92fa18e95fc3ad466483b54c0dcfd684dd11
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator.zip.sha1
index fce592e..f1e87088 100644
--- a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-1d247e75083795a211a08f732981799bb6ffd112
\ No newline at end of file
+c420faf55a29a041120efdc713459ba19b3a6a53
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios.zip.sha1
index 58ac4e2..65ac346 100644
--- a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-bd59c3207012b9788eb9e439c9980cec6fe3ae86
\ No newline at end of file
+6288b25249f085814db8414683ecde1b59a450c8
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator.zip.sha1
index 9f3b255..f2314ee 100644
--- a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-9d6b945a914e75817f9bd33ec7ed940eb964fdfa
\ No newline at end of file
+bcdb811e992c5071e9ebdc742f2c9bfc98b9bbd5
\ No newline at end of file
diff --git a/ios/third_party/material_text_accessibility_ios/BUILD.gn b/ios/third_party/material_text_accessibility_ios/BUILD.gn
index 1e3a67a2..badf9c23 100644
--- a/ios/third_party/material_text_accessibility_ios/BUILD.gn
+++ b/ios/third_party/material_text_accessibility_ios/BUILD.gn
@@ -21,8 +21,6 @@
     "src/src/MDFTextAccessibility.m",
     "src/src/private/MDFColorCalculations.h",
     "src/src/private/MDFColorCalculations.m",
-    "src/src/private/MDFImageCalculations.h",
-    "src/src/private/MDFImageCalculations.m",
     "src/src/private/NSArray+MDFUtils.h",
     "src/src/private/NSArray+MDFUtils.m",
   ]
diff --git a/media/audio/simple_sources.cc b/media/audio/simple_sources.cc
index a24ac24..fefaf960 100644
--- a/media/audio/simple_sources.cc
+++ b/media/audio/simple_sources.cc
@@ -46,7 +46,7 @@
     return nullptr;
   }
 
-  std::unique_ptr<char[]> data(new char[wav_file_length]);
+  auto data = std::make_unique<char[]>(wav_file_length);
   int read_bytes = wav_file.Read(0, data.get(), wav_file_length);
   if (read_bytes != wav_file_length) {
     LOG(ERROR) << "Failed to read all bytes of " << wav_filename.value();
diff --git a/media/audio/win/waveout_output_win.cc b/media/audio/win/waveout_output_win.cc
index 67a732a..09d65e73 100644
--- a/media/audio/win/waveout_output_win.cc
+++ b/media/audio/win/waveout_output_win.cc
@@ -149,7 +149,7 @@
 }
 
 void PCMWaveOutAudioOutputStream::SetupBuffers() {
-  buffers_.reset(new char[BufferSize() * num_buffers_]);
+  buffers_ = std::make_unique<char[]>(BufferSize() * num_buffers_);
   for (int ix = 0; ix != num_buffers_; ++ix) {
     WAVEHDR* buffer = GetBuffer(ix);
     buffer->lpData = reinterpret_cast<char*>(buffer) + sizeof(WAVEHDR);
diff --git a/media/base/android/media_codec_loop_unittest.cc b/media/base/android/media_codec_loop_unittest.cc
index 5cd34014..5613ea9 100644
--- a/media/base/android/media_codec_loop_unittest.cc
+++ b/media/base/android/media_codec_loop_unittest.cc
@@ -92,7 +92,7 @@
 
   void ConstructCodecLoop() {
     int sdk_int = base::android::SDK_VERSION_MARSHMALLOW;
-    std::unique_ptr<MediaCodecBridge> codec(new MockMediaCodecBridge());
+    auto codec = std::make_unique<MockMediaCodecBridge>();
     // Since we're providing a codec, we do not expect an error.
     EXPECT_CALL(*client_, OnCodecLoopError()).Times(0);
     codec_loop_ = std::make_unique<MediaCodecLoop>(
diff --git a/media/base/audio_block_fifo_unittest.cc b/media/base/audio_block_fifo_unittest.cc
index 98961c7..fd443e69 100644
--- a/media/base/audio_block_fifo_unittest.cc
+++ b/media/base/audio_block_fifo_unittest.cc
@@ -39,7 +39,7 @@
     DCHECK_LE(frames_to_push, fifo->GetUnfilledFrames());
     const int bytes_per_sample = 2;
     const int data_byte_size = bytes_per_sample * channels * frames_to_push;
-    std::unique_ptr<uint8_t[]> data(new uint8_t[data_byte_size]);
+    auto data = std::make_unique<uint8_t[]>(data_byte_size);
     memset(data.get(), 1, data_byte_size);
     fifo->Push(data.get(), frames_to_push, bytes_per_sample);
   }
diff --git a/media/base/byte_queue.cc b/media/base/byte_queue.cc
index ed3d67eb..79cd83e 100644
--- a/media/base/byte_queue.cc
+++ b/media/base/byte_queue.cc
@@ -43,14 +43,14 @@
       //
       // In local tests on a few top video sites that ends up being the common
       // case, so just prefer to copy and pack ourselves.
-      std::unique_ptr<uint8_t[]> new_buffer(new uint8_t[new_size]);
+      auto new_buffer = std::make_unique<uint8_t[]>(new_size);
       memcpy(new_buffer.get(), Front(), used_);
       buffer_ = std::move(new_buffer);
     } else {
       // Free the existing |data| first so that the memory can be reused, if
       // possible. Note that the new array is purposely not initialized.
       buffer_.reset();
-      buffer_.reset(new uint8_t[new_size]);
+      buffer_ = std::make_unique<uint8_t[]>(new_size);
     }
 
     size_ = new_size;
diff --git a/media/base/data_buffer.cc b/media/base/data_buffer.cc
index 5cd1bfb..97e90f27 100644
--- a/media/base/data_buffer.cc
+++ b/media/base/data_buffer.cc
@@ -12,7 +12,7 @@
     : buffer_size_(buffer_size),
       data_size_(0) {
   CHECK_GE(buffer_size, 0);
-  data_.reset(new uint8_t[buffer_size_]);
+  data_ = std::make_unique<uint8_t[]>(buffer_size_);
 }
 
 DataBuffer::DataBuffer(std::unique_ptr<uint8_t[]> buffer, int buffer_size)
@@ -31,7 +31,7 @@
   }
 
   CHECK_GE(data_size, 0);
-  data_.reset(new uint8_t[buffer_size_]);
+  data_ = std::make_unique<uint8_t[]>(buffer_size_);
   memcpy(data_.get(), data, data_size_);
 }
 
diff --git a/media/base/decoder_buffer_unittest.cc b/media/base/decoder_buffer_unittest.cc
index 91a871a0..ac3c736a 100644
--- a/media/base/decoder_buffer_unittest.cc
+++ b/media/base/decoder_buffer_unittest.cc
@@ -65,7 +65,7 @@
 TEST(DecoderBufferTest, FromArray) {
   const uint8_t kData[] = "hello";
   const size_t kDataSize = std::size(kData);
-  std::unique_ptr<uint8_t[]> ptr(new uint8_t[kDataSize]);
+  auto ptr = std::make_unique<uint8_t[]>(kDataSize);
   memcpy(ptr.get(), kData, kDataSize);
 
   scoped_refptr<DecoderBuffer> buffer(
diff --git a/media/base/key_systems.cc b/media/base/key_systems.cc
index 0d7283d..ccfd3bcd 100644
--- a/media/base/key_systems.cc
+++ b/media/base/key_systems.cc
@@ -469,7 +469,7 @@
   is_updating_ = false;
 
   // Clear Key is always supported.
-  key_systems.emplace_back(new ClearKeyProperties());
+  key_systems.emplace_back(std::make_unique<ClearKeyProperties>());
 
   ProcessSupportedKeySystems(std::move(key_systems));
 
diff --git a/media/base/key_systems_unittest.cc b/media/base/key_systems_unittest.cc
index b08284f7..fc4df1bc 100644
--- a/media/base/key_systems_unittest.cc
+++ b/media/base/key_systems_unittest.cc
@@ -303,10 +303,10 @@
 KeySystemPropertiesVector TestMediaClient::GetSupportedKeySystemsInternal() {
   KeySystemPropertiesVector key_systems;
 
-  key_systems.emplace_back(new AesKeySystemProperties(kUsesAes));
+  key_systems.emplace_back(std::make_unique<AesKeySystemProperties>(kUsesAes));
 
   if (supports_external_key_system_)
-    key_systems.emplace_back(new ExternalKeySystemProperties());
+    key_systems.emplace_back(std::make_unique<ExternalKeySystemProperties>());
 
   return key_systems;
 }
diff --git a/media/base/null_video_sink_unittest.cc b/media/base/null_video_sink_unittest.cc
index cc89d6fa..63dbe22 100644
--- a/media/base/null_video_sink_unittest.cc
+++ b/media/base/null_video_sink_unittest.cc
@@ -40,11 +40,11 @@
 
   std::unique_ptr<NullVideoSink> ConstructSink(bool clockless,
                                                base::TimeDelta interval) {
-    std::unique_ptr<NullVideoSink> new_sink(
-        new NullVideoSink(clockless, interval,
-                          base::BindRepeating(&NullVideoSinkTest::FrameReceived,
-                                              base::Unretained(this)),
-                          task_environment_.GetMainThreadTaskRunner()));
+    auto new_sink = std::make_unique<NullVideoSink>(
+        clockless, interval,
+        base::BindRepeating(&NullVideoSinkTest::FrameReceived,
+                            base::Unretained(this)),
+        task_environment_.GetMainThreadTaskRunner());
     new_sink->set_tick_clock_for_testing(&tick_clock_);
     return new_sink;
   }
diff --git a/media/base/supported_video_decoder_config.h b/media/base/supported_video_decoder_config.h
index 45ff46a..fc899eba 100644
--- a/media/base/supported_video_decoder_config.h
+++ b/media/base/supported_video_decoder_config.h
@@ -37,6 +37,18 @@
   // Returns true if and only if |config| is a supported config.
   bool Matches(const VideoDecoderConfig& config) const;
 
+  bool operator==(const SupportedVideoDecoderConfig& other) const {
+    return profile_min == other.profile_min &&
+           profile_max == other.profile_max &&
+           coded_size_min == other.coded_size_min &&
+           coded_size_max == other.coded_size_max &&
+           allow_encrypted == other.allow_encrypted &&
+           require_encrypted == other.require_encrypted;
+  }
+  bool operator!=(const SupportedVideoDecoderConfig& other) const {
+    return !(*this == other);
+  }
+
   // Range of VideoCodecProfiles to match, inclusive.
   VideoCodecProfile profile_min = VIDEO_CODEC_PROFILE_UNKNOWN;
   VideoCodecProfile profile_max = VIDEO_CODEC_PROFILE_UNKNOWN;
diff --git a/media/base/video_util_unittest.cc b/media/base/video_util_unittest.cc
index 01eaf76..64dc83e 100644
--- a/media/base/video_util_unittest.cc
+++ b/media/base/video_util_unittest.cc
@@ -167,9 +167,9 @@
     u_stride_ = u_stride;
     v_stride_ = v_stride;
 
-    y_plane_.reset(new uint8_t[y_stride * height]);
-    u_plane_.reset(new uint8_t[u_stride * height / 2]);
-    v_plane_.reset(new uint8_t[v_stride * height / 2]);
+    y_plane_ = std::make_unique<uint8_t[]>(y_stride * height);
+    u_plane_ = std::make_unique<uint8_t[]>(u_stride * height / 2);
+    v_plane_ = std::make_unique<uint8_t[]>(v_stride * height / 2);
   }
 
   void CreateDestinationFrame(int width, int height) {
@@ -328,7 +328,7 @@
     : public testing::TestWithParam<VideoRotationTestData> {
  public:
   VideoUtilRotationTest() {
-    dest_.reset(new uint8_t[GetParam().width * GetParam().height]);
+    dest_ = std::make_unique<uint8_t[]>(GetParam().width * GetParam().height);
   }
   VideoUtilRotationTest(const VideoUtilRotationTest&) = delete;
   VideoUtilRotationTest& operator=(const VideoUtilRotationTest&) = delete;
diff --git a/media/capture/video/android/video_capture_device_android.cc b/media/capture/video/android/video_capture_device_android.cc
index 2e5f448..04aa8e9 100644
--- a/media/capture/video/android/video_capture_device_android.cc
+++ b/media/capture/video/android/video_capture_device_android.cc
@@ -350,7 +350,7 @@
   const int y_plane_length = width * height;
   const int uv_plane_length = y_plane_length / 4;
   const int buffer_length = y_plane_length + uv_plane_length * 2;
-  std::unique_ptr<uint8_t[]> buffer(new uint8_t[buffer_length]);
+  auto buffer = std::make_unique<uint8_t[]>(buffer_length);
 
   libyuv::Android420ToI420(y_src, y_stride, u_src, uv_row_stride, v_src,
                            uv_row_stride, uv_pixel_stride, buffer.get(), width,
@@ -677,8 +677,7 @@
   JNIEnv* env = AttachCurrentThread();
 
   // Make copy on the heap so we can pass the pointer through JNI.
-  std::unique_ptr<TakePhotoCallback> heap_callback(
-      new TakePhotoCallback(std::move(callback)));
+  auto heap_callback = std::make_unique<TakePhotoCallback>(std::move(callback));
   const intptr_t callback_id = reinterpret_cast<intptr_t>(heap_callback.get());
   {
     base::AutoLock lock(photo_callbacks_lock_);
@@ -700,8 +699,8 @@
   JNIEnv* env = AttachCurrentThread();
 
   // Make copy on the heap so we can pass the pointer through JNI.
-  std::unique_ptr<GetPhotoStateCallback> heap_callback(
-      new GetPhotoStateCallback(std::move(callback)));
+  auto heap_callback =
+      std::make_unique<GetPhotoStateCallback>(std::move(callback));
   const intptr_t callback_id = reinterpret_cast<intptr_t>(heap_callback.get());
   {
     base::AutoLock lock(photo_callbacks_lock_);
diff --git a/media/capture/video/android/video_capture_device_factory_android.cc b/media/capture/video/android/video_capture_device_factory_android.cc
index aeddd59..a9a8aa4 100644
--- a/media/capture/video/android/video_capture_device_factory_android.cc
+++ b/media/capture/video/android/video_capture_device_factory_android.cc
@@ -41,8 +41,8 @@
         VideoCaptureError::
             kVideoCaptureControllerInvalidOrUnsupportedVideoCaptureParametersRequested);
 
-  std::unique_ptr<VideoCaptureDeviceAndroid> video_capture_device(
-      new VideoCaptureDeviceAndroid(device_descriptor));
+  auto video_capture_device =
+      std::make_unique<VideoCaptureDeviceAndroid>(device_descriptor);
 
   if (video_capture_device->Init()) {
     if (test_mode_)
diff --git a/media/capture/video/blob_utils.cc b/media/capture/video/blob_utils.cc
index 5be7429..79b436f 100644
--- a/media/capture/video/blob_utils.cc
+++ b/media/capture/video/blob_utils.cc
@@ -109,8 +109,8 @@
 
   const gfx::Size frame_size = capture_format.frame_size;
   // PNGCodec does not support YUV formats, convert to a temporary ARGB buffer.
-  std::unique_ptr<uint8_t[]> tmp_argb(
-      new uint8_t[VideoFrame::AllocationSize(PIXEL_FORMAT_ARGB, frame_size)]);
+  auto tmp_argb = std::make_unique<uint8_t[]>(
+      VideoFrame::AllocationSize(PIXEL_FORMAT_ARGB, frame_size));
   if (ConvertToARGB(buffer, bytesused, tmp_argb.get(), frame_size.width() * 4,
                     0 /* crop_x_pos */, 0 /* crop_y_pos */, frame_size.width(),
                     frame_size.height(), frame_size.width(),
diff --git a/media/capture/video/fake_video_capture_device.cc b/media/capture/video/fake_video_capture_device.cc
index 84740ff5..e33dd01 100644
--- a/media/capture/video/fake_video_capture_device.cc
+++ b/media/capture/video/fake_video_capture_device.cc
@@ -726,8 +726,8 @@
     std::unique_ptr<VideoCaptureDevice::Client> client,
     const FakeDeviceState* device_state) {
   FrameDeliverer::Initialize(pixel_format, std::move(client), device_state);
-  buffer_.reset(new uint8_t[VideoFrame::AllocationSize(
-      pixel_format, device_state->format.frame_size)]);
+  buffer_ = std::make_unique<uint8_t[]>(VideoFrame::AllocationSize(
+      pixel_format, device_state->format.frame_size));
 }
 
 void OwnBufferFrameDeliverer::PaintAndDeliverNextFrame(
diff --git a/media/capture/video/file_video_capture_device.cc b/media/capture/video/file_video_capture_device.cc
index 759077b7..ad5a43b 100644
--- a/media/capture/video/file_video_capture_device.cc
+++ b/media/capture/video/file_video_capture_device.cc
@@ -223,7 +223,7 @@
 
 const uint8_t* Y4mFileParser::GetNextFrame(int* frame_size) {
   if (!video_frame_)
-    video_frame_.reset(new uint8_t[frame_size_]);
+    video_frame_ = std::make_unique<uint8_t[]>(frame_size_);
   int result =
       file_->Read(current_byte_index_,
                   reinterpret_cast<char*>(video_frame_.get()), frame_size_);
@@ -344,7 +344,8 @@
       if ([&frame, &frame_buffer_size, &frame_size, &jpeg_to_i420_buffer_]() {
             const size_t i420_buffer_size =
                 VideoFrame::AllocationSize(PIXEL_FORMAT_I420, frame_size);
-            jpeg_to_i420_buffer_.reset(new uint8_t[i420_buffer_size]);
+            jpeg_to_i420_buffer_ =
+                std::make_unique<uint8_t[]>(i420_buffer_size);
 
             uint8_t* dst_yp = jpeg_to_i420_buffer_.get();
             uint8_t* dst_up =
@@ -394,7 +395,7 @@
                frame_size.height() - crop_height);
   const size_t crop_buffer_size =
       VideoFrame::AllocationSize(PIXEL_FORMAT_I420, crop_size);
-  std::unique_ptr<uint8_t[]> crop_frame(new uint8_t[crop_buffer_size]);
+  auto crop_frame = std::make_unique<uint8_t[]>(crop_buffer_size);
 
   uint8_t* crop_yp = crop_frame.get();
   uint8_t* crop_up =
@@ -423,7 +424,7 @@
   const auto& scale_size = frame_size;
   const size_t scale_buffer_size =
       VideoFrame::AllocationSize(PIXEL_FORMAT_I420, scale_size);
-  std::unique_ptr<uint8_t[]> scale_frame(new uint8_t[scale_buffer_size]);
+  auto scale_frame = std::make_unique<uint8_t[]>(scale_buffer_size);
 
   uint8_t* scale_yp = scale_frame.get();
   uint8_t* scale_up =
diff --git a/media/capture/video/video_capture_device.h b/media/capture/video/video_capture_device.h
index 3ba84b5..480d8d74 100644
--- a/media/capture/video/video_capture_device.h
+++ b/media/capture/video/video_capture_device.h
@@ -318,7 +318,9 @@
   // By including it in frame's metadata, Viz informs Blink what was the
   // latest invocation of cropTo() before a given frame was produced.
   //
-  // The callback reports success/failure.
+  // The callback reports success/failure. It is called on an unspecified
+  // thread, it's the caller's responsibility to wrap it (i.e. via BindPostTask)
+  // as needed.
   virtual void Crop(
       const base::Token& crop_id,
       uint32_t crop_version,
diff --git a/media/capture/video/win/video_capture_device_mf_win_unittest.cc b/media/capture/video/win/video_capture_device_mf_win_unittest.cc
index 4e91155..92a22dc 100644
--- a/media/capture/video/win/video_capture_device_mf_win_unittest.cc
+++ b/media/capture/video/win/video_capture_device_mf_win_unittest.cc
@@ -1474,10 +1474,9 @@
       new MockMFMediaSource();
   Microsoft::WRL::ComPtr<MockMFCaptureEngine> engine =
       new MockMFCaptureEngine();
-  std::unique_ptr<VideoCaptureDeviceMFWin> device =
-      std::make_unique<VideoCaptureDeviceMFWin>(
-          descriptor, media_source,
-          /*mf_dxgi_device_manager=*/nullptr, engine);
+  auto device = std::make_unique<VideoCaptureDeviceMFWin>(
+      descriptor, media_source,
+      /*mf_dxgi_device_manager=*/nullptr, engine);
 
   EXPECT_CALL(*(engine.Get()), OnInitEventGuid).WillOnce([]() {
     return MF_CAPTURE_ENGINE_INITIALIZED;
diff --git a/media/cast/encoding/audio_encoder.cc b/media/cast/encoding/audio_encoder.cc
index 7866da9a..57c1777 100644
--- a/media/cast/encoding/audio_encoder.cc
+++ b/media/cast/encoding/audio_encoder.cc
@@ -487,7 +487,7 @@
                                       &cookie_size, nullptr) != noErr) {
       return false;
     }
-    std::unique_ptr<uint8_t[]> cookie_data(new uint8_t[cookie_size]);
+    auto cookie_data = std::make_unique<uint8_t[]>(cookie_size);
     if (AudioConverterGetProperty(converter_,
                                   kAudioConverterCompressionMagicCookie,
                                   &cookie_size, cookie_data.get()) != noErr) {
@@ -699,7 +699,8 @@
                  sampling_rate / kDefaultFramesPerSecond, /* 10 ms frames */
                  0 /* bitrate, which is unused for the PCM16 implementation */,
                  std::move(callback)),
-        buffer_(new int16_t[num_channels * samples_per_frame_]) {
+        buffer_(
+            std::make_unique<int16_t[]>(num_channels * samples_per_frame_)) {
     if (ImplBase::operational_status_ != STATUS_UNINITIALIZED)
       return;
     operational_status_ = STATUS_INITIALIZED;
diff --git a/media/cast/encoding/external_video_encoder.cc b/media/cast/encoding/external_video_encoder.cc
index c0172b4..8feef5ab 100644
--- a/media/cast/encoding/external_video_encoder.cc
+++ b/media/cast/encoding/external_video_encoder.cc
@@ -417,8 +417,7 @@
       InProgressExternalVideoFrameEncode& request =
           in_progress_frame_encodes_.front();
 
-      std::unique_ptr<SenderEncodedFrame> encoded_frame(
-          new SenderEncodedFrame());
+      auto encoded_frame = std::make_unique<SenderEncodedFrame>();
       encoded_frame->dependency =
           metadata.key_frame ? EncodedFrame::KEY : EncodedFrame::DEPENDENT;
       encoded_frame->frame_id = next_frame_id_++;
@@ -904,7 +903,8 @@
   const int rows_in_subset =
       std::max(1, size.height() * kFrameSamplingPercentage / 100);
   if (last_frame_size_ != size || !last_frame_pixel_buffer_) {
-    last_frame_pixel_buffer_.reset(new uint8_t[size.width() * rows_in_subset]);
+    last_frame_pixel_buffer_ =
+        std::make_unique<uint8_t[]>(size.width() * rows_in_subset);
     last_frame_size_ = size;
   }
 
diff --git a/media/cast/encoding/external_video_encoder_unittest.cc b/media/cast/encoding/external_video_encoder_unittest.cc
index cdf5e72b..7a9ad38 100644
--- a/media/cast/encoding/external_video_encoder_unittest.cc
+++ b/media/cast/encoding/external_video_encoder_unittest.cc
@@ -48,8 +48,8 @@
   QuantizerEstimator qe;
 
   const gfx::Size frame_size(320, 180);
-  const std::unique_ptr<uint8_t[]> black_frame_data(
-      new uint8_t[frame_size.GetArea()]);
+  const auto black_frame_data =
+      std::make_unique<uint8_t[]>(frame_size.GetArea());
   memset(black_frame_data.get(), 0, frame_size.GetArea());
   const scoped_refptr<VideoFrame> black_frame =
       CreateFrame(black_frame_data.get(), frame_size);
@@ -62,8 +62,8 @@
   for (int i = 0; i < 3; ++i)
     EXPECT_EQ(4.0, qe.EstimateForDeltaFrame(*black_frame));
 
-  const std::unique_ptr<uint8_t[]> checkerboard_frame_data(
-      new uint8_t[frame_size.GetArea()]);
+  const auto checkerboard_frame_data =
+      std::make_unique<uint8_t[]>(frame_size.GetArea());
   for (int i = 0, end = frame_size.GetArea(); i < end; ++i)
     checkerboard_frame_data.get()[i] = (((i % 2) == 0) ? 0 : 255);
   const scoped_refptr<VideoFrame> checkerboard_frame =
@@ -79,8 +79,8 @@
   // results in high quantizer estimates.
   for (int i = 0; i < 3; ++i) {
     int rand_seed = 0xdeadbeef + i;
-    const std::unique_ptr<uint8_t[]> random_frame_data(
-        new uint8_t[frame_size.GetArea()]);
+    const auto random_frame_data =
+        std::make_unique<uint8_t[]>(frame_size.GetArea());
     for (int j = 0, end = frame_size.GetArea(); j < end; ++j) {
       rand_seed = (1103515245 * rand_seed + 12345) % (1 << 31);
       random_frame_data.get()[j] = static_cast<uint8_t>(rand_seed & 0xff);
diff --git a/media/cast/encoding/video_encoder_impl.cc b/media/cast/encoding/video_encoder_impl.cc
index a93abce..4c3b3bf 100644
--- a/media/cast/encoding/video_encoder_impl.cc
+++ b/media/cast/encoding/video_encoder_impl.cc
@@ -44,7 +44,7 @@
   }
   encoder->UpdateRates(dynamic_config.bit_rate);
 
-  std::unique_ptr<SenderEncodedFrame> encoded_frame(new SenderEncodedFrame());
+  auto encoded_frame = std::make_unique<SenderEncodedFrame>();
   encoder->Encode(std::move(video_frame), reference_time, encoded_frame.get());
   encoded_frame->encode_completion_time = environment->Clock()->NowTicks();
   environment->PostTask(CastEnvironment::MAIN, FROM_HERE,
diff --git a/media/cast/logging/encoding_event_subscriber_unittest.cc b/media/cast/logging/encoding_event_subscriber_unittest.cc
index 427d0eb..62bd4688 100644
--- a/media/cast/logging/encoding_event_subscriber_unittest.cc
+++ b/media/cast/logging/encoding_event_subscriber_unittest.cc
@@ -80,7 +80,7 @@
   int width = 320;
   int height = 180;
   for (int i = 0; i < 11; i++) {
-    std::unique_ptr<FrameEvent> capture_begin_event(new FrameEvent());
+    auto capture_begin_event = std::make_unique<FrameEvent>();
     capture_begin_event->timestamp = now;
     capture_begin_event->type = FRAME_CAPTURE_BEGIN;
     capture_begin_event->media_type = VIDEO_EVENT;
@@ -89,7 +89,7 @@
     cast_environment_->logger()->DispatchFrameEvent(
         std::move(capture_begin_event));
 
-    std::unique_ptr<FrameEvent> capture_end_event(new FrameEvent());
+    auto capture_end_event = std::make_unique<FrameEvent>();
     capture_end_event->timestamp = now;
     capture_end_event->type = FRAME_CAPTURE_END;
     capture_end_event->media_type = VIDEO_EVENT;
@@ -99,7 +99,7 @@
     cast_environment_->logger()->DispatchFrameEvent(
         std::move(capture_end_event));
 
-    std::unique_ptr<FrameEvent> decoded_event(new FrameEvent());
+    auto decoded_event = std::make_unique<FrameEvent>();
     decoded_event->timestamp = now;
     decoded_event->type = FRAME_DECODED;
     decoded_event->media_type = VIDEO_EVENT;
@@ -133,7 +133,7 @@
 
   // Entry with RTP timestamp 0 should get dropped.
   for (int i = 0; i < 11; i++) {
-    std::unique_ptr<PacketEvent> receive_event(new PacketEvent());
+    auto receive_event = std::make_unique<PacketEvent>();
     receive_event->timestamp = now;
     receive_event->type = PACKET_RECEIVED;
     receive_event->media_type = AUDIO_EVENT;
@@ -160,7 +160,7 @@
 
   for (size_t i = 0; i < num_frame_event_protos; i++) {
     for (int j = 0; j < kMaxEventsPerProto; j++) {
-      std::unique_ptr<FrameEvent> capture_begin_event(new FrameEvent());
+      auto capture_begin_event = std::make_unique<FrameEvent>();
       capture_begin_event->timestamp = now;
       capture_begin_event->type = FRAME_CAPTURE_BEGIN;
       capture_begin_event->media_type = VIDEO_EVENT;
@@ -172,7 +172,7 @@
 
   for (size_t i = 0; i < num_packet_event_protos; i++) {
     for (int j = 0; j < kMaxEventsPerProto; j++) {
-      std::unique_ptr<PacketEvent> receive_event(new PacketEvent());
+      auto receive_event = std::make_unique<PacketEvent>();
       receive_event->timestamp = now;
       receive_event->type = PACKET_RECEIVED;
       receive_event->media_type = VIDEO_EVENT;
@@ -186,7 +186,7 @@
     }
   }
 
-  std::unique_ptr<FrameEvent> capture_begin_event(new FrameEvent());
+  auto capture_begin_event = std::make_unique<FrameEvent>();
   capture_begin_event->timestamp = now;
   capture_begin_event->type = FRAME_CAPTURE_BEGIN;
   capture_begin_event->media_type = VIDEO_EVENT;
@@ -204,7 +204,7 @@
 
   base::TimeTicks now(testing_clock_.NowTicks());
   RtpTimeTicks rtp_timestamp = RtpTimeTicks().Expand(UINT32_C(100));
-  std::unique_ptr<FrameEvent> video_event(new FrameEvent());
+  auto video_event = std::make_unique<FrameEvent>();
   video_event->timestamp = now;
   video_event->type = FRAME_DECODED;
   video_event->media_type = VIDEO_EVENT;
@@ -213,7 +213,7 @@
   cast_environment_->logger()->DispatchFrameEvent(std::move(video_event));
 
   // This is an AUDIO_EVENT and shouldn't be processed by the subscriber.
-  std::unique_ptr<FrameEvent> audio_event(new FrameEvent());
+  auto audio_event = std::make_unique<FrameEvent>();
   audio_event->timestamp = now;
   audio_event->type = FRAME_DECODED;
   audio_event->media_type = AUDIO_EVENT;
@@ -241,7 +241,7 @@
   Init(VIDEO_EVENT);
   base::TimeTicks now(testing_clock_.NowTicks());
   RtpTimeTicks rtp_timestamp = RtpTimeTicks().Expand(UINT32_C(100));
-  std::unique_ptr<FrameEvent> decode_event(new FrameEvent());
+  auto decode_event = std::make_unique<FrameEvent>();
   decode_event->timestamp = now;
   decode_event->type = FRAME_DECODED;
   decode_event->media_type = VIDEO_EVENT;
@@ -277,7 +277,7 @@
   base::TimeTicks now(testing_clock_.NowTicks());
   RtpTimeTicks rtp_timestamp = RtpTimeTicks().Expand(UINT32_C(100));
   int delay_ms = 100;
-  std::unique_ptr<FrameEvent> playout_event(new FrameEvent());
+  auto playout_event = std::make_unique<FrameEvent>();
   playout_event->timestamp = now;
   playout_event->type = FRAME_PLAYOUT;
   playout_event->media_type = AUDIO_EVENT;
@@ -316,7 +316,7 @@
   int target_bitrate = 1024;
   double encoder_cpu_utilization = 0.90;
   double idealized_bitrate_utilization = 0.42;
-  std::unique_ptr<FrameEvent> encode_event(new FrameEvent());
+  auto encode_event = std::make_unique<FrameEvent>();
   encode_event->timestamp = now;
   encode_event->type = FRAME_ENCODED;
   encode_event->media_type = VIDEO_EVENT;
@@ -359,7 +359,7 @@
   RtpTimeTicks rtp_timestamp1 = RtpTimeTicks().Expand(UINT32_C(100));
   RtpTimeTicks rtp_timestamp2 = rtp_timestamp1.Expand(UINT32_C(200));
   base::TimeTicks now1(testing_clock_.NowTicks());
-  std::unique_ptr<FrameEvent> playout_event(new FrameEvent());
+  auto playout_event = std::make_unique<FrameEvent>();
   playout_event->timestamp = now1;
   playout_event->type = FRAME_PLAYOUT;
   playout_event->media_type = AUDIO_EVENT;
@@ -370,7 +370,7 @@
 
   task_runner_->Sleep(base::Milliseconds(20));
   base::TimeTicks now2(testing_clock_.NowTicks());
-  std::unique_ptr<FrameEvent> encode_event(new FrameEvent());
+  auto encode_event = std::make_unique<FrameEvent>();
   encode_event->timestamp = now2;
   encode_event->type = FRAME_ENCODED;
   encode_event->media_type = AUDIO_EVENT;
@@ -383,7 +383,7 @@
 
   task_runner_->Sleep(base::Milliseconds(20));
   base::TimeTicks now3(testing_clock_.NowTicks());
-  std::unique_ptr<FrameEvent> decode_event(new FrameEvent());
+  auto decode_event = std::make_unique<FrameEvent>();
   decode_event->timestamp = now3;
   decode_event->type = FRAME_DECODED;
   decode_event->media_type = AUDIO_EVENT;
@@ -440,7 +440,8 @@
   RtpTimeTicks rtp_timestamp = RtpTimeTicks().Expand(UINT32_C(100));
   int packet_id = 2;
   int size = 100;
-  std::unique_ptr<PacketEvent> receive_event(new PacketEvent());
+  auto receive_event = std::make_unique<PacketEvent>();
+  ;
   receive_event->timestamp = now;
   receive_event->type = PACKET_RECEIVED;
   receive_event->media_type = AUDIO_EVENT;
@@ -482,7 +483,7 @@
   RtpTimeTicks rtp_timestamp = RtpTimeTicks().Expand(UINT32_C(100));
   int packet_id = 2;
   int size = 100;
-  std::unique_ptr<PacketEvent> send_event(new PacketEvent());
+  auto send_event = std::make_unique<PacketEvent>();
   send_event->timestamp = now1;
   send_event->type = PACKET_SENT_TO_NETWORK;
   send_event->media_type = VIDEO_EVENT;
@@ -495,7 +496,7 @@
 
   task_runner_->Sleep(base::Milliseconds(20));
   base::TimeTicks now2(testing_clock_.NowTicks());
-  std::unique_ptr<PacketEvent> retransmit_event(new PacketEvent());
+  auto retransmit_event = std::make_unique<PacketEvent>();
   retransmit_event->timestamp = now2;
   retransmit_event->type = PACKET_RETRANSMITTED;
   retransmit_event->media_type = VIDEO_EVENT;
@@ -537,7 +538,7 @@
   int packet_id_1 = 2;
   int packet_id_2 = 3;
   int size = 100;
-  std::unique_ptr<PacketEvent> send_event(new PacketEvent());
+  auto send_event = std::make_unique<PacketEvent>();
   send_event->timestamp = now1;
   send_event->type = PACKET_SENT_TO_NETWORK;
   send_event->media_type = VIDEO_EVENT;
@@ -550,7 +551,7 @@
 
   task_runner_->Sleep(base::Milliseconds(20));
   base::TimeTicks now2(testing_clock_.NowTicks());
-  std::unique_ptr<PacketEvent> retransmit_event(new PacketEvent());
+  auto retransmit_event = std::make_unique<PacketEvent>();
   retransmit_event->timestamp = now2;
   retransmit_event->type = PACKET_RETRANSMITTED;
   retransmit_event->media_type = VIDEO_EVENT;
@@ -598,7 +599,7 @@
   int packet_id_1 = 2;
   int packet_id_2 = 3;
   int size = 100;
-  std::unique_ptr<PacketEvent> send_event(new PacketEvent());
+  auto send_event = std::make_unique<PacketEvent>();
   send_event->timestamp = now1;
   send_event->type = PACKET_SENT_TO_NETWORK;
   send_event->media_type = VIDEO_EVENT;
@@ -611,7 +612,7 @@
 
   task_runner_->Sleep(base::Milliseconds(20));
   base::TimeTicks now2(testing_clock_.NowTicks());
-  std::unique_ptr<PacketEvent> retransmit_event(new PacketEvent());
+  auto retransmit_event = std::make_unique<PacketEvent>();
   retransmit_event->timestamp = now2;
   retransmit_event->type = PACKET_RETRANSMITTED;
   retransmit_event->media_type = VIDEO_EVENT;
@@ -669,7 +670,7 @@
   RtpTimeTicks rtp_timestamp = RtpTimeTicks().Expand(UINT32_C(12345));
   base::TimeTicks now(testing_clock_.NowTicks());
 
-  std::unique_ptr<FrameEvent> capture_begin_event(new FrameEvent());
+  auto capture_begin_event = std::make_unique<FrameEvent>();
   capture_begin_event->timestamp = now;
   capture_begin_event->type = FRAME_CAPTURE_BEGIN;
   capture_begin_event->media_type = VIDEO_EVENT;
@@ -677,7 +678,7 @@
   cast_environment_->logger()->DispatchFrameEvent(
       std::move(capture_begin_event));
 
-  std::unique_ptr<FrameEvent> capture_end_event(new FrameEvent());
+  auto capture_end_event = std::make_unique<FrameEvent>();
   capture_end_event->timestamp = now;
   capture_end_event->type = FRAME_CAPTURE_END;
   capture_end_event->media_type = VIDEO_EVENT;
@@ -720,7 +721,8 @@
   RtpTimeTicks rtp_timestamp = RtpTimeTicks() - RtpTimeDelta::FromTicks(20);
   base::TimeTicks now(testing_clock_.NowTicks());
 
-  std::unique_ptr<FrameEvent> capture_begin_event(new FrameEvent());
+  auto capture_begin_event = std::make_unique<FrameEvent>();
+  ;
   capture_begin_event->timestamp = now;
   capture_begin_event->type = FRAME_CAPTURE_BEGIN;
   capture_begin_event->media_type = VIDEO_EVENT;
@@ -729,7 +731,7 @@
       std::move(capture_begin_event));
 
   // RtpTimeTicks has now wrapped around.
-  std::unique_ptr<FrameEvent> capture_end_event(new FrameEvent());
+  auto capture_end_event = std::make_unique<FrameEvent>();
   capture_end_event->timestamp = now;
   capture_end_event->type = FRAME_CAPTURE_END;
   capture_end_event->media_type = VIDEO_EVENT;
@@ -756,7 +758,7 @@
   Init(VIDEO_EVENT);
   RtpTimeTicks rtp_timestamp = RtpTimeTicks().Expand(UINT32_C(100));
   for (int i = 0; i < kMaxEventsPerProto + 1; i++) {
-    std::unique_ptr<FrameEvent> ack_event(new FrameEvent());
+    auto ack_event = std::make_unique<FrameEvent>();
     ack_event->timestamp = testing_clock_.NowTicks();
     ack_event->type = FRAME_ACK_RECEIVED;
     ack_event->media_type = VIDEO_EVENT;
@@ -778,7 +780,7 @@
   EXPECT_EQ(kMaxEventsPerProto, frame_event->event_type_size());
 
   for (int i = 0; i < kMaxPacketsPerFrame + 1; i++) {
-    std::unique_ptr<PacketEvent> send_event(new PacketEvent());
+    auto send_event = std::make_unique<PacketEvent>();
     send_event->timestamp = testing_clock_.NowTicks();
     send_event->type = PACKET_SENT_TO_NETWORK;
     send_event->media_type = VIDEO_EVENT;
@@ -812,7 +814,7 @@
   }
 
   for (int j = 0; j < kMaxEventsPerProto + 1; j++) {
-    std::unique_ptr<PacketEvent> send_event(new PacketEvent());
+    auto send_event = std::make_unique<PacketEvent>();
     send_event->timestamp = testing_clock_.NowTicks();
     send_event->type = PACKET_SENT_TO_NETWORK;
     send_event->media_type = VIDEO_EVENT;
diff --git a/media/mojo/services/media_resource_shim.cc b/media/mojo/services/media_resource_shim.cc
index 6c90819..fbb3314 100644
--- a/media/mojo/services/media_resource_shim.cc
+++ b/media/mojo/services/media_resource_shim.cc
@@ -19,7 +19,7 @@
   DCHECK(demuxer_ready_cb_);
 
   for (auto& s : streams) {
-    streams_.emplace_back(new MojoDemuxerStreamAdapter(
+    streams_.emplace_back(std::make_unique<MojoDemuxerStreamAdapter>(
         std::move(s), base::BindOnce(&MediaResourceShim::OnStreamReady,
                                      weak_factory_.GetWeakPtr())));
   }
diff --git a/media/mojo/services/stable_video_decoder_service.cc b/media/mojo/services/stable_video_decoder_service.cc
index c1ef5118..e35e068e 100644
--- a/media/mojo/services/stable_video_decoder_service.cc
+++ b/media/mojo/services/stable_video_decoder_service.cc
@@ -28,7 +28,7 @@
 void StableVideoDecoderService::GetSupportedConfigs(
     GetSupportedConfigsCallback callback) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  NOTIMPLEMENTED();
+  dst_video_decoder_remote_->GetSupportedConfigs(std::move(callback));
 }
 
 void StableVideoDecoderService::Construct(
diff --git a/media/mojo/services/stable_video_decoder_service_unittest.cc b/media/mojo/services/stable_video_decoder_service_unittest.cc
index 48131597..a445bca 100644
--- a/media/mojo/services/stable_video_decoder_service_unittest.cc
+++ b/media/mojo/services/stable_video_decoder_service_unittest.cc
@@ -384,6 +384,58 @@
                                           /*expect_construct_call=*/false));
 }
 
+// Tests that a call to stable::mojom::VideoDecoder::GetSupportedConfigs() gets
+// routed correctly to the underlying mojom::VideoDecoder. Also tests that the
+// underlying mojom::VideoDecoder's reply gets routed correctly back to the
+// client.
+TEST_F(StableVideoDecoderServiceTest,
+       StableVideoDecoderCanGetSupportedConfigs) {
+  auto mock_video_decoder = std::make_unique<StrictMock<MockVideoDecoder>>();
+  auto* mock_video_decoder_raw = mock_video_decoder.get();
+  auto stable_video_decoder_remote =
+      CreateStableVideoDecoder(std::move(mock_video_decoder));
+  ASSERT_TRUE(stable_video_decoder_remote.is_bound());
+  ASSERT_TRUE(stable_video_decoder_remote.is_connected());
+
+  StrictMock<base::MockOnceCallback<void(
+      const std::vector<SupportedVideoDecoderConfig>& supported_configs,
+      VideoDecoderType decoder_type)>>
+      get_supported_configs_cb_to_send;
+  mojom::VideoDecoder::GetSupportedConfigsCallback
+      received_get_supported_configs_cb;
+  constexpr VideoDecoderType kDecoderTypeToReplyWith = VideoDecoderType::kVaapi;
+  const std::vector<SupportedVideoDecoderConfig>
+      supported_configs_to_reply_with({
+          {/*profile_min=*/H264PROFILE_MIN, /*profile_max=*/H264PROFILE_MAX,
+           /*coded_size_min=*/gfx::Size(320, 180),
+           /*coded_size_max=*/gfx::Size(1280, 720), /*allow_encrypted=*/false,
+           /*require_encrypted=*/false},
+          {/*profile_min=*/VP9PROFILE_MIN, /*profile_max=*/VP9PROFILE_MAX,
+           /*coded_size_min=*/gfx::Size(8, 8),
+           /*coded_size_max=*/gfx::Size(640, 360), /*allow_encrypted=*/true,
+           /*require_encrypted=*/true},
+      });
+  std::vector<SupportedVideoDecoderConfig> received_supported_configs;
+
+  EXPECT_CALL(*mock_video_decoder_raw, GetSupportedConfigs(/*callback=*/_))
+      .WillOnce([&](mojom::VideoDecoder::GetSupportedConfigsCallback callback) {
+        received_get_supported_configs_cb = std::move(callback);
+      });
+  EXPECT_CALL(get_supported_configs_cb_to_send, Run(_, kDecoderTypeToReplyWith))
+      .WillOnce(SaveArg<0>(&received_supported_configs));
+
+  stable_video_decoder_remote->GetSupportedConfigs(
+      get_supported_configs_cb_to_send.Get());
+  stable_video_decoder_remote.FlushForTesting();
+  ASSERT_TRUE(Mock::VerifyAndClearExpectations(mock_video_decoder_raw));
+
+  std::move(received_get_supported_configs_cb)
+      .Run(supported_configs_to_reply_with, kDecoderTypeToReplyWith);
+  task_environment_.RunUntilIdle();
+
+  EXPECT_EQ(received_supported_configs, supported_configs_to_reply_with);
+}
+
 // Tests that a call to stable::mojom::VideoDecoder::Initialize() gets routed
 // correctly to the underlying mojom::VideoDecoder. Also tests that when the
 // underlying mojom::VideoDecoder calls the initialization callback, the call
diff --git a/media/test/pipeline_integration_test.cc b/media/test/pipeline_integration_test.cc
index a52365f7..bdfd21e 100644
--- a/media/test/pipeline_integration_test.cc
+++ b/media/test/pipeline_integration_test.cc
@@ -196,23 +196,21 @@
   }
 
   std::unique_ptr<SimpleCdmPromise> CreatePromise(PromiseResult expected) {
-    std::unique_ptr<media::SimpleCdmPromise> promise(
-        new media::CdmCallbackPromise<>(
-            base::BindOnce(&KeyProvidingApp::OnResolve, base::Unretained(this),
-                           expected),
-            base::BindOnce(&KeyProvidingApp::OnReject, base::Unretained(this),
-                           expected)));
+    auto promise = std::make_unique<media::CdmCallbackPromise<>>(
+        base::BindOnce(&KeyProvidingApp::OnResolve, base::Unretained(this),
+                       expected),
+        base::BindOnce(&KeyProvidingApp::OnReject, base::Unretained(this),
+                       expected));
     return promise;
   }
 
   std::unique_ptr<NewSessionCdmPromise> CreateSessionPromise(
       PromiseResult expected) {
-    std::unique_ptr<media::NewSessionCdmPromise> promise(
-        new media::CdmCallbackPromise<std::string>(
-            base::BindOnce(&KeyProvidingApp::OnResolveWithSession,
-                           base::Unretained(this), expected),
-            base::BindOnce(&KeyProvidingApp::OnReject, base::Unretained(this),
-                           expected)));
+    auto promise = std::make_unique<media::CdmCallbackPromise<std::string>>(
+        base::BindOnce(&KeyProvidingApp::OnResolveWithSession,
+                       base::Unretained(this), expected),
+        base::BindOnce(&KeyProvidingApp::OnReject, base::Unretained(this),
+                       expected));
     return promise;
   }
 
diff --git a/media/test/pipeline_integration_test_base.cc b/media/test/pipeline_integration_test_base.cc
index 13724c3..ee0f6b3 100644
--- a/media/test/pipeline_integration_test_base.cc
+++ b/media/test/pipeline_integration_test_base.cc
@@ -489,11 +489,11 @@
       task_environment_.GetMainThreadTaskRunner());
 
   // Disable frame dropping if hashing is enabled.
-  std::unique_ptr<VideoRenderer> video_renderer(new VideoRendererImpl(
+  auto video_renderer = std::make_unique<VideoRendererImpl>(
       task_environment_.GetMainThreadTaskRunner(), video_sink_.get(),
       base::BindRepeating(&CreateVideoDecodersForTest, &media_log_,
                           prepend_video_decoders_cb_),
-      false, &media_log_, nullptr));
+      false, &media_log_, nullptr);
 
   if (!clockless_playback_) {
     DCHECK(!mono_output_) << " NullAudioSink doesn't specify output parameters";
@@ -518,7 +518,7 @@
     }
   }
 
-  std::unique_ptr<AudioRenderer> audio_renderer(new AudioRendererImpl(
+  auto audio_renderer = std::make_unique<AudioRendererImpl>(
       task_environment_.GetMainThreadTaskRunner(),
       (clockless_playback_)
           ? static_cast<AudioRendererSink*>(clockless_audio_sink_.get())
@@ -526,7 +526,7 @@
       base::BindRepeating(&CreateAudioDecodersForTest, &media_log_,
                           task_environment_.GetMainThreadTaskRunner(),
                           prepend_audio_decoders_cb_),
-      &media_log_, nullptr));
+      &media_log_, nullptr);
   if (hashing_enabled_) {
     if (clockless_playback_)
       clockless_audio_sink_->StartAudioHashForTesting();
@@ -537,9 +537,9 @@
   static_cast<AudioRendererImpl*>(audio_renderer.get())
       ->SetPlayDelayCBForTesting(std::move(audio_play_delay_cb_));
 
-  std::unique_ptr<RendererImpl> renderer_impl(
-      new RendererImpl(task_environment_.GetMainThreadTaskRunner(),
-                       std::move(audio_renderer), std::move(video_renderer)));
+  std::unique_ptr<RendererImpl> renderer_impl = std::make_unique<RendererImpl>(
+      task_environment_.GetMainThreadTaskRunner(), std::move(audio_renderer),
+      std::move(video_renderer));
 
   // Prevent non-deterministic buffering state callbacks from firing (e.g., slow
   // machine, valgrind).
diff --git a/media/video/av1_video_encoder.cc b/media/video/av1_video_encoder.cc
index 50121a0..4c86087 100644
--- a/media/video/av1_video_encoder.cc
+++ b/media/video/av1_video_encoder.cc
@@ -498,7 +498,7 @@
       result.temporal_id = temporal_id;
     }
 
-    result.data.reset(new uint8_t[result.size]);
+    result.data = std::make_unique<uint8_t[]>(result.size);
     memcpy(result.data.get(), pkt->data.frame.buf, result.size);
     output_cb_.Run(std::move(result), {});
   }
diff --git a/media/video/mock_gpu_video_accelerator_factories.cc b/media/video/mock_gpu_video_accelerator_factories.cc
index be039e6..88532ab 100644
--- a/media/video/mock_gpu_video_accelerator_factories.cc
+++ b/media/video/mock_gpu_video_accelerator_factories.cc
@@ -121,8 +121,8 @@
   base::AutoLock guard(lock_);
   if (fail_to_allocate_gpu_memory_buffer_)
     return nullptr;
-  std::unique_ptr<gfx::GpuMemoryBuffer> ret(
-      new GpuMemoryBufferImpl(size, format, fail_to_map_gpu_memory_buffer_));
+  auto ret = std::make_unique<GpuMemoryBufferImpl>(
+      size, format, fail_to_map_gpu_memory_buffer_);
   created_memory_buffers_.push_back(ret.get());
   return ret;
 }
diff --git a/media/video/openh264_video_encoder.cc b/media/video/openh264_video_encoder.cc
index 3fe94ce..86f37258 100644
--- a/media/video/openh264_video_encoder.cc
+++ b/media/video/openh264_video_encoder.cc
@@ -172,7 +172,7 @@
 
   DCHECK_GT(frame_info.iFrameSizeInBytes, 0);
   size_t total_chunk_size = frame_info.iFrameSizeInBytes;
-  result.data.reset(new uint8_t[total_chunk_size]);
+  result.data = std::make_unique<uint8_t[]>(total_chunk_size);
   auto* gather_buffer = result.data.get();
 
   if (h264_converter_) {
diff --git a/media/video/video_encode_accelerator_adapter.cc b/media/video/video_encode_accelerator_adapter.cc
index e21885b..47b6f94 100644
--- a/media/video/video_encode_accelerator_adapter.cc
+++ b/media/video/video_encode_accelerator_adapter.cc
@@ -502,7 +502,7 @@
       size_t dst_size = result.size;
       size_t actual_output_size = 0;
       bool config_changed = false;
-      std::unique_ptr<uint8_t[]> dst(new uint8_t[dst_size]);
+      auto dst = std::make_unique<uint8_t[]>(dst_size);
 
       auto status = h264_converter_->ConvertChunk(
           base::span<uint8_t>(src, result.size),
@@ -514,7 +514,7 @@
         // http://www.itu.int/rec/T-REC-H.264. Retry the conversion if the
         // output buffer size is too small.
         dst_size = actual_output_size;
-        dst.reset(new uint8_t[dst_size]);
+        dst = std::make_unique<uint8_t[]>(dst_size);
         status = h264_converter_->ConvertChunk(
             base::span<uint8_t>(src, result.size),
             base::span<uint8_t>(dst.get(), dst_size), &config_changed,
@@ -539,7 +539,7 @@
       }
     } else {
 #endif  // BUILDFLAG(USE_PROPRIETARY_CODECS)
-      result.data.reset(new uint8_t[result.size]);
+      result.data = std::make_unique<uint8_t[]>(result.size);
       memcpy(result.data.get(), mapping.memory(), result.size);
 #if BUILDFLAG(USE_PROPRIETARY_CODECS)
     }
diff --git a/media/video/vpx_video_encoder.cc b/media/video/vpx_video_encoder.cc
index 2ed71b0..969e6d2 100644
--- a/media/video/vpx_video_encoder.cc
+++ b/media/video/vpx_video_encoder.cc
@@ -717,7 +717,7 @@
       result.timestamp = ts;
       result.color_space = color_space;
       result.size = pkt->data.frame.sz;
-      result.data.reset(new uint8_t[result.size]);
+      result.data = std::make_unique<uint8_t[]>(result.size);
       memcpy(result.data.get(), pkt->data.frame.buf, result.size);
       output_cb_.Run(std::move(result), {});
     }
diff --git a/media/webrtc/audio_processor_test.cc b/media/webrtc/audio_processor_test.cc
index 941443d..b09edba2 100644
--- a/media/webrtc/audio_processor_test.cc
+++ b/media/webrtc/audio_processor_test.cc
@@ -113,7 +113,7 @@
     const int packet_size =
         input_params.frames_per_buffer() * 2 * input_params.channels();
     const size_t length = packet_size * kNumberOfPacketsForTest;
-    std::unique_ptr<char[]> capture_data(new char[length]);
+    auto capture_data = std::make_unique<char[]>(length);
     ReadDataFromSpeechFile(capture_data.get(), static_cast<int>(length));
     const int16_t* data_ptr =
         reinterpret_cast<const int16_t*>(capture_data.get());
diff --git a/net/http/transport_security_state_static.pins b/net/http/transport_security_state_static.pins
index a83b411..5a0f852 100644
--- a/net/http/transport_security_state_static.pins
+++ b/net/http/transport_security_state_static.pins
@@ -43,9 +43,9 @@
 #   hash function for preloaded entries again (we have already done so once).
 #
 
-# Last updated: 2022-04-25 00:00 UTC
+# Last updated: 2022-06-30 12:53 UTC
 PinsListTimestamp
-1650844800
+1656593634
 
 TestSPKI
 sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
diff --git a/net/third_party/quiche/BUILD.gn b/net/third_party/quiche/BUILD.gn
index 75a6a64..6a95c06 100644
--- a/net/third_party/quiche/BUILD.gn
+++ b/net/third_party/quiche/BUILD.gn
@@ -649,6 +649,7 @@
     "src/quiche/quic/platform/api/quic_hostname_utils.h",
     "src/quiche/quic/platform/api/quic_ip_address.cc",
     "src/quiche/quic/platform/api/quic_ip_address.h",
+    "src/quiche/quic/platform/api/quic_ip_address_family.cc",
     "src/quiche/quic/platform/api/quic_ip_address_family.h",
     "src/quiche/quic/platform/api/quic_logging.h",
     "src/quiche/quic/platform/api/quic_mutex.h",
@@ -777,6 +778,8 @@
       "src/quiche/epoll_server/platform/api/epoll_thread.h",
       "src/quiche/epoll_server/simple_epoll_server.cc",
       "src/quiche/epoll_server/simple_epoll_server.h",
+      "src/quiche/quic/core/io/socket.h",
+      "src/quiche/quic/core/io/socket_posix.cc",
       "src/quiche/quic/core/quic_default_packet_writer.cc",
       "src/quiche/quic/core/quic_default_packet_writer.h",
       "src/quiche/quic/core/quic_epoll_alarm_factory.cc",
@@ -810,6 +813,8 @@
       "src/quiche/quic/masque/masque_utils.cc",
       "src/quiche/quic/masque/masque_utils.h",
       "src/quiche/quic/platform/api/quic_epoll.h",
+      "src/quiche/quic/platform/api/quic_ip_address_family.h",
+      "src/quiche/quic/platform/api/quic_ip_address_family.cc",
       "src/quiche/quic/platform/api/quic_udp_socket_platform_api.h",
       "src/quiche/quic/tools/quic_client.cc",
       "src/quiche/quic/tools/quic_client.h",
diff --git a/storage/browser/BUILD.gn b/storage/browser/BUILD.gn
index f6fabb8c..9c1badd 100644
--- a/storage/browser/BUILD.gn
+++ b/storage/browser/BUILD.gn
@@ -145,8 +145,6 @@
     "file_system/obfuscated_file_util_memory_delegate.cc",
     "file_system/obfuscated_file_util_memory_delegate.h",
     "file_system/open_file_system_mode.h",
-    "file_system/plugin_private_file_system_backend.cc",
-    "file_system/plugin_private_file_system_backend.h",
     "file_system/quota/open_file_handle.cc",
     "file_system/quota/open_file_handle.h",
     "file_system/quota/open_file_handle_context.cc",
@@ -320,7 +318,6 @@
     "file_system/native_file_util_unittest.cc",
     "file_system/obfuscated_file_util_memory_delegate_unittest.cc",
     "file_system/obfuscated_file_util_unittest.cc",
-    "file_system/plugin_private_file_system_backend_unittest.cc",
     "file_system/quota/quota_backend_impl_unittest.cc",
     "file_system/quota/quota_reservation_manager_unittest.cc",
     "file_system/recursive_operation_delegate_unittest.cc",
diff --git a/storage/browser/file_system/file_system_context.cc b/storage/browser/file_system/file_system_context.cc
index 037dd61..1780694 100644
--- a/storage/browser/file_system/file_system_context.cc
+++ b/storage/browser/file_system/file_system_context.cc
@@ -125,7 +125,6 @@
     // don't have their own permission policies.
     case kFileSystemTypeDragged:
     case kFileSystemTypeForTransientFile:
-    case kFileSystemTypePluginPrivate:
       return FILE_PERMISSION_ALWAYS_DENY;
 
     // Following types only appear as mount_type, and will not be
@@ -205,12 +204,6 @@
           env_override_.get())),
       sandbox_backend_(
           std::make_unique<SandboxFileSystemBackend>(sandbox_delegate_.get())),
-      plugin_private_backend_(std::make_unique<PluginPrivateFileSystemBackend>(
-          default_file_task_runner_,
-          partition_path,
-          std::move(special_storage_policy),
-          options,
-          env_override_.get())),
       additional_backends_(std::move(additional_backends)),
       auto_mount_handlers_(auto_mount_handlers),
       external_mount_points_(std::move(external_mount_points)),
@@ -223,7 +216,6 @@
           std::make_unique<mojo::Receiver<mojom::QuotaClient>>(
               quota_client_wrapper_.get())) {
   RegisterBackend(sandbox_backend_.get());
-  RegisterBackend(plugin_private_backend_.get());
 
   for (const auto& backend : additional_backends_)
     RegisterBackend(backend.get());
@@ -242,7 +234,6 @@
 void FileSystemContext::Initialize() {
   sandbox_backend_->Initialize(this);
   isolated_backend_->Initialize(this);
-  plugin_private_backend_->Initialize(this);
   for (const auto& backend : additional_backends_)
     backend->Initialize(this);
 
diff --git a/storage/browser/file_system/file_system_context.h b/storage/browser/file_system/file_system_context.h
index f4401cdbe..cc80a12 100644
--- a/storage/browser/file_system/file_system_context.h
+++ b/storage/browser/file_system/file_system_context.h
@@ -28,7 +28,6 @@
 #include "storage/browser/file_system/file_system_request_info.h"
 #include "storage/browser/file_system/file_system_url.h"
 #include "storage/browser/file_system/open_file_system_mode.h"
-#include "storage/browser/file_system/plugin_private_file_system_backend.h"
 #include "storage/browser/file_system/sandbox_file_system_backend_delegate.h"
 #include "storage/browser/file_system/task_runner_bound_observer_list.h"
 #include "storage/common/file_system/file_system_types.h"
@@ -336,12 +335,6 @@
 
   bool is_incognito() { return is_incognito_; }
 
-  // TODO(crbug.com/1231162): Remove this. The Plugin Private File System is in
-  // the process of being removed.
-  PluginPrivateFileSystemBackend* plugin_private_backend() const {
-    return plugin_private_backend_.get();
-  }
-
   void ResolveURLOnOpenFileSystemForTesting(
       const blink::StorageKey& storage_key,
       const absl::optional<storage::BucketLocator>& bucket,
@@ -359,9 +352,6 @@
   // For sandbox_backend().
   friend class SandboxFileSystemTestHelper;
 
-  // For plugin_private_backend().
-  friend class PluginPrivateFileSystemBackendTest;
-
   // Deleters.
   friend class base::DeleteHelper<FileSystemContext>;
   friend class base::RefCountedDeleteOnSequence<FileSystemContext>;
@@ -451,7 +441,6 @@
   std::unique_ptr<IsolatedFileSystemBackend> isolated_backend_;
 
   // Additional file system backends.
-  const std::unique_ptr<PluginPrivateFileSystemBackend> plugin_private_backend_;
   const std::vector<std::unique_ptr<FileSystemBackend>> additional_backends_;
 
   std::vector<URLRequestAutoMountHandler> auto_mount_handlers_;
diff --git a/storage/browser/file_system/file_system_util.cc b/storage/browser/file_system/file_system_util.cc
index a32a53f..2b7657f 100644
--- a/storage/browser/file_system/file_system_util.cc
+++ b/storage/browser/file_system/file_system_util.cc
@@ -26,8 +26,6 @@
     case kFileSystemTypeSyncable:
     case kFileSystemTypeSyncableForInternalSync:
       return blink::mojom::StorageType::kSyncable;
-    case kFileSystemTypePluginPrivate:
-      return blink::mojom::StorageType::kQuotaNotManaged;
     default:
       return blink::mojom::StorageType::kUnknown;
   }
diff --git a/storage/browser/file_system/plugin_private_file_system_backend.cc b/storage/browser/file_system/plugin_private_file_system_backend.cc
deleted file mode 100644
index 603dc66..0000000
--- a/storage/browser/file_system/plugin_private_file_system_backend.cc
+++ /dev/null
@@ -1,474 +0,0 @@
-// Copyright 2013 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "storage/browser/file_system/plugin_private_file_system_backend.h"
-
-#include <stdint.h>
-
-#include <map>
-#include <memory>
-#include <utility>
-#include <vector>
-
-#include "base/bind.h"
-#include "base/containers/contains.h"
-#include "base/files/file_enumerator.h"
-#include "base/files/file_path.h"
-#include "base/memory/scoped_refptr.h"
-#include "base/synchronization/lock.h"
-#include "base/task/task_runner_util.h"
-#include "base/threading/thread_task_runner_handle.h"
-#include "base/time/time.h"
-#include "storage/browser/file_system/async_file_util_adapter.h"
-#include "storage/browser/file_system/file_system_context.h"
-#include "storage/browser/file_system/file_system_operation.h"
-#include "storage/browser/file_system/file_system_operation_context.h"
-#include "storage/browser/file_system/isolated_context.h"
-#include "storage/browser/file_system/obfuscated_file_util.h"
-#include "storage/browser/file_system/obfuscated_file_util_memory_delegate.h"
-#include "storage/browser/file_system/quota/quota_reservation.h"
-#include "storage/browser/file_system/sandbox_file_stream_reader.h"
-#include "storage/browser/file_system/sandbox_file_stream_writer.h"
-#include "storage/browser/quota/special_storage_policy.h"
-#include "storage/common/file_system/file_system_util.h"
-#include "third_party/blink/public/common/storage_key/storage_key.h"
-#include "url/origin.h"
-
-namespace storage {
-
-class PluginPrivateFileSystemBackend::FileSystemIDToPluginMap {
- public:
-  explicit FileSystemIDToPluginMap(
-      scoped_refptr<base::SequencedTaskRunner> task_runner)
-      : task_runner_(std::move(task_runner)) {}
-  ~FileSystemIDToPluginMap() = default;
-
-  std::string GetPluginIDForURL(const FileSystemURL& url) {
-    DCHECK(task_runner_->RunsTasksInCurrentSequence());
-    auto found = map_.find(url.filesystem_id());
-    if (url.type() != kFileSystemTypePluginPrivate || found == map_.end()) {
-      NOTREACHED() << "Unsupported url is given: " << url.DebugString();
-      return std::string();
-    }
-    return found->second;
-  }
-
-  void RegisterFileSystem(const std::string& filesystem_id,
-                          const std::string& plugin_id) {
-    DCHECK(task_runner_->RunsTasksInCurrentSequence());
-    DCHECK(!filesystem_id.empty());
-    DCHECK(!base::Contains(map_, filesystem_id)) << filesystem_id;
-    map_[filesystem_id] = plugin_id;
-  }
-
-  void RemoveFileSystem(const std::string& filesystem_id) {
-    DCHECK(task_runner_->RunsTasksInCurrentSequence());
-    map_.erase(filesystem_id);
-  }
-
- private:
-  using Map = std::map<std::string, std::string>;
-  const scoped_refptr<base::SequencedTaskRunner> task_runner_;
-  Map map_;
-};
-
-namespace {
-
-const base::FilePath::CharType* kFileSystemDirectory =
-    SandboxFileSystemBackendDelegate::kFileSystemDirectory;
-const base::FilePath::CharType* kPluginPrivateDirectory =
-    FILE_PATH_LITERAL("Plugins");
-
-base::File::Error OpenFileSystemOnFileTaskRunner(
-    ObfuscatedFileUtil* file_util,
-    PluginPrivateFileSystemBackend::FileSystemIDToPluginMap* plugin_map,
-    const url::Origin& origin,
-    const std::string& filesystem_id,
-    const std::string& plugin_id,
-    OpenFileSystemMode mode) {
-  base::File::Error error = base::File::FILE_ERROR_FAILED;
-  const bool create = (mode == OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT);
-  // TODO(https://crbug.com/1231162): determine whether EME/CDM/plugin private
-  // file system will be partitioned; if so, replace the in-line conversion with
-  // the correct third-party StorageKey.
-  file_util->GetDirectoryForStorageKeyAndType(blink::StorageKey(origin),
-                                              plugin_id, create, &error);
-  if (error == base::File::FILE_OK)
-    plugin_map->RegisterFileSystem(filesystem_id, plugin_id);
-  return error;
-}
-
-}  // namespace
-
-PluginPrivateFileSystemBackend::CdmFileInfo::CdmFileInfo(
-    const std::string& name,
-    const std::string& legacy_file_system_id)
-    : name(name), legacy_file_system_id(legacy_file_system_id) {}
-PluginPrivateFileSystemBackend::CdmFileInfo::CdmFileInfo(const CdmFileInfo&) =
-    default;
-PluginPrivateFileSystemBackend::CdmFileInfo::CdmFileInfo(CdmFileInfo&&) =
-    default;
-PluginPrivateFileSystemBackend::CdmFileInfo::~CdmFileInfo() = default;
-
-PluginPrivateFileSystemBackend::PluginPrivateFileSystemBackend(
-    scoped_refptr<base::SequencedTaskRunner> file_task_runner,
-    const base::FilePath& profile_path,
-    scoped_refptr<SpecialStoragePolicy> special_storage_policy,
-    const FileSystemOptions& file_system_options,
-    leveldb::Env* env_override)
-    : file_task_runner_(std::move(file_task_runner)),
-      file_system_options_(file_system_options),
-      base_path_(profile_path.Append(kFileSystemDirectory)
-                     .Append(kPluginPrivateDirectory)),
-      plugin_map_(new FileSystemIDToPluginMap(file_task_runner_)) {
-  file_util_ = std::make_unique<AsyncFileUtilAdapter>(
-      std::make_unique<ObfuscatedFileUtil>(
-          std::move(special_storage_policy), base_path_, env_override,
-          base::BindRepeating(&FileSystemIDToPluginMap::GetPluginIDForURL,
-                              base::Owned(plugin_map_.get())),
-          std::set<std::string>(), nullptr,
-          file_system_options.is_incognito()));
-}
-
-PluginPrivateFileSystemBackend::~PluginPrivateFileSystemBackend() {
-  if (!file_task_runner_->RunsTasksInCurrentSequence()) {
-    AsyncFileUtil* file_util = file_util_.release();
-    if (!file_task_runner_->DeleteSoon(FROM_HERE, file_util))
-      delete file_util;
-  }
-}
-
-void PluginPrivateFileSystemBackend::OpenPrivateFileSystem(
-    const url::Origin& origin,
-    FileSystemType type,
-    const std::string& filesystem_id,
-    const std::string& plugin_id,
-    OpenFileSystemMode mode,
-    StatusCallback callback) {
-  if (!CanHandleType(type)) {
-    base::ThreadTaskRunnerHandle::Get()->PostTask(
-        FROM_HERE,
-        base::BindOnce(std::move(callback), base::File::FILE_ERROR_SECURITY));
-    return;
-  }
-
-  PostTaskAndReplyWithResult(
-      file_task_runner_.get(), FROM_HERE,
-      base::BindOnce(&OpenFileSystemOnFileTaskRunner, obfuscated_file_util(),
-                     plugin_map_, origin, filesystem_id, plugin_id, mode),
-      std::move(callback));
-}
-
-bool PluginPrivateFileSystemBackend::CanHandleType(FileSystemType type) const {
-  return type == kFileSystemTypePluginPrivate;
-}
-
-void PluginPrivateFileSystemBackend::Initialize(FileSystemContext* context) {}
-
-void PluginPrivateFileSystemBackend::ResolveURL(const FileSystemURL& url,
-                                                OpenFileSystemMode mode,
-                                                ResolveURLCallback callback) {
-  // We never allow opening a new plugin-private filesystem via usual
-  // ResolveURL.
-  base::ThreadTaskRunnerHandle::Get()->PostTask(
-      FROM_HERE, base::BindOnce(std::move(callback), GURL(), std::string(),
-                                base::File::FILE_ERROR_SECURITY));
-}
-
-AsyncFileUtil* PluginPrivateFileSystemBackend::GetAsyncFileUtil(
-    FileSystemType type) {
-  return file_util_.get();
-}
-
-WatcherManager* PluginPrivateFileSystemBackend::GetWatcherManager(
-    FileSystemType type) {
-  return nullptr;
-}
-
-CopyOrMoveFileValidatorFactory*
-PluginPrivateFileSystemBackend::GetCopyOrMoveFileValidatorFactory(
-    FileSystemType type,
-    base::File::Error* error_code) {
-  DCHECK(error_code);
-  *error_code = base::File::FILE_OK;
-  return nullptr;
-}
-
-std::unique_ptr<FileSystemOperation>
-PluginPrivateFileSystemBackend::CreateFileSystemOperation(
-    const FileSystemURL& url,
-    FileSystemContext* context,
-    base::File::Error* error_code) const {
-  auto operation_context =
-      std::make_unique<FileSystemOperationContext>(context);
-  return FileSystemOperation::Create(url, context,
-                                     std::move(operation_context));
-}
-
-bool PluginPrivateFileSystemBackend::SupportsStreaming(
-    const FileSystemURL& url) const {
-  // Streaming is required for incognito file systems in order to access
-  // memory-backed files.
-  DCHECK(CanHandleType(url.type()));
-  return file_system_options_.is_incognito();
-}
-
-bool PluginPrivateFileSystemBackend::HasInplaceCopyImplementation(
-    FileSystemType type) const {
-  return false;
-}
-
-std::unique_ptr<FileStreamReader>
-PluginPrivateFileSystemBackend::CreateFileStreamReader(
-    const FileSystemURL& url,
-    int64_t offset,
-    int64_t max_bytes_to_read,
-    const base::Time& expected_modification_time,
-    FileSystemContext* context) const {
-  DCHECK(CanHandleType(url.type()));
-  return std::make_unique<SandboxFileStreamReader>(context, url, offset,
-                                                   expected_modification_time);
-}
-
-std::unique_ptr<FileStreamWriter>
-PluginPrivateFileSystemBackend::CreateFileStreamWriter(
-    const FileSystemURL& url,
-    int64_t offset,
-    FileSystemContext* context) const {
-  DCHECK(CanHandleType(url.type()));
-
-  // Observers not supported by PluginPrivateFileSystemBackend.
-  return std::make_unique<SandboxFileStreamWriter>(context, url, offset,
-                                                   UpdateObserverList());
-}
-
-FileSystemQuotaUtil* PluginPrivateFileSystemBackend::GetQuotaUtil() {
-  return this;
-}
-
-base::File::Error
-PluginPrivateFileSystemBackend::DeleteStorageKeyDataOnFileTaskRunner(
-    FileSystemContext* context,
-    QuotaManagerProxy* proxy,
-    const blink::StorageKey& storage_key,
-    FileSystemType type) {
-  if (!CanHandleType(type))
-    return base::File::FILE_ERROR_SECURITY;
-  bool result = obfuscated_file_util()->DeleteDirectoryForStorageKeyAndType(
-      storage_key, std::string());
-  if (result)
-    return base::File::FILE_OK;
-  return base::File::FILE_ERROR_FAILED;
-}
-
-void PluginPrivateFileSystemBackend::PerformStorageCleanupOnFileTaskRunner(
-    FileSystemContext* context,
-    QuotaManagerProxy* proxy,
-    FileSystemType type) {
-  if (!CanHandleType(type))
-    return;
-  obfuscated_file_util()->RewriteDatabases();
-}
-
-std::vector<blink::StorageKey>
-PluginPrivateFileSystemBackend::GetStorageKeysForTypeOnFileTaskRunner(
-    FileSystemType type) {
-  if (!CanHandleType(type))
-    return std::vector<blink::StorageKey>();
-  std::unique_ptr<ObfuscatedFileUtil::AbstractStorageKeyEnumerator> enumerator(
-      obfuscated_file_util()->CreateStorageKeyEnumerator());
-  std::vector<blink::StorageKey> storage_keys;
-  absl::optional<blink::StorageKey> storage_key;
-  while ((storage_key = enumerator->Next()).has_value())
-    storage_keys.push_back(std::move(storage_key).value());
-  return storage_keys;
-}
-
-int64_t PluginPrivateFileSystemBackend::GetStorageKeyUsageOnFileTaskRunner(
-    FileSystemContext* context,
-    const blink::StorageKey& storage_key,
-    FileSystemType type) {
-  DCHECK(file_task_runner_->RunsTasksInCurrentSequence());
-
-  if (!CanHandleType(type))
-    return 0;
-
-  int64_t total_size;
-  base::Time last_modified_time;
-  GetOriginDetailsOnFileTaskRunner(context, storage_key.origin(), &total_size,
-                                   &last_modified_time);
-  return total_size;
-}
-
-int64_t PluginPrivateFileSystemBackend::GetBucketUsageOnFileTaskRunner(
-    FileSystemContext* context,
-    const BucketLocator& bucket_locator,
-    FileSystemType type) {
-  return GetStorageKeyUsageOnFileTaskRunner(context, bucket_locator.storage_key,
-                                            type);
-}
-
-void PluginPrivateFileSystemBackend::GetOriginDetailsOnFileTaskRunner(
-    FileSystemContext* context,
-    const url::Origin& origin,
-    int64_t* total_size,
-    base::Time* last_modified_time) {
-  DCHECK(file_task_runner_->RunsTasksInCurrentSequence());
-
-  *total_size = 0;
-  *last_modified_time = base::Time::UnixEpoch();
-  std::string fsid =
-      IsolatedContext::GetInstance()->RegisterFileSystemForVirtualPath(
-          kFileSystemTypePluginPrivate, kPluginPrivateRootName,
-          base::FilePath());
-  DCHECK(ValidateIsolatedFileSystemId(fsid));
-
-  std::string root = GetIsolatedFileSystemRootURIString(origin.GetURL(), fsid,
-                                                        kPluginPrivateRootName);
-
-  std::unique_ptr<FileSystemOperationContext> operation_context(
-      std::make_unique<FileSystemOperationContext>(context));
-
-  // Determine the available plugin private filesystem directories for this
-  // origin. Currently the plugin private filesystem is only used by Encrypted
-  // Media Content Decryption Modules. Each CDM gets a directory based on the
-  // mimetype (e.g. plugin application/x-ppapi-widevine-cdm uses directory
-  // application_x-ppapi-widevine-cdm). Enumerate through the set of
-  // directories so that data from any CDM used by this origin is counted.
-  base::File::Error error;
-  // TODO(https://crbug.com/1231162): determine whether EME/CDM/plugin private
-  // file system will be partitioned; if so, replace the in-line conversion with
-  // the correct third-party StorageKey.
-  base::FilePath path =
-      obfuscated_file_util()->GetDirectoryForStorageKeyAndType(
-          blink::StorageKey(origin), "", false, &error);
-  if (error != base::File::FILE_OK) {
-    DLOG(ERROR) << "Unable to read directory for " << origin;
-    return;
-  }
-
-  base::FileEnumerator directory_enumerator(path, false,
-                                            base::FileEnumerator::DIRECTORIES);
-  base::FilePath plugin_path;
-  while (!(plugin_path = directory_enumerator.Next()).empty()) {
-    std::string plugin_name = plugin_path.BaseName().MaybeAsASCII();
-    if (OpenFileSystemOnFileTaskRunner(
-            obfuscated_file_util(), plugin_map_, origin, fsid, plugin_name,
-            OPEN_FILE_SYSTEM_FAIL_IF_NONEXISTENT) != base::File::FILE_OK) {
-      continue;
-    }
-
-    // TODO(https://crbug.com/1231162): determine whether EME/CDM/plugin private
-    // file system will be partitioned and use the appropriate StorageKey
-    std::unique_ptr<FileSystemFileUtil::AbstractFileEnumerator> enumerator(
-        obfuscated_file_util()->CreateFileEnumerator(
-            operation_context.get(),
-            context->CrackURL(
-                GURL(root), blink::StorageKey(url::Origin::Create(GURL(root)))),
-            true));
-
-    while (!enumerator->Next().empty()) {
-      *total_size += enumerator->Size();
-      if (enumerator->LastModifiedTime() > *last_modified_time)
-        *last_modified_time = enumerator->LastModifiedTime();
-    }
-  }
-}
-
-std::vector<PluginPrivateFileSystemBackend::CdmFileInfo>
-PluginPrivateFileSystemBackend::GetMediaLicenseFilesForOriginOnFileTaskRunner(
-    FileSystemContext* context,
-    const url::Origin& origin) {
-  DCHECK(file_task_runner_->RunsTasksInCurrentSequence());
-
-  std::unique_ptr<FileSystemOperationContext> operation_context(
-      std::make_unique<FileSystemOperationContext>(context));
-
-  // Determine the available plugin private filesystem directories for this
-  // origin. Currently the plugin private filesystem is only used by Encrypted
-  // Media Content Decryption Modules. Each CDM gets a directory based on the
-  // mimetype (e.g. plugin application/x-ppapi-widevine-cdm uses directory
-  // application_x-ppapi-widevine-cdm). Enumerate through the set of
-  // directories so that data from any CDM used by this origin is counted.
-  base::File::Error error;
-  base::FilePath path =
-      obfuscated_file_util()->GetDirectoryForStorageKeyAndType(
-          blink::StorageKey(origin), "", false, &error);
-  if (error != base::File::FILE_OK)
-    return {};
-
-  std::vector<CdmFileInfo> cdm_files;
-  base::FileEnumerator directory_enumerator(path, false,
-                                            base::FileEnumerator::DIRECTORIES);
-  base::FilePath plugin_path;
-  while (!(plugin_path = directory_enumerator.Next()).empty()) {
-    std::string plugin_name = plugin_path.BaseName().MaybeAsASCII();
-
-    std::string fsid =
-        IsolatedContext::GetInstance()->RegisterFileSystemForVirtualPath(
-            kFileSystemTypePluginPrivate, kPluginPrivateRootName,
-            base::FilePath());
-    DCHECK(ValidateIsolatedFileSystemId(fsid));
-    std::string root = GetIsolatedFileSystemRootURIString(
-        origin.GetURL(), fsid, kPluginPrivateRootName);
-
-    if (OpenFileSystemOnFileTaskRunner(
-            obfuscated_file_util(), plugin_map_, origin, fsid, plugin_name,
-            OPEN_FILE_SYSTEM_FAIL_IF_NONEXISTENT) != base::File::FILE_OK) {
-      continue;
-    }
-    std::unique_ptr<FileSystemFileUtil::AbstractFileEnumerator> enumerator(
-        obfuscated_file_util()->CreateFileEnumerator(
-            operation_context.get(),
-            context->CrackURL(
-                GURL(root), blink::StorageKey(url::Origin::Create(GURL(root)))),
-            true));
-
-    base::FilePath cdm_file_path;
-    while (!(cdm_file_path = enumerator->Next()).empty()) {
-      cdm_files.emplace_back(cdm_file_path.BaseName().AsUTF8Unsafe(),
-                             plugin_path.BaseName().AsUTF8Unsafe());
-    }
-  }
-
-  return cdm_files;
-}
-
-scoped_refptr<QuotaReservation>
-PluginPrivateFileSystemBackend::CreateQuotaReservationOnFileTaskRunner(
-    const blink::StorageKey& storage_key,
-    FileSystemType type) {
-  // We don't track usage on this filesystem.
-  NOTREACHED();
-  return scoped_refptr<QuotaReservation>();
-}
-
-const UpdateObserverList* PluginPrivateFileSystemBackend::GetUpdateObservers(
-    FileSystemType type) const {
-  return nullptr;
-}
-
-const ChangeObserverList* PluginPrivateFileSystemBackend::GetChangeObservers(
-    FileSystemType type) const {
-  return nullptr;
-}
-
-const AccessObserverList* PluginPrivateFileSystemBackend::GetAccessObservers(
-    FileSystemType type) const {
-  return nullptr;
-}
-
-ObfuscatedFileUtil* PluginPrivateFileSystemBackend::obfuscated_file_util() {
-  return static_cast<ObfuscatedFileUtil*>(
-      static_cast<AsyncFileUtilAdapter*>(file_util_.get())->sync_file_util());
-}
-
-ObfuscatedFileUtilMemoryDelegate*
-PluginPrivateFileSystemBackend::obfuscated_file_util_memory_delegate() {
-  auto* file_util = obfuscated_file_util();
-  DCHECK(file_util->is_incognito());
-  return static_cast<ObfuscatedFileUtilMemoryDelegate*>(file_util->delegate());
-}
-
-}  // namespace storage
diff --git a/storage/browser/file_system/plugin_private_file_system_backend.h b/storage/browser/file_system/plugin_private_file_system_backend.h
deleted file mode 100644
index 05b63cc..0000000
--- a/storage/browser/file_system/plugin_private_file_system_backend.h
+++ /dev/null
@@ -1,188 +0,0 @@
-// Copyright 2013 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef STORAGE_BROWSER_FILE_SYSTEM_PLUGIN_PRIVATE_FILE_SYSTEM_BACKEND_H_
-#define STORAGE_BROWSER_FILE_SYSTEM_PLUGIN_PRIVATE_FILE_SYSTEM_BACKEND_H_
-
-#include <stdint.h>
-
-#include <memory>
-#include <string>
-#include <vector>
-
-#include "base/component_export.h"
-#include "base/memory/raw_ptr.h"
-#include "base/memory/ref_counted.h"
-#include "base/memory/weak_ptr.h"
-#include "storage/browser/file_system/file_system_backend.h"
-#include "storage/browser/file_system/file_system_options.h"
-#include "storage/browser/file_system/file_system_quota_util.h"
-#include "storage/browser/file_system/task_runner_bound_observer_list.h"
-
-namespace base {
-class SequencedTaskRunner;
-}  // namespace base
-
-namespace blink {
-class StorageKey;
-}  // namespace blink
-
-namespace leveldb {
-class Env;
-}  // namespace leveldb
-
-namespace url {
-class Origin;
-}  // namespace url
-
-namespace storage {
-
-class ObfuscatedFileUtil;
-class ObfuscatedFileUtilMemoryDelegate;
-class SpecialStoragePolicy;
-class WatcherManager;
-
-// TODO(crbug.com/1231162): Remove this when removing the plugin private FS.
-// Name of the root directory in the plugin private file system.
-const char kPluginPrivateRootName[] = "pluginprivate";
-
-class COMPONENT_EXPORT(STORAGE_BROWSER) PluginPrivateFileSystemBackend
-    : public FileSystemBackend,
-      public FileSystemQuotaUtil {
- public:
-  class FileSystemIDToPluginMap;
-  using StatusCallback = base::OnceCallback<void(base::File::Error result)>;
-
-  // Used to migrate media license data to the new backend.
-  struct COMPONENT_EXPORT(STORAGE_BROWSER) CdmFileInfo {
-    CdmFileInfo(const std::string& name,
-                const std::string& legacy_file_system_id);
-    CdmFileInfo(const CdmFileInfo&);
-    CdmFileInfo(CdmFileInfo&&);
-    ~CdmFileInfo();
-
-    const std::string name;
-    const std::string legacy_file_system_id;
-  };
-
-  PluginPrivateFileSystemBackend(
-      scoped_refptr<base::SequencedTaskRunner> file_task_runner,
-      const base::FilePath& profile_path,
-      scoped_refptr<SpecialStoragePolicy> special_storage_policy,
-      const FileSystemOptions& file_system_options,
-      leveldb::Env* env_override);
-
-  PluginPrivateFileSystemBackend(const PluginPrivateFileSystemBackend&) =
-      delete;
-  PluginPrivateFileSystemBackend& operator=(
-      const PluginPrivateFileSystemBackend&) = delete;
-
-  ~PluginPrivateFileSystemBackend() override;
-
-  // This must be used to open 'private' filesystem instead of regular
-  // OpenFileSystem.
-  // |plugin_id| must be an identifier string for per-plugin
-  // isolation, e.g. name, MIME type etc.
-  // NOTE: |plugin_id| must be sanitized ASCII string that doesn't
-  // include *any* dangerous character like '/'.
-  void OpenPrivateFileSystem(const url::Origin& origin,
-                             FileSystemType type,
-                             const std::string& filesystem_id,
-                             const std::string& plugin_id,
-                             OpenFileSystemMode mode,
-                             StatusCallback callback);
-
-  // FileSystemBackend overrides.
-  bool CanHandleType(FileSystemType type) const override;
-  void Initialize(FileSystemContext* context) override;
-  void ResolveURL(const FileSystemURL& url,
-                  OpenFileSystemMode mode,
-                  ResolveURLCallback callback) override;
-  AsyncFileUtil* GetAsyncFileUtil(FileSystemType type) override;
-  WatcherManager* GetWatcherManager(FileSystemType type) override;
-  CopyOrMoveFileValidatorFactory* GetCopyOrMoveFileValidatorFactory(
-      FileSystemType type,
-      base::File::Error* error_code) override;
-  std::unique_ptr<FileSystemOperation> CreateFileSystemOperation(
-      const FileSystemURL& url,
-      FileSystemContext* context,
-      base::File::Error* error_code) const override;
-  bool SupportsStreaming(const FileSystemURL& url) const override;
-  bool HasInplaceCopyImplementation(FileSystemType type) const override;
-  std::unique_ptr<FileStreamReader> CreateFileStreamReader(
-      const FileSystemURL& url,
-      int64_t offset,
-      int64_t max_bytes_to_read,
-      const base::Time& expected_modification_time,
-      FileSystemContext* context) const override;
-  std::unique_ptr<FileStreamWriter> CreateFileStreamWriter(
-      const FileSystemURL& url,
-      int64_t offset,
-      FileSystemContext* context) const override;
-  FileSystemQuotaUtil* GetQuotaUtil() override;
-  const UpdateObserverList* GetUpdateObservers(
-      FileSystemType type) const override;
-  const ChangeObserverList* GetChangeObservers(
-      FileSystemType type) const override;
-  const AccessObserverList* GetAccessObservers(
-      FileSystemType type) const override;
-
-  // FileSystemQuotaUtil overrides.
-  base::File::Error DeleteStorageKeyDataOnFileTaskRunner(
-      FileSystemContext* context,
-      QuotaManagerProxy* proxy,
-      const blink::StorageKey& storage_key,
-      FileSystemType type) override;
-  void PerformStorageCleanupOnFileTaskRunner(FileSystemContext* context,
-                                             QuotaManagerProxy* proxy,
-                                             FileSystemType type) override;
-  std::vector<blink::StorageKey> GetStorageKeysForTypeOnFileTaskRunner(
-      FileSystemType type) override;
-  int64_t GetStorageKeyUsageOnFileTaskRunner(
-      FileSystemContext* context,
-      const blink::StorageKey& storage_key,
-      FileSystemType type) override;
-  int64_t GetBucketUsageOnFileTaskRunner(FileSystemContext* context,
-                                         const BucketLocator& bucket_locator,
-                                         FileSystemType type) override;
-  scoped_refptr<QuotaReservation> CreateQuotaReservationOnFileTaskRunner(
-      const blink::StorageKey& storage_key,
-      FileSystemType type) override;
-
-  // Get details on the files saved for the specified |origin_url|. Returns
-  // the total size and last modified time for the set of all files stored
-  // for the particular origin. |total_size| = 0 and |last_modified_time| =
-  // base::Time::UnixEpoch() if no files found.
-  void GetOriginDetailsOnFileTaskRunner(FileSystemContext* context,
-                                        const url::Origin& origin,
-                                        int64_t* total_size,
-                                        base::Time* last_modified_time);
-
-  // Used to migrate media license data to the new backend.
-  // TODO(crbug.com/1231162): Once all media license data has been migrated, the
-  // PPFS will have no more consumers and we can remove it entirely.
-  std::vector<CdmFileInfo> GetMediaLicenseFilesForOriginOnFileTaskRunner(
-      FileSystemContext* context,
-      const url::Origin& origin);
-
-  ObfuscatedFileUtilMemoryDelegate* obfuscated_file_util_memory_delegate();
-  const base::FilePath& base_path() const { return base_path_; }
-
- private:
-  friend class PluginPrivateFileSystemBackendTest;
-
-  ObfuscatedFileUtil* obfuscated_file_util();
-
-  const scoped_refptr<base::SequencedTaskRunner> file_task_runner_;
-  const FileSystemOptions file_system_options_;
-  const base::FilePath base_path_;
-  std::unique_ptr<AsyncFileUtil> file_util_;
-  raw_ptr<FileSystemIDToPluginMap, DanglingUntriaged>
-      plugin_map_;  // Owned by file_util_.
-  base::WeakPtrFactory<PluginPrivateFileSystemBackend> weak_factory_{this};
-};
-
-}  // namespace storage
-
-#endif  // STORAGE_BROWSER_FILE_SYSTEM_PLUGIN_PRIVATE_FILE_SYSTEM_BACKEND_H_
diff --git a/storage/browser/file_system/plugin_private_file_system_backend_unittest.cc b/storage/browser/file_system/plugin_private_file_system_backend_unittest.cc
deleted file mode 100644
index f162888f..0000000
--- a/storage/browser/file_system/plugin_private_file_system_backend_unittest.cc
+++ /dev/null
@@ -1,268 +0,0 @@
-// Copyright 2013 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include <memory>
-
-#include "base/bind.h"
-#include "base/files/file_util.h"
-#include "base/files/scoped_temp_dir.h"
-#include "base/run_loop.h"
-#include "base/test/task_environment.h"
-#include "storage/browser/file_system/file_system_context.h"
-#include "storage/browser/file_system/isolated_context.h"
-#include "storage/browser/file_system/obfuscated_file_util.h"
-#include "storage/browser/file_system/plugin_private_file_system_backend.h"
-#include "storage/browser/quota/quota_manager_proxy.h"
-#include "storage/browser/test/async_file_test_helper.h"
-#include "storage/browser/test/test_file_system_context.h"
-#include "storage/browser/test/test_file_system_options.h"
-#include "storage/common/file_system/file_system_util.h"
-#include "testing/gtest/include/gtest/gtest.h"
-#include "third_party/blink/public/common/storage_key/storage_key.h"
-#include "url/gurl.h"
-#include "url/origin.h"
-
-using url::Origin;
-
-namespace storage {
-
-namespace {
-
-const FileSystemType kType = kFileSystemTypePluginPrivate;
-
-void DidOpenFileSystem(base::File::Error* error_out, base::File::Error error) {
-  *error_out = error;
-}
-
-}  // namespace
-
-class PluginPrivateFileSystemBackendTest : public testing::Test {
- protected:
-  void SetUp() override {
-    ASSERT_TRUE(data_dir_.CreateUniqueTempDir());
-    context_ = CreateFileSystemContextForTesting(
-        /*quota_manager_proxy=*/nullptr, data_dir_.GetPath());
-  }
-
-  // TODO(https://crbug.com/1231162): determine whether EME/CDM/plugin private
-  // file system will be partitioned and use the appropriate StorageKey
-  FileSystemURL CreateURL(const GURL& root_url, const std::string& relative) {
-    FileSystemURL root = context_->CrackURL(
-        root_url, blink::StorageKey(url::Origin::Create(root_url)));
-    return context_->CreateCrackedFileSystemURL(
-        root.storage_key(), root.mount_type(),
-        root.virtual_path().AppendASCII(relative));
-  }
-
-  PluginPrivateFileSystemBackend* backend() const {
-    return context_->plugin_private_backend();
-  }
-
-  std::string RegisterFileSystem() {
-    return IsolatedContext::GetInstance()->RegisterFileSystemForVirtualPath(
-        kType, kRootName, base::FilePath());
-  }
-
-  const base::FilePath& base_path() const { return backend()->base_path(); }
-
-  const std::string kPlugin1 = "plugin1";
-  const std::string kPlugin2 = "plugin2";
-  const std::string kRootName = "pluginprivate";
-
-  base::ScopedTempDir data_dir_;
-  base::test::SingleThreadTaskEnvironment task_environment_;
-  scoped_refptr<FileSystemContext> context_;
-};
-
-// TODO(kinuko,nhiroki): There are a lot of duplicate code in these tests. Write
-// helper functions to simplify the tests.
-
-TEST_F(PluginPrivateFileSystemBackendTest, OpenFileSystemBasic) {
-  const Origin kOrigin = Origin::Create(GURL("http://www.example.com"));
-
-  const std::string filesystem_id1 = RegisterFileSystem();
-  base::File::Error error = base::File::FILE_ERROR_FAILED;
-  backend()->OpenPrivateFileSystem(kOrigin, kType, filesystem_id1, kPlugin1,
-                                   OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT,
-                                   base::BindOnce(&DidOpenFileSystem, &error));
-  base::RunLoop().RunUntilIdle();
-  ASSERT_EQ(base::File::FILE_OK, error);
-
-  // Run this again with FAIL_IF_NONEXISTENT to see if it succeeds.
-  const std::string filesystem_id2 = RegisterFileSystem();
-  error = base::File::FILE_ERROR_FAILED;
-  backend()->OpenPrivateFileSystem(kOrigin, kType, filesystem_id2, kPlugin1,
-                                   OPEN_FILE_SYSTEM_FAIL_IF_NONEXISTENT,
-                                   base::BindOnce(&DidOpenFileSystem, &error));
-  base::RunLoop().RunUntilIdle();
-  ASSERT_EQ(base::File::FILE_OK, error);
-
-  const GURL root_url(GetIsolatedFileSystemRootURIString(
-      kOrigin.GetURL(), filesystem_id1, kRootName));
-  FileSystemURL file = CreateURL(root_url, "foo");
-  base::FilePath platform_path;
-  EXPECT_EQ(base::File::FILE_OK,
-            AsyncFileTestHelper::CreateFile(context_.get(), file));
-  EXPECT_EQ(base::File::FILE_OK, AsyncFileTestHelper::GetPlatformPath(
-                                     context_.get(), file, &platform_path));
-  EXPECT_TRUE(base_path().AppendASCII("000").AppendASCII(kPlugin1).IsParent(
-      platform_path));
-}
-
-TEST_F(PluginPrivateFileSystemBackendTest, PluginIsolation) {
-  const Origin kOrigin = Origin::Create(GURL("http://www.example.com"));
-
-  // Open filesystem for kPlugin1 and kPlugin2.
-  const std::string filesystem_id1 = RegisterFileSystem();
-  base::File::Error error = base::File::FILE_ERROR_FAILED;
-  backend()->OpenPrivateFileSystem(kOrigin, kType, filesystem_id1, kPlugin1,
-                                   OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT,
-                                   base::BindOnce(&DidOpenFileSystem, &error));
-  base::RunLoop().RunUntilIdle();
-  ASSERT_EQ(base::File::FILE_OK, error);
-
-  const std::string filesystem_id2 = RegisterFileSystem();
-  error = base::File::FILE_ERROR_FAILED;
-  backend()->OpenPrivateFileSystem(kOrigin, kType, filesystem_id2, kPlugin2,
-                                   OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT,
-                                   base::BindOnce(&DidOpenFileSystem, &error));
-  base::RunLoop().RunUntilIdle();
-  ASSERT_EQ(base::File::FILE_OK, error);
-
-  // Create 'foo' in kPlugin1.
-  const GURL root_url1(GetIsolatedFileSystemRootURIString(
-      kOrigin.GetURL(), filesystem_id1, kRootName));
-  FileSystemURL file1 = CreateURL(root_url1, "foo");
-  EXPECT_EQ(base::File::FILE_OK,
-            AsyncFileTestHelper::CreateFile(context_.get(), file1));
-  EXPECT_TRUE(AsyncFileTestHelper::FileExists(
-      context_.get(), file1, AsyncFileTestHelper::kDontCheckSize));
-
-  // See the same path is not available in kPlugin2.
-  const GURL root_url2(GetIsolatedFileSystemRootURIString(
-      kOrigin.GetURL(), filesystem_id2, kRootName));
-  FileSystemURL file2 = CreateURL(root_url2, "foo");
-  EXPECT_FALSE(AsyncFileTestHelper::FileExists(
-      context_.get(), file2, AsyncFileTestHelper::kDontCheckSize));
-}
-
-TEST_F(PluginPrivateFileSystemBackendTest, OriginIsolation) {
-  const Origin kOrigin1 = Origin::Create(GURL("http://www.example.com"));
-  const Origin kOrigin2 = Origin::Create(GURL("https://www.example.com"));
-
-  // Open filesystem for kOrigin1 and kOrigin2.
-  const std::string filesystem_id1 = RegisterFileSystem();
-  base::File::Error error = base::File::FILE_ERROR_FAILED;
-  backend()->OpenPrivateFileSystem(kOrigin1, kType, filesystem_id1, kPlugin1,
-                                   OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT,
-                                   base::BindOnce(&DidOpenFileSystem, &error));
-  base::RunLoop().RunUntilIdle();
-  ASSERT_EQ(base::File::FILE_OK, error);
-
-  const std::string filesystem_id2 = RegisterFileSystem();
-  error = base::File::FILE_ERROR_FAILED;
-  backend()->OpenPrivateFileSystem(kOrigin2, kType, filesystem_id2, kPlugin1,
-                                   OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT,
-                                   base::BindOnce(&DidOpenFileSystem, &error));
-  base::RunLoop().RunUntilIdle();
-  ASSERT_EQ(base::File::FILE_OK, error);
-
-  // Create 'foo' in kOrigin1.
-  const GURL root_url1(GetIsolatedFileSystemRootURIString(
-      kOrigin1.GetURL(), filesystem_id1, kRootName));
-  FileSystemURL file1 = CreateURL(root_url1, "foo");
-  EXPECT_EQ(base::File::FILE_OK,
-            AsyncFileTestHelper::CreateFile(context_.get(), file1));
-  EXPECT_TRUE(AsyncFileTestHelper::FileExists(
-      context_.get(), file1, AsyncFileTestHelper::kDontCheckSize));
-
-  // See the same path is not available in kOrigin2.
-  const GURL root_url2(GetIsolatedFileSystemRootURIString(
-      kOrigin2.GetURL(), filesystem_id2, kRootName));
-  FileSystemURL file2 = CreateURL(root_url2, "foo");
-  EXPECT_FALSE(AsyncFileTestHelper::FileExists(
-      context_.get(), file2, AsyncFileTestHelper::kDontCheckSize));
-}
-
-TEST_F(PluginPrivateFileSystemBackendTest, DeleteOriginDirectory) {
-  const blink::StorageKey kStorageKey1 =
-      blink::StorageKey::CreateFromStringForTesting("http://www.example.com");
-  const blink::StorageKey kStorageKey2 =
-      blink::StorageKey::CreateFromStringForTesting("https://www.example.com");
-
-  // Open filesystem for kStorageKey1.origin() and kStorageKey2.origin().
-  const std::string filesystem_id1 = RegisterFileSystem();
-  base::File::Error error = base::File::FILE_ERROR_FAILED;
-  backend()->OpenPrivateFileSystem(kStorageKey1.origin(), kType, filesystem_id1,
-                                   kPlugin1,
-                                   OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT,
-                                   base::BindOnce(&DidOpenFileSystem, &error));
-  base::RunLoop().RunUntilIdle();
-  ASSERT_EQ(base::File::FILE_OK, error);
-
-  const std::string filesystem_id2 = RegisterFileSystem();
-  error = base::File::FILE_ERROR_FAILED;
-  backend()->OpenPrivateFileSystem(kStorageKey2.origin(), kType, filesystem_id2,
-                                   kPlugin1,
-                                   OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT,
-                                   base::BindOnce(&DidOpenFileSystem, &error));
-  base::RunLoop().RunUntilIdle();
-  ASSERT_EQ(base::File::FILE_OK, error);
-
-  // Create 'foo' in kStorageKey1.origin().
-  const GURL root_url1(GetIsolatedFileSystemRootURIString(
-      kStorageKey1.origin().GetURL(), filesystem_id1, kRootName));
-  FileSystemURL file1 = CreateURL(root_url1, "foo");
-  EXPECT_EQ(base::File::FILE_OK,
-            AsyncFileTestHelper::CreateFile(context_.get(), file1));
-  EXPECT_TRUE(AsyncFileTestHelper::FileExists(
-      context_.get(), file1, AsyncFileTestHelper::kDontCheckSize));
-
-  // Create 'foo' in kStorageKey2.origin().
-  const GURL root_url2(GetIsolatedFileSystemRootURIString(
-      kStorageKey2.origin().GetURL(), filesystem_id2, kRootName));
-  FileSystemURL file2 = CreateURL(root_url2, "foo");
-  EXPECT_EQ(base::File::FILE_OK,
-            AsyncFileTestHelper::CreateFile(context_.get(), file2));
-  EXPECT_TRUE(AsyncFileTestHelper::FileExists(
-      context_.get(), file2, AsyncFileTestHelper::kDontCheckSize));
-
-  // Delete data for kStorageKey1.origin().
-  error = backend()->DeleteStorageKeyDataOnFileTaskRunner(
-      context_.get(), nullptr, kStorageKey1, kType);
-  EXPECT_EQ(base::File::FILE_OK, error);
-
-  // Confirm 'foo' in kStorageKey1.origin() is deleted.
-  EXPECT_FALSE(AsyncFileTestHelper::FileExists(
-      context_.get(), file1, AsyncFileTestHelper::kDontCheckSize));
-
-  // Confirm 'foo' in kStorageKey2.origin() is NOT deleted.
-  EXPECT_TRUE(AsyncFileTestHelper::FileExists(
-      context_.get(), file2, AsyncFileTestHelper::kDontCheckSize));
-
-  // Re-open filesystem for kStorageKey1.origin().
-  const std::string filesystem_id3 = RegisterFileSystem();
-  error = base::File::FILE_ERROR_FAILED;
-  backend()->OpenPrivateFileSystem(kStorageKey1.origin(), kType, filesystem_id3,
-                                   kPlugin1,
-                                   OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT,
-                                   base::BindOnce(&DidOpenFileSystem, &error));
-  base::RunLoop().RunUntilIdle();
-  ASSERT_EQ(base::File::FILE_OK, error);
-
-  // Re-create 'foo' in kStorageKey1.origin().
-  const GURL root_url3(GetIsolatedFileSystemRootURIString(
-      kStorageKey1.origin().GetURL(), filesystem_id3, kRootName));
-  FileSystemURL file3 = CreateURL(root_url3, "foo");
-  EXPECT_EQ(base::File::FILE_OK,
-            AsyncFileTestHelper::CreateFile(context_.get(), file3));
-  EXPECT_TRUE(AsyncFileTestHelper::FileExists(
-      context_.get(), file3, AsyncFileTestHelper::kDontCheckSize));
-
-  // Confirm 'foo' in kStorageKey1.origin() is re-created.
-  EXPECT_TRUE(AsyncFileTestHelper::FileExists(
-      context_.get(), file3, AsyncFileTestHelper::kDontCheckSize));
-}
-
-}  // namespace storage
diff --git a/storage/browser/file_system/sandbox_file_stream_reader.cc b/storage/browser/file_system/sandbox_file_stream_reader.cc
index cf8b331..41cf7f0 100644
--- a/storage/browser/file_system/sandbox_file_stream_reader.cc
+++ b/storage/browser/file_system/sandbox_file_stream_reader.cc
@@ -18,7 +18,6 @@
 #include "storage/browser/file_system/file_system_operation_runner.h"
 #include "storage/browser/file_system/memory_file_stream_reader.h"
 #include "storage/browser/file_system/obfuscated_file_util_memory_delegate.h"
-#include "storage/browser/file_system/plugin_private_file_system_backend.h"
 
 // TODO(kinuko): Remove this temporary namespace hack after we move both
 // blob and fileapi into content namespace.
@@ -92,17 +91,8 @@
   snapshot_ref_ = std::move(file_ref);
 
   if (file_system_context_->is_incognito()) {
-    base::WeakPtr<ObfuscatedFileUtilMemoryDelegate> memory_file_util_delegate;
-    if (url_.type() == kFileSystemTypePluginPrivate) {
-      auto* backend = static_cast<PluginPrivateFileSystemBackend*>(
-          file_system_context_->GetFileSystemBackend(
-              kFileSystemTypePluginPrivate));
-      memory_file_util_delegate =
-          backend->obfuscated_file_util_memory_delegate()->GetWeakPtr();
-    } else {
-      memory_file_util_delegate =
-          file_system_context_->sandbox_delegate()->memory_file_util_delegate();
-    }
+    base::WeakPtr<ObfuscatedFileUtilMemoryDelegate> memory_file_util_delegate =
+        file_system_context_->sandbox_delegate()->memory_file_util_delegate();
     file_reader_ = std::make_unique<MemoryFileStreamReader>(
         file_system_context_->default_file_task_runner(),
         memory_file_util_delegate, platform_path, initial_offset_,
diff --git a/storage/browser/file_system/sandbox_file_stream_writer.cc b/storage/browser/file_system/sandbox_file_stream_writer.cc
index f82f43b..27f568fc 100644
--- a/storage/browser/file_system/sandbox_file_stream_writer.cc
+++ b/storage/browser/file_system/sandbox_file_stream_writer.cc
@@ -22,7 +22,6 @@
 #include "storage/browser/file_system/file_system_util.h"
 #include "storage/browser/file_system/memory_file_stream_writer.h"
 #include "storage/browser/file_system/obfuscated_file_util_memory_delegate.h"
-#include "storage/browser/file_system/plugin_private_file_system_backend.h"
 #include "storage/browser/quota/quota_manager_proxy.h"
 #include "storage/common/file_system/file_system_util.h"
 #include "third_party/blink/public/common/storage_key/storage_key.h"
@@ -147,17 +146,8 @@
   DCHECK(!file_writer_.get());
 
   if (file_system_context_->is_incognito()) {
-    base::WeakPtr<ObfuscatedFileUtilMemoryDelegate> memory_file_util_delegate;
-    if (url_.type() == kFileSystemTypePluginPrivate) {
-      auto* backend = static_cast<PluginPrivateFileSystemBackend*>(
-          file_system_context_->GetFileSystemBackend(
-              kFileSystemTypePluginPrivate));
-      memory_file_util_delegate =
-          backend->obfuscated_file_util_memory_delegate()->GetWeakPtr();
-    } else {
-      memory_file_util_delegate =
-          file_system_context_->sandbox_delegate()->memory_file_util_delegate();
-    }
+    base::WeakPtr<ObfuscatedFileUtilMemoryDelegate> memory_file_util_delegate =
+        file_system_context_->sandbox_delegate()->memory_file_util_delegate();
     file_writer_ = std::make_unique<MemoryFileStreamWriter>(
         file_system_context_->default_file_task_runner(),
         memory_file_util_delegate, platform_path, initial_offset_);
diff --git a/storage/common/file_system/file_system_types.h b/storage/common/file_system/file_system_types.h
index e5630d1..4df4497 100644
--- a/storage/common/file_system/file_system_types.h
+++ b/storage/common/file_system/file_system_types.h
@@ -98,11 +98,6 @@
   // file which must go away when the blob's last reference is dropped.
   kFileSystemTypeForTransientFile,
 
-  // Sandboxed private filesystem. This filesystem cannot be opened
-  // via regular OpenFileSystem, and provides private filesystem space for
-  // given identifier in each origin.
-  kFileSystemTypePluginPrivate,
-
   // A filesystem that is mounted via the FileSystemProvider API.
   kFileSystemTypeProvided,
 
diff --git a/storage/common/file_system/file_system_util.cc b/storage/common/file_system/file_system_util.cc
index 1dd214b..bb541ce 100644
--- a/storage/common/file_system/file_system_util.cc
+++ b/storage/common/file_system/file_system_util.cc
@@ -263,8 +263,6 @@
       return "LocalForPlatformApp";
     case kFileSystemTypeForTransientFile:
       return "TransientFile";
-    case kFileSystemTypePluginPrivate:
-      return "PluginPrivate";
     case kFileSystemTypeProvided:
       return "Provided";
     case kFileSystemTypeDeviceMediaAsFileStorage:
diff --git a/testing/buildbot/chromium.android.fyi.json b/testing/buildbot/chromium.android.fyi.json
index ef87c5e..ec73dbc 100644
--- a/testing/buildbot/chromium.android.fyi.json
+++ b/testing/buildbot/chromium.android.fyi.json
@@ -8330,15 +8330,15 @@
       {
         "args": [
           "--additional-apk=apks/WebLayerShellSystemWebView.apk",
+          "--webview-apk-path=apks/SystemWebView.apk",
           "--test-runner-outdir",
           ".",
+          "--client-outdir",
+          "../../weblayer_instrumentation_test_M103/out/Release",
           "--implementation-outdir",
           ".",
           "--test-expectations",
           "../../weblayer/browser/android/javatests/skew/expectations.txt",
-          "--webview-apk-path=apks/SystemWebView.apk",
-          "--client-outdir",
-          "../../weblayer_instrumentation_test_M103/out/Release",
           "--client-version=103",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -8364,7 +8364,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M103",
-              "revision": "version:103.0.5060.101"
+              "revision": "version:103.0.5060.102"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -8840,15 +8840,15 @@
       {
         "args": [
           "--additional-apk=apks/WebLayerShellSystemWebView.apk",
+          "--webview-apk-path=apks/AOSP_SystemWebView.apk",
           "--test-runner-outdir",
           ".",
           "--client-outdir",
           ".",
-          "--test-expectations",
-          "../../weblayer/browser/android/javatests/skew/expectations.txt",
-          "--webview-apk-path=apks/AOSP_SystemWebView.apk",
           "--implementation-outdir",
           "../../weblayer_instrumentation_test_M103/out/Release",
+          "--test-expectations",
+          "../../weblayer/browser/android/javatests/skew/expectations.txt",
           "--impl-version=103",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -8874,7 +8874,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M103",
-              "revision": "version:103.0.5060.101"
+              "revision": "version:103.0.5060.102"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
diff --git a/testing/buildbot/chromium.android.json b/testing/buildbot/chromium.android.json
index dfef149..13958ea 100644
--- a/testing/buildbot/chromium.android.json
+++ b/testing/buildbot/chromium.android.json
@@ -46508,15 +46508,15 @@
       {
         "args": [
           "--additional-apk=apks/WebLayerShellSystemWebView.apk",
+          "--webview-apk-path=apks/SystemWebView.apk",
           "--test-runner-outdir",
           ".",
+          "--client-outdir",
+          "../../weblayer_instrumentation_test_M103/out/Release",
           "--implementation-outdir",
           ".",
           "--test-expectations",
           "../../weblayer/browser/android/javatests/skew/expectations.txt",
-          "--webview-apk-path=apks/SystemWebView.apk",
-          "--client-outdir",
-          "../../weblayer_instrumentation_test_M103/out/Release",
           "--client-version=103",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -46542,7 +46542,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M103",
-              "revision": "version:103.0.5060.101"
+              "revision": "version:103.0.5060.102"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -47018,15 +47018,15 @@
       {
         "args": [
           "--additional-apk=apks/WebLayerShellSystemWebView.apk",
+          "--webview-apk-path=apks/AOSP_SystemWebView.apk",
           "--test-runner-outdir",
           ".",
           "--client-outdir",
           ".",
-          "--test-expectations",
-          "../../weblayer/browser/android/javatests/skew/expectations.txt",
-          "--webview-apk-path=apks/AOSP_SystemWebView.apk",
           "--implementation-outdir",
           "../../weblayer_instrumentation_test_M103/out/Release",
+          "--test-expectations",
+          "../../weblayer/browser/android/javatests/skew/expectations.txt",
           "--impl-version=103",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -47052,7 +47052,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M103",
-              "revision": "version:103.0.5060.101"
+              "revision": "version:103.0.5060.102"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -47532,15 +47532,15 @@
       {
         "args": [
           "--additional-apk=apks/ChromePublic.apk",
+          "--webview-apk-path=apks/SystemWebView.apk",
           "--test-runner-outdir",
           ".",
+          "--client-outdir",
+          "../../weblayer_instrumentation_test_M103/out/Release",
           "--implementation-outdir",
           ".",
           "--test-expectations",
           "../../weblayer/browser/android/javatests/skew/expectations.txt",
-          "--webview-apk-path=apks/SystemWebView.apk",
-          "--client-outdir",
-          "../../weblayer_instrumentation_test_M103/out/Release",
           "--client-version=103",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -47566,7 +47566,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M103",
-              "revision": "version:103.0.5060.101"
+              "revision": "version:103.0.5060.102"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -48042,15 +48042,15 @@
       {
         "args": [
           "--additional-apk=apks/ChromePublic.apk",
+          "--webview-apk-path=apks/AOSP_SystemWebView.apk",
           "--test-runner-outdir",
           ".",
           "--client-outdir",
           ".",
-          "--test-expectations",
-          "../../weblayer/browser/android/javatests/skew/expectations.txt",
-          "--webview-apk-path=apks/AOSP_SystemWebView.apk",
           "--implementation-outdir",
           "../../weblayer_instrumentation_test_M103/out/Release",
+          "--test-expectations",
+          "../../weblayer/browser/android/javatests/skew/expectations.txt",
           "--impl-version=103",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -48076,7 +48076,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M103",
-              "revision": "version:103.0.5060.101"
+              "revision": "version:103.0.5060.102"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -48624,15 +48624,15 @@
       {
         "args": [
           "--additional-apk=apks/WebLayerShellSystemWebView.apk",
+          "--webview-apk-path=apks/SystemWebView.apk",
           "--test-runner-outdir",
           ".",
+          "--client-outdir",
+          "../../weblayer_instrumentation_test_M103/out/Release",
           "--implementation-outdir",
           ".",
           "--test-expectations",
           "../../weblayer/browser/android/javatests/skew/expectations.txt",
-          "--webview-apk-path=apks/SystemWebView.apk",
-          "--client-outdir",
-          "../../weblayer_instrumentation_test_M103/out/Release",
           "--client-version=103",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -48658,7 +48658,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M103",
-              "revision": "version:103.0.5060.101"
+              "revision": "version:103.0.5060.102"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -49134,15 +49134,15 @@
       {
         "args": [
           "--additional-apk=apks/WebLayerShellSystemWebView.apk",
+          "--webview-apk-path=apks/SystemWebView.apk",
           "--test-runner-outdir",
           ".",
           "--client-outdir",
           ".",
-          "--test-expectations",
-          "../../weblayer/browser/android/javatests/skew/expectations.txt",
-          "--webview-apk-path=apks/SystemWebView.apk",
           "--implementation-outdir",
           "../../weblayer_instrumentation_test_M103/out/Release",
+          "--test-expectations",
+          "../../weblayer/browser/android/javatests/skew/expectations.txt",
           "--impl-version=103",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -49168,7 +49168,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M103",
-              "revision": "version:103.0.5060.101"
+              "revision": "version:103.0.5060.102"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -49716,15 +49716,15 @@
       {
         "args": [
           "--additional-apk=apks/WebLayerShellSystemWebView.apk",
+          "--webview-apk-path=apks/SystemWebView.apk",
           "--test-runner-outdir",
           ".",
+          "--client-outdir",
+          "../../weblayer_instrumentation_test_M103/out/Release",
           "--implementation-outdir",
           ".",
           "--test-expectations",
           "../../weblayer/browser/android/javatests/skew/expectations.txt",
-          "--webview-apk-path=apks/SystemWebView.apk",
-          "--client-outdir",
-          "../../weblayer_instrumentation_test_M103/out/Release",
           "--client-version=103",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -49750,7 +49750,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M103",
-              "revision": "version:103.0.5060.101"
+              "revision": "version:103.0.5060.102"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -50226,15 +50226,15 @@
       {
         "args": [
           "--additional-apk=apks/WebLayerShellSystemWebView.apk",
+          "--webview-apk-path=apks/SystemWebView.apk",
           "--test-runner-outdir",
           ".",
           "--client-outdir",
           ".",
-          "--test-expectations",
-          "../../weblayer/browser/android/javatests/skew/expectations.txt",
-          "--webview-apk-path=apks/SystemWebView.apk",
           "--implementation-outdir",
           "../../weblayer_instrumentation_test_M103/out/Release",
+          "--test-expectations",
+          "../../weblayer/browser/android/javatests/skew/expectations.txt",
           "--impl-version=103",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -50260,7 +50260,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M103",
-              "revision": "version:103.0.5060.101"
+              "revision": "version:103.0.5060.102"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
diff --git a/testing/buildbot/chromium.fuchsia.fyi.json b/testing/buildbot/chromium.fuchsia.fyi.json
index 5a4b390..128e896f 100644
--- a/testing/buildbot/chromium.fuchsia.fyi.json
+++ b/testing/buildbot/chromium.fuchsia.fyi.json
@@ -1185,9 +1185,6 @@
         "test_id_prefix": "ninja://third_party/abseil-cpp:absl_hardening_tests/"
       },
       {
-        "args": [
-          "--test-launcher-filter-file=../../testing/buildbot/filters/fuchsia.lsan.accessibility_unittests.filter"
-        ],
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -2267,7 +2264,7 @@
       },
       {
         "args": [
-          "--test-launcher-filter-file=../../testing/buildbot/filters/fuchsia.views_unittests.filter;../../testing/buildbot/filters/fuchsia.lsan.views_unittests.filter"
+          "--test-launcher-filter-file=../../testing/buildbot/filters/fuchsia.views_unittests.filter"
         ],
         "merge": {
           "args": [],
diff --git a/testing/buildbot/filters/BUILD.gn b/testing/buildbot/filters/BUILD.gn
index b852a54..79e0478 100644
--- a/testing/buildbot/filters/BUILD.gn
+++ b/testing/buildbot/filters/BUILD.gn
@@ -215,7 +215,6 @@
   data = [
     "//testing/buildbot/filters/fuchsia.debug.views_unittests.filter",
     "//testing/buildbot/filters/fuchsia.views_unittests.filter",
-    "//testing/buildbot/filters/fuchsia.lsan.views_unittests.filter",
   ]
 }
 
@@ -238,14 +237,6 @@
       [ "//testing/buildbot/filters/fuchsia.views_examples_unittests.filter" ]
 }
 
-source_set("accessibility_unittests_filters") {
-  testonly = true
-
-  data = [
-    "//testing/buildbot/filters/fuchsia.lsan.accessibility_unittests.filter",
-  ]
-}
-
 source_set("extensions_unittests_filters") {
   testonly = true
 
diff --git a/testing/buildbot/filters/fuchsia.lsan.accessibility_unittests.filter b/testing/buildbot/filters/fuchsia.lsan.accessibility_unittests.filter
deleted file mode 100644
index 6c0ccdf..0000000
--- a/testing/buildbot/filters/fuchsia.lsan.accessibility_unittests.filter
+++ /dev/null
@@ -1,2 +0,0 @@
-# crbug.com/1286005
--AXFuchsiaSemanticProviderTest*
diff --git a/testing/buildbot/filters/fuchsia.lsan.base_unittests.filter b/testing/buildbot/filters/fuchsia.lsan.base_unittests.filter
index 34e9cb9..259a8a4f 100644
--- a/testing/buildbot/filters/fuchsia.lsan.base_unittests.filter
+++ b/testing/buildbot/filters/fuchsia.lsan.base_unittests.filter
@@ -1,4 +1,4 @@
-# https://crbug.com/1287367
+# https://crbug.com/1339149
 -TimeZoneDataTest.CompareSystemRevisionWithExpected
 -TimeZoneDataTest.DoesNotCrashWithInvalidPath
 -TimeZoneDataTest.TestLoadingTimeZoneDataFromKnownConfigs
diff --git a/testing/buildbot/filters/fuchsia.lsan.content_unittests.filter b/testing/buildbot/filters/fuchsia.lsan.content_unittests.filter
index e091649..c03622b4 100644
--- a/testing/buildbot/filters/fuchsia.lsan.content_unittests.filter
+++ b/testing/buildbot/filters/fuchsia.lsan.content_unittests.filter
@@ -1,5 +1,7 @@
-# crbug.com/416665, Fuchsia's sanitizer runtimes don't support source-based
-# suppressions
+# On other platforms, these are suppressed by the suppressions for
+# http://crbug.com/416665 in //build/sanitizers/lsan_suppressions.cc.
+# Fuchsia's sanitizer runtimes don't support source-based suppressions.
+# TODO(http://crbug.com/1339448): Add suppression in source code.
 -SharedStorageAddModuleTest*
 -SharedStorageObjectMethodTest*
 -SharedStorageRunOperationTest*
diff --git a/testing/buildbot/filters/fuchsia.lsan.extensions_unittests.filter b/testing/buildbot/filters/fuchsia.lsan.extensions_unittests.filter
index 9eb49ad..356d73e 100644
--- a/testing/buildbot/filters/fuchsia.lsan.extensions_unittests.filter
+++ b/testing/buildbot/filters/fuchsia.lsan.extensions_unittests.filter
@@ -1,5 +1,7 @@
-# crbug.com/431213, Fuchsia's sanitizer runtimes don't support source-based
-# suppressions
+# On other platforms, these are suppressed by the suppressions for
+# http://crbug.com/416665 in //build/sanitizers/lsan_suppressions.cc.
+# Fuchsia's sanitizer runtimes don't support source-based suppressions.
+# TODO(http://crbug.com/1339448): Add suppression in source code.
 -APIBinding*
 -APIEventHandlerTest*
 -APILastErrorTest*
diff --git a/testing/buildbot/filters/fuchsia.lsan.gin_unittests.filter b/testing/buildbot/filters/fuchsia.lsan.gin_unittests.filter
index 28ad6ca5..36b088f 100644
--- a/testing/buildbot/filters/fuchsia.lsan.gin_unittests.filter
+++ b/testing/buildbot/filters/fuchsia.lsan.gin_unittests.filter
@@ -1,5 +1,7 @@
-# crbug.com/416665, Fuchsia's sanitizer runtimes don't support source-based
-# suppressions
+# On other platforms, these are suppressed by the suppressions for
+# http://crbug.com/416665 in //build/sanitizers/lsan_suppressions.cc.
+# Fuchsia's sanitizer runtimes don't support source-based suppressions.
+# TODO(http://crbug.com/1339448): Add suppression in source code.
 -ArgumentsTest.TestArgumentsHolderCreationContext
 -ArgumentsTest.TestGetAll
 -ConverterTest.MoveOnlyParameters
diff --git a/testing/buildbot/filters/fuchsia.lsan.views_unittests.filter b/testing/buildbot/filters/fuchsia.lsan.views_unittests.filter
deleted file mode 100644
index 731e308..0000000
--- a/testing/buildbot/filters/fuchsia.lsan.views_unittests.filter
+++ /dev/null
@@ -1,6 +0,0 @@
-https://crbug.com/1265033
--AccessiblePaneViewTest.DoesntCrashOnEscapeWithRemovedView
--All/TableViewTest.TableHeaderColumnAccessibleViewsFocusable/0
--All/TableViewTest.TableHeaderColumnAccessibleViewsFocusable/1
--All/TableViewTest.TableHeaderRowAccessibleViewFocusable/0
--All/TableViewTest.TableHeaderRowAccessibleViewFocusable/1
diff --git a/testing/buildbot/filters/pixel_tests.filter b/testing/buildbot/filters/pixel_tests.filter
index 4b33ef4b..23037c1 100644
--- a/testing/buildbot/filters/pixel_tests.filter
+++ b/testing/buildbot/filters/pixel_tests.filter
@@ -67,7 +67,6 @@
 SessionCrashedBubbleViewTest.*
 TabGroupEditorBubbleViewDialogBrowserTest.*
 TabHoverCardBubbleViewBrowserTest.*
-*TailoredSecurityDesktopModalTest.InvokeUi_*
 TranslateBubbleVisualTest.InvokeUi_*
 UpdateRecommendedDialogTest.*
 WebAppConfirmViewBrowserTest.*
diff --git a/testing/buildbot/test_suite_exceptions.pyl b/testing/buildbot/test_suite_exceptions.pyl
index d2e225f..4b0b6b02e5 100644
--- a/testing/buildbot/test_suite_exceptions.pyl
+++ b/testing/buildbot/test_suite_exceptions.pyl
@@ -23,15 +23,6 @@
 # The goal is to drive the number of exceptions to zero, to make all
 # the bots behave similarly.
 {
-  'accessibility_unittests':{
-    'modifications': {
-      'fuchsia-fyi-x64-asan': {
-        'args': [
-          '--test-launcher-filter-file=../../testing/buildbot/filters/fuchsia.lsan.accessibility_unittests.filter',
-        ],
-      },
-    },
-  },
   'android_browsertests': {
     'modifications': {
       'Marshmallow Tablet Tester': {
@@ -3482,11 +3473,6 @@
           '--test-launcher-filter-file=../../testing/buildbot/filters/fuchsia.debug.views_unittests.filter',
         ],
       },
-      'fuchsia-fyi-x64-asan': {
-        'args': [
-          '--test-launcher-filter-file=../../testing/buildbot/filters/fuchsia.lsan.views_unittests.filter',
-        ],
-      },
       # https://crbug.com/1111979,
       'linux-lacros-code-coverage': {
         'args': [
diff --git a/testing/buildbot/variants.pyl b/testing/buildbot/variants.pyl
index 72c16884..36c1e5d0 100644
--- a/testing/buildbot/variants.pyl
+++ b/testing/buildbot/variants.pyl
@@ -553,16 +553,16 @@
   },
   'WEBLAYER_10_AND_M_IMPL_SKEW_TESTS_NTH_MINUS_ONE_MILESTONE': {
     'args': [
+      '--webview-apk-path=apks/AOSP_SystemWebView.apk',
       '--test-runner-outdir',
       '.',
       '--client-outdir',
       '.',
-      '--test-expectations',
-      '../../weblayer/browser/android/javatests/skew/expectations.txt',
-      '--webview-apk-path=apks/AOSP_SystemWebView.apk',
       '--implementation-outdir',
       '../../weblayer_instrumentation_test_M103/out/Release',
-      '--impl-version=103'
+      '--test-expectations',
+      '../../weblayer/browser/android/javatests/skew/expectations.txt',
+      '--impl-version=103',
     ],
     'identifier': 'with_impl_from_103',
     'swarming': {
@@ -570,10 +570,10 @@
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M103',
-          'revision': 'version:103.0.5060.101'
+          'revision': 'version:103.0.5060.102',
         }
-      ]
-    }
+      ],
+    },
   },
   'WEBLAYER_10_AND_M_IMPL_SKEW_TESTS_NTH_MINUS_TWO_MILESTONE': {
     'args': [
@@ -697,16 +697,16 @@
   },
   'WEBLAYER_IMPL_SKEW_TESTS_NTH_MINUS_ONE_MILESTONE': {
     'args': [
+      '--webview-apk-path=apks/SystemWebView.apk',
       '--test-runner-outdir',
       '.',
       '--client-outdir',
       '.',
-      '--test-expectations',
-      '../../weblayer/browser/android/javatests/skew/expectations.txt',
-      '--webview-apk-path=apks/SystemWebView.apk',
       '--implementation-outdir',
       '../../weblayer_instrumentation_test_M103/out/Release',
-      '--impl-version=103'
+      '--test-expectations',
+      '../../weblayer/browser/android/javatests/skew/expectations.txt',
+      '--impl-version=103',
     ],
     'identifier': 'with_impl_from_103',
     'swarming': {
@@ -714,10 +714,10 @@
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M103',
-          'revision': 'version:103.0.5060.101'
+          'revision': 'version:103.0.5060.102',
         }
-      ]
-    }
+      ],
+    },
   },
   'WEBLAYER_IMPL_SKEW_TESTS_NTH_MINUS_TWO_MILESTONE': {
     'args': [
@@ -841,16 +841,16 @@
   },
   'WEBLAYER_CLIENT_SKEW_TESTS_NTH_MINUS_ONE_MILESTONE': {
     'args': [
+      '--webview-apk-path=apks/SystemWebView.apk',
       '--test-runner-outdir',
       '.',
+      '--client-outdir',
+      '../../weblayer_instrumentation_test_M103/out/Release',
       '--implementation-outdir',
       '.',
       '--test-expectations',
       '../../weblayer/browser/android/javatests/skew/expectations.txt',
-      '--webview-apk-path=apks/SystemWebView.apk',
-      '--client-outdir',
-      '../../weblayer_instrumentation_test_M103/out/Release',
-      '--client-version=103'
+      '--client-version=103',
     ],
     'identifier': 'with_client_from_103',
     'swarming': {
@@ -858,10 +858,10 @@
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M103',
-          'revision': 'version:103.0.5060.101'
+          'revision': 'version:103.0.5060.102',
         }
-      ]
-    }
+      ],
+    },
   },
   'WEBLAYER_CLIENT_SKEW_TESTS_NTH_MINUS_TWO_MILESTONE': {
     'args': [
diff --git a/testing/scripts/headless_python_unittests.py b/testing/scripts/headless_python_unittests.py
index 4e32907..cb0bb375 100755
--- a/testing/scripts/headless_python_unittests.py
+++ b/testing/scripts/headless_python_unittests.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env vpython3
 # 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.
@@ -38,7 +38,7 @@
   valid = bool(rc <= common.MAX_FAILURES_EXIT_STATUS and
                ((rc == 0) or failures))
   common.record_local_script_results(
-      'headless_python_unittests', args.output, failures.keys(), valid)
+      'headless_python_unittests', args.output, list(failures.keys()), valid)
 
   return rc
 
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index 7493cde..931edff1 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -4174,8 +4174,8 @@
                 {
                     "name": "Enabled",
                     "enable_features": [
-                        "OmniboxMostVisitedTiles",
-                        "OmniboxOnFocusSuggestionsContextualWebOnContent"
+                        "OmniboxFocusTriggersContextualWebZeroSuggest",
+                        "OmniboxMostVisitedTiles"
                     ]
                 }
             ]
@@ -6016,7 +6016,7 @@
                         "ShortBookmarkSuggestionsByTotalInputLengthThreshold": "3",
                         "UIMaxAutocompleteMatches": "8",
                         "ZeroSuggestCacheCounterfactual": "true",
-                        "ZeroSuggestCacheDurationSec": "180",
+                        "ZeroSuggestCacheDurationSec": "0",
                         "ZeroSuggestPrefetchBypassCache": "true"
                     },
                     "enable_features": [
@@ -6053,15 +6053,15 @@
                         "OmniboxMaxURLMatches": "8",
                         "UIMaxAutocompleteMatches": "10",
                         "ZeroSuggestCacheCounterfactual": "true",
-                        "ZeroSuggestCacheDurationSec": "180",
+                        "ZeroSuggestCacheDurationSec": "0",
                         "ZeroSuggestPrefetchBypassCache": "true"
                     },
                     "enable_features": [
                         "AndroidAuxiliarySearch",
                         "OmniboxDynamicMaxAutocomplete",
+                        "OmniboxFocusTriggersSRPZeroSuggest",
                         "OmniboxLocalZeroSuggestAgeThreshold",
                         "OmniboxMaxURLMatches",
-                        "OmniboxOnFocusSuggestionsContextualWebAllowSRP",
                         "OmniboxRetainSuggestionsWithHeaders",
                         "OmniboxUIExperimentMaxAutocompleteMatches",
                         "ZeroSuggestPrefetching"
@@ -6275,7 +6275,6 @@
         {
             "platforms": [
                 "android",
-                "android_weblayer",
                 "android_webview",
                 "chromeos",
                 "chromeos_lacros",
@@ -6285,7 +6284,11 @@
             ],
             "experiments": [
                 {
-                    "name": "Enabled",
+                    "name": "64KB_20220629",
+                    "params": {
+                        "bytes_read_limit": "65536",
+                        "filter_source_stream_buffer_size": "65536"
+                    },
                     "enable_features": [
                         "OptimizeNetworkBuffers2"
                     ]
@@ -6362,25 +6365,6 @@
             ]
         }
     ],
-    "PageEntitiesModelBypassFilters": [
-        {
-            "platforms": [
-                "chromeos",
-                "chromeos_lacros",
-                "linux",
-                "mac",
-                "windows"
-            ],
-            "experiments": [
-                {
-                    "name": "Enabled_20220330",
-                    "enable_features": [
-                        "PageEntitiesModelBypassFilters"
-                    ]
-                }
-            ]
-        }
-    ],
     "PageInfoDiscoverabilityTimeout": [
         {
             "platforms": [
diff --git a/third_party/android_build_tools/OWNERS b/third_party/android_build_tools/OWNERS
new file mode 100644
index 0000000..08543fd6
--- /dev/null
+++ b/third_party/android_build_tools/OWNERS
@@ -0,0 +1,5 @@
+# The following OWNERS are only for adding / removing / renaming directories
+# that are conceptually the same as existing ones (which would have already gone
+# through third_party review).
+agrieve@chromium.org
+wnwen@chromium.org
diff --git a/third_party/android_build_tools/README.chromium b/third_party/android_build_tools/README.chromium
index f5b33e48..0c3fdc3 100644
--- a/third_party/android_build_tools/README.chromium
+++ b/third_party/android_build_tools/README.chromium
@@ -1,9 +1,9 @@
-Name: Android SDK App Bundle related tools
-Short Name:  Android App bundle tools
+Name: Android SDK related build tools
+Short Name:  Android tools
 Version: 0
 License: Apache Version 2.0
 License File: NOT_SHIPPED
-URL: https://developer.android.com/guide/app-bundle/build
+URL: https://developer.android.com/studio
 Security Critical: No
 
 Description: The tools in this directory either require more recent
@@ -11,7 +11,7 @@
 public Chrome source checkouts, or are not provided by the SDK at
 all.
 
-See the readme and *.yaml files in each directory for details.
+See the README.chromium in each directory for details.
 
 Local Modifications:
 None
diff --git a/third_party/android_build_tools/manifest_merger/.gitignore b/third_party/android_build_tools/manifest_merger/.gitignore
new file mode 100644
index 0000000..43349dec8
--- /dev/null
+++ b/third_party/android_build_tools/manifest_merger/.gitignore
@@ -0,0 +1 @@
+manifest-merger.jar
diff --git a/third_party/android_build_tools/manifest_merger/3pp/3pp.pb b/third_party/android_build_tools/manifest_merger/3pp/3pp.pb
new file mode 100644
index 0000000..5274a2b
--- /dev/null
+++ b/third_party/android_build_tools/manifest_merger/3pp/3pp.pb
@@ -0,0 +1,23 @@
+# Copyright 2022 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.
+
+create {
+  source {
+    script {
+      name: "fetch.py"
+      use_fetch_checkout_workflow: true
+    }
+  }
+
+  build {
+    install: "install.py"
+    tool: "chromium/third_party/maven"
+    dep: "chromium/third_party/jdk"
+  }
+}
+
+upload {
+  pkg_prefix: "chromium/third_party/android_build_tools"
+  universal: true
+}
diff --git a/third_party/android_build_tools/manifest_merger/3pp/fetch.py b/third_party/android_build_tools/manifest_merger/3pp/fetch.py
new file mode 100755
index 0000000..58c1fcb9
--- /dev/null
+++ b/third_party/android_build_tools/manifest_merger/3pp/fetch.py
@@ -0,0 +1,62 @@
+#!/usr/bin/env python3
+# Copyright 2022 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.
+
+# This script is customized based on 3ppFetch.template.
+
+import argparse
+import re
+import urllib.request
+
+_REPO_URL = 'https://dl.google.com/android/maven2'
+_GROUP_NAME = 'com/android/tools/build'
+_MODULE_NAME = 'manifest-merger'
+_OVERRIDE_LATEST = None
+_PATCH_VERSION = 'cr0'
+
+
+def do_latest():
+    if _OVERRIDE_LATEST is not None:
+        print(_OVERRIDE_LATEST + f'.{_PATCH_VERSION}')
+        return
+    maven_metadata_url = '{}/{}/{}/maven-metadata.xml'.format(
+        _REPO_URL, _GROUP_NAME, _MODULE_NAME)
+    metadata = urllib.request.urlopen(maven_metadata_url).read().decode(
+        'utf-8')
+    # Do not parse xml with the python included parser since it is susceptible
+    # to maliciously crafted xmls. Only use regular expression parsing to be
+    # safe. RE should be enough to handle what we need to extract.
+    match = re.search('<latest>([^<]+)</latest>', metadata)
+    if match:
+        latest = match.group(1)
+    else:
+        # if no latest info was found just hope the versions are sorted and the
+        # last one is the latest (as is commonly the case).
+        latest = re.findall('<version>([^<]+)</version>', metadata)[-1]
+    print(latest + f'.{_PATCH_VERSION}')
+
+
+def do_checkout():
+    # Everything is done in install.py. This method allows us to bypass having
+    # to download an unnecessary file.
+    pass
+
+
+def main():
+    argparser = argparse.ArgumentParser()
+    subparser = argparser.add_subparsers()
+
+    latest = subparser.add_parser('latest')
+    latest.set_defaults(func=do_latest)
+
+    checkout = subparser.add_parser('checkout')
+    checkout.add_argument("checkout_path")  # Needed only to avoid parse error.
+    checkout.set_defaults(func=do_checkout)
+
+    args = argparser.parse_args()
+    args.func()
+
+
+if __name__ == '__main__':
+    main()
diff --git a/third_party/android_build_tools/manifest_merger/3pp/install.py b/third_party/android_build_tools/manifest_merger/3pp/install.py
new file mode 100755
index 0000000..0ef12125
--- /dev/null
+++ b/third_party/android_build_tools/manifest_merger/3pp/install.py
@@ -0,0 +1,90 @@
+#!/usr/bin/env python3
+# Copyright 2022 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 os
+import shutil
+import subprocess
+import sys
+
+_GROUP_ID = 'com.android.tools.build'
+_ARTIFACT_ID = 'manifest-merger'
+_FINAL_NAME = 'manifest-merger.jar'
+
+_POM_TEMPLATE = """\
+<project>
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>group</groupId>
+  <artifactId>artifact</artifactId>
+  <version>1</version>
+  <dependencies>
+    <dependency>
+      <groupId>{group_id}</groupId>
+      <artifactId>{artifact_id}</artifactId>
+      <version>{version}</version>
+      <scope>runtime</scope>
+    </dependency>
+  </dependencies>
+  <build>
+    <plugins>
+      <plugin>
+        <artifactId>maven-assembly-plugin</artifactId>
+        <version>3.3.0</version>
+        <configuration>
+          <descriptorRefs>
+            <descriptorRef>jar-with-dependencies</descriptorRef>
+          </descriptorRefs>
+        </configuration>
+        <executions>
+          <execution>
+            <phase>package</phase>
+            <goals>
+              <goal>single</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+  </build>
+  <repositories>
+    <repository>
+      <id>google</id>
+      <name>google</name>
+      <url>https://maven.google.com/</url>
+    </repository>
+  </repositories>
+</project>
+"""
+
+
+def main(output_prefix: str, deps_prefix: str):
+    # Remove the patch version at the end: 30.4.0-alpha05.cr0 => 30.4.0-alpha05
+    version = os.environ['_3PP_VERSION'].rsplit('.', 1)[0]
+    with open('pom.xml', 'w') as f:
+        f.write(
+            _POM_TEMPLATE.format(version=version,
+                                 group_id=_GROUP_ID,
+                                 artifact_id=_ARTIFACT_ID))
+
+    # Set up JAVA_HOME for the mvn command to find the JDK.
+    env = os.environ.copy()
+    env['JAVA_HOME'] = os.path.join(deps_prefix, 'current')
+
+    # Ensure that mvn works and the environment is set up correctly.
+    subprocess.run(['mvn', '-v'], check=True, env=env)
+
+    # Build the jar file, explicitly specify -f to reduce sources of error.
+    subprocess.run(['mvn', 'clean', 'assembly:single', '-f', 'pom.xml'],
+                   check=True,
+                   env=env)
+
+    # Move and rename output to the upload directory. Moving only the jar avoids
+    # polluting the output directory with maven intermediate outputs.
+    os.makedirs(output_prefix, exist_ok=True)
+    shutil.move('target/artifact-1-jar-with-dependencies.jar',
+                os.path.join(output_prefix, _FINAL_NAME))
+
+
+if __name__ == '__main__':
+    main(sys.argv[1], sys.argv[2])
diff --git a/third_party/android_build_tools/manifest_merger/LICENSE b/third_party/android_build_tools/manifest_merger/LICENSE
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/third_party/android_build_tools/manifest_merger/LICENSE
@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
diff --git a/third_party/android_build_tools/manifest_merger/OWNERS b/third_party/android_build_tools/manifest_merger/OWNERS
new file mode 100644
index 0000000..7200601
--- /dev/null
+++ b/third_party/android_build_tools/manifest_merger/OWNERS
@@ -0,0 +1,3 @@
+file://build/android/OWNERS
+
+wnwen@chromium.org
diff --git a/third_party/android_build_tools/manifest_merger/README.chromium b/third_party/android_build_tools/manifest_merger/README.chromium
new file mode 100644
index 0000000..140058b
--- /dev/null
+++ b/third_party/android_build_tools/manifest_merger/README.chromium
@@ -0,0 +1,20 @@
+Name: Android SDK manifest merger
+Short Name: manifest merger
+Version: unknown
+License: Apache Version 2.0
+License File: NOT_SHIPPED
+Security Critical: No
+URL: https://developer.android.com/studio/build
+
+Description:
+A Library to merge Android manifests.
+
+Local Modifications:
+None
+
+What version is this:
+  * New instances are uploaded by the packager bot:
+    https://ci.chromium.org/p/chromium/builders/ci/3pp-linux-amd64-packager
+  * The bot autoruns every 6 hours. Ping a trooper or a clank-build-core@ dev to
+    trigger it if you need it sooner:
+    https://luci-scheduler.appspot.com/jobs/chromium/3pp-linux-amd64-packager
diff --git a/third_party/blink/public/platform/web_runtime_features.h b/third_party/blink/public/platform/web_runtime_features.h
index 7ebfcf1..f23687a 100644
--- a/third_party/blink/public/platform/web_runtime_features.h
+++ b/third_party/blink/public/platform/web_runtime_features.h
@@ -164,7 +164,6 @@
   BLINK_PLATFORM_EXPORT static void EnableCSSSelectorFragmentAnchor(bool);
   BLINK_PLATFORM_EXPORT static void EnableTouchDragAndContextMenu(bool enable);
   BLINK_PLATFORM_EXPORT static void EnableTouchEventFeatureDetection(bool);
-  BLINK_PLATFORM_EXPORT static void EnableScrollUnification(bool);
   BLINK_PLATFORM_EXPORT static void EnableSpeculationRulesPrefetchProxy(bool);
   BLINK_PLATFORM_EXPORT static void EnableUserActivationSameOriginVisibility(
       bool);
diff --git a/third_party/blink/public/web/web_local_frame.h b/third_party/blink/public/web/web_local_frame.h
index 3e225e11f..403e172 100644
--- a/third_party/blink/public/web/web_local_frame.h
+++ b/third_party/blink/public/web/web_local_frame.h
@@ -53,8 +53,13 @@
 #include "ui/gfx/range/range.h"
 #include "v8/include/v8-forward.h"
 
+template <typename T>
+class sk_sp;
+
 namespace cc {
 class PaintCanvas;
+class PaintOpBuffer;
+using PaintRecord = PaintOpBuffer;
 }  // namespace cc
 
 namespace gfx {
@@ -912,6 +917,10 @@
       base::RepeatingCallback<void(const blink::WebHitTestResult&)>
           callback) = 0;
 
+  // Get the PaintRecord based on the cached paint artifact generated during
+  // the last paint in lifecycle update.
+  virtual sk_sp<cc::PaintRecord> GetPaintRecord() const = 0;
+
  protected:
   explicit WebLocalFrame(mojom::TreeScopeType scope,
                          const LocalFrameToken& frame_token)
diff --git a/third_party/blink/renderer/bindings/core/v8/serialization/v8_script_value_serializer.h b/third_party/blink/renderer/bindings/core/v8/serialization/v8_script_value_serializer.h
index a65a200..e1a493a5 100644
--- a/third_party/blink/renderer/bindings/core/v8/serialization/v8_script_value_serializer.h
+++ b/third_party/blink/renderer/bindings/core/v8/serialization/v8_script_value_serializer.h
@@ -89,6 +89,8 @@
 
   bool IsForStorage() const { return for_storage_; }
 
+  const Transferables* GetTransferables() const { return transferables_; }
+
  private:
   // Transfer is split into two phases: scanning the transferables so that we
   // don't have to serialize the data (just an index), and finalizing (to
diff --git a/third_party/blink/renderer/bindings/modules/v8/serialization/v8_script_value_serializer_for_modules.cc b/third_party/blink/renderer/bindings/modules/v8/serialization/v8_script_value_serializer_for_modules.cc
index 9549748..6d6c575e 100644
--- a/third_party/blink/renderer/bindings/modules/v8/serialization/v8_script_value_serializer_for_modules.cc
+++ b/third_party/blink/renderer/bindings/modules/v8/serialization/v8_script_value_serializer_for_modules.cc
@@ -36,6 +36,7 @@
 #include "third_party/blink/renderer/modules/mediasource/media_source_attachment_supplement.h"
 #include "third_party/blink/renderer/modules/mediasource/media_source_handle_attachment.h"
 #include "third_party/blink/renderer/modules/mediasource/media_source_handle_impl.h"
+#include "third_party/blink/renderer/modules/mediasource/media_source_handle_transfer_list.h"
 #include "third_party/blink/renderer/modules/mediastream/crop_target.h"
 #include "third_party/blink/renderer/modules/mediastream/media_stream_track.h"
 #include "third_party/blink/renderer/modules/peerconnection/rtc_certificate.h"
@@ -103,6 +104,41 @@
     return true;
   }
 
+  if (V8MediaSourceHandle::HasInstance(object, isolate) &&
+      RuntimeEnabledFeatures::MediaSourceInWorkersEnabled(
+          CurrentExecutionContext(isolate)) &&
+      RuntimeEnabledFeatures::MediaSourceInWorkersUsingHandleEnabled(
+          CurrentExecutionContext(isolate))) {
+    MediaSourceHandleImpl* media_source_handle =
+        V8MediaSourceHandle::ToImpl(v8::Local<v8::Object>::Cast(object));
+    MediaSourceHandleTransferList* transfer_list =
+        transferables.GetOrCreateTransferList<MediaSourceHandleTransferList>();
+    if (transfer_list->media_source_handles.Contains(media_source_handle)) {
+      exception_state.ThrowDOMException(
+          DOMExceptionCode::kDataCloneError,
+          "MediaSourceHandle at index " + String::Number(object_index) +
+              " is a duplicate of an earlier MediaSourceHandle.");
+      return false;
+    }
+    if (media_source_handle->is_detached()) {
+      exception_state.ThrowDOMException(
+          DOMExceptionCode::kDataCloneError,
+          "MediaSourceHandle at index " + String::Number(object_index) +
+              " is detached and cannot be transferred.");
+      return false;
+    }
+    if (media_source_handle->is_used()) {
+      exception_state.ThrowDOMException(
+          DOMExceptionCode::kDataCloneError,
+          "MediaSourceHandle at index " + String::Number(object_index) +
+              " has been used as srcObject of media element already, and "
+              "cannot be transferred.");
+      return false;
+    }
+    transfer_list->media_source_handles.push_back(media_source_handle);
+    return true;
+  }
+
   return false;
 }
 
@@ -269,8 +305,27 @@
       return false;
     }
 
-    return WriteMediaSourceHandle(wrappable->ToImpl<MediaSourceHandleImpl>(),
-                                  exception_state);
+    const Transferables* transferables = GetTransferables();
+    const MediaSourceHandleTransferList* transfer_list = nullptr;
+
+    if (transferables) {
+      transfer_list =
+          transferables
+              ->GetTransferListIfExists<MediaSourceHandleTransferList>();
+      if (transfer_list) {
+        MediaSourceHandleImpl* media_source_handle =
+            wrappable->ToImpl<MediaSourceHandleImpl>();
+        if (transfer_list->media_source_handles.Find(media_source_handle) !=
+            kNotFound) {
+          return WriteMediaSourceHandle(media_source_handle, exception_state);
+        }
+      }
+    }
+
+    exception_state.ThrowDOMException(DOMExceptionCode::kDataCloneError,
+                                      "A MediaSourceHandle could not be cloned "
+                                      "because it was not transferred.");
+    return false;
   }
 
   return false;
diff --git a/third_party/blink/renderer/core/css/css_properties.json5 b/third_party/blink/renderer/core/css/css_properties.json5
index b8650cb2..3ab5abe 100644
--- a/third_party/blink/renderer/core/css/css_properties.json5
+++ b/third_party/blink/renderer/core/css/css_properties.json5
@@ -3797,7 +3797,7 @@
       inherited: true,
       field_group: "*",
       field_template: "pointer",
-      include_paths: ["third_party/blink/renderer/core/style/quotes_data.h"],
+      include_paths: ["third_party/blink/renderer/platform/text/quotes_data.h"],
       wrapper_pointer_name: "scoped_refptr",
       default_value: "nullptr",
       type_name: "QuotesData",
diff --git a/third_party/blink/renderer/core/css/css_value_keywords.json5 b/third_party/blink/renderer/core/css/css_value_keywords.json5
index 44eda8e1..ac6d802 100644
--- a/third_party/blink/renderer/core/css/css_value_keywords.json5
+++ b/third_party/blink/renderer/core/css/css_value_keywords.json5
@@ -1482,6 +1482,13 @@
     // toggle-root
     "group",
     "self",
+    "cycle",
+    "cycle-on",
+
+    // toggle-trigger
+    "prev",
+    "next",
+    "set",
 
     // color-contrast()
     "color-contrast",
diff --git a/third_party/blink/renderer/core/css/properties/css_parsing_utils.cc b/third_party/blink/renderer/core/css/properties/css_parsing_utils.cc
index 938990a1..517f6ec 100644
--- a/third_party/blink/renderer/core/css/properties/css_parsing_utils.cc
+++ b/third_party/blink/renderer/core/css/properties/css_parsing_utils.cc
@@ -1285,6 +1285,29 @@
                                      context);
 }
 
+// Consume a custom ident more conservatively, for use in new uses of custom
+// idents, while we figure out if we can make these changes to existing uses.
+// Making new uses different avoids adding to the compatibility problem.
+CSSCustomIdentValue* ConsumeCustomIdentConservatively(
+    CSSParserTokenRange& range,
+    const CSSParserContext& context) {
+  if (range.Peek().GetType() != kIdentToken)
+    return nullptr;
+  switch (range.Peek().Id()) {
+    // TODO(crbug.com/882285): ConsumeCustomIdent should not allow "default".
+    case CSSValueID::kDefault:
+    // TODO(crbug.com/1340852): Find out if we can make auto/none/normal
+    // invalid generally.  For now, avoid allowing them on new custom idents.
+    case CSSValueID::kNone:
+    case CSSValueID::kNormal:
+    case CSSValueID::kAuto:
+      return nullptr;
+    default:
+      break;
+  }
+  return ConsumeCustomIdent(range, context);
+}
+
 CSSCustomIdentValue* ConsumeDashedIdent(CSSParserTokenRange& range,
                                         const CSSParserContext& context) {
   CSSCustomIdentValue* custom_ident = ConsumeCustomIdent(range, context);
@@ -5271,7 +5294,8 @@
                              const CSSParserContext& context) {
   if (range.Peek().Id() == CSSValueID::kNone)
     return nullptr;
-  CSSCustomIdentValue* toggle_name = ConsumeCustomIdent(range, context);
+  CSSCustomIdentValue* toggle_name =
+      ConsumeCustomIdentConservatively(range, context);
   if (!toggle_name)
     return nullptr;
 
@@ -5285,24 +5309,41 @@
   return list;
 }
 
+// <toggle-value> = <integer [0,∞]> | <custom-ident>
+static CSSValue* ConsumeToggleValue(CSSParserTokenRange& range,
+                                    const CSSParserContext& context) {
+  if (CSSPrimitiveValue* integer_value = ConsumeIntegerOrNumberCalc(
+          range, context, CSSPrimitiveValue::ValueRange::kNonNegativeInteger)) {
+    return integer_value;
+  }
+
+  return ConsumeCustomIdentConservatively(range, context);
+}
+
 CSSValue* ConsumeToggleSpecifier(CSSParserTokenRange& range,
                                  const CSSParserContext& context) {
   if (range.Peek().Id() == CSSValueID::kNone)
     return nullptr;
-  CSSCustomIdentValue* toggle_name = ConsumeCustomIdent(range, context);
+  CSSCustomIdentValue* toggle_name =
+      ConsumeCustomIdentConservatively(range, context);
   if (!toggle_name)
     return nullptr;
 
-  CSSPrimitiveValue* initial_state_value = nullptr;
-  CSSPrimitiveValue* maximum_state_value = nullptr;
-  CSSIdentifierValue* sticky_value = nullptr;
+  // Create the list now so that we can append the states to it when we
+  // find them; save the other values for the end.
+  CSSValueList* list = CSSValueList::CreateSpaceSeparated();
+  list->Append(*toggle_name);
+
+  bool found_states = false;
+  CSSIdentifierValue* overflow_value = nullptr;
   CSSIdentifierValue* group_value = nullptr;
   CSSIdentifierValue* self_value = nullptr;
 
   while (!range.AtEnd()) {
-    if (!sticky_value) {
-      sticky_value = ConsumeIdent<CSSValueID::kSticky>(range);
-      if (sticky_value)
+    if (!overflow_value) {
+      overflow_value = ConsumeIdent<CSSValueID::kCycle, CSSValueID::kCycleOn,
+                                    CSSValueID::kSticky>(range);
+      if (overflow_value)
         continue;
     }
     if (!group_value) {
@@ -5315,47 +5356,67 @@
       if (self_value)
         continue;
     }
-    if (!maximum_state_value) {
-      DCHECK(!initial_state_value);
+    if (!found_states) {
+      // <toggle-states> [at <toggle-value>]?
+      //   where:
+      //     <toggle-states> = <integer [1,∞]> | '[' <custom-ident>* ']'
+      //     <toggle-value> = <integer [0,∞]> | <custom-ident>
+      if (CSSPrimitiveValue* maximum_state_value = ConsumeIntegerOrNumberCalc(
+              range, context,
+              CSSPrimitiveValue::ValueRange::kPositiveInteger)) {
+        found_states = true;
+        list->Append(*maximum_state_value);
+      } else if (range.Peek().GetType() == kLeftBracketToken) {
+        CSSParserTokenRange block = range.ConsumeBlock();
+        block.ConsumeWhitespace();
+        range.ConsumeWhitespace();
 
-      // [ <integer [0,∞]> / ]? <integer [1,∞]>
-      CSSParserTokenRange saved_range(range);
-      initial_state_value = ConsumeIntegerOrNumberCalc(
-          range, context, CSSPrimitiveValue::ValueRange::kNonNegativeInteger);
-      if (initial_state_value) {
-        if (!ConsumeSlashIncludingWhitespace(range)) {
-          // Retry as just <integer [1,∞]>.
-          range = saved_range;
-          initial_state_value = nullptr;
+        auto* state_list = MakeGarbageCollected<CSSBracketedValueList>();
+        HashSet<AtomicString> states_found;
+
+        while (true) {
+          CSSCustomIdentValue* state_name =
+              ConsumeCustomIdentConservatively(block, context);
+          if (!state_name)
+            break;
+
+          // If <toggle-states> is a bracketed list, and there are any
+          // repeated <custom-ident>s among its items, the property is
+          // invalid.
+          if (!states_found.insert(state_name->Value()).is_new_entry) {
+            return nullptr;
+          }
+
+          state_list->Append(*state_name);
         }
 
-        maximum_state_value = ConsumeIntegerOrNumberCalc(
-            range, context, CSSPrimitiveValue::ValueRange::kPositiveInteger);
-        if (maximum_state_value)
-          continue;
-        // Note: If this is ever used in a context where it could be
-        // followed by another slash, we'd need to retry here if we
-        // didn't already retry above, or better separate the code for
-        // parsing two numbers from the code for parsing one.
-        range = saved_range;
-        initial_state_value = nullptr;
+        // TODO(https://github.com/tabatkins/css-toggle/issues/19): The
+        // spec currently allows an empty list of states, but this
+        // doesn't make sense, so we do not.
+        if (state_list->length() == 0 || !block.AtEnd()) {
+          return nullptr;
+        }
+        list->Append(*state_list);
+        found_states = true;
+      }
+
+      if (found_states) {
+        if (CSSValue* at_value = ConsumeIdent<CSSValueID::kAt>(range)) {
+          list->Append(*at_value);
+          if (CSSValue* toggle_value = ConsumeToggleValue(range, context)) {
+            list->Append(*toggle_value);
+          } else {
+            return nullptr;
+          }
+        }
+        continue;
       }
     }
     break;
   }
 
-  CSSValueList* list = CSSValueList::CreateSpaceSeparated();
-  list->Append(*toggle_name);
-  if (maximum_state_value) {
-    CSSValueList* number_list = CSSValueList::CreateSlashSeparated();
-    if (initial_state_value) {
-      number_list->Append(*initial_state_value);
-    }
-    number_list->Append(*maximum_state_value);
-    list->Append(*number_list);
-  }
-  if (sticky_value)
-    list->Append(*sticky_value);
+  if (overflow_value)
+    list->Append(*overflow_value);
   if (group_value)
     list->Append(*group_value);
   if (self_value)
@@ -5368,17 +5429,39 @@
                                const CSSParserContext& context) {
   if (range.Peek().Id() == CSSValueID::kNone)
     return nullptr;
-  CSSCustomIdentValue* toggle_name = ConsumeCustomIdent(range, context);
+  CSSCustomIdentValue* toggle_name =
+      ConsumeCustomIdentConservatively(range, context);
   if (!toggle_name)
     return nullptr;
 
-  CSSPrimitiveValue* target_value = ConsumeIntegerOrNumberCalc(
-      range, context, CSSPrimitiveValue::ValueRange::kNonNegativeInteger);
-
   CSSValueList* list = CSSValueList::CreateSpaceSeparated();
   list->Append(*toggle_name);
-  if (target_value)
-    list->Append(*target_value);
+
+  CSSIdentifierValue* mode_value =
+      ConsumeIdent<CSSValueID::kPrev, CSSValueID::kNext, CSSValueID::kSet>(
+          range);
+  if (!mode_value)
+    return list;
+
+  list->Append(*mode_value);
+
+  if (mode_value->GetValueID() != CSSValueID::kSet) {
+    // [prev | next] <integer [1,∞]>?
+    DCHECK(mode_value->GetValueID() == CSSValueID::kPrev ||
+           mode_value->GetValueID() == CSSValueID::kNext);
+    CSSPrimitiveValue* increment_value = ConsumeIntegerOrNumberCalc(
+        range, context, CSSPrimitiveValue::ValueRange::kPositiveInteger);
+    if (increment_value)
+      list->Append(*increment_value);
+  } else {
+    // set <toggle-value>
+    DCHECK_EQ(mode_value->GetValueID(), CSSValueID::kSet);
+    if (CSSValue* target_value = ConsumeToggleValue(range, context)) {
+      list->Append(*target_value);
+    } else {
+      return nullptr;
+    }
+  }
 
   return list;
 }
@@ -5575,22 +5658,13 @@
     return nullptr;
   if (range.Peek().Id() == CSSValueID::kNormal)
     return nullptr;
-  // TODO(crbug.com/1066390): ConsumeCustomIdent should not allow "default".
-  if (range.Peek().Id() == CSSValueID::kDefault)
-    return nullptr;
-  // TODO(crbug.com/1340852): Find out if we can make auto/none invalid
-  // generally.
-  if (range.Peek().Id() == CSSValueID::kNone)
-    return nullptr;
-  if (range.Peek().Id() == CSSValueID::kAuto)
-    return nullptr;
   if (EqualIgnoringASCIICase(range.Peek().Value(), "not"))
     return nullptr;
   if (EqualIgnoringASCIICase(range.Peek().Value(), "and"))
     return nullptr;
   if (EqualIgnoringASCIICase(range.Peek().Value(), "or"))
     return nullptr;
-  return ConsumeCustomIdent(range, context);
+  return ConsumeCustomIdentConservatively(range, context);
 }
 
 CSSValue* ConsumeContainerName(CSSParserTokenRange& range,
diff --git a/third_party/blink/renderer/core/css/properties/css_parsing_utils.h b/third_party/blink/renderer/core/css/properties/css_parsing_utils.h
index 2741f35..a144266 100644
--- a/third_party/blink/renderer/core/css/properties/css_parsing_utils.h
+++ b/third_party/blink/renderer/core/css/properties/css_parsing_utils.h
@@ -154,6 +154,8 @@
 
 CSSCustomIdentValue* ConsumeCustomIdent(CSSParserTokenRange&,
                                         const CSSParserContext&);
+CSSCustomIdentValue* ConsumeCustomIdentConservatively(CSSParserTokenRange&,
+                                                      const CSSParserContext&);
 CSSCustomIdentValue* ConsumeDashedIdent(CSSParserTokenRange&,
                                         const CSSParserContext&);
 CSSStringValue* ConsumeString(CSSParserTokenRange&);
diff --git a/third_party/blink/renderer/core/css/properties/longhands/longhands_custom.cc b/third_party/blink/renderer/core/css/properties/longhands/longhands_custom.cc
index 5f7e6d5..99dc47e 100644
--- a/third_party/blink/renderer/core/css/properties/longhands/longhands_custom.cc
+++ b/third_party/blink/renderer/core/css/properties/longhands/longhands_custom.cc
@@ -6,6 +6,7 @@
 #include "third_party/blink/renderer/core/css/basic_shape_functions.h"
 #include "third_party/blink/renderer/core/css/css_anchor_query_type.h"
 #include "third_party/blink/renderer/core/css/css_axis_value.h"
+#include "third_party/blink/renderer/core/css/css_bracketed_value_list.h"
 #include "third_party/blink/renderer/core/css/css_color.h"
 #include "third_party/blink/renderer/core/css/css_counter_value.h"
 #include "third_party/blink/renderer/core/css/css_cursor_image_value.h"
@@ -67,6 +68,7 @@
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
 #include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
 #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
+#include "third_party/blink/renderer/platform/text/quotes_data.h"
 
 // Implementations of methods in Longhand subclasses that aren't generated.
 
@@ -8775,18 +8777,62 @@
   for (const auto& item : toggle_root->Roots()) {
     CSSValueList* item_list = CSSValueList::CreateSpaceSeparated();
     item_list->Append(*MakeGarbageCollected<CSSCustomIdentValue>(item.Name()));
-    if (item.MaximumState() != 1 || item.InitialState() != 0) {
-      CSSValueList* number_list = CSSValueList::CreateSlashSeparated();
-      if (item.InitialState() != 0) {
-        number_list->Append(*CSSNumericLiteralValue::Create(
-            item.InitialState(), CSSPrimitiveValue::UnitType::kInteger));
+    const auto& states = item.StateSet();
+    bool states_is_default = states.IsInteger() && states.AsInteger() == 1u;
+    const auto& initial_state = item.InitialState();
+    bool initial_is_default =
+        initial_state.IsInteger() && initial_state.AsInteger() == 0u;
+    if (!states_is_default || !initial_is_default) {
+      switch (states.GetType()) {
+        using Type = decltype(states.GetType());
+        case Type::Integer: {
+          auto maximum_state = states.AsInteger();
+          item_list->Append(*CSSNumericLiteralValue::Create(
+              maximum_state, CSSPrimitiveValue::UnitType::kInteger));
+          break;
+        }
+        case Type::Names: {
+          auto* state_list =
+              MakeGarbageCollected<cssvalue::CSSBracketedValueList>();
+          for (const auto& state_name : states.AsNames()) {
+            state_list->Append(
+                *MakeGarbageCollected<CSSCustomIdentValue>(state_name));
+          }
+          item_list->Append(*state_list);
+          break;
+        }
       }
-      number_list->Append(*CSSNumericLiteralValue::Create(
-          item.MaximumState(), CSSPrimitiveValue::UnitType::kInteger));
-      item_list->Append(*number_list);
+
+      if (!initial_is_default) {
+        item_list->Append(*CSSIdentifierValue::Create(CSSValueID::kAt));
+        switch (initial_state.GetType()) {
+          using Type = decltype(initial_state.GetType());
+          case Type::Integer: {
+            auto initial_state_index = initial_state.AsInteger();
+            item_list->Append(*CSSNumericLiteralValue::Create(
+                initial_state_index, CSSPrimitiveValue::UnitType::kInteger));
+            break;
+          }
+          case Type::Name: {
+            const AtomicString& initial_state_name = initial_state.AsName();
+            item_list->Append(
+                *MakeGarbageCollected<CSSCustomIdentValue>(initial_state_name));
+            break;
+          }
+        }
+      }
     }
-    if (item.IsSticky())
-      item_list->Append(*CSSIdentifierValue::Create(CSSValueID::kSticky));
+    switch (item.Overflow()) {
+      case ToggleOverflow::kCycle:
+        // serialize nothing since it's the default
+        break;
+      case ToggleOverflow::kCycleOn:
+        item_list->Append(*CSSIdentifierValue::Create(CSSValueID::kCycleOn));
+        break;
+      case ToggleOverflow::kSticky:
+        item_list->Append(*CSSIdentifierValue::Create(CSSValueID::kSticky));
+        break;
+    }
     if (item.IsGroup())
       item_list->Append(*CSSIdentifierValue::Create(CSSValueID::kGroup));
     switch (item.Scope()) {
@@ -8823,15 +8869,40 @@
   for (const auto& item : toggle_trigger->Triggers()) {
     CSSValueList* item_list = CSSValueList::CreateSpaceSeparated();
     item_list->Append(*MakeGarbageCollected<CSSCustomIdentValue>(item.Name()));
+    CSSValueID id = CSSValueID::kInvalid;
     switch (item.Mode()) {
-      case ToggleTriggerMode::kAdd:
-        DCHECK_EQ(item.Value(), 1u);
+      case ToggleTriggerMode::kPrev:
+        id = CSSValueID::kPrev;
+        break;
+      case ToggleTriggerMode::kNext:
+        id = CSSValueID::kNext;
         break;
       case ToggleTriggerMode::kSet:
-        item_list->Append(*CSSNumericLiteralValue::Create(
-            item.Value(), CSSPrimitiveValue::UnitType::kInteger));
+        id = CSSValueID::kSet;
         break;
     }
+    const auto& value = item.Value();
+    switch (value.GetType()) {
+      using Type = decltype(value.GetType());
+      case Type::Integer: {
+        auto int_value = value.AsInteger();
+        if (id == CSSValueID::kSet || int_value != 1u) {
+          item_list->Append(*CSSIdentifierValue::Create(id));
+          item_list->Append(*CSSNumericLiteralValue::Create(
+              int_value, CSSPrimitiveValue::UnitType::kInteger));
+        } else if (id != CSSValueID::kNext) {
+          item_list->Append(*CSSIdentifierValue::Create(id));
+        }
+        break;
+      }
+      case Type::Name: {
+        DCHECK_EQ(id, CSSValueID::kSet);
+        item_list->Append(*CSSIdentifierValue::Create(id));
+        item_list->Append(
+            *MakeGarbageCollected<CSSCustomIdentValue>(value.AsName()));
+        break;
+      }
+    }
     result_list->Append(*item_list);
   }
   return result_list;
diff --git a/third_party/blink/renderer/core/css/resolver/style_builder_converter.cc b/third_party/blink/renderer/core/css/resolver/style_builder_converter.cc
index 55d9a04a..33a6a299 100644
--- a/third_party/blink/renderer/core/css/resolver/style_builder_converter.cc
+++ b/third_party/blink/renderer/core/css/resolver/style_builder_converter.cc
@@ -2400,29 +2400,68 @@
 
     wtf_size_t index = 1u;
 
-    uint32_t initial_state = 0;
-    uint32_t maximum_state = 1;
+    ToggleRoot::States states(1u);
+    ToggleRoot::State initial_state(0u);
     if (index < item_list->length()) {
-      if (const auto* number_list =
-              DynamicTo<CSSValueList>(item_list->Item(index))) {
+      bool found_states = false;
+      const CSSValue& state_value = item_list->Item(index);
+      if (const auto* states_list = DynamicTo<CSSValueList>(state_value)) {
         ++index;
-        DCHECK_LE(1u, number_list->length());
-        DCHECK_LE(number_list->length(), 2u);
-        if (number_list->length() == 2u) {
-          initial_state =
-              To<CSSPrimitiveValue>(number_list->Item(0)).GetValue<uint32_t>();
+        found_states = true;
+        DCHECK_LE(1u, states_list->length());
+        ToggleRoot::States::NamesType states_vec;
+        states_vec.ReserveInitialCapacity(states_list->length());
+        for (const auto& item : *states_list) {
+          states_vec.push_back(To<CSSCustomIdentValue>(*item).Value());
         }
-        maximum_state =
-            To<CSSPrimitiveValue>(number_list->Last()).GetValue<uint32_t>();
+        states = ToggleRoot::States(std::move(states_vec));
+      } else if (const auto* maximum_state_number =
+                     DynamicTo<CSSPrimitiveValue>(state_value)) {
+        ++index;
+        found_states = true;
+        states = ToggleRoot::States(maximum_state_number->GetValue<uint32_t>());
+      }
+
+      if (found_states && index < item_list->length()) {
+        const auto* at_value =
+            DynamicTo<CSSIdentifierValue>(item_list->Item(index));
+        if (at_value && at_value->GetValueID() == CSSValueID::kAt) {
+          ++index;
+
+          DCHECK_LT(index, item_list->length());
+          const CSSValue& initial_state_value = item_list->Item(index);
+          ++index;
+          if (const auto* initial_state_ident =
+                  DynamicTo<CSSCustomIdentValue>(initial_state_value)) {
+            initial_state = ToggleRoot::State(initial_state_ident->Value());
+          } else {
+            const auto& initial_state_number =
+                To<CSSPrimitiveValue>(initial_state_value);
+            initial_state =
+                ToggleRoot::State(initial_state_number.GetValue<uint32_t>());
+          }
+        }
       }
     }
 
-    bool is_sticky = false;
-    if (index < item_list->length() &&
-        To<CSSIdentifierValue>(item_list->Item(index)).GetValueID() ==
-            CSSValueID::kSticky) {
-      ++index;
-      is_sticky = true;
+    ToggleOverflow overflow = ToggleOverflow::kCycle;
+    if (index < item_list->length()) {
+      switch (To<CSSIdentifierValue>(item_list->Item(index)).GetValueID()) {
+        case CSSValueID::kCycle:
+          overflow = ToggleOverflow::kCycle;
+          ++index;
+          break;
+        case CSSValueID::kCycleOn:
+          overflow = ToggleOverflow::kCycleOn;
+          ++index;
+          break;
+        case CSSValueID::kSticky:
+          overflow = ToggleOverflow::kSticky;
+          ++index;
+          break;
+        default:
+          break;
+      }
     }
 
     bool is_group = false;
@@ -2442,8 +2481,8 @@
     }
     DCHECK_EQ(item_list->length(), index);
 
-    result->Append(ToggleRoot(name, initial_state, maximum_state, is_sticky,
-                              is_group, scope));
+    result->Append(
+        ToggleRoot(name, states, initial_state, overflow, is_group, scope));
   }
   return result;
 }
@@ -2459,17 +2498,39 @@
   for (const auto& item : To<CSSValueList>(value)) {
     const auto* item_list = To<CSSValueList>(item.Get());
     DCHECK_LE(1u, item_list->length());
-    DCHECK_LE(item_list->length(), 2u);
+    DCHECK_LE(item_list->length(), 3u);
     const AtomicString& name =
         To<CSSCustomIdentValue>(item_list->Item(0)).Value();
-    ToggleTriggerMode mode;
-    uint32_t value;
-    if (item_list->length() == 2u) {
-      mode = ToggleTriggerMode::kSet;
-      value = To<CSSPrimitiveValue>(item_list->Item(1)).GetValue<uint32_t>();
+
+    ToggleTriggerMode mode = ToggleTriggerMode::kNext;
+    if (item_list->length() > 1u) {
+      switch (To<CSSIdentifierValue>(item_list->Item(1)).GetValueID()) {
+        case CSSValueID::kPrev:
+          mode = ToggleTriggerMode::kPrev;
+          break;
+        case CSSValueID::kNext:
+          mode = ToggleTriggerMode::kNext;
+          break;
+        case CSSValueID::kSet:
+          mode = ToggleTriggerMode::kSet;
+          break;
+        default:
+          NOTREACHED();
+      }
+    }
+
+    ToggleTrigger::State value(1);
+    if (item_list->length() == 3u) {
+      const CSSValue& target_value = item_list->Item(2);
+      if (const auto* target_ident =
+              DynamicTo<CSSCustomIdentValue>(target_value)) {
+        value = ToggleTrigger::State(target_ident->Value());
+      } else {
+        const auto& target_number = To<CSSPrimitiveValue>(target_value);
+        value = ToggleTrigger::State(target_number.GetValue<uint32_t>());
+      }
     } else {
-      mode = ToggleTriggerMode::kAdd;
-      value = 1;
+      DCHECK_NE(mode, ToggleTriggerMode::kSet);
     }
 
     result->Append(ToggleTrigger(name, mode, value));
diff --git a/third_party/blink/renderer/core/css/resolver/style_builder_converter.h b/third_party/blink/renderer/core/css/resolver/style_builder_converter.h
index 3c188de..f95b3337e 100644
--- a/third_party/blink/renderer/core/css/resolver/style_builder_converter.h
+++ b/third_party/blink/renderer/core/css/resolver/style_builder_converter.h
@@ -47,7 +47,6 @@
 #include "third_party/blink/renderer/core/style/grid_positions_resolver.h"
 #include "third_party/blink/renderer/core/style/named_grid_lines_map.h"
 #include "third_party/blink/renderer/core/style/ordered_named_grid_lines.h"
-#include "third_party/blink/renderer/core/style/quotes_data.h"
 #include "third_party/blink/renderer/core/style/shadow_list.h"
 #include "third_party/blink/renderer/core/style/style_offset_rotation.h"
 #include "third_party/blink/renderer/core/style/style_overflow_clip_margin.h"
@@ -56,6 +55,7 @@
 #include "third_party/blink/renderer/platform/fonts/font_description.h"
 #include "third_party/blink/renderer/platform/geometry/length_size.h"
 #include "third_party/blink/renderer/platform/graphics/image_orientation.h"
+#include "third_party/blink/renderer/platform/text/quotes_data.h"
 #include "third_party/blink/renderer/platform/text/tab_size.h"
 #include "third_party/blink/renderer/platform/transforms/rotation.h"
 #include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
diff --git a/third_party/blink/renderer/core/dom/document.h b/third_party/blink/renderer/core/dom/document.h
index a065dd3a..223cca2 100644
--- a/third_party/blink/renderer/core/dom/document.h
+++ b/third_party/blink/renderer/core/dom/document.h
@@ -376,6 +376,14 @@
 
   bool IsPrerendering() const { return is_prerendering_; }
 
+  void SetIsTrackingSoftNavigationHeuristics(bool value) {
+    is_tracking_soft_navigation_heuristics_ = value;
+  }
+
+  bool IsTrackingSoftNavigationHeuristics() const {
+    return is_tracking_soft_navigation_heuristics_;
+  }
+
   network::mojom::ReferrerPolicy GetReferrerPolicy() const;
 
   bool DocumentPolicyFeatureObserved(
@@ -2111,6 +2119,8 @@
 
   bool well_formed_;
 
+  bool is_tracking_soft_navigation_heuristics_ = false;
+
   // Document URLs.
   KURL url_;  // Document.URL: The URL from which this document was retrieved.
   KURL base_url_;  // Node.baseURI: The URL to use when resolving relative URLs.
diff --git a/third_party/blink/renderer/core/exported/web_page_popup_impl.cc b/third_party/blink/renderer/core/exported/web_page_popup_impl.cc
index 40f20de..1f44b31 100644
--- a/third_party/blink/renderer/core/exported/web_page_popup_impl.cc
+++ b/third_party/blink/renderer/core/exported/web_page_popup_impl.cc
@@ -33,6 +33,7 @@
 
 #include "cc/animation/animation_host.h"
 #include "cc/animation/animation_timeline.h"
+#include "cc/base/features.h"
 #include "cc/layers/picture_layer.h"
 #include "cc/trees/ukm_manager.h"
 #include "third_party/blink/public/common/tokens/tokens.h"
@@ -728,7 +729,7 @@
         event.PositionInScreen(),
         WebFeature::kPopupGestureTapExceedsOwnerWindowBounds);
   }
-  if (RuntimeEnabledFeatures::ScrollUnificationEnabled()) {
+  if (base::FeatureList::IsEnabled(::features::kScrollUnification)) {
     if (event.GetType() == WebInputEvent::Type::kGestureScrollBegin) {
       HitTestLocation locationScroll(event.PositionInWidget());
       HitTestResult resultScroll =
diff --git a/third_party/blink/renderer/core/frame/local_frame_view.cc b/third_party/blink/renderer/core/frame/local_frame_view.cc
index f70cd4b..c35cc11 100644
--- a/third_party/blink/renderer/core/frame/local_frame_view.cc
+++ b/third_party/blink/renderer/core/frame/local_frame_view.cc
@@ -40,6 +40,7 @@
 #include "base/trace_event/typed_macros.h"
 #include "cc/animation/animation_host.h"
 #include "cc/animation/animation_timeline.h"
+#include "cc/base/features.h"
 #include "cc/document_transition/document_transition_request.h"
 #include "cc/input/main_thread_scrolling_reason.h"
 #include "cc/layers/picture_layer.h"
@@ -3056,7 +3057,7 @@
   }
 
   Vector<const TransformPaintPropertyNode*> scroll_translation_nodes;
-  if (RuntimeEnabledFeatures::ScrollUnificationEnabled()) {
+  if (base::FeatureList::IsEnabled(::features::kScrollUnification)) {
     ForAllNonThrottledLocalFrameViews(
         [&scroll_translation_nodes](LocalFrameView& frame_view) {
           frame_view.GetUserScrollTranslationNodes(scroll_translation_nodes);
diff --git a/third_party/blink/renderer/core/frame/visual_viewport_test.cc b/third_party/blink/renderer/core/frame/visual_viewport_test.cc
index 947839c..5f7a6369f 100644
--- a/third_party/blink/renderer/core/frame/visual_viewport_test.cc
+++ b/third_party/blink/renderer/core/frame/visual_viewport_test.cc
@@ -1846,7 +1846,7 @@
   // codepath which is extensively tested (including pinch interactions) in
   // cc/trees/layer_tree_host_unittest.cc and
   // cc/trees/layer_tree_host_unittest_scroll.cc.
-  if (RuntimeEnabledFeatures::ScrollUnificationEnabled())
+  if (base::FeatureList::IsEnabled(::features::kScrollUnification))
     return;
 
   InitializeWithDesktopSettings();
diff --git a/third_party/blink/renderer/core/frame/web_frame_widget_impl.cc b/third_party/blink/renderer/core/frame/web_frame_widget_impl.cc
index 46c2bf96..5e26e09 100644
--- a/third_party/blink/renderer/core/frame/web_frame_widget_impl.cc
+++ b/third_party/blink/renderer/core/frame/web_frame_widget_impl.cc
@@ -41,6 +41,7 @@
 #include "base/task/single_thread_task_runner.h"
 #include "base/time/time.h"
 #include "build/build_config.h"
+#include "cc/base/features.h"
 #include "cc/trees/compositor_commit_data.h"
 #include "cc/trees/layer_tree_host.h"
 #include "cc/trees/swap_promise.h"
@@ -3314,7 +3315,7 @@
     ui::ScrollGranularity granularity,
     cc::ElementId scrollable_area_element_id,
     blink::WebInputEvent::Type injected_type) {
-  if (RuntimeEnabledFeatures::ScrollUnificationEnabled()) {
+  if (base::FeatureList::IsEnabled(::features::kScrollUnification)) {
     // create a GestureScroll Event and post it to the compositor thread
     // TODO(crbug.com/1126098) use original input event's timestamp.
     // TODO(crbug.com/1082590) ensure continuity in scroll metrics collection
diff --git a/third_party/blink/renderer/core/frame/web_local_frame_impl.cc b/third_party/blink/renderer/core/frame/web_local_frame_impl.cc
index 92c06d39..868cb68 100644
--- a/third_party/blink/renderer/core/frame/web_local_frame_impl.cc
+++ b/third_party/blink/renderer/core/frame/web_local_frame_impl.cc
@@ -2978,4 +2978,8 @@
   has_scrolled_focused_editable_node_into_rect_ = false;
 }
 
+sk_sp<cc::PaintRecord> WebLocalFrameImpl::GetPaintRecord() const {
+  return GetFrame()->View()->GetPaintRecord();
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/frame/web_local_frame_impl.h b/third_party/blink/renderer/core/frame/web_local_frame_impl.h
index c7c0c74..d307068 100644
--- a/third_party/blink/renderer/core/frame/web_local_frame_impl.h
+++ b/third_party/blink/renderer/core/frame/web_local_frame_impl.h
@@ -514,6 +514,8 @@
       const blink::ContextMenuData& data,
       const absl::optional<gfx::Point>& host_context_menu_location);
 
+  sk_sp<cc::PaintRecord> GetPaintRecord() const override;
+
   virtual void Trace(Visitor*) const;
 
  protected:
diff --git a/third_party/blink/renderer/core/html/html_element.cc b/third_party/blink/renderer/core/html/html_element.cc
index ccfd16d..894697a 100644
--- a/third_party/blink/renderer/core/html/html_element.cc
+++ b/third_party/blink/renderer/core/html/html_element.cc
@@ -84,6 +84,7 @@
 #include "third_party/blink/renderer/core/page/spatial_navigation.h"
 #include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h"
 #include "third_party/blink/renderer/core/svg/svg_svg_element.h"
+#include "third_party/blink/renderer/core/timing/soft_navigation_heuristics.h"
 #include "third_party/blink/renderer/core/trustedtypes/trusted_script.h"
 #include "third_party/blink/renderer/core/xml_names.h"
 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
@@ -178,6 +179,42 @@
   return DynamicTo<HTMLElement>(FlatTreeTraversal::ParentElement(element));
 }
 
+bool IsDescendentOfMainElement(const Node* node) {
+  DCHECK(node);
+  do {
+    if (IsA<HTMLMainElement>(node)) {
+      return true;
+    }
+    node = node->parentNode();
+  } while (node);
+  return false;
+}
+
+void CheckSoftNavigationHeuristicsTracking(const Document& document,
+                                           const Node* insertion_point) {
+  DCHECK(insertion_point);
+  if (document.IsTrackingSoftNavigationHeuristics() &&
+      IsDescendentOfMainElement(insertion_point)) {
+    LocalDOMWindow* window = document.domWindow();
+    if (!window) {
+      return;
+    }
+    LocalFrame* frame = window->GetFrame();
+    if (!frame || !frame->IsMainFrame()) {
+      return;
+    }
+    ScriptState* script_state = ToScriptStateForMainWorld(frame);
+    if (!script_state) {
+      return;
+    }
+
+    SoftNavigationHeuristics* heuristics =
+        SoftNavigationHeuristics::From(*window);
+    DCHECK(heuristics);
+    heuristics->ModifiedMain(script_state);
+  }
+}
+
 }  // anonymous namespace
 
 String HTMLElement::DebugNodeName() const {
@@ -1295,6 +1332,10 @@
         element->UpdateDirectionalityAndDescendant(CachedDirectionality());
     }
   }
+  if (change.IsChildInsertion()) {
+    CheckSoftNavigationHeuristicsTracking(GetDocument(),
+                                          change.sibling_changed);
+  }
 }
 
 bool HTMLElement::HasDirectionAuto() const {
diff --git a/third_party/blink/renderer/core/html/html_main_element.cc b/third_party/blink/renderer/core/html/html_main_element.cc
index 2404f28..0a15525 100644
--- a/third_party/blink/renderer/core/html/html_main_element.cc
+++ b/third_party/blink/renderer/core/html/html_main_element.cc
@@ -12,13 +12,6 @@
 HTMLMainElement::HTMLMainElement(Document& document)
     : HTMLElement(html_names::kMainTag, document) {}
 
-void HTMLMainElement::ChildrenChanged(const ChildrenChange& children_change) {
-  HTMLElement::ChildrenChanged(children_change);
-  if (!children_change.ByParser()) {
-    NotifySoftNavigationHeuristics();
-  }
-}
-
 Node::InsertionNotificationRequest HTMLMainElement::InsertedInto(
     ContainerNode& container_node) {
   // Here we don't really know that the insertion was API driven rather than
diff --git a/third_party/blink/renderer/core/html/html_main_element.h b/third_party/blink/renderer/core/html/html_main_element.h
index 709b265..26d0b04 100644
--- a/third_party/blink/renderer/core/html/html_main_element.h
+++ b/third_party/blink/renderer/core/html/html_main_element.h
@@ -14,7 +14,6 @@
 class CORE_EXPORT HTMLMainElement final : public HTMLElement {
  public:
   explicit HTMLMainElement(Document&);
-  void ChildrenChanged(const ChildrenChange&) override;
   InsertionNotificationRequest InsertedInto(ContainerNode&) override;
 
  private:
diff --git a/third_party/blink/renderer/core/input/scroll_manager.cc b/third_party/blink/renderer/core/input/scroll_manager.cc
index 0206ce5..d8fa46f 100644
--- a/third_party/blink/renderer/core/input/scroll_manager.cc
+++ b/third_party/blink/renderer/core/input/scroll_manager.cc
@@ -6,6 +6,7 @@
 
 #include <utility>
 
+#include "cc/base/features.h"
 #include "cc/input/main_thread_scrolling_reason.h"
 #include "cc/input/snap_selection_strategy.h"
 #include "third_party/blink/renderer/core/dom/dom_node_ids.h"
@@ -495,8 +496,8 @@
 WebInputEventResult ScrollManager::HandleGestureScrollBegin(
     const WebGestureEvent& gesture_event) {
   TRACE_EVENT0("input", "ScrollManager::handleGestureScrollBegin");
-  DCHECK(!RuntimeEnabledFeatures::ScrollUnificationEnabled());
-  if (RuntimeEnabledFeatures::ScrollUnificationEnabled()) {
+  DCHECK(!base::FeatureList::IsEnabled(::features::kScrollUnification));
+  if (base::FeatureList::IsEnabled(::features::kScrollUnification)) {
     return WebInputEventResult::kNotHandled;
   }
 
@@ -584,8 +585,8 @@
   TRACE_EVENT0("input", "ScrollManager::handleGestureScrollUpdate");
   DCHECK_EQ(gesture_event.GetType(), WebInputEvent::Type::kGestureScrollUpdate);
 
-  DCHECK(!RuntimeEnabledFeatures::ScrollUnificationEnabled());
-  if (RuntimeEnabledFeatures::ScrollUnificationEnabled()) {
+  DCHECK(!base::FeatureList::IsEnabled(::features::kScrollUnification));
+  if (base::FeatureList::IsEnabled(::features::kScrollUnification)) {
     return WebInputEventResult::kNotHandled;
   }
 
@@ -751,8 +752,8 @@
     const WebGestureEvent& gesture_event) {
   TRACE_EVENT0("input", "ScrollManager::handleGestureScrollEnd");
 
-  DCHECK(!RuntimeEnabledFeatures::ScrollUnificationEnabled());
-  if (RuntimeEnabledFeatures::ScrollUnificationEnabled()) {
+  DCHECK(!base::FeatureList::IsEnabled(::features::kScrollUnification));
+  if (base::FeatureList::IsEnabled(::features::kScrollUnification)) {
     return WebInputEventResult::kNotHandled;
   }
 
diff --git a/third_party/blink/renderer/core/layout/layout_quote.cc b/third_party/blink/renderer/core/layout/layout_quote.cc
index 93638eb..75b0f5e 100644
--- a/third_party/blink/renderer/core/layout/layout_quote.cc
+++ b/third_party/blink/renderer/core/layout/layout_quote.cc
@@ -27,6 +27,7 @@
 #include "third_party/blink/renderer/core/layout/layout_text_fragment.h"
 #include "third_party/blink/renderer/core/layout/layout_view.h"
 #include "third_party/blink/renderer/core/layout/ng/inline/layout_ng_text_combine.h"
+#include "third_party/blink/renderer/platform/text/layout_locale.h"
 #include "third_party/blink/renderer/platform/wtf/std_lib_extras.h"
 #include "third_party/blink/renderer/platform/wtf/text/atomic_string.h"
 
@@ -75,209 +76,7 @@
   UpdateText();
 }
 
-struct Language {
-  const char* lang;
-  UChar open1;
-  UChar close1;
-  UChar open2;
-  UChar close2;
-  QuotesData* data;
-
-  bool operator<(const Language& b) const { return strcmp(lang, b.lang) < 0; }
-};
-
-// Table of quotes from
-// http://www.whatwg.org/specs/web-apps/current-work/multipage/rendering.html#quote
-Language g_languages[] = {
-    {"af", 0x201c, 0x201d, 0x2018, 0x2019, nullptr},
-    {"agq", 0x201e, 0x201d, 0x201a, 0x2019, nullptr},
-    {"ak", 0x201c, 0x201d, 0x2018, 0x2019, nullptr},
-    {"am", 0x00ab, 0x00bb, 0x2039, 0x203a, nullptr},
-    {"ar", 0x201d, 0x201c, 0x2019, 0x2018, nullptr},
-    {"asa", 0x201c, 0x201d, 0x2018, 0x2019, nullptr},
-    {"az-cyrl", 0x00ab, 0x00bb, 0x2039, 0x203a, nullptr},
-    {"bas", 0x00ab, 0x00bb, 0x201e, 0x201c, nullptr},
-    {"bem", 0x201c, 0x201d, 0x2018, 0x2019, nullptr},
-    {"bez", 0x201c, 0x201d, 0x2018, 0x2019, nullptr},
-    {"bg", 0x201e, 0x201c, 0x201a, 0x2018, nullptr},
-    {"bm", 0x00ab, 0x00bb, 0x201c, 0x201d, nullptr},
-    {"bn", 0x201c, 0x201d, 0x2018, 0x2019, nullptr},
-    {"br", 0x00ab, 0x00bb, 0x2039, 0x203a, nullptr},
-    {"brx", 0x201c, 0x201d, 0x2018, 0x2019, nullptr},
-    {"bs-cyrl", 0x201e, 0x201c, 0x201a, 0x2018, nullptr},
-    {"ca", 0x201c, 0x201d, 0x00ab, 0x00bb, nullptr},
-    {"cgg", 0x201c, 0x201d, 0x2018, 0x2019, nullptr},
-    {"chr", 0x201c, 0x201d, 0x2018, 0x2019, nullptr},
-    {"cs", 0x201e, 0x201c, 0x201a, 0x2018, nullptr},
-    {"da", 0x201c, 0x201d, 0x2018, 0x2019, nullptr},
-    {"dav", 0x201c, 0x201d, 0x2018, 0x2019, nullptr},
-    {"de", 0x201e, 0x201c, 0x201a, 0x2018, nullptr},
-    {"de-ch", 0x00ab, 0x00bb, 0x2039, 0x203a, nullptr},
-    {"dje", 0x201c, 0x201d, 0x2018, 0x2019, nullptr},
-    {"dua", 0x00ab, 0x00bb, 0x2018, 0x2019, nullptr},
-    {"dyo", 0x00ab, 0x00bb, 0x201c, 0x201d, nullptr},
-    {"dz", 0x201c, 0x201d, 0x2018, 0x2019, nullptr},
-    {"ebu", 0x201c, 0x201d, 0x2018, 0x2019, nullptr},
-    {"ee", 0x201c, 0x201d, 0x2018, 0x2019, nullptr},
-    {"el", 0x00ab, 0x00bb, 0x201c, 0x201d, nullptr},
-    {"en", 0x201c, 0x201d, 0x2018, 0x2019, nullptr},
-    {"en-gb", 0x201c, 0x201d, 0x2018, 0x2019, nullptr},
-    {"es", 0x201c, 0x201d, 0x00ab, 0x00bb, nullptr},
-    {"et", 0x201e, 0x201c, 0x201a, 0x2018, nullptr},
-    {"eu", 0x201c, 0x201d, 0x00ab, 0x00bb, nullptr},
-    {"ewo", 0x00ab, 0x00bb, 0x201c, 0x201d, nullptr},
-    {"fa", 0x00ab, 0x00bb, 0x2039, 0x203a, nullptr},
-    {"ff", 0x201e, 0x201d, 0x201a, 0x2019, nullptr},
-    {"fi", 0x201d, 0x201d, 0x2019, 0x2019, nullptr},
-    {"fr", 0x00ab, 0x00bb, 0x00ab, 0x00bb, nullptr},
-    {"fr-ca", 0x00ab, 0x00bb, 0x2039, 0x203a, nullptr},
-    {"fr-ch", 0x00ab, 0x00bb, 0x2039, 0x203a, nullptr},
-    {"gsw", 0x00ab, 0x00bb, 0x2039, 0x203a, nullptr},
-    {"gu", 0x201c, 0x201d, 0x2018, 0x2019, nullptr},
-    {"guz", 0x201c, 0x201d, 0x2018, 0x2019, nullptr},
-    {"ha", 0x201c, 0x201d, 0x2018, 0x2019, nullptr},
-    {"he", 0x0022, 0x0022, 0x0027, 0x0027, nullptr},
-    {"hi", 0x201c, 0x201d, 0x2018, 0x2019, nullptr},
-    {"hr", 0x201e, 0x201c, 0x201a, 0x2018, nullptr},
-    {"hu", 0x201e, 0x201d, 0x00bb, 0x00ab, nullptr},
-    {"id", 0x201c, 0x201d, 0x2018, 0x2019, nullptr},
-    {"ig", 0x201c, 0x201d, 0x2018, 0x2019, nullptr},
-    {"it", 0x00ab, 0x00bb, 0x201c, 0x201d, nullptr},
-    {"ja", 0x300c, 0x300d, 0x300e, 0x300f, nullptr},
-    {"jgo", 0x00ab, 0x00bb, 0x2039, 0x203a, nullptr},
-    {"jmc", 0x201c, 0x201d, 0x2018, 0x2019, nullptr},
-    {"kab", 0x00ab, 0x00bb, 0x201c, 0x201d, nullptr},
-    {"kam", 0x201c, 0x201d, 0x2018, 0x2019, nullptr},
-    {"kde", 0x201c, 0x201d, 0x2018, 0x2019, nullptr},
-    {"kea", 0x201c, 0x201d, 0x2018, 0x2019, nullptr},
-    {"khq", 0x201c, 0x201d, 0x2018, 0x2019, nullptr},
-    {"ki", 0x201c, 0x201d, 0x2018, 0x2019, nullptr},
-    {"kkj", 0x00ab, 0x00bb, 0x2039, 0x203a, nullptr},
-    {"kln", 0x201c, 0x201d, 0x2018, 0x2019, nullptr},
-    {"km", 0x201c, 0x201d, 0x2018, 0x2019, nullptr},
-    {"kn", 0x201c, 0x201d, 0x2018, 0x2019, nullptr},
-    {"ko", 0x201c, 0x201d, 0x2018, 0x2019, nullptr},
-    {"ksb", 0x201c, 0x201d, 0x2018, 0x2019, nullptr},
-    {"ksf", 0x00ab, 0x00bb, 0x2018, 0x2019, nullptr},
-    {"lag", 0x201d, 0x201d, 0x2019, 0x2019, nullptr},
-    {"lg", 0x201c, 0x201d, 0x2018, 0x2019, nullptr},
-    {"ln", 0x201c, 0x201d, 0x2018, 0x2019, nullptr},
-    {"lo", 0x201c, 0x201d, 0x2018, 0x2019, nullptr},
-    {"lt", 0x201e, 0x201c, 0x201e, 0x201c, nullptr},
-    {"lu", 0x201c, 0x201d, 0x2018, 0x2019, nullptr},
-    {"luo", 0x201c, 0x201d, 0x2018, 0x2019, nullptr},
-    {"luy", 0x201e, 0x201c, 0x201a, 0x2018, nullptr},
-    {"lv", 0x201c, 0x201d, 0x2018, 0x2019, nullptr},
-    {"mas", 0x201c, 0x201d, 0x2018, 0x2019, nullptr},
-    {"mer", 0x201c, 0x201d, 0x2018, 0x2019, nullptr},
-    {"mfe", 0x201c, 0x201d, 0x2018, 0x2019, nullptr},
-    {"mg", 0x00ab, 0x00bb, 0x201c, 0x201d, nullptr},
-    {"mgo", 0x201c, 0x201d, 0x2018, 0x2019, nullptr},
-    {"mk", 0x201e, 0x201c, 0x201a, 0x2018, nullptr},
-    {"ml", 0x201c, 0x201d, 0x2018, 0x2019, nullptr},
-    {"mr", 0x201c, 0x201d, 0x2018, 0x2019, nullptr},
-    {"ms", 0x201c, 0x201d, 0x2018, 0x2019, nullptr},
-    {"mua", 0x00ab, 0x00bb, 0x201c, 0x201d, nullptr},
-    {"my", 0x201c, 0x201d, 0x2018, 0x2019, nullptr},
-    {"naq", 0x201c, 0x201d, 0x2018, 0x2019, nullptr},
-    {"nb", 0x00ab, 0x00bb, 0x2018, 0x2019, nullptr},
-    {"nd", 0x201c, 0x201d, 0x2018, 0x2019, nullptr},
-    {"nl", 0x201c, 0x201d, 0x2018, 0x2019, nullptr},
-    {"nmg", 0x201e, 0x201d, 0x00ab, 0x00bb, nullptr},
-    {"nn", 0x00ab, 0x00bb, 0x2018, 0x2019, nullptr},
-    {"nnh", 0x00ab, 0x00bb, 0x201c, 0x201d, nullptr},
-    {"nus", 0x201c, 0x201d, 0x2018, 0x2019, nullptr},
-    {"nyn", 0x201c, 0x201d, 0x2018, 0x2019, nullptr},
-    {"pl", 0x201e, 0x201d, 0x00ab, 0x00bb, nullptr},
-    {"pt", 0x201c, 0x201d, 0x2018, 0x2019, nullptr},
-    {"pt-pt", 0x00ab, 0x00bb, 0x201c, 0x201d, nullptr},
-    {"rn", 0x201d, 0x201d, 0x2019, 0x2019, nullptr},
-    {"ro", 0x201e, 0x201d, 0x00ab, 0x00bb, nullptr},
-    {"rof", 0x201c, 0x201d, 0x2018, 0x2019, nullptr},
-    {"ru", 0x00ab, 0x00bb, 0x201e, 0x201c, nullptr},
-    {"rw", 0x00ab, 0x00bb, 0x2018, 0x2019, nullptr},
-    {"rwk", 0x201c, 0x201d, 0x2018, 0x2019, nullptr},
-    {"saq", 0x201c, 0x201d, 0x2018, 0x2019, nullptr},
-    {"sbp", 0x201c, 0x201d, 0x2018, 0x2019, nullptr},
-    {"seh", 0x201c, 0x201d, 0x2018, 0x2019, nullptr},
-    {"ses", 0x201c, 0x201d, 0x2018, 0x2019, nullptr},
-    {"sg", 0x00ab, 0x00bb, 0x201c, 0x201d, nullptr},
-    {"shi", 0x00ab, 0x00bb, 0x201e, 0x201d, nullptr},
-    {"shi-tfng", 0x00ab, 0x00bb, 0x201e, 0x201d, nullptr},
-    {"si", 0x201c, 0x201d, 0x2018, 0x2019, nullptr},
-    {"sk", 0x201e, 0x201c, 0x201a, 0x2018, nullptr},
-    {"sl", 0x201e, 0x201c, 0x201a, 0x2018, nullptr},
-    {"sn", 0x201d, 0x201d, 0x2019, 0x2019, nullptr},
-    {"so", 0x201c, 0x201d, 0x2018, 0x2019, nullptr},
-    {"sq", 0x201e, 0x201c, 0x201a, 0x2018, nullptr},
-    {"sr", 0x201e, 0x201c, 0x201a, 0x2018, nullptr},
-    {"sr-latn", 0x201e, 0x201c, 0x201a, 0x2018, nullptr},
-    {"sv", 0x201d, 0x201d, 0x2019, 0x2019, nullptr},
-    {"sw", 0x201c, 0x201d, 0x2018, 0x2019, nullptr},
-    {"swc", 0x201c, 0x201d, 0x2018, 0x2019, nullptr},
-    {"ta", 0x201c, 0x201d, 0x2018, 0x2019, nullptr},
-    {"te", 0x201c, 0x201d, 0x2018, 0x2019, nullptr},
-    {"teo", 0x201c, 0x201d, 0x2018, 0x2019, nullptr},
-    {"th", 0x201c, 0x201d, 0x2018, 0x2019, nullptr},
-    {"ti-er", 0x2018, 0x2019, 0x201c, 0x201d, nullptr},
-    {"to", 0x201c, 0x201d, 0x2018, 0x2019, nullptr},
-    {"tr", 0x201c, 0x201d, 0x2018, 0x2019, nullptr},
-    {"twq", 0x201c, 0x201d, 0x2018, 0x2019, nullptr},
-    {"tzm", 0x201c, 0x201d, 0x2018, 0x2019, nullptr},
-    {"uk", 0x00ab, 0x00bb, 0x201e, 0x201c, nullptr},
-    {"ur", 0x201d, 0x201c, 0x2019, 0x2018, nullptr},
-    {"vai", 0x201c, 0x201d, 0x2018, 0x2019, nullptr},
-    {"vai-latn", 0x201c, 0x201d, 0x2018, 0x2019, nullptr},
-    {"vi", 0x201c, 0x201d, 0x2018, 0x2019, nullptr},
-    {"vun", 0x201c, 0x201d, 0x2018, 0x2019, nullptr},
-    {"xh", 0x2018, 0x2019, 0x201c, 0x201d, nullptr},
-    {"xog", 0x201c, 0x201d, 0x2018, 0x2019, nullptr},
-    {"yav", 0x00ab, 0x00bb, 0x00ab, 0x00bb, nullptr},
-    {"yo", 0x201c, 0x201d, 0x2018, 0x2019, nullptr},
-    {"zh", 0x201c, 0x201d, 0x2018, 0x2019, nullptr},
-    {"zh-hant", 0x300c, 0x300d, 0x300e, 0x300f, nullptr},
-    {"zu", 0x201c, 0x201d, 0x2018, 0x2019, nullptr},
-};
-
-const QuotesData* QuotesDataForLanguage(const AtomicString& lang) {
-  if (lang.IsNull())
-    return nullptr;
-
-  // This could be just a hash table, but doing that adds 200k to LayoutQuote.o
-  Language* languages_end = g_languages + std::size(g_languages);
-  std::string lowercase_lang = lang.LowerASCII().Utf8();
-  Language key = {lowercase_lang.c_str(), 0, 0, 0, 0, nullptr};
-  Language* match = std::lower_bound(g_languages, languages_end, key);
-
-  if (match == languages_end)
-    --match;
-
-  if (strcmp(match->lang, key.lang)) {
-    // No exact match, try to find without subtags.
-    std::size_t hyphen_offset = lowercase_lang.find('-');
-    if (hyphen_offset == std::string::npos)
-      return nullptr;
-
-    std::string locale = lowercase_lang.substr(0, hyphen_offset);
-    while (match != g_languages && strcmp(match->lang, locale.c_str()) > 0) {
-      --match;
-    }
-
-    if (strcmp(match->lang, locale.c_str()))
-      return nullptr;
-  }
-
-  if (!match->data) {
-    auto data = QuotesData::Create(match->open1, match->close1, match->open2,
-                                   match->close2);
-    data->AddRef();
-    match->data = data.get();
-  }
-
-  return match->data;
-}
-
-static const QuotesData* BasicQuotesData() {
+static scoped_refptr<const QuotesData> BasicQuotesData() {
   DEFINE_STATIC_REF(QuotesData, static_basic_quotes,
                     (QuotesData::Create(0x201c, 0x201d, 0x2018, 0x2019)));
   return static_basic_quotes;
@@ -334,13 +133,15 @@
   return g_empty_string;
 }
 
-const QuotesData* LayoutQuote::GetQuotesData() const {
+scoped_refptr<const QuotesData> LayoutQuote::GetQuotesData() const {
   NOT_DESTROYED();
-  if (const QuotesData* custom_quotes = StyleRef().Quotes())
+  if (scoped_refptr<const QuotesData> custom_quotes = StyleRef().Quotes())
     return custom_quotes;
 
-  if (const QuotesData* quotes = QuotesDataForLanguage(StyleRef().Locale()))
-    return quotes;
+  if (const LayoutLocale* locale = StyleRef().GetFontDescription().Locale()) {
+    if (scoped_refptr<const QuotesData> custom_quotes = locale->GetQuotesData())
+      return custom_quotes;
+  }
 
   return BasicQuotesData();
 }
diff --git a/third_party/blink/renderer/core/layout/layout_quote.h b/third_party/blink/renderer/core/layout/layout_quote.h
index 2129bee6..ef7fbc3 100644
--- a/third_party/blink/renderer/core/layout/layout_quote.h
+++ b/third_party/blink/renderer/core/layout/layout_quote.h
@@ -23,7 +23,7 @@
 #define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_LAYOUT_QUOTE_H_
 
 #include "third_party/blink/renderer/core/layout/layout_inline.h"
-#include "third_party/blink/renderer/core/style/quotes_data.h"
+#include "third_party/blink/renderer/platform/text/quotes_data.h"
 
 namespace blink {
 
@@ -64,7 +64,7 @@
 
   String ComputeText() const;
   void UpdateText();
-  const QuotesData* GetQuotesData() const;
+  scoped_refptr<const QuotesData> GetQuotesData() const;
   void UpdateDepth();
   bool IsAttached() {
     NOT_DESTROYED();
diff --git a/third_party/blink/renderer/core/layout/ng/table/ng_table_layout_algorithm.cc b/third_party/blink/renderer/core/layout/ng/table/ng_table_layout_algorithm.cc
index 195ae66..a82849c 100644
--- a/third_party/blink/renderer/core/layout/ng/table/ng_table_layout_algorithm.cc
+++ b/third_party/blink/renderer/core/layout/ng/table/ng_table_layout_algorithm.cc
@@ -89,19 +89,35 @@
     LayoutUnit table_inline_size,
     const NGConstraintSpace& caption_constraint_space,
     const NGBlockNode& caption,
+    NGBoxStrut margins,
     const NGBlockBreakToken* break_token = nullptr) {
   const NGLayoutResult* layout_result =
       caption.Layout(caption_constraint_space, break_token);
   NGFragment fragment(table_constraint_space.GetWritingDirection(),
                       layout_result->PhysicalFragment());
-  NGBoxStrut margins = ComputeMarginsFor(
-      caption_constraint_space, caption.Style(), table_constraint_space);
   ResolveInlineMargins(caption.Style(), table_style, table_inline_size,
                        fragment.InlineSize(), &margins);
 
   return {caption, layout_result, margins};
 }
 
+// Compute the margins of a caption as best as we can before layout (we need to
+// lay out before we can resolve auto inline-margins). Remember that captions
+// aren't actually inside the table, so its the *border-box* size of the table
+// that matters here (not the content-box) when it comes to resolving
+// percentages.
+NGBoxStrut ComputeCaptionMargins(
+    const NGConstraintSpace& table_constraint_space,
+    const NGBlockNode& caption,
+    LayoutUnit table_border_box_inline_size,
+    const NGBlockBreakToken* caption_break_token = nullptr) {
+  NGBoxStrut margins =
+      ComputeMarginsFor(caption.Style(), table_border_box_inline_size,
+                        table_constraint_space.GetWritingDirection());
+  AdjustMarginsForFragmentation(caption_break_token, &margins);
+  return margins;
+}
+
 void ComputeCaptionFragments(
     const NGConstraintSpace& table_constraint_space,
     const ComputedStyle& table_style,
@@ -111,6 +127,8 @@
     LayoutUnit& captions_block_size) {
   const LogicalSize available_size = {table_inline_size, kIndefiniteSize};
   for (NGBlockNode caption : grouped_children.captions) {
+    NGBoxStrut margins = ComputeCaptionMargins(table_constraint_space, caption,
+                                               table_inline_size);
     NGConstraintSpace caption_constraint_space = CreateCaptionConstraintSpace(
         table_constraint_space, table_style, caption, available_size);
 
@@ -129,7 +147,7 @@
 
     NGTableLayoutAlgorithm::CaptionResult caption_result =
         LayoutCaption(table_constraint_space, table_style, table_inline_size,
-                      caption_constraint_space, caption);
+                      caption_constraint_space, caption, margins);
     NGFragment fragment(table_constraint_space.GetWritingDirection(),
                         caption_result.layout_result->PhysicalFragment());
     captions_block_size +=
@@ -1053,6 +1071,7 @@
     const NGBlockBreakToken* child_break_token = entry.GetBreakToken();
     const NGLayoutResult* child_result;
     LayoutUnit child_inline_offset;
+    LayoutUnit child_block_end_margin;  // Captions allow margins.
     absl::optional<TableBoxExtent> new_table_box_extent;
     bool is_repeated_section = false;
     if (child.IsTableCaption()) {
@@ -1089,14 +1108,19 @@
 
       LogicalSize available_size(container_builder_.InlineSize(),
                                  kIndefiniteSize);
+      NGBoxStrut margins = ComputeCaptionMargins(
+          ConstraintSpace(), child, container_builder_.InlineSize(),
+          child_break_token);
+      child_block_offset += margins.block_start;
+      child_block_end_margin = margins.block_end;
+
       NGConstraintSpace child_space =
           CreateCaptionConstraintSpace(ConstraintSpace(), Style(), child,
                                        available_size, child_block_offset);
       CaptionResult caption = LayoutCaption(
           ConstraintSpace(), Style(), container_builder_.InlineSize(),
-          child_space, child, child_break_token);
+          child_space, child, margins, child_break_token);
       child_result = caption.layout_result;
-      child_block_offset += caption.margins.block_start;
       child_inline_offset = caption.margins.inline_start;
     } else {
       DCHECK(child.IsTableSection());
@@ -1220,7 +1244,7 @@
 
     container_builder_.AddResult(
         *child_result, LogicalOffset(child_inline_offset, child_block_offset));
-    child_block_offset += fragment.BlockSize();
+    child_block_offset += fragment.BlockSize() + child_block_end_margin;
 
     if (child.IsTableSection()) {
       // Update the "table box" extent, now that we're past one section.
diff --git a/third_party/blink/renderer/core/layout/scrollbars_test.cc b/third_party/blink/renderer/core/layout/scrollbars_test.cc
index 86f11b7c..19698095 100644
--- a/third_party/blink/renderer/core/layout/scrollbars_test.cc
+++ b/third_party/blink/renderer/core/layout/scrollbars_test.cc
@@ -113,9 +113,6 @@
 class ScrollbarsTest : public PaintTestConfigurations, public SimTest {
  public:
   void SetUp() override {
-    if (RuntimeEnabledFeatures::ScrollUnificationEnabled())
-      feature_list_.InitAndEnableFeature(::features::kScrollUnification);
-
     SimTest::SetUp();
     // We don't use the mock scrollbar theme in this file, but use the normal
     // scrollbar theme with mock WebThemeEngine, for better control of testing
@@ -131,7 +128,6 @@
     SetOverlayScrollbarsEnabled(original_overlay_scrollbars_enabled_);
     mock_overlay_scrollbars_.reset();
     SimTest::TearDown();
-    feature_list_.Reset();
   }
 
   void SetOverlayScrollbarsEnabled(bool enabled) {
@@ -272,7 +268,6 @@
   ScopedStubThemeEngine scoped_theme_;
   std::unique_ptr<ScopedMockOverlayScrollbars> mock_overlay_scrollbars_;
   bool original_overlay_scrollbars_enabled_;
-  base::test::ScopedFeatureList feature_list_;
 };
 
 INSTANTIATE_PAINT_TEST_SUITE_P(ScrollbarsTest);
diff --git a/third_party/blink/renderer/core/page/scrolling/scrolling_test.cc b/third_party/blink/renderer/core/page/scrolling/scrolling_test.cc
index 7889cf1..74d9ddc 100644
--- a/third_party/blink/renderer/core/page/scrolling/scrolling_test.cc
+++ b/third_party/blink/renderer/core/page/scrolling/scrolling_test.cc
@@ -2067,7 +2067,7 @@
 
 class UnifiedScrollingSimTest : public SimTest, public PaintTestConfigurations {
  public:
-  UnifiedScrollingSimTest() : scroll_unification_enabled_(true) {}
+  UnifiedScrollingSimTest() = default;
 
   void SetUp() override {
     SimTest::SetUp();
@@ -2105,10 +2105,6 @@
                     ->GetLayoutBoxForScrolling();
     return box ? box->GetScrollableArea() : nullptr;
   }
-
- protected:
-  RuntimeEnabledFeaturesTestHelpers::ScopedScrollUnification
-      scroll_unification_enabled_;
 };
 
 INSTANTIATE_PAINT_TEST_SUITE_P(UnifiedScrollingSimTest);
@@ -2119,6 +2115,10 @@
 // noncomposited reasons set. It then removes the box-shadow property and
 // ensures the compositor node updates accordingly.
 TEST_P(UnifiedScrollingSimTest, ScrollNodeForNonCompositedScroller) {
+  // This test requires scroll unification.
+  if (!base::FeatureList::IsEnabled(::features::kScrollUnification))
+    return;
+
   SimRequest request("https://example.com/test.html", "text/html");
   LoadURL("https://example.com/test.html");
   request.Complete(R"HTML(
@@ -2182,6 +2182,10 @@
 // IsComposited state updated accordingly.
 TEST_P(UnifiedScrollingSimTest,
        ScrollNodeForCompositedToNonCompositedScroller) {
+  // This test requires scroll unification.
+  if (!base::FeatureList::IsEnabled(::features::kScrollUnification))
+    return;
+
   SimRequest request("https://example.com/test.html", "text/html");
   LoadURL("https://example.com/test.html");
   request.Complete(R"HTML(
@@ -2244,6 +2248,10 @@
 // scroller with an inset box shadow, and ensuring that scroller generates a
 // compositor scroll node with the proper noncomposited reasons set.
 TEST_P(UnifiedScrollingSimTest, ScrollNodeForEmbeddedScrollers) {
+  // This test requires scroll unification.
+  if (!base::FeatureList::IsEnabled(::features::kScrollUnification))
+    return;
+
   SimRequest request("https://example.com/test.html", "text/html");
   LoadURL("https://example.com/test.html");
   request.Complete(R"HTML(
@@ -2330,6 +2338,10 @@
 // Similar to the above test, but for deeper nesting iframes to ensure we
 // generate scroll nodes that are deeper than the main frame's children.
 TEST_P(UnifiedScrollingSimTest, ScrollNodeForNestedEmbeddedScrollers) {
+  // This test requires scroll unification.
+  if (!base::FeatureList::IsEnabled(::features::kScrollUnification))
+    return;
+
   SimRequest request("https://example.com/test.html", "text/html");
   SimRequest child_request_1("https://example.com/child1.html", "text/html");
   SimRequest child_request_2("https://example.com/child2.html", "text/html");
@@ -2420,6 +2432,10 @@
 // is no scroll node for a display:none scroller, as there is no scrollable
 // area.
 TEST_P(UnifiedScrollingSimTest, ScrollNodeForInvisibleNonCompositedScroller) {
+  // This test requires scroll unification.
+  if (!base::FeatureList::IsEnabled(::features::kScrollUnification))
+    return;
+
   SimRequest request("https://example.com/test.html", "text/html");
   LoadURL("https://example.com/test.html");
   request.Complete(R"HTML(
@@ -2482,6 +2498,10 @@
 // which are unique as they are not a composited scroller but also do not have
 // NonCompositedMainThreadScrollingReasons.
 TEST_P(UnifiedScrollingSimTest, ScrollNodeForInputBox) {
+  // This test requires scroll unification.
+  if (!base::FeatureList::IsEnabled(::features::kScrollUnification))
+    return;
+
   SimRequest request("https://example.com/test.html", "text/html");
   LoadURL("https://example.com/test.html");
   request.Complete(R"HTML(
@@ -2512,11 +2532,14 @@
 class ScrollingSimTest : public SimTest,
                          public testing::WithParamInterface<bool> {
  public:
-  ScrollingSimTest() : scroll_unification_enabled_(GetParam()) {}
+  ScrollingSimTest() = default;
 
   void SetUp() override {
-    if (RuntimeEnabledFeatures::ScrollUnificationEnabled())
+    if (GetParam())
       feature_list_.InitAndEnableFeature(::features::kScrollUnification);
+    else
+      feature_list_.InitAndDisableFeature(::features::kScrollUnification);
+
     SimTest::SetUp();
     WebView().GetSettings()->SetPreferCompositingToLCDTextEnabled(true);
     ResizeView(gfx::Size(1000, 1000));
@@ -2563,8 +2586,6 @@
   }
 
  protected:
-  RuntimeEnabledFeaturesTestHelpers::ScopedScrollUnification
-      scroll_unification_enabled_;
   base::test::ScopedFeatureList feature_list_;
 };
 
@@ -2623,7 +2644,7 @@
   ASSERT_EQ(0u, NumObjectsNeedingLayout());
 
   Element* box = GetDocument().getElementById("box");
-  if (RuntimeEnabledFeatures::ScrollUnificationEnabled()) {
+  if (base::FeatureList::IsEnabled(::features::kScrollUnification)) {
     // Dirty the layout
     box->setAttribute(html_names::kStyleAttr, "height: 10px");
     GetDocument().UpdateStyleAndLayoutTree();
diff --git a/third_party/blink/renderer/core/paint/compositing/compositing_test.cc b/third_party/blink/renderer/core/paint/compositing/compositing_test.cc
index dbbd27d..5e5a26b2 100644
--- a/third_party/blink/renderer/core/paint/compositing/compositing_test.cc
+++ b/third_party/blink/renderer/core/paint/compositing/compositing_test.cc
@@ -617,19 +617,10 @@
 
  private:
   void SetUp() override {
-    if (RuntimeEnabledFeatures::ScrollUnificationEnabled())
-      feature_list_.InitAndEnableFeature(::features::kScrollUnification);
-
     SimTest::SetUp();
     // Ensure a non-empty size so painting does not early-out.
     WebView().Resize(gfx::Size(800, 600));
   }
-  void TearDown() override {
-    SimTest::TearDown();
-    feature_list_.Reset();
-  }
-
-  base::test::ScopedFeatureList feature_list_;
 };
 
 INSTANTIATE_PAINT_TEST_SUITE_P(CompositingSimTest);
@@ -1882,7 +1873,7 @@
 
 TEST_P(CompositingSimTest, UnifiedScrollWithMainThreadReasonsNeedsCommit) {
   // This test requires scroll unification.
-  if (!RuntimeEnabledFeatures::ScrollUnificationEnabled())
+  if (!base::FeatureList::IsEnabled(::features::kScrollUnification))
     return;
 
   InitializeWithHTML(R"HTML(
diff --git a/third_party/blink/renderer/core/style/build.gni b/third_party/blink/renderer/core/style/build.gni
index 0268d4de..7ddde620 100644
--- a/third_party/blink/renderer/core/style/build.gni
+++ b/third_party/blink/renderer/core/style/build.gni
@@ -49,8 +49,6 @@
   "ordered_named_grid_lines.h",
   "paint_images.h",
   "quad_length_value.h",
-  "quotes_data.cc",
-  "quotes_data.h",
   "reference_clip_path_operation.cc",
   "reference_clip_path_operation.h",
   "shadow_data.cc",
diff --git a/third_party/blink/renderer/core/style/computed_style.cc b/third_party/blink/renderer/core/style/computed_style.cc
index bb5622f..e98a5cf 100644
--- a/third_party/blink/renderer/core/style/computed_style.cc
+++ b/third_party/blink/renderer/core/style/computed_style.cc
@@ -59,7 +59,6 @@
 #include "third_party/blink/renderer/core/style/computed_style_initial_values.h"
 #include "third_party/blink/renderer/core/style/content_data.h"
 #include "third_party/blink/renderer/core/style/cursor_data.h"
-#include "third_party/blink/renderer/core/style/quotes_data.h"
 #include "third_party/blink/renderer/core/style/shadow_list.h"
 #include "third_party/blink/renderer/core/style/style_difference.h"
 #include "third_party/blink/renderer/core/style/style_fetched_image.h"
@@ -76,6 +75,7 @@
 #include "third_party/blink/renderer/platform/graphics/path.h"
 #include "third_party/blink/renderer/platform/text/capitalize.h"
 #include "third_party/blink/renderer/platform/text/character.h"
+#include "third_party/blink/renderer/platform/text/quotes_data.h"
 #include "third_party/blink/renderer/platform/transforms/rotate_transform_operation.h"
 #include "third_party/blink/renderer/platform/transforms/scale_transform_operation.h"
 #include "third_party/blink/renderer/platform/transforms/translate_transform_operation.h"
diff --git a/third_party/blink/renderer/core/style/toggle_root.h b/third_party/blink/renderer/core/style/toggle_root.h
index 871331e..a657d89 100644
--- a/third_party/blink/renderer/core/style/toggle_root.h
+++ b/third_party/blink/renderer/core/style/toggle_root.h
@@ -6,53 +6,120 @@
 #define THIRD_PARTY_BLINK_RENDERER_CORE_STYLE_TOGGLE_ROOT_H_
 
 #include "base/check_op.h"
+#include "third_party/abseil-cpp/absl/types/variant.h"
 #include "third_party/blink/renderer/core/style/toggle_group.h"
 #include "third_party/blink/renderer/platform/wtf/text/atomic_string.h"
+#include "third_party/blink/renderer/platform/wtf/vector.h"
 
 namespace blink {
 
+enum class ToggleOverflow : uint8_t { kCycle = 0, kCycleOn = 1, kSticky = 2 };
+
 class ToggleRoot {
   DISALLOW_NEW();
 
  public:
+  class States {
+   public:
+    using IntegerType = uint32_t;
+    using NamesType = WTF::Vector<AtomicString>;
+
+    enum class Type : std::size_t { Integer = 0, Names = 1 };
+
+    explicit States(IntegerType integer) : value_(integer) {}
+    explicit States(NamesType names) : value_(names) {}
+
+    bool operator==(const States& other) const {
+      return value_ == other.value_;
+    }
+    bool operator!=(const States& other) const {
+      return value_ != other.value_;
+    }
+
+    Type GetType() const { return Type(value_.index()); }
+
+    bool IsInteger() const { return GetType() == Type::Integer; }
+    bool IsNames() const { return GetType() == Type::Names; }
+
+    IntegerType AsInteger() const {
+      return absl::get<std::size_t(Type::Integer)>(value_);
+    }
+    const NamesType& AsNames() const {
+      return absl::get<std::size_t(Type::Names)>(value_);
+    }
+
+   private:
+    absl::variant<IntegerType, NamesType> value_;
+  };
+
+  class State {
+   public:
+    using IntegerType = uint32_t;
+    using NameType = AtomicString;
+
+    enum class Type : std::size_t { Integer = 0, Name = 1 };
+
+    explicit State(IntegerType integer) : value_(integer) {}
+    explicit State(NameType names) : value_(names) {}
+
+    bool operator==(const State& other) const { return value_ == other.value_; }
+    bool operator!=(const State& other) const { return value_ != other.value_; }
+
+    Type GetType() const { return Type(value_.index()); }
+
+    bool IsInteger() const { return GetType() == Type::Integer; }
+    bool IsName() const { return GetType() == Type::Name; }
+
+    IntegerType AsInteger() const {
+      return absl::get<std::size_t(Type::Integer)>(value_);
+    }
+    const NameType& AsName() const {
+      return absl::get<std::size_t(Type::Name)>(value_);
+    }
+
+   private:
+    absl::variant<IntegerType, NameType> value_;
+  };
+
   ToggleRoot(const AtomicString& name,
-             uint32_t initial_state,
-             uint32_t maximum_state,
-             bool is_sticky,
+             States states,
+             State initial_state,
+             ToggleOverflow overflow,
              bool is_group,
              ToggleScope scope)
       : name_(name),
+        states_(states),
         initial_state_(initial_state),
-        maximum_state_(maximum_state),
-        is_sticky_(is_sticky),
+        overflow_(overflow),
         is_group_(is_group),
         scope_(scope) {
     DCHECK_EQ(scope_, scope) << "sufficient field width";
+    DCHECK_EQ(overflow_, overflow) << "sufficient field width";
   }
 
   ToggleRoot(const ToggleRoot&) = default;
   ~ToggleRoot() = default;
 
   bool operator==(const ToggleRoot& other) const {
-    return name_ == other.name_ && initial_state_ == other.initial_state_ &&
-           maximum_state_ == other.maximum_state_ &&
-           is_sticky_ == other.is_sticky_ && is_group_ == other.is_group_ &&
+    return name_ == other.name_ && states_ == other.states_ &&
+           initial_state_ == other.initial_state_ &&
+           overflow_ == other.overflow_ && is_group_ == other.is_group_ &&
            scope_ == other.scope_;
   }
   bool operator!=(const ToggleRoot& other) const { return !(*this == other); }
 
   const AtomicString& Name() const { return name_; }
-  uint32_t InitialState() const { return initial_state_; }
-  uint32_t MaximumState() const { return maximum_state_; }
-  bool IsSticky() const { return is_sticky_; }
+  States StateSet() const { return states_; }
+  State InitialState() const { return initial_state_; }
+  ToggleOverflow Overflow() const { return overflow_; }
   bool IsGroup() const { return is_group_; }
   ToggleScope Scope() const { return scope_; }
 
  private:
   const AtomicString name_;
-  const uint32_t initial_state_;
-  const uint32_t maximum_state_;
-  const bool is_sticky_ : 1;
+  const States states_;
+  const State initial_state_;
+  const ToggleOverflow overflow_ : 2;
   const bool is_group_ : 1;
   const ToggleScope scope_ : 1;
 };
diff --git a/third_party/blink/renderer/core/style/toggle_trigger.h b/third_party/blink/renderer/core/style/toggle_trigger.h
index a3b300db..12b8f82 100644
--- a/third_party/blink/renderer/core/style/toggle_trigger.h
+++ b/third_party/blink/renderer/core/style/toggle_trigger.h
@@ -5,19 +5,21 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_STYLE_TOGGLE_TRIGGER_H_
 #define THIRD_PARTY_BLINK_RENDERER_CORE_STYLE_TOGGLE_TRIGGER_H_
 
+#include "third_party/abseil-cpp/absl/types/variant.h"
+#include "third_party/blink/renderer/core/style/toggle_root.h"
 #include "third_party/blink/renderer/platform/wtf/text/atomic_string.h"
 
 namespace blink {
 
-enum class ToggleTriggerMode : uint8_t { kAdd = 0, kSet = 1 };
+enum class ToggleTriggerMode : uint8_t { kNext = 0, kPrev = 1, kSet = 2 };
 
 class ToggleTrigger {
   DISALLOW_NEW();
 
+  using State = ToggleRoot::State;
+
  public:
-  ToggleTrigger(const AtomicString& name,
-                ToggleTriggerMode mode,
-                uint32_t value)
+  ToggleTrigger(const AtomicString& name, ToggleTriggerMode mode, State value)
       : name_(name), mode_(mode), value_(value) {}
 
   ToggleTrigger(const ToggleTrigger&) = default;
@@ -33,12 +35,12 @@
 
   const AtomicString& Name() const { return name_; }
   ToggleTriggerMode Mode() const { return mode_; }
-  uint32_t Value() const { return value_; }
+  State Value() const { return value_; }
 
  private:
   const AtomicString name_;
   const ToggleTriggerMode mode_;
-  const uint32_t value_;
+  const State value_;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/timing/soft_navigation_heuristics.cc b/third_party/blink/renderer/core/timing/soft_navigation_heuristics.cc
index 77ae32b..8e276bd 100644
--- a/third_party/blink/renderer/core/timing/soft_navigation_heuristics.cc
+++ b/third_party/blink/renderer/core/timing/soft_navigation_heuristics.cc
@@ -59,10 +59,22 @@
   return heuristics;
 }
 
+void SoftNavigationHeuristics::SetIsTrackingSoftNavigationHeuristicsOnDocument(
+    bool value) const {
+  LocalDOMWindow* window = GetSupplementable();
+  if (!window) {
+    return;
+  }
+  if (Document* document = window->document()) {
+    document->SetIsTrackingSoftNavigationHeuristics(value);
+  }
+}
+
 void SoftNavigationHeuristics::ResetHeuristic() {
   // Reset previously seen indicators and task IDs.
   flag_set_.Clear();
   potential_soft_navigation_task_ids_.clear();
+  SetIsTrackingSoftNavigationHeuristicsOnDocument(false);
 }
 
 void SoftNavigationHeuristics::UserInitiatedClick(ScriptState* script_state) {
@@ -73,6 +85,7 @@
   DCHECK(scheduler->GetTaskAttributionTracker());
   ResetHeuristic();
   scheduler->GetTaskAttributionTracker()->RegisterObserver(this);
+  SetIsTrackingSoftNavigationHeuristicsOnDocument(true);
 }
 
 bool SoftNavigationHeuristics::IsCurrentTaskDescendantOfClickEventHandler(
@@ -126,6 +139,7 @@
     LogToConsole(script_state, mojom::blink::ConsoleMessageLevel::kVerbose,
                  String("Modified main element."));
   }
+  SetIsTrackingSoftNavigationHeuristicsOnDocument(false);
 }
 
 void SoftNavigationHeuristics::CheckSoftNavigation(ScriptState* script_state) {
diff --git a/third_party/blink/renderer/core/timing/soft_navigation_heuristics.h b/third_party/blink/renderer/core/timing/soft_navigation_heuristics.h
index 779b7ed..34df3b0 100644
--- a/third_party/blink/renderer/core/timing/soft_navigation_heuristics.h
+++ b/third_party/blink/renderer/core/timing/soft_navigation_heuristics.h
@@ -45,6 +45,7 @@
 
  private:
   void CheckSoftNavigation(ScriptState*);
+  void SetIsTrackingSoftNavigationHeuristicsOnDocument(bool value) const;
   enum FlagType : uint8_t {
     kURLChange,
     kMainModification,
diff --git a/third_party/blink/renderer/modules/accessibility/ax_node_object.cc b/third_party/blink/renderer/modules/accessibility/ax_node_object.cc
index b111e0c..bd81484 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_node_object.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_node_object.cc
@@ -4595,6 +4595,19 @@
     return true;
   }
 
+  // If this node is already the currently focused node, then calling
+  // focus() won't do anything.  That is a problem when focus is removed
+  // from the webpage to chrome, and then returns.  In these cases, we need
+  // to do what keyboard and mouse focus do, which is reset focus first.
+  if (document->FocusedElement() == element) {
+    document->ClearFocusedElement();
+
+    // Calling ClearFocusedElement could result in changes to the document,
+    // like this AXObject becoming detached.
+    if (IsDetached())
+      return false;
+  }
+
   if (base::FeatureList::IsEnabled(blink::features::kSimulateClickOnAXFocus)) {
     // If the object is not natively focusable but can be focused using an ARIA
     // active descendant, perform a native click instead. This will enable Web
diff --git a/third_party/blink/renderer/modules/mediasource/BUILD.gn b/third_party/blink/renderer/modules/mediasource/BUILD.gn
index 1cf60cf..b70c573 100644
--- a/third_party/blink/renderer/modules/mediasource/BUILD.gn
+++ b/third_party/blink/renderer/modules/mediasource/BUILD.gn
@@ -22,6 +22,8 @@
     "media_source_handle_attachment.h",
     "media_source_handle_impl.cc",
     "media_source_handle_impl.h",
+    "media_source_handle_transfer_list.cc",
+    "media_source_handle_transfer_list.h",
     "media_source_registry_impl.cc",
     "media_source_registry_impl.h",
     "same_thread_media_source_attachment.cc",
diff --git a/third_party/blink/renderer/modules/mediasource/media_source_handle.idl b/third_party/blink/renderer/modules/mediasource/media_source_handle.idl
index 4d67883..0085e52 100644
--- a/third_party/blink/renderer/modules/mediasource/media_source_handle.idl
+++ b/third_party/blink/renderer/modules/mediasource/media_source_handle.idl
@@ -9,7 +9,7 @@
 [
     Exposed=(Window,DedicatedWorker),
     ImplementedAs=MediaSourceHandleImpl,
-    Serializable,
+    Transferable,
     RuntimeEnabled=MediaSourceInWorkersUsingHandle
 ] interface MediaSourceHandle {
   // Trivial handle object useful for attaching a DedicatedWorker MediaSource
diff --git a/third_party/blink/renderer/modules/mediasource/media_source_handle_attachment.cc b/third_party/blink/renderer/modules/mediasource/media_source_handle_attachment.cc
index 3669aaa..49289bd 100644
--- a/third_party/blink/renderer/modules/mediasource/media_source_handle_attachment.cc
+++ b/third_party/blink/renderer/modules/mediasource/media_source_handle_attachment.cc
@@ -6,6 +6,11 @@
 
 namespace blink {
 
+// static
 const void* const MediaSourceHandleAttachment::kAttachmentKey = nullptr;
 
+MediaSourceHandleAttachment::MediaSourceHandleAttachment() = default;
+
+MediaSourceHandleAttachment::~MediaSourceHandleAttachment() = default;
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/mediasource/media_source_handle_attachment.h b/third_party/blink/renderer/modules/mediasource/media_source_handle_attachment.h
index 43c4db87..f90660e 100644
--- a/third_party/blink/renderer/modules/mediasource/media_source_handle_attachment.h
+++ b/third_party/blink/renderer/modules/mediasource/media_source_handle_attachment.h
@@ -25,8 +25,8 @@
   using MediaSourceHandleVector = Vector<HandleInternals>;
 
   static const void* const kAttachmentKey;
-  MediaSourceHandleAttachment() = default;
-  ~MediaSourceHandleAttachment() override = default;
+  MediaSourceHandleAttachment();
+  ~MediaSourceHandleAttachment() override;
 
   bool IsLockedToAgentCluster() const override {
     return !attachments_.IsEmpty();
diff --git a/third_party/blink/renderer/modules/mediasource/media_source_handle_impl.cc b/third_party/blink/renderer/modules/mediasource/media_source_handle_impl.cc
index 8858691..6813a513 100644
--- a/third_party/blink/renderer/modules/mediasource/media_source_handle_impl.cc
+++ b/third_party/blink/renderer/modules/mediasource/media_source_handle_impl.cc
@@ -31,23 +31,25 @@
 
 scoped_refptr<HandleAttachmentProvider>
 MediaSourceHandleImpl::TakeAttachmentProvider() {
+  DVLOG(1) << __func__ << " this=" << this;
   return std::move(attachment_provider_);
 }
 
 scoped_refptr<MediaSourceAttachment> MediaSourceHandleImpl::TakeAttachment() {
+  DVLOG(1) << __func__ << " this=" << this;
   if (!attachment_provider_) {
-    // Either this handle instance has already been serialized, or it has been
-    // assigned as srcObject on an HTMLMediaElement and used later to begin
-    // asynchronous attachment start.
-    DCHECK(is_serialized() || is_used());
+    // Either this handle instance has already been serialized, has been
+    // transferred, or it has been assigned as srcObject on an HTMLMediaElement
+    // and used later to begin asynchronous attachment start.
+    DCHECK(is_serialized() || detached_ || is_used());
     return nullptr;
   }
 
-  // Otherwise, this handle instance must not yet have been serialized or used
-  // to begin an attachment. The only case we should be here is when this
-  // instance is being used to attempt asynchronous attachment start after it
-  // was set as srcObject on an HTMLMediaElement.
-  DCHECK(is_used() && !is_serialized());
+  // Otherwise, this handle instance must not yet have been serialized,
+  // transferred or used to begin an attachment. The only case we should be here
+  // is when this instance is being used to attempt asynchronous attachment
+  // start after it was set as srcObject on an HTMLMediaElement.
+  DCHECK(is_used() && !is_serialized() && !detached_);
   scoped_refptr<MediaSourceAttachment> result =
       attachment_provider_->TakeAttachment();
   attachment_provider_ = nullptr;
@@ -60,10 +62,12 @@
 }
 
 String MediaSourceHandleImpl::GetInternalBlobURL() {
+  DVLOG(1) << __func__ << " this=" << this;
   return internal_blob_url_;
 }
 
 void MediaSourceHandleImpl::mark_serialized() {
+  DVLOG(1) << __func__ << " this=" << this;
   DCHECK(!serialized_);
   serialized_ = true;
 
@@ -75,6 +79,13 @@
   DCHECK(!attachment_provider_);
 }
 
+void MediaSourceHandleImpl::mark_detached() {
+  DVLOG(1) << __func__ << " this=" << this;
+  DCHECK(!detached_);
+  detached_ = true;
+  attachment_provider_ = nullptr;
+}
+
 void MediaSourceHandleImpl::Trace(Visitor* visitor) const {
   ScriptWrappable::Trace(visitor);
 }
diff --git a/third_party/blink/renderer/modules/mediasource/media_source_handle_impl.h b/third_party/blink/renderer/modules/mediasource/media_source_handle_impl.h
index f6c72ac..2d0e8919 100644
--- a/third_party/blink/renderer/modules/mediasource/media_source_handle_impl.h
+++ b/third_party/blink/renderer/modules/mediasource/media_source_handle_impl.h
@@ -29,13 +29,24 @@
   scoped_refptr<MediaSourceAttachment> TakeAttachment() override;
   String GetInternalBlobURL() override;
 
+  // Occurs during serialization, and prevents further serialization success.
   void mark_serialized();
 
+  // Occurs during transfer finalization, and prevents further transfer attempt.
+  // Note, this is needed because an app may transfer without serialization,
+  // e.g. postMessage(null, [handle]), and this must prevent successful transfer
+  // again of the same handle instance, even though the way the message was
+  // posted prevents successful serialization and deserialization of the handle.
+  // This also prevents successful use of the handle instance for attachment.
+  void mark_detached();
+  bool is_detached() const { return detached_; }
+
   void Trace(Visitor*) const override;
 
  private:
   scoped_refptr<HandleAttachmentProvider> attachment_provider_;
   String internal_blob_url_;
+  bool detached_ = false;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/mediasource/media_source_handle_transfer_list.cc b/third_party/blink/renderer/modules/mediasource/media_source_handle_transfer_list.cc
new file mode 100644
index 0000000..60e1cff
--- /dev/null
+++ b/third_party/blink/renderer/modules/mediasource/media_source_handle_transfer_list.cc
@@ -0,0 +1,30 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/blink/renderer/modules/mediasource/media_source_handle_transfer_list.h"
+
+#include "base/logging.h"
+#include "third_party/blink/renderer/modules/mediasource/media_source_handle_impl.h"
+
+namespace blink {
+
+// static
+const void* const MediaSourceHandleTransferList::kTransferListKey = nullptr;
+
+MediaSourceHandleTransferList::MediaSourceHandleTransferList() = default;
+
+MediaSourceHandleTransferList::~MediaSourceHandleTransferList() = default;
+
+void MediaSourceHandleTransferList::FinalizeTransfer(ExceptionState&) {
+  DVLOG(3) << __func__ << " this=" << this;
+  for (MediaSourceHandleImpl* handle : media_source_handles) {
+    handle->mark_detached();
+  }
+}
+
+void MediaSourceHandleTransferList::Trace(Visitor* visitor) const {
+  visitor->Trace(media_source_handles);
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/modules/mediasource/media_source_handle_transfer_list.h b/third_party/blink/renderer/modules/mediasource/media_source_handle_transfer_list.h
new file mode 100644
index 0000000..ff0b8287
--- /dev/null
+++ b/third_party/blink/renderer/modules/mediasource/media_source_handle_transfer_list.h
@@ -0,0 +1,36 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_MEDIASOURCE_MEDIA_SOURCE_HANDLE_TRANSFER_LIST_H_
+#define THIRD_PARTY_BLINK_RENDERER_MODULES_MEDIASOURCE_MEDIA_SOURCE_HANDLE_TRANSFER_LIST_H_
+
+#include "third_party/blink/renderer/bindings/core/v8/serialization/transferables.h"
+#include "third_party/blink/renderer/modules/modules_export.h"
+#include "third_party/blink/renderer/platform/heap/collection_support/heap_vector.h"
+#include "third_party/blink/renderer/platform/heap/garbage_collected.h"
+
+namespace blink {
+
+class ExceptionState;
+class MediaSourceHandleImpl;
+
+class MODULES_EXPORT MediaSourceHandleTransferList
+    : public GarbageCollected<MediaSourceHandleTransferList>,
+      public Transferables::TransferList {
+ public:
+  static const void* const kTransferListKey;
+
+  MediaSourceHandleTransferList();
+  ~MediaSourceHandleTransferList() override;
+
+  void FinalizeTransfer(ExceptionState&) override;
+
+  void Trace(Visitor*) const override;
+
+  HeapVector<Member<MediaSourceHandleImpl>> media_source_handles;
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_MODULES_MEDIASOURCE_MEDIA_SOURCE_HANDLE_TRANSFER_LIST_H_
diff --git a/third_party/blink/renderer/modules/xr/xr_frame.cc b/third_party/blink/renderer/modules/xr/xr_frame.cc
index a258d70..47624f0 100644
--- a/third_party/blink/renderer/modules/xr/xr_frame.cc
+++ b/third_party/blink/renderer/modules/xr/xr_frame.cc
@@ -27,9 +27,6 @@
 
 const char kSessionMismatch[] = "XRSpace and XRFrame sessions do not match.";
 
-const char kCannotReportPoses[] =
-    "Poses cannot be given out for the current state.";
-
 const char kHitTestSourceUnavailable[] =
     "Unable to obtain hit test results for specified hit test source. Ensure "
     "that it was not already canceled.";
@@ -86,7 +83,7 @@
   }
 
   if (!session_->CanReportPoses()) {
-    exception_state.ThrowSecurityError(kCannotReportPoses);
+    exception_state.ThrowSecurityError(XRSession::kCannotReportPoses);
     return nullptr;
   }
 
@@ -237,7 +234,7 @@
   }
 
   if (!session_->CanReportPoses()) {
-    exception_state.ThrowSecurityError(kCannotReportPoses);
+    exception_state.ThrowSecurityError(XRSession::kCannotReportPoses);
     return nullptr;
   }
 
@@ -455,7 +452,7 @@
   }
 
   if (!session_->CanReportPoses()) {
-    exception_state.ThrowSecurityError(kCannotReportPoses);
+    exception_state.ThrowSecurityError(XRSession::kCannotReportPoses);
     return nullptr;
   }
 
@@ -533,7 +530,7 @@
   }
 
   if (!session_->CanReportPoses()) {
-    exception_state.ThrowSecurityError(kCannotReportPoses);
+    exception_state.ThrowSecurityError(XRSession::kCannotReportPoses);
     return false;
   }
 
diff --git a/third_party/blink/renderer/modules/xr/xr_hit_test_result.cc b/third_party/blink/renderer/modules/xr/xr_hit_test_result.cc
index ab8173e..a367f66 100644
--- a/third_party/blink/renderer/modules/xr/xr_hit_test_result.cc
+++ b/third_party/blink/renderer/modules/xr/xr_hit_test_result.cc
@@ -25,9 +25,18 @@
                     ? absl::optional<uint64_t>(hit_result.plane_id)
                     : absl::nullopt) {}
 
-XRPose* XRHitTestResult::getPose(XRSpace* other) {
+XRPose* XRHitTestResult::getPose(XRSpace* other,
+                                 ExceptionState& exception_state) {
+  if (!session_->CanReportPoses()) {
+    DVLOG(3) << __func__ << ": cannot report poses";
+    exception_state.ThrowSecurityError(XRSession::kCannotReportPoses);
+    return nullptr;
+  }
+
   auto maybe_other_space_native_from_mojo = other->NativeFromMojo();
-  DCHECK(maybe_other_space_native_from_mojo);
+  if (!maybe_other_space_native_from_mojo) {
+    return nullptr;
+  }
 
   auto mojo_from_this = TransformationMatrix(mojo_from_this_.ToTransform());
 
diff --git a/third_party/blink/renderer/modules/xr/xr_hit_test_result.h b/third_party/blink/renderer/modules/xr/xr_hit_test_result.h
index 62f7c73..7aa4a58 100644
--- a/third_party/blink/renderer/modules/xr/xr_hit_test_result.h
+++ b/third_party/blink/renderer/modules/xr/xr_hit_test_result.h
@@ -25,7 +25,7 @@
   explicit XRHitTestResult(XRSession* session,
                            const device::mojom::blink::XRHitResult& hit_result);
 
-  XRPose* getPose(XRSpace* relative_to);
+  XRPose* getPose(XRSpace* relative_to, ExceptionState& exception_state);
 
   ScriptPromise createAnchor(ScriptState* script_state,
                              ExceptionState& exception_state);
diff --git a/third_party/blink/renderer/modules/xr/xr_hit_test_result.idl b/third_party/blink/renderer/modules/xr/xr_hit_test_result.idl
index a5c4d24..2c87d875 100644
--- a/third_party/blink/renderer/modules/xr/xr_hit_test_result.idl
+++ b/third_party/blink/renderer/modules/xr/xr_hit_test_result.idl
@@ -4,6 +4,7 @@
 
 [SecureContext, Exposed=Window, RuntimeEnabled=WebXRHitTest]
 interface XRHitTestResult {
+  [RaisesException]
   XRPose? getPose(XRSpace relative_to);
 
   [RuntimeEnabled=WebXRAnchors, CallWith=ScriptState, RaisesException, MeasureAs=XRHitTestResultCreateAnchor]
diff --git a/third_party/blink/renderer/modules/xr/xr_session.cc b/third_party/blink/renderer/modules/xr/xr_session.cc
index cc21a2877..f8b8fb82 100644
--- a/third_party/blink/renderer/modules/xr/xr_session.cc
+++ b/third_party/blink/renderer/modules/xr/xr_session.cc
@@ -232,6 +232,7 @@
 constexpr char XRSession::kDepthSensingFeatureNotSupported[];
 constexpr char XRSession::kRawCameraAccessFeatureNotSupported[];
 constexpr char XRSession::kCannotCancelHitTestSource[];
+constexpr char XRSession::kCannotReportPoses[];
 
 class XRSession::XRSessionResizeObserverDelegate final
     : public ResizeObserver::Delegate {
diff --git a/third_party/blink/renderer/modules/xr/xr_session.h b/third_party/blink/renderer/modules/xr/xr_session.h
index c794d90..f979d66 100644
--- a/third_party/blink/renderer/modules/xr/xr_session.h
+++ b/third_party/blink/renderer/modules/xr/xr_session.h
@@ -89,6 +89,8 @@
   static constexpr char kCannotCancelHitTestSource[] =
       "Hit test source could not be canceled! Ensure that it was not already "
       "canceled.";
+  static constexpr char kCannotReportPoses[] =
+      "Poses cannot be given out for the current state.";
 
   // Runs all the video.requestVideoFrameCallback() callbacks associated with
   // one HTMLVideoElement. |double| is the |high_res_now_ms|, derived from
diff --git a/third_party/blink/renderer/platform/BUILD.gn b/third_party/blink/renderer/platform/BUILD.gn
index 1bd408d..e587d03 100644
--- a/third_party/blink/renderer/platform/BUILD.gn
+++ b/third_party/blink/renderer/platform/BUILD.gn
@@ -1368,6 +1368,8 @@
     "text/mathml_operator_dictionary.h",
     "text/platform_locale.cc",
     "text/platform_locale.h",
+    "text/quotes_data.cc",
+    "text/quotes_data.h",
     "text/segmented_string.cc",
     "text/segmented_string.h",
     "text/suffix_tree.h",
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 e767c614..17ab6d16 100644
--- a/third_party/blink/renderer/platform/exported/web_runtime_features.cc
+++ b/third_party/blink/renderer/platform/exported/web_runtime_features.cc
@@ -325,10 +325,6 @@
   RuntimeEnabledFeatures::SetTouchEventFeatureDetectionEnabled(enable);
 }
 
-void WebRuntimeFeatures::EnableScrollUnification(bool enable) {
-  RuntimeEnabledFeatures::SetScrollUnificationEnabled(enable);
-}
-
 void WebRuntimeFeatures::EnableWebGLDeveloperExtensions(bool enable) {
   RuntimeEnabledFeatures::SetWebGLDeveloperExtensionsEnabled(enable);
 }
diff --git a/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.cc b/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.cc
index 196618c..3401c3b 100644
--- a/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.cc
+++ b/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.cc
@@ -8,6 +8,7 @@
 #include <utility>
 
 #include "base/logging.h"
+#include "cc/base/features.h"
 #include "cc/document_transition/document_transition_request.h"
 #include "cc/paint/display_item_list.h"
 #include "cc/paint/paint_flags.h"
@@ -621,10 +622,11 @@
     const Vector<const TransformPaintPropertyNode*>& scroll_translation_nodes,
     Vector<std::unique_ptr<cc::DocumentTransitionRequest>>
         transition_requests) {
+  const bool unification_enabled =
+      base::FeatureList::IsEnabled(features::kScrollUnification);
   // See: |UpdateRepaintedLayers| for repaint updates.
   DCHECK(needs_update_);
-  DCHECK(scroll_translation_nodes.IsEmpty() ||
-         RuntimeEnabledFeatures::ScrollUnificationEnabled());
+  DCHECK(scroll_translation_nodes.IsEmpty() || unification_enabled);
   DCHECK(root_layer_);
 
   TRACE_EVENT0("blink", "PaintArtifactCompositor::Update");
@@ -661,7 +663,7 @@
 
   // With ScrollUnification, we ensure a cc::ScrollNode for all
   // |scroll_translation_nodes|.
-  if (RuntimeEnabledFeatures::ScrollUnificationEnabled())
+  if (unification_enabled)
     property_tree_manager.EnsureCompositorScrollNodes(scroll_translation_nodes);
 
   for (auto& entry : synthesized_clip_cache_)
@@ -706,7 +708,7 @@
     // property_tree_manager.SetCcScrollNodeIsComposited(scroll_translation);
     int scroll_id =
         property_tree_manager.EnsureCompositorScrollNode(scroll_translation);
-    if (RuntimeEnabledFeatures::ScrollUnificationEnabled())
+    if (unification_enabled)
       property_tree_manager.SetCcScrollNodeIsComposited(scroll_id);
 
     layer_list_builder.Add(&layer);
diff --git a/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor_test.cc b/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor_test.cc
index dd6bd31..5bc14511 100644
--- a/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor_test.cc
+++ b/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor_test.cc
@@ -4670,8 +4670,9 @@
 }
 
 TEST_P(PaintArtifactCompositorTest, AddNonCompositedScrollNodes) {
-  RuntimeEnabledFeaturesTestHelpers::ScopedScrollUnification
-      scroll_unification_enabled_(true);
+  // This test requires scroll unification.
+  if (!base::FeatureList::IsEnabled(::features::kScrollUnification))
+    return;
 
   const uint32_t main_thread_scrolling_reason =
       cc::MainThreadScrollingReason::kNotOpaqueForTextAndLCDText;
diff --git a/third_party/blink/renderer/platform/graphics/compositing/property_tree_manager.cc b/third_party/blink/renderer/platform/graphics/compositing/property_tree_manager.cc
index 30158e9..853cea3 100644
--- a/third_party/blink/renderer/platform/graphics/compositing/property_tree_manager.cc
+++ b/third_party/blink/renderer/platform/graphics/compositing/property_tree_manager.cc
@@ -5,6 +5,7 @@
 #include "third_party/blink/renderer/platform/graphics/compositing/property_tree_manager.h"
 
 #include "build/build_config.h"
+#include "cc/base/features.h"
 #include "cc/input/overscroll_behavior.h"
 #include "cc/layers/layer.h"
 #include "cc/trees/clip_node.h"
@@ -19,7 +20,6 @@
 #include "third_party/blink/renderer/platform/graphics/paint/geometry_mapper.h"
 #include "third_party/blink/renderer/platform/graphics/paint/scroll_paint_property_node.h"
 #include "third_party/blink/renderer/platform/graphics/paint/transform_paint_property_node.h"
-#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
 
 namespace blink {
 
@@ -217,14 +217,14 @@
 
 void PropertyTreeManager::EnsureCompositorScrollNodes(
     const Vector<const TransformPaintPropertyNode*>& scroll_translation_nodes) {
-  DCHECK(RuntimeEnabledFeatures::ScrollUnificationEnabled());
+  DCHECK(base::FeatureList::IsEnabled(features::kScrollUnification));
 
   for (auto* node : scroll_translation_nodes)
     EnsureCompositorScrollNode(*node);
 }
 
 void PropertyTreeManager::SetCcScrollNodeIsComposited(int cc_node_id) {
-  DCHECK(RuntimeEnabledFeatures::ScrollUnificationEnabled());
+  DCHECK(base::FeatureList::IsEnabled(features::kScrollUnification));
   scroll_tree_.Node(cc_node_id)->is_composited = true;
 }
 
@@ -401,7 +401,7 @@
 
   // ScrollUnification creates the entire scroll tree and will already have done
   // this.
-  if (!RuntimeEnabledFeatures::ScrollUnificationEnabled()) {
+  if (!base::FeatureList::IsEnabled(features::kScrollUnification)) {
     if (auto* scroll_translation_for_fixed =
             transform_node.ScrollTranslationForFixed()) {
       // Fixed-position can cause different topologies of the transform tree and
diff --git a/third_party/blink/renderer/platform/image-decoders/avif/avif_image_decoder.cc b/third_party/blink/renderer/platform/image-decoders/avif/avif_image_decoder.cc
index 5b5f514..c24bf8e 100644
--- a/third_party/blink/renderer/platform/image-decoders/avif/avif_image_decoder.cc
+++ b/third_party/blink/renderer/platform/image-decoders/avif/avif_image_decoder.cc
@@ -857,8 +857,14 @@
                container->transferCharacteristics !=
                    AVIF_TRANSFER_CHARACTERISTICS_UNSPECIFIED) {
       gfx::ColorSpace frame_cs = GetColorSpace(container);
+
       sk_sp<SkColorSpace> sk_color_space =
           frame_cs.GetAsFullRangeRGB().ToSkColorSpace();
+      if (!sk_color_space) {
+        DVLOG(1) << "Image contains an unsupported color space";
+        return false;
+      }
+
       skcms_ICCProfile profile;
       sk_color_space->toProfile(&profile);
       SetEmbeddedColorProfile(std::make_unique<ColorProfile>(profile));
diff --git a/third_party/blink/renderer/platform/image-decoders/png/png_image_decoder.cc b/third_party/blink/renderer/platform/image-decoders/png/png_image_decoder.cc
index d048130..c6e48e9 100644
--- a/third_party/blink/renderer/platform/image-decoders/png/png_image_decoder.cc
+++ b/third_party/blink/renderer/platform/image-decoders/png/png_image_decoder.cc
@@ -203,6 +203,9 @@
 
   sk_sp<SkColorSpace> sk_color_space =
       color_space.ToGfxColorSpace().GetAsFullRangeRGB().ToSkColorSpace();
+  if (!sk_color_space)
+    return nullptr;
+
   skcms_ICCProfile profile;
   sk_color_space->toProfile(&profile);
 
diff --git a/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.cc b/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.cc
index 0ee2db5..86d2b99 100644
--- a/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.cc
+++ b/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.cc
@@ -1064,8 +1064,15 @@
   absl::optional<ResourceRequestBlockedReason> blocked_reason =
       PrepareRequest(params, factory, pauser);
   if (blocked_reason) {
-    return ResourceForBlockedRequest(params, factory, blocked_reason.value(),
-                                     client);
+    auto* resource = ResourceForBlockedRequest(params, factory,
+                                               blocked_reason.value(), client);
+    StorePerformanceTimingInitiatorInformation(resource);
+    if (auto info = resource_timing_info_map_.Take(resource)) {
+      PopulateAndAddResourceTimingInfo(resource, info,
+                                       /*response_end=*/base::TimeTicks::Now());
+      Context().AddResourceTiming(*info);
+    }
+    return resource;
   }
 
   Resource* resource = nullptr;
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index a98047f..8ba8819 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -2132,9 +2132,6 @@
       status: "stable",
     },
     {
-      name: "ScrollUnification",
-    },
-    {
       name: "SecureContextFixForSharedWorkers",
       status: "experimental",
     },
diff --git a/third_party/blink/renderer/platform/testing/paint_test_configurations.h b/third_party/blink/renderer/platform/testing/paint_test_configurations.h
index 88794dc..541a4b1 100644
--- a/third_party/blink/renderer/platform/testing/paint_test_configurations.h
+++ b/third_party/blink/renderer/platform/testing/paint_test_configurations.h
@@ -7,7 +7,9 @@
 
 #include <gtest/gtest.h>
 #include "base/task/thread_pool/thread_pool_instance.h"
+#include "base/test/scoped_feature_list.h"
 #include "base/test/task_environment.h"
+#include "cc/base/features.h"
 #include "third_party/blink/public/web/web_heap.h"
 #include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h"
 
@@ -17,13 +19,16 @@
 
 class PaintTestConfigurations
     : public testing::WithParamInterface<unsigned>,
-      private ScopedPaintUnderInvalidationCheckingForTest,
-      private ScopedScrollUnificationForTest {
+      private ScopedPaintUnderInvalidationCheckingForTest {
  public:
   PaintTestConfigurations()
-      : ScopedPaintUnderInvalidationCheckingForTest(GetParam() &
-                                                    kUnderInvalidationChecking),
-        ScopedScrollUnificationForTest(GetParam() & kScrollUnification) {}
+      : ScopedPaintUnderInvalidationCheckingForTest(
+            GetParam() & kUnderInvalidationChecking) {
+    if (GetParam() & kScrollUnification)
+      feature_list_.InitAndEnableFeature(::features::kScrollUnification);
+    else
+      feature_list_.InitAndDisableFeature(::features::kScrollUnification);
+  }
   ~PaintTestConfigurations() override {
     // Must destruct all objects before toggling back feature flags.
     std::unique_ptr<base::test::TaskEnvironment> task_environment;
@@ -31,8 +36,12 @@
       // Create a TaskEnvironment for the garbage collection below.
       task_environment = std::make_unique<base::test::TaskEnvironment>();
     }
+    feature_list_.Reset();
     WebHeap::CollectAllGarbageForTesting();
   }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
 };
 
 // For now this has only one configuration, but can be extended in the future
diff --git a/third_party/blink/renderer/platform/text/layout_locale.cc b/third_party/blink/renderer/platform/text/layout_locale.cc
index af9e381..aad39b9 100644
--- a/third_party/blink/renderer/platform/text/layout_locale.cc
+++ b/third_party/blink/renderer/platform/text/layout_locale.cc
@@ -17,6 +17,7 @@
 
 #include <hb.h>
 #include <unicode/locid.h>
+#include <unicode/ulocdata.h>
 
 namespace blink {
 
@@ -37,6 +38,45 @@
   return *data;
 }
 
+struct DelimiterConfig {
+  ULocaleDataDelimiterType type;
+  UChar* result;
+};
+// Use  ICU ulocdata to find quote delimiters for an ICU locale
+// https://unicode-org.github.io/icu-docs/apidoc/dev/icu4c/ulocdata_8h.html#a0bf1fdd1a86918871ae2c84b5ce8421f
+scoped_refptr<QuotesData> GetQuotesDataForLanguage(const char* locale) {
+  UErrorCode status = U_ZERO_ERROR;
+  // Expect returned buffer size is 1 to match QuotesData type
+  constexpr int ucharDelimMaxLength = 1;
+
+  ULocaleData* uld = ulocdata_open(locale, &status);
+  if (U_FAILURE(status)) {
+    ulocdata_close(uld);
+    return nullptr;
+  }
+  UChar open1[ucharDelimMaxLength], close1[ucharDelimMaxLength],
+      open2[ucharDelimMaxLength], close2[ucharDelimMaxLength];
+
+  int32_t delimResultLength;
+  struct DelimiterConfig delimiters[] = {
+      {ULOCDATA_QUOTATION_START, open1},
+      {ULOCDATA_QUOTATION_END, close1},
+      {ULOCDATA_ALT_QUOTATION_START, open2},
+      {ULOCDATA_ALT_QUOTATION_END, close2},
+  };
+  for (DelimiterConfig delim : delimiters) {
+    delimResultLength = ulocdata_getDelimiter(uld, delim.type, delim.result,
+                                              ucharDelimMaxLength, &status);
+    if (U_FAILURE(status) || delimResultLength != 1) {
+      ulocdata_close(uld);
+      return nullptr;
+    }
+  }
+  ulocdata_close(uld);
+
+  return QuotesData::Create(open1[0], close1[0], open2[0], close2[0]);
+};
+
 }  // namespace
 
 static hb_language_t ToHarfbuzLanguage(const AtomicString& locale) {
@@ -163,6 +203,7 @@
       script_for_han_(USCRIPT_COMMON),
       has_script_for_han_(false),
       hyphenation_computed_(false),
+      quotes_data_computed_(false),
       case_map_computed_(false) {}
 
 // static
@@ -222,6 +263,65 @@
   locale.hyphenation_ = std::move(hyphenation);
 }
 
+scoped_refptr<QuotesData> LayoutLocale::GetQuotesData() const {
+  if (quotes_data_computed_)
+    return quotes_data_;
+  quotes_data_computed_ = true;
+
+  // BCP 47 uses '-' as the delimiter but ICU uses '_'.
+  // https://tools.ietf.org/html/bcp47
+  String normalized_lang = LocaleString();
+  normalized_lang.Replace('-', '_');
+
+  UErrorCode status = U_ZERO_ERROR;
+  // Use uloc_openAvailableByType() to find all CLDR recognized locales
+  // https://unicode-org.github.io/icu-docs/apidoc/dev/icu4c/uloc_8h.html#aa0332857185774f3e0520a0823c14d16
+  UEnumeration* ulocales =
+      uloc_openAvailableByType(ULOC_AVAILABLE_DEFAULT, &status);
+  if (U_FAILURE(status)) {
+    uenum_close(ulocales);
+    return nullptr;
+  }
+
+  // Try to find exact match
+  while (const char* loc = uenum_next(ulocales, nullptr, &status)) {
+    if (U_FAILURE(status)) {
+      uenum_close(ulocales);
+      return nullptr;
+    }
+    if (EqualIgnoringASCIICase(loc, normalized_lang)) {
+      quotes_data_ = GetQuotesDataForLanguage(loc);
+      uenum_close(ulocales);
+      return quotes_data_;
+    }
+  }
+  uenum_close(ulocales);
+
+  // No exact match, try to find without subtags.
+  wtf_size_t hyphen_offset = normalized_lang.ReverseFind('_');
+  if (hyphen_offset == kNotFound)
+    return nullptr;
+  normalized_lang = normalized_lang.Substring(0, hyphen_offset);
+  ulocales = uloc_openAvailableByType(ULOC_AVAILABLE_DEFAULT, &status);
+  if (U_FAILURE(status)) {
+    uenum_close(ulocales);
+    return nullptr;
+  }
+  while (const char* loc = uenum_next(ulocales, nullptr, &status)) {
+    if (U_FAILURE(status)) {
+      uenum_close(ulocales);
+      return nullptr;
+    }
+    if (EqualIgnoringASCIICase(loc, normalized_lang)) {
+      quotes_data_ = GetQuotesDataForLanguage(loc);
+      uenum_close(ulocales);
+      return quotes_data_;
+    }
+  }
+  uenum_close(ulocales);
+  return nullptr;
+}
+
 AtomicString LayoutLocale::LocaleWithBreakKeyword(
     LineBreakIteratorMode mode) const {
   if (string_.IsEmpty())
diff --git a/third_party/blink/renderer/platform/text/layout_locale.h b/third_party/blink/renderer/platform/text/layout_locale.h
index 3adb401d..cd2f93b 100644
--- a/third_party/blink/renderer/platform/text/layout_locale.h
+++ b/third_party/blink/renderer/platform/text/layout_locale.h
@@ -7,6 +7,7 @@
 
 #include "third_party/blink/renderer/platform/platform_export.h"
 #include "third_party/blink/renderer/platform/text/hyphenation.h"
+#include "third_party/blink/renderer/platform/text/quotes_data.h"
 #include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 #include "third_party/blink/renderer/platform/wtf/forward.h"
 #include "third_party/blink/renderer/platform/wtf/std_lib_extras.h"
@@ -70,6 +71,7 @@
   }
 
   Hyphenation* GetHyphenation() const;
+  scoped_refptr<QuotesData> GetQuotesData() const;
 
   AtomicString LocaleWithBreakKeyword(LineBreakIteratorMode) const;
 
@@ -91,6 +93,7 @@
   mutable std::string string_for_sk_font_mgr_;
   mutable CaseMap::Locale locale_for_case_map_;
   mutable scoped_refptr<Hyphenation> hyphenation_;
+  mutable scoped_refptr<QuotesData> quotes_data_;
 
   // hb_language_t is defined in hb.h, which not all files can include.
   const hb_language_impl_t* harfbuzz_language_;
@@ -100,6 +103,7 @@
 
   mutable unsigned has_script_for_han_ : 1;
   mutable unsigned hyphenation_computed_ : 1;
+  mutable unsigned quotes_data_computed_ : 1;
   mutable unsigned case_map_computed_ : 1;
 };
 
diff --git a/third_party/blink/renderer/platform/text/layout_locale_test.cc b/third_party/blink/renderer/platform/text/layout_locale_test.cc
index e99f1146..f72538b5 100644
--- a/third_party/blink/renderer/platform/text/layout_locale_test.cc
+++ b/third_party/blink/renderer/platform/text/layout_locale_test.cc
@@ -178,6 +178,34 @@
   }
 }
 
+TEST(LayoutLocaleTest, GetQuotesData) {
+  auto enQuotes = (QuotesData::Create(0x201c, 0x201d, 0x2018, 0x2019));
+  auto frQuotes = (QuotesData::Create(0xab, 0xbb, 0xab, 0xbb));
+  auto frCAQuotes = (QuotesData::Create(0xab, 0xbb, 0x201d, 0x201c));
+  struct {
+    const char* locale;
+    const scoped_refptr<QuotesData> expected;
+  } tests[] = {
+      {nullptr, nullptr},    // no match
+      {"loc-DNE", nullptr},  // no match
+      {"en", enQuotes},      {"fr", frQuotes},
+      {"fr-CA", frCAQuotes}, {"fr-DNE", frQuotes},  // use fr
+  };
+  for (const auto& test : tests) {
+    scoped_refptr<LayoutLocale> locale =
+        LayoutLocale::CreateForTesting(test.locale);
+    scoped_refptr<QuotesData> quotes = locale->GetQuotesData();
+    if (test.expected) {
+      EXPECT_EQ(test.expected->GetOpenQuote(0), quotes->GetOpenQuote(0));
+      EXPECT_EQ(test.expected->GetOpenQuote(1), quotes->GetOpenQuote(1));
+      EXPECT_EQ(test.expected->GetCloseQuote(-1), quotes->GetCloseQuote(-1));
+      EXPECT_EQ(test.expected->GetCloseQuote(0), quotes->GetCloseQuote(0));
+    } else {
+      EXPECT_EQ(test.expected, quotes);
+    }
+  }
+}
+
 TEST(LayoutLocaleTest, ExistingKeywordName) {
   const char* tests[] = {
       "en@x=", "en@lb=xyz", "en@ =",
diff --git a/third_party/blink/renderer/core/style/quotes_data.cc b/third_party/blink/renderer/platform/text/quotes_data.cc
similarity index 96%
rename from third_party/blink/renderer/core/style/quotes_data.cc
rename to third_party/blink/renderer/platform/text/quotes_data.cc
index 4e39eb0..758dba3 100644
--- a/third_party/blink/renderer/core/style/quotes_data.cc
+++ b/third_party/blink/renderer/platform/text/quotes_data.cc
@@ -19,7 +19,7 @@
  *
  */
 
-#include "third_party/blink/renderer/core/style/quotes_data.h"
+#include "third_party/blink/renderer/platform/text/quotes_data.h"
 
 namespace blink {
 
diff --git a/third_party/blink/renderer/core/style/quotes_data.h b/third_party/blink/renderer/platform/text/quotes_data.h
similarity index 86%
rename from third_party/blink/renderer/core/style/quotes_data.h
rename to third_party/blink/renderer/platform/text/quotes_data.h
index ad734a3..4beb666 100644
--- a/third_party/blink/renderer/core/style/quotes_data.h
+++ b/third_party/blink/renderer/platform/text/quotes_data.h
@@ -19,10 +19,11 @@
  *
  */
 
-#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_STYLE_QUOTES_DATA_H_
-#define THIRD_PARTY_BLINK_RENDERER_CORE_STYLE_QUOTES_DATA_H_
+#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_TEXT_QUOTES_DATA_H_
+#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_TEXT_QUOTES_DATA_H_
 
 #include "base/memory/scoped_refptr.h"
+#include "third_party/blink/renderer/platform/platform_export.h"
 #include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 #include "third_party/blink/renderer/platform/wtf/ref_counted.h"
 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
@@ -30,7 +31,7 @@
 
 namespace blink {
 
-class QuotesData : public RefCounted<QuotesData> {
+class PLATFORM_EXPORT QuotesData : public RefCounted<QuotesData> {
   USING_FAST_MALLOC(QuotesData);
 
  public:
@@ -60,4 +61,4 @@
 
 }  // namespace blink
 
-#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_STYLE_QUOTES_DATA_H_
+#endif  // THIRD_PARTY_BLINK_RENDERER_PLATFORM_TEXT_QUOTES_DATA_H_
diff --git a/third_party/blink/tools/lint_test_expectations.py b/third_party/blink/tools/lint_test_expectations.py
index df6b3456..31963a28 100755
--- a/third_party/blink/tools/lint_test_expectations.py
+++ b/third_party/blink/tools/lint_test_expectations.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env vpython
+#!/usr/bin/env vpython3
 # Copyright 2018 The Chromium Authors. All rights reserved.
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
diff --git a/third_party/blink/web_tests/FlagExpectations/disable-layout-ng b/third_party/blink/web_tests/FlagExpectations/disable-layout-ng
index 582f874..d4c3a28 100644
--- a/third_party/blink/web_tests/FlagExpectations/disable-layout-ng
+++ b/third_party/blink/web_tests/FlagExpectations/disable-layout-ng
@@ -61,8 +61,6 @@
 crbug.com/626703 external/wpt/infrastructure/channels/test_call.html [ Timeout ]
 crbug.com/626703 external/wpt/infrastructure/channels/test_postMessage.html [ Timeout ]
 crbug.com/626703 external/wpt/infrastructure/channels/test_serialize.html [ Timeout ]
-crbug.com/626703 external/wpt/resource-timing/entries-for-network-errors.sub.https.html [ Timeout ]
-crbug.com/626703 virtual/plz-dedicated-worker/external/wpt/resource-timing/entries-for-network-errors.sub.https.html [ Timeout ]
 crbug.com/626703 external/wpt/css/css-flexbox/percentage-heights-019.html [ Failure ]
 crbug.com/626703 external/wpt/css/css-grid/grid-items/aspect-ratio-005.html [ Failure ]
 crbug.com/626703 external/wpt/mediacapture-fromelement/capture.html [ Crash ]
diff --git a/third_party/blink/web_tests/FlagExpectations/disable-site-isolation-trials b/third_party/blink/web_tests/FlagExpectations/disable-site-isolation-trials
index 8ca64b4..7e45e80 100644
--- a/third_party/blink/web_tests/FlagExpectations/disable-site-isolation-trials
+++ b/third_party/blink/web_tests/FlagExpectations/disable-site-isolation-trials
@@ -83,7 +83,6 @@
 crbug.com/626703 external/wpt/infrastructure/channels/test_call.html [ Timeout ]
 crbug.com/626703 external/wpt/infrastructure/channels/test_postMessage.html [ Timeout ]
 crbug.com/626703 external/wpt/infrastructure/channels/test_serialize.html [ Timeout ]
-crbug.com/626703 external/wpt/resource-timing/entries-for-network-errors.sub.https.html [ Timeout ]
 
 # crbug.com/1339051: some ref tests generate output with minor differences.
 crbug.com/1339051 external/wpt/css/css-transforms/perspective-split-by-zero-w.html [ Failure ]
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 74b3444..9ddd517 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -1466,7 +1466,7 @@
 crbug.com/1240236 external/wpt/css/css-fonts/system-ui-ja.html [ Failure ]
 crbug.com/1240236 external/wpt/css/css-fonts/system-ui-ur-vs-ar.html [ Failure ]
 crbug.com/1240236 external/wpt/css/css-fonts/system-ui-zh.html [ Failure ]
-crbug.com/1269537 [ Mac ] fast/css-generated-content/quotes-lang.html [ Failure Pass ]
+crbug.com/1269537 fast/css-generated-content/quotes-lang.html [ Failure Pass ]
 
 crbug.com/1317062 external/wpt/html/rendering/non-replaced-elements/phrasing-content-0/br-wbr-content/content-property.tentative.html [ Failure ]
 
@@ -1605,6 +1605,7 @@
 virtual/layout_ng_table_frag/external/wpt/css/css-break/table/break-before-expansion-002.html [ Pass ]
 virtual/layout_ng_table_frag/external/wpt/css/css-break/table/break-before-table-cell.html [ Pass ]
 virtual/layout_ng_table_frag/external/wpt/css/css-break/table/break-before-table-cell-child.html [ Pass ]
+virtual/layout_ng_table_frag/external/wpt/css/css-break/table/caption-margin-002.html [ Pass ]
 virtual/layout_ng_table_frag/external/wpt/css/css-break/table/repeated-section/abspos.tentative.html [ Pass ]
 virtual/layout_ng_table_frag/external/wpt/css/css-break/table/repeated-section/abspos-in-monolithic.tentative.html [ Pass ]
 virtual/layout_ng_table_frag/external/wpt/css/css-break/table/repeated-section/block-in-inline.tentative.html [ Pass ]
@@ -1803,8 +1804,6 @@
 
 crbug.com/753671 [ Linux ] external/wpt/css/css-content/quotes-001.html [ Failure ]
 crbug.com/753671 [ Mac ] external/wpt/css/css-content/quotes-006.html [ Failure ]
-crbug.com/753671 [ Mac10.13 ] external/wpt/css/css-content/quotes-007.html [ Failure ]
-crbug.com/753671 [ Mac10.14 ] external/wpt/css/css-content/quotes-007.html [ Failure ]
 crbug.com/753671 [ Mac ] external/wpt/css/css-content/quotes-009.html [ Failure ]
 crbug.com/753671 [ Mac10.13 ] external/wpt/css/css-content/quotes-012.html [ Failure ]
 crbug.com/753671 [ Mac10.14 ] external/wpt/css/css-content/quotes-012.html [ Failure ]
@@ -1815,7 +1814,6 @@
 crbug.com/753671 [ Mac11-arm64 ] external/wpt/css/css-content/quotes-020.html [ Failure ]
 crbug.com/753671 [ Mac12 ] external/wpt/css/css-content/quotes-020.html [ Failure ]
 crbug.com/753671 [ Mac12-arm64 ] external/wpt/css/css-content/quotes-020.html [ Failure ]
-crbug.com/753671 external/wpt/css/css-content/quotes-021.html [ Failure ]
 crbug.com/753671 external/wpt/css/css-content/quotes-030.html [ Failure ]
 crbug.com/1067277 external/wpt/css/css-content/element-replacement-on-replaced-element.tentative.html [ Failure ]
 
@@ -3508,8 +3506,6 @@
 crbug.com/626703 external/wpt/infrastructure/channels/test_serialize.html [ Timeout ]
 crbug.com/626703 [ Win10.20h2 ] external/wpt/mediacapture-streams/MediaDevices-enumerateDevices-per-origin-ids.sub.https.html [ Failure Timeout ]
 crbug.com/626703 [ Win10.20h2 ] virtual/feature-policy-permissions/external/wpt/mediacapture-streams/MediaDevices-enumerateDevices-per-origin-ids.sub.https.html [ Failure Timeout ]
-crbug.com/626703 external/wpt/resource-timing/entries-for-network-errors.sub.https.html [ Timeout ]
-crbug.com/626703 virtual/plz-dedicated-worker/external/wpt/resource-timing/entries-for-network-errors.sub.https.html [ Timeout ]
 crbug.com/626703 [ Mac11-arm64 ] external/wpt/webrtc/RTCDataChannel-close.html [ Skip Timeout ]
 crbug.com/626703 [ Mac11-arm64 ] external/wpt/webrtc/RTCPeerConnection-capture-video.https.html [ Skip Timeout ]
 crbug.com/626703 [ Mac11-arm64 ] external/wpt/webrtc/RTCPeerConnection-iceConnectionState.https.html [ Skip Timeout ]
@@ -3935,6 +3931,7 @@
 crbug.com/1078927 external/wpt/css/css-break/table/break-before-expansion-002.html [ Failure ]
 crbug.com/1078927 external/wpt/css/css-break/table/break-before-table-cell.html [ Failure ]
 crbug.com/1078927 external/wpt/css/css-break/table/break-before-table-cell-child.html [ Failure ]
+crbug.com/1078927 external/wpt/css/css-break/table/caption-margin-002.html [ Failure ]
 crbug.com/1078927 external/wpt/css/css-break/table/repeated-section/abspos.tentative.html [ Failure ]
 crbug.com/1078927 external/wpt/css/css-break/table/repeated-section/abspos-in-monolithic.tentative.html [ Failure ]
 crbug.com/1078927 external/wpt/css/css-break/table/repeated-section/block-in-inline.tentative.html [ Failure ]
diff --git a/third_party/blink/web_tests/external/wpt/content-security-policy/support/prefetch-helper.js b/third_party/blink/web_tests/external/wpt/content-security-policy/support/prefetch-helper.js
index 4beae88..cb6fd48 100644
--- a/third_party/blink/web_tests/external/wpt/content-security-policy/support/prefetch-helper.js
+++ b/third_party/blink/web_tests/external/wpt/content-security-policy/support/prefetch-helper.js
@@ -22,9 +22,14 @@
   });
 }
 
-async function assert_resource_not_downloaded(test, url) {
-  if (performance.getEntriesByName(url).length >= 1) {
-    (test.unreached_func(`'${url}' should not have downloaded.`))();
+function assert_resource_not_downloaded(test, url) {
+  // CSP failures generate resource timing entries, so let's make sure that
+  // download sizes are 0.
+  const entries = performance.getEntriesByName(url, 'resource');
+  for (const entry of entries) {
+    assert_equals(entry.transferSize, 0, 'transferSize');
+    assert_equals(entry.encodedBodySize, 0, 'encodedBodySize');
+    assert_equals(entry.decodedBodySize, 0, 'decodedBodySize');
   }
 }
 
diff --git a/third_party/blink/web_tests/external/wpt/css/css-break/table/caption-margin-001.html b/third_party/blink/web_tests/external/wpt/css/css-break/table/caption-margin-001.html
new file mode 100644
index 0000000..cb2746e
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-break/table/caption-margin-001.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
+<link rel="help" href="https://www.w3.org/TR/css-break-3/#box-splitting">
+<link rel="match" href="../../reference/ref-filled-green-100px-square.xht">
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div style="columns:2; gap:0; column-fill:auto; width:100px; height:100px; background:red;">
+  <div style="height:50px; background:green;"></div>
+  <div style="display:table-caption; margin-top:-30px;">
+    <div style="width:50px;">
+      <div style="height:30px;"></div>
+      <div style="height:150px; background:green;"></div>
+    </div>
+  </div>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-break/table/caption-margin-002.html b/third_party/blink/web_tests/external/wpt/css/css-break/table/caption-margin-002.html
new file mode 100644
index 0000000..3bb06bd
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-break/table/caption-margin-002.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
+<link rel="help" href="https://www.w3.org/TR/css-break-3/#box-splitting">
+<link rel="match" href="../../reference/ref-filled-green-100px-square.xht">
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div style="width:100px; height:100px; background:red;">
+  <div style="columns:2; gap:0; column-fill:auto; height:150px;">
+    <div style="height:100px; background:green;"></div>
+    <div style="display:table;">
+      <div style="display:table-caption;"></div>
+      <!-- We want a break opportunity before the second caption inside the
+           table, which is why we need another empty caption before it. -->
+      <div style="display:table-caption; margin-top:200px; contain:size; width:50px; height:100px; background:green;"></div>
+    </div>
+  </div>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-break/table/caption-margin-003.html b/third_party/blink/web_tests/external/wpt/css/css-break/table/caption-margin-003.html
new file mode 100644
index 0000000..e591c9e
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-break/table/caption-margin-003.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
+<link rel="help" href="https://www.w3.org/TR/css-break-3/#box-splitting">
+<link rel="match" href="../../reference/ref-filled-green-100px-square.xht">
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div style="columns:2; gap:0; column-fill:auto; width:100px; height:100px; background:red;">
+  <div style="display:table;">
+    <div style="display:table-caption; margin-bottom:20px; width:50px; height:50px; background:green;"></div>
+    <div style="margin-top:-20px; width:50px; height:150px; background:green;"></div>
+  </div>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-break/table/caption-margin-004.html b/third_party/blink/web_tests/external/wpt/css/css-break/table/caption-margin-004.html
new file mode 100644
index 0000000..5c70c70
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-break/table/caption-margin-004.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
+<link rel="help" href="https://www.w3.org/TR/css-break-3/#box-splitting">
+<link rel="match" href="../../reference/ref-filled-green-100px-square.xht">
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div style="width:100px; height:100px; background:red;">
+  <div style="columns:2; gap:0; column-fill:auto; height:150px;">
+    <div style="display:table-caption; caption-side:bottom; margin-bottom:50px; width:50px; height:100px; background:green;"></div>
+    <div style="height:100px; background:green;"></div>
+  </div>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-content/reference/quotes-034-ref.html b/third_party/blink/web_tests/external/wpt/css/css-content/reference/quotes-034-ref.html
index 7ec4cd8..bfe7163 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-content/reference/quotes-034-ref.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-content/reference/quotes-034-ref.html
@@ -20,8 +20,8 @@
 <p lang="de">&#x201e;de FALLBACK&#x201c</p>
 <p lang="de">&#x201e;de FALLBACK&#x201c</p>
 
-<p lang="he">&#x0022;he FALLBACK&#x0022</p>
-<p lang="he">&#x0022;he FALLBACK&#x0022</p>
+<p lang="he">&#x201d;he FALLBACK&#x201d</p>
+<p lang="he">&#x201d;he FALLBACK&#x201d</p>
 
 <p lang="ja">&#x300c;ja FALLBACK&#x300d</p>
 <p lang="ja">&#x300c;ja FALLBACK&#x300d</p>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-toggle/animations/toggle-root-interpolation.tentative.html b/third_party/blink/web_tests/external/wpt/css/css-toggle/animations/toggle-root-interpolation.tentative.html
index 5cc3257..855b7e6 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-toggle/animations/toggle-root-interpolation.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-toggle/animations/toggle-root-interpolation.tentative.html
@@ -13,7 +13,7 @@
 test_not_animatable({
   property: 'toggle-root',
   from: 'none',
-  to: 'yourtoggle 3 / 1',
+  to: 'yourtoggle 3 at 1',
   underlying: 'mytoggle 2',
 });
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-toggle/animations/toggle-trigger-interpolation.tentative.html b/third_party/blink/web_tests/external/wpt/css/css-toggle/animations/toggle-trigger-interpolation.tentative.html
index 1ea9e65..1ebec55 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-toggle/animations/toggle-trigger-interpolation.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-toggle/animations/toggle-trigger-interpolation.tentative.html
@@ -12,9 +12,9 @@
 <script>
 test_not_animatable({
   property: 'toggle-trigger',
-  from: 'atoggle 4',
-  to: 'yourtoggle 3',
-  underlying: 'mytoggle 2',
+  from: 'atoggle set 4',
+  to: 'yourtoggle set 3',
+  underlying: 'mytoggle set 2',
 });
 </script>
 </body>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-toggle/parsing/support/toggle-root-values.js b/third_party/blink/web_tests/external/wpt/css/css-toggle/parsing/support/toggle-root-values.js
index 85239db0..0bedfb9 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-toggle/parsing/support/toggle-root-values.js
+++ b/third_party/blink/web_tests/external/wpt/css/css-toggle/parsing/support/toggle-root-values.js
@@ -7,22 +7,31 @@
   test_computed_value(property, 'mytoggle');
   test_computed_value(property, 'mytoggle, yourtoggle');
   test_computed_value(property, 'mytoggle, mytoggle');
-  test_computed_value(property, 'mytoggle 0 / 3 sticky self, yourtoggle 1 group self', 'mytoggle 3 sticky self, yourtoggle group self');
-  test_computed_value(property, 'mytoggle 1 / 3 sticky self, yourtoggle 2 group self');
-  test_computed_value(property, 'mytoggle 0/1', 'mytoggle');
-  test_computed_value(property, 'mytoggle +0/1', 'mytoggle');
-  test_computed_value(property, 'mytoggle 0/+1', 'mytoggle');
-  test_computed_value(property, 'mytoggle -0/1', 'mytoggle');
-  test_computed_value(property, 'mytoggle 2/+1', 'mytoggle 2 / 1');
-  test_computed_value(property, 'mytoggle calc(-3)/1', 'mytoggle');
-  test_computed_value(property, 'mytoggle 0/calc(-3)', 'mytoggle');
-  test_computed_value(property, 'mytoggle calc(-3)/7', 'mytoggle 7');
-  test_computed_value(property, 'mytoggle 7/calc(-3)', 'mytoggle 7 / 1');
-  test_computed_value(property, 'mytoggle calc(6)/calc(9)', 'mytoggle 6 / 9');
-  test_computed_value(property, 'mytoggle calc(6.4)/calc(9.6)', 'mytoggle 6 / 10');
-  test_computed_value(property, 'mytoggle calc(6.5)/calc(-9.5)', 'mytoggle 7 / 1');
+  test_computed_value(property, 'mytoggle 3 at 0 sticky self, yourtoggle 1 group self', 'mytoggle 3 sticky self, yourtoggle group self');
+  test_computed_value(property, 'mytoggle 3 at 1 sticky self, yourtoggle 2 group self');
+  test_computed_value(property, 'mytoggle 1 at 0', 'mytoggle');
+  test_computed_value(property, 'mytoggle 1 at +0', 'mytoggle');
+  test_computed_value(property, 'mytoggle +1 at 0', 'mytoggle');
+  test_computed_value(property, 'mytoggle 1 at -0', 'mytoggle');
+  test_computed_value(property, 'mytoggle +1 at 2', 'mytoggle 1 at 2');
+  test_computed_value(property, 'mytoggle 1 at calc(-3)', 'mytoggle');
+  test_computed_value(property, 'mytoggle calc(-3) at 0', 'mytoggle');
+  test_computed_value(property, 'mytoggle 7 at calc(-3)', 'mytoggle 7');
+  test_computed_value(property, 'mytoggle calc(-3) at 7', 'mytoggle 1 at 7');
+  test_computed_value(property, 'mytoggle calc(9) at calc(6)', 'mytoggle 9 at 6');
+  test_computed_value(property, 'mytoggle calc(9.6) at calc(6.4)', 'mytoggle 10 at 6');
+  test_computed_value(property, 'mytoggle calc(-9.5) at calc(6.5)', 'mytoggle 1 at 7');
   test_computed_value(property, 'mytoggle group sticky self, yourtoggle self sticky', 'mytoggle sticky group self, yourtoggle sticky self');
-  test_computed_value(property, 'mytoggle group 1 / 2', 'mytoggle 1 / 2 group');
+  test_computed_value(property, 'mytoggle group 2 at 1', 'mytoggle 2 at 1 group');
+  test_computed_value(property, 'mytoggle [one two three]');
+  test_computed_value(property, 'mytoggle [one two three] at 0', 'mytoggle [one two three]');
+  test_computed_value(property, 'mytoggle [ one two three ] at 0', 'mytoggle [one two three]');
+  test_computed_value(property, 'mytoggle[one two three]at 0', 'mytoggle [one two three]');
+  test_computed_value(property, 'mytoggle [one two three] at 1');
+  test_computed_value(property, 'mytoggle [ one two three ] at 1', 'mytoggle [one two three] at 1');
+  test_computed_value(property, 'mytoggle[one two three]at 1', 'mytoggle [one two three] at 1');
+  test_computed_value(property, 'mytoggle [ one two three ] at two', 'mytoggle [one two three] at two');
+  test_computed_value(property, 'mytoggle 3 at two');
 }
 
 function test_toggle_root_valid_values(property) {
@@ -33,25 +42,33 @@
   test_valid_value(property, 'mytoggle');
   test_valid_value(property, 'mytoggle, yourtoggle');
   test_valid_value(property, 'mytoggle, mytoggle');
-  test_valid_value(property, 'mytoggle 0 / 3 sticky self, yourtoggle 1 group self');
-  test_valid_value(property, 'mytoggle 0/1', 'mytoggle 0 / 1');
-  test_valid_value(property, 'mytoggle +0/1', 'mytoggle 0 / 1');
-  test_valid_value(property, 'mytoggle 0/+1', 'mytoggle 0 / 1');
-  test_valid_value(property, 'mytoggle -0/1', 'mytoggle 0 / 1');
-  test_valid_value(property, 'mytoggle calc(-3) / 1');
-  test_valid_value(property, 'mytoggle 0 / calc(-3)');
-  test_valid_value(property, 'mytoggle calc(-3) / 7');
-  test_valid_value(property, 'mytoggle 7 / calc(-3)');
-  test_valid_value(property, 'mytoggle calc(6) / calc(9)');
-  test_valid_value(property, 'mytoggle calc(6.4) / calc(9.6)');
-  test_valid_value(property, 'mytoggle calc(6.5) / calc(-9.5)');
+  test_valid_value(property, 'mytoggle 3 at 0 sticky self, yourtoggle 1 group self');
+  test_valid_value(property, 'mytoggle 1 at 0');
+  test_valid_value(property, 'mytoggle 1 at +0', 'mytoggle 1 at 0');
+  test_valid_value(property, 'mytoggle +1 at 0', 'mytoggle 1 at 0');
+  test_valid_value(property, 'mytoggle 1 at -0', 'mytoggle 1 at 0');
+  test_valid_value(property, 'mytoggle 1 at calc(-3)');
+  test_valid_value(property, 'mytoggle calc(-3) at 0');
+  test_valid_value(property, 'mytoggle 7 at calc(-3)');
+  test_valid_value(property, 'mytoggle calc(-3) at 7');
+  test_valid_value(property, 'mytoggle calc(9) at calc(6)');
+  test_valid_value(property, 'mytoggle calc(9.6) at calc(6.4)');
+  test_valid_value(property, 'mytoggle calc(-9.5) at calc(6.5)');
   test_valid_value(property, 'mytoggle group sticky self, yourtoggle self sticky', 'mytoggle sticky group self, yourtoggle sticky self');
-  test_valid_value(property, 'mytoggle group 1 / 2', 'mytoggle 1 / 2 group');
+  test_valid_value(property, 'mytoggle group 2 at 1', 'mytoggle 2 at 1 group');
+  test_valid_value(property, 'mytoggle [one two three]');
+  test_valid_value(property, 'mytoggle [one two three] at 0');
+  test_valid_value(property, 'mytoggle [ one two three ] at 0', 'mytoggle [one two three] at 0');
+  test_valid_value(property, 'mytoggle[one two three]at 0', 'mytoggle [one two three] at 0');
+  test_valid_value(property, 'mytoggle [ one two three ] at two', 'mytoggle [one two three] at two');
+  test_valid_value(property, 'mytoggle 3 at two');
 }
 
 function test_toggle_root_invalid_values(property) {
   test_invalid_value(property, 'none 1');
   test_invalid_value(property, 'none sticky');
+  test_invalid_value(property, 'none cycle');
+  test_invalid_value(property, 'none cycle-on');
   test_invalid_value(property, 'none group');
   test_invalid_value(property, 'none self');
   test_invalid_value(property, 'None self');
@@ -64,19 +81,24 @@
   test_invalid_value(property, 'none self self');
   test_invalid_value(property, 'none, mytoggle');
   test_invalid_value(property, 'mytoggle, none');
+  test_invalid_value(property, 'mytoggle 1 at');
+  test_invalid_value(property, 'mytoggle []');
+  test_invalid_value(property, 'mytoggle [one] at');
+  test_invalid_value(property, 'mytoggle [one two two three]');
+  test_invalid_value(property, 'mytoggle [one two one three]');
   test_invalid_value(property, 'mytoggle 0 sticky self');
-  test_invalid_value(property, 'mytoggle 0 / 0 sticky self');
-  test_invalid_value(property, 'mytoggle 1 / -1 sticky self');
-  test_invalid_value(property, 'mytoggle -1 / 1 sticky self');
-  test_invalid_value(property, 'mytoggle -1 / -1 sticky self');
-  test_invalid_value(property, 'mytoggle 0/-1');
-  test_invalid_value(property, 'mytoggle 0/0');
-  test_invalid_value(property, 'mytoggle 0/-0');
-  test_invalid_value(property, 'mytoggle 0/+0');
-  test_invalid_value(property, 'mytoggle sticky 1 / 3 group self sticky');
-  test_invalid_value(property, 'mytoggle sticky 1 / 3 group self group');
-  test_invalid_value(property, 'mytoggle sticky 1 / 3 group self self');
-  test_invalid_value(property, 'mytoggle sticky 1 / 3 group self 1');
-  test_invalid_value(property, 'mytoggle sticky 1 / group');
-  test_invalid_value(property, 'mytoggle sticky 1 / group 1');
+  test_invalid_value(property, 'mytoggle 0 at 0 sticky self');
+  test_invalid_value(property, 'mytoggle -1 at 1 sticky self');
+  test_invalid_value(property, 'mytoggle 1 at -1 sticky self');
+  test_invalid_value(property, 'mytoggle -1 at -1 sticky self');
+  test_invalid_value(property, 'mytoggle -1 at 0');
+  test_invalid_value(property, 'mytoggle 0 at 0');
+  test_invalid_value(property, 'mytoggle -0 at 0');
+  test_invalid_value(property, 'mytoggle +0 at 0');
+  test_invalid_value(property, 'mytoggle sticky 3 at 1 group self sticky');
+  test_invalid_value(property, 'mytoggle sticky 3 at 1 group self group');
+  test_invalid_value(property, 'mytoggle sticky 3 at 1 group self self');
+  test_invalid_value(property, 'mytoggle sticky 3 at 1 group self 1');
+  test_invalid_value(property, 'mytoggle sticky group at 1');
+  test_invalid_value(property, 'mytoggle sticky group at 1 1');
 }
diff --git a/third_party/blink/web_tests/external/wpt/css/css-toggle/parsing/toggle-trigger-computed.tentative.html b/third_party/blink/web_tests/external/wpt/css/css-toggle/parsing/toggle-trigger-computed.tentative.html
index 571cac91..4dab2b6 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-toggle/parsing/toggle-trigger-computed.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-toggle/parsing/toggle-trigger-computed.tentative.html
@@ -13,23 +13,34 @@
 <script>
 
 test_computed_value('toggle-trigger', 'none');
-test_computed_value('toggle-trigger', 'self 1');
-test_computed_value('toggle-trigger', 'sticky 1');
-test_computed_value('toggle-trigger', 'group 1');
+test_computed_value('toggle-trigger', 'self set 1');
+test_computed_value('toggle-trigger', 'sticky set 1');
+test_computed_value('toggle-trigger', 'group set 1');
 test_computed_value('toggle-trigger', 'mytoggle');
-test_computed_value('toggle-trigger', 'mytoggle 0');
-test_computed_value('toggle-trigger', 'mytoggle +0', 'mytoggle 0');
-test_computed_value('toggle-trigger', 'mytoggle -0', 'mytoggle 0');
-test_computed_value('toggle-trigger', 'mytoggle +5', 'mytoggle 5');
+test_computed_value('toggle-trigger', 'mytoggle set 0');
+test_computed_value('toggle-trigger', 'mytoggle set +0', 'mytoggle set 0');
+test_computed_value('toggle-trigger', 'mytoggle set -0', 'mytoggle set 0');
+test_computed_value('toggle-trigger', 'mytoggle set +5', 'mytoggle set 5');
 test_computed_value('toggle-trigger', 'mytoggle, yourtoggle');
 test_computed_value('toggle-trigger', 'mytoggle, mytoggle');
-test_computed_value('toggle-trigger', 'mytoggle 1, yourtoggle');
-test_computed_value('toggle-trigger', 'mytoggle 1 , yourtoggle 1', 'mytoggle 1, yourtoggle 1');
-test_computed_value('toggle-trigger', 'mytoggle,yourtoggle 1', 'mytoggle, yourtoggle 1');
-test_computed_value('toggle-trigger', 'mytoggle calc(-3)', 'mytoggle 0');
-test_computed_value('toggle-trigger', 'mytoggle calc(6)', 'mytoggle 6');
-test_computed_value('toggle-trigger', 'mytoggle calc(6.4)', 'mytoggle 6');
-test_computed_value('toggle-trigger', 'mytoggle calc(6.5)', 'mytoggle 7');
-test_computed_value('toggle-trigger', 'mytoggle calc(6.6)', 'mytoggle 7');
+test_computed_value('toggle-trigger', 'mytoggle set 1, yourtoggle');
+test_computed_value('toggle-trigger', 'mytoggle set 1 , yourtoggle set 1', 'mytoggle set 1, yourtoggle set 1');
+test_computed_value('toggle-trigger', 'mytoggle,yourtoggle set 1', 'mytoggle, yourtoggle set 1');
+test_computed_value('toggle-trigger', 'mytoggle set calc(-3)', 'mytoggle set 0');
+test_computed_value('toggle-trigger', 'mytoggle set calc(6)', 'mytoggle set 6');
+test_computed_value('toggle-trigger', 'mytoggle set calc(6.4)', 'mytoggle set 6');
+test_computed_value('toggle-trigger', 'mytoggle set calc(6.5)', 'mytoggle set 7');
+test_computed_value('toggle-trigger', 'mytoggle set calc(6.6)', 'mytoggle set 7');
+test_computed_value('toggle-trigger', 'mytoggle set two');
+test_computed_value('toggle-trigger', 'mytoggle next 1', 'mytoggle');
+test_computed_value('toggle-trigger', 'mytoggle next 2');
+test_computed_value('toggle-trigger', 'mytoggle prev 1', 'mytoggle prev');
+test_computed_value('toggle-trigger', 'mytoggle prev 2');
+test_computed_value('toggle-trigger', 'mytoggle next calc(-3)', 'mytoggle');
+test_computed_value('toggle-trigger', 'mytoggle prev calc(-3)', 'mytoggle prev');
+test_computed_value('toggle-trigger', 'mytoggle next calc(6.4)', 'mytoggle next 6');
+test_computed_value('toggle-trigger', 'mytoggle next calc(6.5)', 'mytoggle next 7');
+test_computed_value('toggle-trigger', 'mytoggle prev calc(6.4)', 'mytoggle prev 6');
+test_computed_value('toggle-trigger', 'mytoggle prev calc(6.5)', 'mytoggle prev 7');
 
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-toggle/parsing/toggle-trigger-invalid.tentative.html b/third_party/blink/web_tests/external/wpt/css/css-toggle/parsing/toggle-trigger-invalid.tentative.html
index 333cfbaa..584c4986f 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-toggle/parsing/toggle-trigger-invalid.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-toggle/parsing/toggle-trigger-invalid.tentative.html
@@ -12,16 +12,20 @@
 <script>
 
 test_invalid_value('toggle-trigger', 'none, none');
-test_invalid_value('toggle-trigger', 'none 1');
-test_invalid_value('toggle-trigger', 'None 1');
-test_invalid_value('toggle-trigger', 'NONE 1');
-test_invalid_value('toggle-trigger', 'mytoggle 1 1');
-test_invalid_value('toggle-trigger', 'mytoggle -1');
+test_invalid_value('toggle-trigger', 'none set 1');
+test_invalid_value('toggle-trigger', 'None set 1');
+test_invalid_value('toggle-trigger', 'NONE set 1');
+test_invalid_value('toggle-trigger', 'mytoggle set 1 1');
+test_invalid_value('toggle-trigger', 'mytoggle set -1');
 test_invalid_value('toggle-trigger', 'none, mytoggle');
 test_invalid_value('toggle-trigger', 'mytoggle, none');
 test_invalid_value('toggle-trigger', 'mytoggle self 1');
 test_invalid_value('toggle-trigger', 'mytoggle 1 self');
 test_invalid_value('toggle-trigger', 'mytoggle sticky 1');
 test_invalid_value('toggle-trigger', 'mytoggle 1 sticky');
+test_invalid_value('toggle-trigger', 'mytoggle self set 1');
+test_invalid_value('toggle-trigger', 'mytoggle set 1 self');
+test_invalid_value('toggle-trigger', 'mytoggle sticky set 1');
+test_invalid_value('toggle-trigger', 'mytoggle set 1 sticky');
 
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-toggle/parsing/toggle-trigger-valid.tentative.html b/third_party/blink/web_tests/external/wpt/css/css-toggle/parsing/toggle-trigger-valid.tentative.html
index d64b3cd4..1b4a896f 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-toggle/parsing/toggle-trigger-valid.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-toggle/parsing/toggle-trigger-valid.tentative.html
@@ -12,23 +12,34 @@
 <script>
 
 test_valid_value('toggle-trigger', 'none');
-test_valid_value('toggle-trigger', 'self 1');
-test_valid_value('toggle-trigger', 'sticky 1');
-test_valid_value('toggle-trigger', 'group 1');
+test_valid_value('toggle-trigger', 'self set 1');
+test_valid_value('toggle-trigger', 'sticky set 1');
+test_valid_value('toggle-trigger', 'group set 1');
 test_valid_value('toggle-trigger', 'mytoggle');
-test_valid_value('toggle-trigger', 'mytoggle 0');
-test_valid_value('toggle-trigger', 'mytoggle +0', 'mytoggle 0');
-test_valid_value('toggle-trigger', 'mytoggle -0', 'mytoggle 0');
-test_valid_value('toggle-trigger', 'mytoggle +5', 'mytoggle 5');
+test_valid_value('toggle-trigger', 'mytoggle set 0');
+test_valid_value('toggle-trigger', 'mytoggle set +0', 'mytoggle set 0');
+test_valid_value('toggle-trigger', 'mytoggle set -0', 'mytoggle set 0');
+test_valid_value('toggle-trigger', 'mytoggle set +5', 'mytoggle set 5');
 test_valid_value('toggle-trigger', 'mytoggle, yourtoggle');
 test_valid_value('toggle-trigger', 'mytoggle, mytoggle');
-test_valid_value('toggle-trigger', 'mytoggle 1, yourtoggle');
-test_valid_value('toggle-trigger', 'mytoggle 1 , yourtoggle 1', 'mytoggle 1, yourtoggle 1');
-test_valid_value('toggle-trigger', 'mytoggle,yourtoggle 1', 'mytoggle, yourtoggle 1');
-test_valid_value('toggle-trigger', 'mytoggle calc(-3)');
-test_valid_value('toggle-trigger', 'mytoggle calc(6)');
-test_valid_value('toggle-trigger', 'mytoggle calc(6.4)');
-test_valid_value('toggle-trigger', 'mytoggle calc(6.5)');
-test_valid_value('toggle-trigger', 'mytoggle calc(6.6)');
+test_valid_value('toggle-trigger', 'mytoggle set 1, yourtoggle');
+test_valid_value('toggle-trigger', 'mytoggle set 1 , yourtoggle set 1', 'mytoggle set 1, yourtoggle set 1');
+test_valid_value('toggle-trigger', 'mytoggle,yourtoggle set 1', 'mytoggle, yourtoggle set 1');
+test_valid_value('toggle-trigger', 'mytoggle set calc(-3)');
+test_valid_value('toggle-trigger', 'mytoggle set calc(6)');
+test_valid_value('toggle-trigger', 'mytoggle set calc(6.4)');
+test_valid_value('toggle-trigger', 'mytoggle set calc(6.5)');
+test_valid_value('toggle-trigger', 'mytoggle set calc(6.6)');
+test_valid_value('toggle-trigger', 'mytoggle set two');
+test_valid_value('toggle-trigger', 'mytoggle next 1');
+test_valid_value('toggle-trigger', 'mytoggle next 2');
+test_valid_value('toggle-trigger', 'mytoggle prev 1');
+test_valid_value('toggle-trigger', 'mytoggle prev 2');
+test_valid_value('toggle-trigger', 'mytoggle next calc(-3)');
+test_valid_value('toggle-trigger', 'mytoggle prev calc(-3)');
+test_valid_value('toggle-trigger', 'mytoggle next calc(6.4)');
+test_valid_value('toggle-trigger', 'mytoggle next calc(6.5)');
+test_valid_value('toggle-trigger', 'mytoggle prev calc(6.4)');
+test_valid_value('toggle-trigger', 'mytoggle prev calc(6.5)');
 
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-toggle/toggle-shorthand-serialization.tentative.html b/third_party/blink/web_tests/external/wpt/css/css-toggle/toggle-shorthand-serialization.tentative.html
index e98a4819..682945c 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-toggle/toggle-shorthand-serialization.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-toggle/toggle-shorthand-serialization.tentative.html
@@ -41,21 +41,24 @@
   { "toggle-root": "mytoggle 2",
     "toggle-trigger": "mytoggle 2",
     "expected": "" },
-  { "toggle-root": "mytoggle 2 / 1",
+  { "toggle-root": "mytoggle 2 at 1",
     "toggle-trigger": "mytoggle",
-    "expected": "mytoggle 2 / 1" },
-  { "toggle-root": "mytoggle 2 / 1",
+    "expected": "mytoggle 2 at 1" },
+  { "toggle-root": "mytoggle 2 at 1",
     "toggle-trigger": "mytoggle 1",
     "expected": "" },
-  { "toggle-root": "mytoggle 2 / 1",
+  { "toggle-root": "mytoggle 2 at 1",
     "toggle-trigger": "mytoggle 2",
     "expected": "" },
-  { "toggle-root": "mytoggle 2 / 1 sticky group self",
+  { "toggle-root": "mytoggle 2 at 1 sticky group self",
     "toggle-trigger": "mytoggle",
-    "expected": "mytoggle 2 / 1 sticky group self" },
-  { "toggle-root": "mytoggle 4 / 2 self sticky group",
+    "expected": "mytoggle 2 at 1 sticky group self" },
+  { "toggle-root": "mytoggle 4 at 2 self sticky group",
     "toggle-trigger": "mytoggle",
-    "expected": "mytoggle 4 / 2 sticky group self" },
+    "expected": "mytoggle 4 at 2 sticky group self" },
+  { "toggle-root": "mytoggle [one two three] at two self cycle-on group",
+    "toggle-trigger": "mytoggle",
+    "expected": "mytoggle [one two three] at two cycle-on group self" },
 ];
 
 for (let t of tests) {
diff --git a/third_party/blink/web_tests/external/wpt/media-source/dedicated-worker/mediasource-worker-detach-element.js b/third_party/blink/web_tests/external/wpt/media-source/dedicated-worker/mediasource-worker-detach-element.js
index 10c2f84db..3007f6e 100644
--- a/third_party/blink/web_tests/external/wpt/media-source/dedicated-worker/mediasource-worker-detach-element.js
+++ b/third_party/blink/web_tests/external/wpt/media-source/dedicated-worker/mediasource-worker-detach-element.js
@@ -32,7 +32,9 @@
                              err => { postMessage({ subject: messageSubject.ERROR, info: err }) } );
 }, { once : true });
 
-postMessage({ subject: messageSubject.HANDLE, info: util.mediaSource.getHandle() } );
+let handle = util.mediaSource.getHandle();
+
+postMessage({ subject: messageSubject.HANDLE, info: handle }, { transfer: [handle] } );
 
 // Append increasingly large pieces at a time, starting/continuing at |position|.
 // This allows buffering the test media without timeout, but also with enough
diff --git a/third_party/blink/web_tests/external/wpt/media-source/dedicated-worker/mediasource-worker-duration.js b/third_party/blink/web_tests/external/wpt/media-source/dedicated-worker/mediasource-worker-duration.js
index e490134..d868fc4a 100644
--- a/third_party/blink/web_tests/external/wpt/media-source/dedicated-worker/mediasource-worker-duration.js
+++ b/third_party/blink/web_tests/external/wpt/media-source/dedicated-worker/mediasource-worker-duration.js
@@ -180,7 +180,8 @@
     case testPhase.kInitial:
       assert(Number.isNaN(util.mediaSource.duration), "Initial unattached MediaSource duration must be NaN, but instead is " + util.mediaSource.duration);
       phase = testPhase.kAttaching;
-      postMessage({ subject: messageSubject.HANDLE, info: util.mediaSource.getHandle() });
+      let handle = util.mediaSource.getHandle();
+      postMessage({ subject: messageSubject.HANDLE, info: handle }, { transfer: [handle] } );
       break;
 
     case testPhase.kAttaching:
diff --git a/third_party/blink/web_tests/external/wpt/media-source/dedicated-worker/mediasource-worker-play.js b/third_party/blink/web_tests/external/wpt/media-source/dedicated-worker/mediasource-worker-play.js
index e29b1b8d..d2f3aa0 100644
--- a/third_party/blink/web_tests/external/wpt/media-source/dedicated-worker/mediasource-worker-play.js
+++ b/third_party/blink/web_tests/external/wpt/media-source/dedicated-worker/mediasource-worker-play.js
@@ -42,4 +42,5 @@
                              err => { postMessage({ subject: messageSubject.ERROR, info: err }) });
 }, { once : true });
 
-postMessage({ subject: messageSubject.HANDLE, info: util.mediaSource.getHandle() });
+let handle = util.mediaSource.getHandle();
+postMessage({ subject: messageSubject.HANDLE, info: handle }, { transfer: [handle] });
diff --git a/third_party/blink/web_tests/external/wpt/resource-timing/entries-for-network-errors.sub.https.html b/third_party/blink/web_tests/external/wpt/resource-timing/entries-for-network-errors.sub.https.html
index a3c4791f..95849d2 100644
--- a/third_party/blink/web_tests/external/wpt/resource-timing/entries-for-network-errors.sub.https.html
+++ b/third_party/blink/web_tests/external/wpt/resource-timing/entries-for-network-errors.sub.https.html
@@ -24,7 +24,7 @@
 network_error_entry_test('//{{hosts[][nonexistent]}}/common/dummy.xml', null, "DNS failure");
 network_error_entry_test(`http://${ORIGINAL_HOST}:${HTTP_PORT}/commo/dummy.xml`, null, "Mixed content");
 
-network_error_entry_test('/common/dummy.xml', {cache: 'only-if-cached'},
+network_error_entry_test('/common/dummy.xml', {cache: 'only-if-cached', mode: 'same-origin'},
     "only-if-cached resource that was not cached");
 
 network_error_entry_test(
diff --git a/third_party/blink/web_tests/external/wpt/resource-timing/resources/entry-invariants.js b/third_party/blink/web_tests/external/wpt/resource-timing/resources/entry-invariants.js
index 0bb18c59..4bef949 100644
--- a/third_party/blink/web_tests/external/wpt/resource-timing/resources/entry-invariants.js
+++ b/third_party/blink/web_tests/external/wpt/resource-timing/resources/entry-invariants.js
@@ -423,6 +423,9 @@
 
     assert_positive_(entry, [
       "startTime",
+    ]);
+
+    assert_not_negative_(entry, [
       "duration",
     ]);
 
@@ -501,4 +504,4 @@
       assert_greater_than_equal(timeAfter, entry.responseEnd, 'endTime should be less than the time right after returning from the fetch');
       invariants.assert_tao_failure_resource(entry);
   }, `A ResourceTiming entry should be created for network error of type ${label}`);
-}
\ No newline at end of file
+}
diff --git a/third_party/blink/web_tests/external/wpt/web-animations/timing-model/animations/document-timeline-animation.html b/third_party/blink/web_tests/external/wpt/web-animations/timing-model/animations/document-timeline-animation.html
index 5fbfd2ac..7d4dc768 100644
--- a/third_party/blink/web_tests/external/wpt/web-animations/timing-model/animations/document-timeline-animation.html
+++ b/third_party/blink/web_tests/external/wpt/web-animations/timing-model/animations/document-timeline-animation.html
@@ -50,10 +50,12 @@
     ];
     const effect =
         new KeyframeEffect(elem, keyframes,
-                           {iterations: 1, duration: 500, fill: 'forwards'});
+                           {iterations: 1, duration: 10000, fill: 'forwards'});
     const timeline = new DocumentTimeline();
     const animation = new Animation(effect, timeline);
     animation.play();
+    await animation.ready;
+    animation.finish();
     await animation.finished;
     await waitForAnimationFrames(2);
     takeScreenshot();
diff --git a/third_party/blink/web_tests/external/wpt/webxr/hit-test/ar_hittest_subscription_unlocalizable.https.html b/third_party/blink/web_tests/external/wpt/webxr/hit-test/ar_hittest_subscription_unlocalizable.https.html
new file mode 100644
index 0000000..d1cb1a5a
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/webxr/hit-test/ar_hittest_subscription_unlocalizable.https.html
@@ -0,0 +1,108 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/webxr_util.js"></script>
+<script src="../resources/webxr_math_utils.js"></script>
+<script src="../resources/webxr_test_asserts.js"></script>
+<script src="../resources/webxr_test_constants.js"></script>
+<script src="../resources/webxr_test_constants_fake_world.js"></script>
+
+<script>
+
+// 1m above world origin.
+const VIEWER_ORIGIN_TRANSFORM = {
+  position: [0, 1, 0],
+  orientation: [0, 0, 0, 1],
+};
+
+// 0.25m above world origin.
+const FLOOR_ORIGIN_TRANSFORM = {
+  position: [0, 0.25, 0],
+  orientation: [0, 0, 0, 1],
+};
+
+const fakeDeviceInitParams = {
+  supportedModes: ["immersive-ar"],
+  views: VALID_VIEWS,
+  floorOrigin: FLOOR_ORIGIN_TRANSFORM,    // aka mojo_from_floor
+  viewerOrigin: VIEWER_ORIGIN_TRANSFORM,  // aka mojo_from_viewer
+  supportedFeatures: ALL_FEATURES,
+  world: createFakeWorld(5.0, 2.0, 5.0),  // webxr_test_constants_fake_world.js has detailed description of the fake world
+};
+
+// Generates a test function given the parameters for the hit test. It will subscribe
+// to a hit test using |refSpaceName| and |ray|, and attempt to obtain poses from returned hit
+// test results using a space that is known to be unlocalizable, with the expectation of
+// obtaining a null pose for each hit test result.
+// |ray| - ray that will be used to subscribe to hit test.
+// |refSpaceName| - XRReferenceSpaceType - either 'local', 'local-floor' or 'viewer'.
+let testFunctionGenerator = function(ray, refSpaceName) {
+  const testFunction = function(session, fakeDeviceController, t) {
+
+    const input_source_controller = fakeDeviceController.simulateInputSourceConnection({
+      handedness: "right",
+      targetRayMode: "tracked-pointer",
+      pointerOrigin: IDENTITY_TRANSFORM,
+      profiles: []
+    });
+
+    return Promise.all([
+      session.requestReferenceSpace('local'),
+      session.requestReferenceSpace('viewer'),
+      session.requestReferenceSpace('local-floor'),
+    ]).then(([localRefSpace, viewerRefSpace, localFloorRefSpace]) => {
+
+      const refSpaceNameToSpace = {
+        'local' : localRefSpace,
+        'viewer' : viewerRefSpace,
+        'local-floor' : localFloorRefSpace
+      };
+
+      const hitTestOptionsInit = {
+        space: refSpaceNameToSpace[refSpaceName],
+        offsetRay: ray,
+      };
+
+      return session.requestHitTestSource(hitTestOptionsInit).then(
+        (hitTestSource) => new Promise((resolve, reject) => {
+
+        const requestAnimationFrameCallback = function(time, frame) {
+
+          const hitTestResults = frame.getHitTestResults(hitTestSource);
+
+          t.step(() => {
+            assert_true(session.inputSources.length > 0, "session.inputSources should not be empty!");
+            assert_true(hitTestResults.length > 0, "Results should not be empty!");
+
+            const input_source = session.inputSources[0];
+
+            for(const [index, hitTestResult] of hitTestResults.entries()) {
+              const pose = hitTestResult.getPose(input_source.targetRaySpace);
+              assert_true(pose == null, "Pose should be null since input source is not localizable");
+            }
+          });
+
+          resolve();
+        };
+
+        t.step(() => {
+          assert_true(hitTestSource != null, "Hit test source should not be null");
+        });
+
+        session.requestAnimationFrame(requestAnimationFrameCallback);
+      }));
+    });
+  };
+
+  return testFunction;
+};
+
+// All test cases require local-floor and hit-test.
+const sessionInit = { 'requiredFeatures': ['local-floor', 'hit-test'] };
+
+xr_session_promise_test(
+  "Ensures hit test result returns null pose w/unlocalizable space - viewer space",
+  testFunctionGenerator(new XRRay(), 'viewer'),
+  fakeDeviceInitParams, 'immersive-ar', sessionInit);
+
+</script>
diff --git a/third_party/blink/web_tests/virtual/threaded-no-composited-antialiasing/animations/img-element-transform.html b/third_party/blink/web_tests/virtual/threaded-no-composited-antialiasing/animations/img-element-transform.html
index 8a84b4ae..333e2f4 100644
--- a/third_party/blink/web_tests/virtual/threaded-no-composited-antialiasing/animations/img-element-transform.html
+++ b/third_party/blink/web_tests/virtual/threaded-no-composited-antialiasing/animations/img-element-transform.html
@@ -1,12 +1,18 @@
 <!DOCTYPE html>
 <script src="../../../resources/testharness.js"></script>
 <script src="../../../resources/testharnessreport.js"></script>
-<img id="target" src="../../../animations/resources/green-100.png">
+
+<body onload="runTest()">
+  <img id="target" src="../../../animations/resources/green-100.png">
+</body>
+
 <script>
-promise_test(function() {
-  var animation = target.animate({transform: ['rotate(0deg)', 'rotate(180deg)']}, 100000);
-  return animation.ready.then(() => {
-    assert_true(internals.isCompositedAnimation(animation));
-  });
-}, '<img> elements should run compositor animations');
+  function runTest() {
+    promise_test(async(t) => {
+      let animation = target.animate({transform: ['rotate(0deg)', 'rotate(180deg)']}, 100000);
+        return animation.ready.then(() => {
+          assert_true(internals.isCompositedAnimation(animation));
+        });
+    }, '<img> elements should run compositor animations');
+  }
 </script>
diff --git a/third_party/blink/web_tests/wpt_internal/task-tracking/soft-navigation-heuristics/soft-navigation-detection-main-descendent.html b/third_party/blink/web_tests/wpt_internal/task-tracking/soft-navigation-heuristics/soft-navigation-detection-main-descendent.html
new file mode 100644
index 0000000..3c50343
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/task-tracking/soft-navigation-heuristics/soft-navigation-detection-main-descendent.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Detect soft navigation adding content to a main descendent.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+</head>
+<body>
+  <main id=main>
+  <a id=link>Click me!</a>
+  <div id=descendent></div>
+  </main>
+  <script>
+    promise_test(async t => {
+      const link = document.getElementById("link");
+      const clicked = new Promise(resolve => {
+        link.addEventListener("click", async e => {
+          // Jump through a task, to ensure task tracking is working properly.
+          await new Promise(r => setTimeout(r, 0));
+
+          // Fetch some content
+          const response = await fetch("../resources/content.json");
+          const json = await response.json();
+
+          // Change the URL
+          history.pushState({}, '', "/foobar.html");
+
+          // Add the content to a child of the main element
+          const descendent= document.getElementById("descendent");
+          const content = document.createTextNode(json["content"]);
+          descendent.appendChild(content);
+
+          resolve();
+        });
+      });
+      if (test_driver) {
+        test_driver.click(link);
+      }
+      await clicked;
+      assert_equals(document.softNavigations, 1);
+    }, "Test that a soft navigation is detected");
+  </script>
+</body>
+</html>
diff --git a/third_party/closure_compiler/externs/passwords_private.js b/third_party/closure_compiler/externs/passwords_private.js
index 1a00bcf..32210d77 100644
--- a/third_party/closure_compiler/externs/passwords_private.js
+++ b/third_party/closure_compiler/externs/passwords_private.js
@@ -114,6 +114,7 @@
  *   detailedOrigin: string,
  *   isAndroidCredential: boolean,
  *   changePasswordUrl: (string|undefined),
+ *   hasStartableScript: boolean,
  *   signonRealm: string,
  *   username: string,
  *   password: (string|undefined),
diff --git a/third_party/libaom/README.chromium b/third_party/libaom/README.chromium
index 3bccf075..ece3ca6 100644
--- a/third_party/libaom/README.chromium
+++ b/third_party/libaom/README.chromium
@@ -3,9 +3,9 @@
 URL: https://aomedia.googlesource.com/aom/
 Version: 3.1.2
 CPEPrefix: cpe:/a:aomedia:aomedia:3.1.2
-Date: Wednesday June 22 2022
+Date: Wednesday June 29 2022
 Branch: main
-Commit: 42223eee8546d8f0d6a16c3f5f78a01298139976
+Commit: 7ace1184f1101e859e1cc9de317b48065f73d2c7
 License: BSD
 License File: source/libaom/LICENSE
 Security Critical: yes
diff --git a/third_party/libaom/source/config/config/aom_version.h b/third_party/libaom/source/config/config/aom_version.h
index 56cd6eaa..ff52b39 100644
--- a/third_party/libaom/source/config/config/aom_version.h
+++ b/third_party/libaom/source/config/config/aom_version.h
@@ -12,8 +12,8 @@
 #define VERSION_MAJOR 3
 #define VERSION_MINOR 4
 #define VERSION_PATCH 0
-#define VERSION_EXTRA "117-g42223eee8"
+#define VERSION_EXTRA "138-g7ace1184f"
 #define VERSION_PACKED \
   ((VERSION_MAJOR << 16) | (VERSION_MINOR << 8) | (VERSION_PATCH))
-#define VERSION_STRING_NOSP "3.4.0-117-g42223eee8"
-#define VERSION_STRING " 3.4.0-117-g42223eee8"
+#define VERSION_STRING_NOSP "3.4.0-138-g7ace1184f"
+#define VERSION_STRING " 3.4.0-138-g7ace1184f"
diff --git a/third_party/libaom/source/config/ios/arm-neon/config/aom_config.asm b/third_party/libaom/source/config/ios/arm-neon/config/aom_config.asm
index 15df49e..7b20640f 100644
--- a/third_party/libaom/source/config/ios/arm-neon/config/aom_config.asm
+++ b/third_party/libaom/source/config/ios/arm-neon/config/aom_config.asm
@@ -33,8 +33,6 @@
 CONFIG_ENTROPY_STATS equ 0
 CONFIG_EXCLUDE_SIMD_MISMATCH equ 0
 CONFIG_FPMT_TEST equ 0
-CONFIG_FRAME_PARALLEL_ENCODE equ 0
-CONFIG_FRAME_PARALLEL_ENCODE_2 equ 0
 CONFIG_GCC equ 1
 CONFIG_GCOV equ 0
 CONFIG_GPROF equ 0
diff --git a/third_party/libaom/source/config/ios/arm-neon/config/aom_config.h b/third_party/libaom/source/config/ios/arm-neon/config/aom_config.h
index f8e23f25..562a994 100644
--- a/third_party/libaom/source/config/ios/arm-neon/config/aom_config.h
+++ b/third_party/libaom/source/config/ios/arm-neon/config/aom_config.h
@@ -35,8 +35,6 @@
 #define CONFIG_ENTROPY_STATS 0
 #define CONFIG_EXCLUDE_SIMD_MISMATCH 0
 #define CONFIG_FPMT_TEST 0
-#define CONFIG_FRAME_PARALLEL_ENCODE 0
-#define CONFIG_FRAME_PARALLEL_ENCODE_2 0
 #define CONFIG_GCC 1
 #define CONFIG_GCOV 0
 #define CONFIG_GPROF 0
diff --git a/third_party/libaom/source/config/ios/arm64/config/aom_config.asm b/third_party/libaom/source/config/ios/arm64/config/aom_config.asm
index 15df49e..7b20640f 100644
--- a/third_party/libaom/source/config/ios/arm64/config/aom_config.asm
+++ b/third_party/libaom/source/config/ios/arm64/config/aom_config.asm
@@ -33,8 +33,6 @@
 CONFIG_ENTROPY_STATS equ 0
 CONFIG_EXCLUDE_SIMD_MISMATCH equ 0
 CONFIG_FPMT_TEST equ 0
-CONFIG_FRAME_PARALLEL_ENCODE equ 0
-CONFIG_FRAME_PARALLEL_ENCODE_2 equ 0
 CONFIG_GCC equ 1
 CONFIG_GCOV equ 0
 CONFIG_GPROF equ 0
diff --git a/third_party/libaom/source/config/ios/arm64/config/aom_config.h b/third_party/libaom/source/config/ios/arm64/config/aom_config.h
index f8e23f25..562a994 100644
--- a/third_party/libaom/source/config/ios/arm64/config/aom_config.h
+++ b/third_party/libaom/source/config/ios/arm64/config/aom_config.h
@@ -35,8 +35,6 @@
 #define CONFIG_ENTROPY_STATS 0
 #define CONFIG_EXCLUDE_SIMD_MISMATCH 0
 #define CONFIG_FPMT_TEST 0
-#define CONFIG_FRAME_PARALLEL_ENCODE 0
-#define CONFIG_FRAME_PARALLEL_ENCODE_2 0
 #define CONFIG_GCC 1
 #define CONFIG_GCOV 0
 #define CONFIG_GPROF 0
diff --git a/third_party/libaom/source/config/linux/arm-neon-cpu-detect/config/aom_config.asm b/third_party/libaom/source/config/linux/arm-neon-cpu-detect/config/aom_config.asm
index 8bfcc2b3..10d7e79 100644
--- a/third_party/libaom/source/config/linux/arm-neon-cpu-detect/config/aom_config.asm
+++ b/third_party/libaom/source/config/linux/arm-neon-cpu-detect/config/aom_config.asm
@@ -33,8 +33,6 @@
 CONFIG_ENTROPY_STATS equ 0
 CONFIG_EXCLUDE_SIMD_MISMATCH equ 0
 CONFIG_FPMT_TEST equ 0
-CONFIG_FRAME_PARALLEL_ENCODE equ 0
-CONFIG_FRAME_PARALLEL_ENCODE_2 equ 0
 CONFIG_GCC equ 1
 CONFIG_GCOV equ 0
 CONFIG_GPROF equ 0
diff --git a/third_party/libaom/source/config/linux/arm-neon-cpu-detect/config/aom_config.h b/third_party/libaom/source/config/linux/arm-neon-cpu-detect/config/aom_config.h
index 72d2e65..cc10d33f 100644
--- a/third_party/libaom/source/config/linux/arm-neon-cpu-detect/config/aom_config.h
+++ b/third_party/libaom/source/config/linux/arm-neon-cpu-detect/config/aom_config.h
@@ -35,8 +35,6 @@
 #define CONFIG_ENTROPY_STATS 0
 #define CONFIG_EXCLUDE_SIMD_MISMATCH 0
 #define CONFIG_FPMT_TEST 0
-#define CONFIG_FRAME_PARALLEL_ENCODE 0
-#define CONFIG_FRAME_PARALLEL_ENCODE_2 0
 #define CONFIG_GCC 1
 #define CONFIG_GCOV 0
 #define CONFIG_GPROF 0
diff --git a/third_party/libaom/source/config/linux/arm-neon/config/aom_config.asm b/third_party/libaom/source/config/linux/arm-neon/config/aom_config.asm
index 2d6812c..e994c757 100644
--- a/third_party/libaom/source/config/linux/arm-neon/config/aom_config.asm
+++ b/third_party/libaom/source/config/linux/arm-neon/config/aom_config.asm
@@ -33,8 +33,6 @@
 CONFIG_ENTROPY_STATS equ 0
 CONFIG_EXCLUDE_SIMD_MISMATCH equ 0
 CONFIG_FPMT_TEST equ 0
-CONFIG_FRAME_PARALLEL_ENCODE equ 0
-CONFIG_FRAME_PARALLEL_ENCODE_2 equ 0
 CONFIG_GCC equ 1
 CONFIG_GCOV equ 0
 CONFIG_GPROF equ 0
diff --git a/third_party/libaom/source/config/linux/arm-neon/config/aom_config.h b/third_party/libaom/source/config/linux/arm-neon/config/aom_config.h
index 68499511..353c6440 100644
--- a/third_party/libaom/source/config/linux/arm-neon/config/aom_config.h
+++ b/third_party/libaom/source/config/linux/arm-neon/config/aom_config.h
@@ -35,8 +35,6 @@
 #define CONFIG_ENTROPY_STATS 0
 #define CONFIG_EXCLUDE_SIMD_MISMATCH 0
 #define CONFIG_FPMT_TEST 0
-#define CONFIG_FRAME_PARALLEL_ENCODE 0
-#define CONFIG_FRAME_PARALLEL_ENCODE_2 0
 #define CONFIG_GCC 1
 #define CONFIG_GCOV 0
 #define CONFIG_GPROF 0
diff --git a/third_party/libaom/source/config/linux/arm/config/aom_config.asm b/third_party/libaom/source/config/linux/arm/config/aom_config.asm
index 8bb4cc1..dce8580b 100644
--- a/third_party/libaom/source/config/linux/arm/config/aom_config.asm
+++ b/third_party/libaom/source/config/linux/arm/config/aom_config.asm
@@ -33,8 +33,6 @@
 CONFIG_ENTROPY_STATS equ 0
 CONFIG_EXCLUDE_SIMD_MISMATCH equ 0
 CONFIG_FPMT_TEST equ 0
-CONFIG_FRAME_PARALLEL_ENCODE equ 0
-CONFIG_FRAME_PARALLEL_ENCODE_2 equ 0
 CONFIG_GCC equ 1
 CONFIG_GCOV equ 0
 CONFIG_GPROF equ 0
diff --git a/third_party/libaom/source/config/linux/arm/config/aom_config.h b/third_party/libaom/source/config/linux/arm/config/aom_config.h
index 9976d9d..33e9259 100644
--- a/third_party/libaom/source/config/linux/arm/config/aom_config.h
+++ b/third_party/libaom/source/config/linux/arm/config/aom_config.h
@@ -35,8 +35,6 @@
 #define CONFIG_ENTROPY_STATS 0
 #define CONFIG_EXCLUDE_SIMD_MISMATCH 0
 #define CONFIG_FPMT_TEST 0
-#define CONFIG_FRAME_PARALLEL_ENCODE 0
-#define CONFIG_FRAME_PARALLEL_ENCODE_2 0
 #define CONFIG_GCC 1
 #define CONFIG_GCOV 0
 #define CONFIG_GPROF 0
diff --git a/third_party/libaom/source/config/linux/arm64/config/aom_config.asm b/third_party/libaom/source/config/linux/arm64/config/aom_config.asm
index 2d6812c..e994c757 100644
--- a/third_party/libaom/source/config/linux/arm64/config/aom_config.asm
+++ b/third_party/libaom/source/config/linux/arm64/config/aom_config.asm
@@ -33,8 +33,6 @@
 CONFIG_ENTROPY_STATS equ 0
 CONFIG_EXCLUDE_SIMD_MISMATCH equ 0
 CONFIG_FPMT_TEST equ 0
-CONFIG_FRAME_PARALLEL_ENCODE equ 0
-CONFIG_FRAME_PARALLEL_ENCODE_2 equ 0
 CONFIG_GCC equ 1
 CONFIG_GCOV equ 0
 CONFIG_GPROF equ 0
diff --git a/third_party/libaom/source/config/linux/arm64/config/aom_config.h b/third_party/libaom/source/config/linux/arm64/config/aom_config.h
index 68499511..353c6440 100644
--- a/third_party/libaom/source/config/linux/arm64/config/aom_config.h
+++ b/third_party/libaom/source/config/linux/arm64/config/aom_config.h
@@ -35,8 +35,6 @@
 #define CONFIG_ENTROPY_STATS 0
 #define CONFIG_EXCLUDE_SIMD_MISMATCH 0
 #define CONFIG_FPMT_TEST 0
-#define CONFIG_FRAME_PARALLEL_ENCODE 0
-#define CONFIG_FRAME_PARALLEL_ENCODE_2 0
 #define CONFIG_GCC 1
 #define CONFIG_GCOV 0
 #define CONFIG_GPROF 0
diff --git a/third_party/libaom/source/config/linux/generic/config/aom_config.asm b/third_party/libaom/source/config/linux/generic/config/aom_config.asm
index 080d1c2..2ccd13c 100644
--- a/third_party/libaom/source/config/linux/generic/config/aom_config.asm
+++ b/third_party/libaom/source/config/linux/generic/config/aom_config.asm
@@ -33,8 +33,6 @@
 CONFIG_ENTROPY_STATS equ 0
 CONFIG_EXCLUDE_SIMD_MISMATCH equ 0
 CONFIG_FPMT_TEST equ 0
-CONFIG_FRAME_PARALLEL_ENCODE equ 0
-CONFIG_FRAME_PARALLEL_ENCODE_2 equ 0
 CONFIG_GCC equ 1
 CONFIG_GCOV equ 0
 CONFIG_GPROF equ 0
diff --git a/third_party/libaom/source/config/linux/generic/config/aom_config.h b/third_party/libaom/source/config/linux/generic/config/aom_config.h
index 1a65c4ae..a624bd0 100644
--- a/third_party/libaom/source/config/linux/generic/config/aom_config.h
+++ b/third_party/libaom/source/config/linux/generic/config/aom_config.h
@@ -35,8 +35,6 @@
 #define CONFIG_ENTROPY_STATS 0
 #define CONFIG_EXCLUDE_SIMD_MISMATCH 0
 #define CONFIG_FPMT_TEST 0
-#define CONFIG_FRAME_PARALLEL_ENCODE 0
-#define CONFIG_FRAME_PARALLEL_ENCODE_2 0
 #define CONFIG_GCC 1
 #define CONFIG_GCOV 0
 #define CONFIG_GPROF 0
diff --git a/third_party/libaom/source/config/linux/ia32/config/aom_config.asm b/third_party/libaom/source/config/linux/ia32/config/aom_config.asm
index 9fe46ac..250999e 100644
--- a/third_party/libaom/source/config/linux/ia32/config/aom_config.asm
+++ b/third_party/libaom/source/config/linux/ia32/config/aom_config.asm
@@ -23,8 +23,6 @@
 %define CONFIG_ENTROPY_STATS 0
 %define CONFIG_EXCLUDE_SIMD_MISMATCH 0
 %define CONFIG_FPMT_TEST 0
-%define CONFIG_FRAME_PARALLEL_ENCODE 0
-%define CONFIG_FRAME_PARALLEL_ENCODE_2 0
 %define CONFIG_GCC 1
 %define CONFIG_GCOV 0
 %define CONFIG_GPROF 0
diff --git a/third_party/libaom/source/config/linux/ia32/config/aom_config.h b/third_party/libaom/source/config/linux/ia32/config/aom_config.h
index 6c9ef357..76ff4c5 100644
--- a/third_party/libaom/source/config/linux/ia32/config/aom_config.h
+++ b/third_party/libaom/source/config/linux/ia32/config/aom_config.h
@@ -35,8 +35,6 @@
 #define CONFIG_ENTROPY_STATS 0
 #define CONFIG_EXCLUDE_SIMD_MISMATCH 0
 #define CONFIG_FPMT_TEST 0
-#define CONFIG_FRAME_PARALLEL_ENCODE 0
-#define CONFIG_FRAME_PARALLEL_ENCODE_2 0
 #define CONFIG_GCC 1
 #define CONFIG_GCOV 0
 #define CONFIG_GPROF 0
diff --git a/third_party/libaom/source/config/linux/x64/config/aom_config.asm b/third_party/libaom/source/config/linux/x64/config/aom_config.asm
index f1f6cab..077086f 100644
--- a/third_party/libaom/source/config/linux/x64/config/aom_config.asm
+++ b/third_party/libaom/source/config/linux/x64/config/aom_config.asm
@@ -23,8 +23,6 @@
 %define CONFIG_ENTROPY_STATS 0
 %define CONFIG_EXCLUDE_SIMD_MISMATCH 0
 %define CONFIG_FPMT_TEST 0
-%define CONFIG_FRAME_PARALLEL_ENCODE 0
-%define CONFIG_FRAME_PARALLEL_ENCODE_2 0
 %define CONFIG_GCC 1
 %define CONFIG_GCOV 0
 %define CONFIG_GPROF 0
diff --git a/third_party/libaom/source/config/linux/x64/config/aom_config.h b/third_party/libaom/source/config/linux/x64/config/aom_config.h
index f0ddad24..c4027d0b 100644
--- a/third_party/libaom/source/config/linux/x64/config/aom_config.h
+++ b/third_party/libaom/source/config/linux/x64/config/aom_config.h
@@ -35,8 +35,6 @@
 #define CONFIG_ENTROPY_STATS 0
 #define CONFIG_EXCLUDE_SIMD_MISMATCH 0
 #define CONFIG_FPMT_TEST 0
-#define CONFIG_FRAME_PARALLEL_ENCODE 0
-#define CONFIG_FRAME_PARALLEL_ENCODE_2 0
 #define CONFIG_GCC 1
 #define CONFIG_GCOV 0
 #define CONFIG_GPROF 0
diff --git a/third_party/libaom/source/config/win/arm64/config/aom_config.asm b/third_party/libaom/source/config/win/arm64/config/aom_config.asm
index 2d6812c..e994c757 100644
--- a/third_party/libaom/source/config/win/arm64/config/aom_config.asm
+++ b/third_party/libaom/source/config/win/arm64/config/aom_config.asm
@@ -33,8 +33,6 @@
 CONFIG_ENTROPY_STATS equ 0
 CONFIG_EXCLUDE_SIMD_MISMATCH equ 0
 CONFIG_FPMT_TEST equ 0
-CONFIG_FRAME_PARALLEL_ENCODE equ 0
-CONFIG_FRAME_PARALLEL_ENCODE_2 equ 0
 CONFIG_GCC equ 1
 CONFIG_GCOV equ 0
 CONFIG_GPROF equ 0
diff --git a/third_party/libaom/source/config/win/arm64/config/aom_config.h b/third_party/libaom/source/config/win/arm64/config/aom_config.h
index 7d41792..2b280d4 100644
--- a/third_party/libaom/source/config/win/arm64/config/aom_config.h
+++ b/third_party/libaom/source/config/win/arm64/config/aom_config.h
@@ -35,8 +35,6 @@
 #define CONFIG_ENTROPY_STATS 0
 #define CONFIG_EXCLUDE_SIMD_MISMATCH 0
 #define CONFIG_FPMT_TEST 0
-#define CONFIG_FRAME_PARALLEL_ENCODE 0
-#define CONFIG_FRAME_PARALLEL_ENCODE_2 0
 #define CONFIG_GCC 0
 #define CONFIG_GCOV 0
 #define CONFIG_GPROF 0
diff --git a/third_party/libaom/source/config/win/ia32/config/aom_config.asm b/third_party/libaom/source/config/win/ia32/config/aom_config.asm
index 80045fdd..a3b0159 100644
--- a/third_party/libaom/source/config/win/ia32/config/aom_config.asm
+++ b/third_party/libaom/source/config/win/ia32/config/aom_config.asm
@@ -23,8 +23,6 @@
 %define CONFIG_ENTROPY_STATS 0
 %define CONFIG_EXCLUDE_SIMD_MISMATCH 0
 %define CONFIG_FPMT_TEST 0
-%define CONFIG_FRAME_PARALLEL_ENCODE 0
-%define CONFIG_FRAME_PARALLEL_ENCODE_2 0
 %define CONFIG_GCC 0
 %define CONFIG_GCOV 0
 %define CONFIG_GPROF 0
diff --git a/third_party/libaom/source/config/win/ia32/config/aom_config.h b/third_party/libaom/source/config/win/ia32/config/aom_config.h
index a0592c25..135e476 100644
--- a/third_party/libaom/source/config/win/ia32/config/aom_config.h
+++ b/third_party/libaom/source/config/win/ia32/config/aom_config.h
@@ -35,8 +35,6 @@
 #define CONFIG_ENTROPY_STATS 0
 #define CONFIG_EXCLUDE_SIMD_MISMATCH 0
 #define CONFIG_FPMT_TEST 0
-#define CONFIG_FRAME_PARALLEL_ENCODE 0
-#define CONFIG_FRAME_PARALLEL_ENCODE_2 0
 #define CONFIG_GCC 0
 #define CONFIG_GCOV 0
 #define CONFIG_GPROF 0
diff --git a/third_party/libaom/source/config/win/x64/config/aom_config.asm b/third_party/libaom/source/config/win/x64/config/aom_config.asm
index 789530d..2eac734 100644
--- a/third_party/libaom/source/config/win/x64/config/aom_config.asm
+++ b/third_party/libaom/source/config/win/x64/config/aom_config.asm
@@ -23,8 +23,6 @@
 %define CONFIG_ENTROPY_STATS 0
 %define CONFIG_EXCLUDE_SIMD_MISMATCH 0
 %define CONFIG_FPMT_TEST 0
-%define CONFIG_FRAME_PARALLEL_ENCODE 0
-%define CONFIG_FRAME_PARALLEL_ENCODE_2 0
 %define CONFIG_GCC 0
 %define CONFIG_GCOV 0
 %define CONFIG_GPROF 0
diff --git a/third_party/libaom/source/config/win/x64/config/aom_config.h b/third_party/libaom/source/config/win/x64/config/aom_config.h
index 4f8322d..96eea88 100644
--- a/third_party/libaom/source/config/win/x64/config/aom_config.h
+++ b/third_party/libaom/source/config/win/x64/config/aom_config.h
@@ -35,8 +35,6 @@
 #define CONFIG_ENTROPY_STATS 0
 #define CONFIG_EXCLUDE_SIMD_MISMATCH 0
 #define CONFIG_FPMT_TEST 0
-#define CONFIG_FRAME_PARALLEL_ENCODE 0
-#define CONFIG_FRAME_PARALLEL_ENCODE_2 0
 #define CONFIG_GCC 0
 #define CONFIG_GCOV 0
 #define CONFIG_GPROF 0
diff --git a/third_party/libvpx/README.chromium b/third_party/libvpx/README.chromium
index f2d22db3..7a21f64 100644
--- a/third_party/libvpx/README.chromium
+++ b/third_party/libvpx/README.chromium
@@ -1,14 +1,14 @@
 Name: libvpx
 URL: http://www.webmproject.org
-Version: v1.10.0
-CPEPrefix: cpe:/a:webmproject:libvpx:1.10.0
+Version: v1.12.0
+CPEPrefix: cpe:/a:webmproject:libvpx:1.12.0
 License: BSD
 License File: source/libvpx/LICENSE
 Security Critical: yes
 
-Date: Tuesday June 28 2022
+Date: Wednesday June 29 2022
 Branch: main
-Commit: b355ab504667c352d96ab70bcb92165b8fc32813
+Commit: 711bef67400f096416cb1ba7f6560e533871490f
 
 Description:
 Contains the sources used to compile libvpx binaries used by Google Chrome and
diff --git a/third_party/libvpx/source/config/vpx_version.h b/third_party/libvpx/source/config/vpx_version.h
index 6e5a0df..2a8891e 100644
--- a/third_party/libvpx/source/config/vpx_version.h
+++ b/third_party/libvpx/source/config/vpx_version.h
@@ -1,9 +1,9 @@
 // This file is generated. Do not edit.
 #define VERSION_MAJOR 1
-#define VERSION_MINOR 11
+#define VERSION_MINOR 12
 #define VERSION_PATCH 0
-#define VERSION_EXTRA "248-gb355ab504"
+#define VERSION_EXTRA "34-g711bef674"
 #define VERSION_PACKED \
   ((VERSION_MAJOR << 16) | (VERSION_MINOR << 8) | (VERSION_PATCH))
-#define VERSION_STRING_NOSP "v1.11.0-248-gb355ab504"
-#define VERSION_STRING " v1.11.0-248-gb355ab504"
+#define VERSION_STRING_NOSP "v1.12.0-34-g711bef674"
+#define VERSION_STRING " v1.12.0-34-g711bef674"
diff --git a/third_party/mako/README.chromium b/third_party/mako/README.chromium
index 307da9d..8a76367 100644
--- a/third_party/mako/README.chromium
+++ b/third_party/mako/README.chromium
@@ -1,8 +1,8 @@
 Name: Mako Templates for Python
 Short Name: python-mako
 URL: https://www.makotemplates.org
-Version: 1.2.0
-Date: March 11, 2022
+Version: 1.2.1
+Date: July 1, 2022
 License: MIT
 License File: NOT_SHIPPED
 Security Critical: no
@@ -10,11 +10,11 @@
 Description:
 Template engine in Python, used for code generation in Blink.
 
-Source: https://files.pythonhosted.org/packages/50/ec/1d687348f0954bda388bfd1330c158ba8d7dea4044fc160e74e080babdb9/Mako-1.2.0.tar.gz
-SHA-256: 9a7c7e922b87db3686210cf49d5d767033a41d4010b284e747682c92bddd8b39
+Source: https://files.pythonhosted.org/packages/ad/dd/34201dae727bb183ca14fd8417e61f936fa068d6f503991f09ee3cac6697/Mako-1.2.1.tar.gz
+SHA-256: f054a5ff4743492f1aa9ecc47172cb33b42b9d993cffcc146c9de17e717b0307
 
 Local Modifications:
 * add DIR_METADATA, OWNERS, README.chromium, and .gitattributes files in this directory.
 * run following commands to pass PRESUBMIT.
-  $ rm -rf mako/doc/ mako/example/ mako/Mako.egg-info mako/PKG-INFO
+  $ rm -rf mako/doc/ mako/examples/ mako/Mako.egg-info mako/PKG-INFO
   $ chmod -x mako/mako/cmd.py
diff --git a/third_party/mako/mako/LICENSE b/third_party/mako/mako/LICENSE
index 2342a9d3..be84a38e 100644
--- a/third_party/mako/mako/LICENSE
+++ b/third_party/mako/mako/LICENSE
@@ -1,4 +1,4 @@
-Copyright 2006-2021 the Mako authors and contributors <see AUTHORS file>.
+Copyright 2006-2022 the Mako authors and contributors <see AUTHORS file>.
 
 Permission is hereby granted, free of charge, to any person obtaining a copy of
 this software and associated documentation files (the "Software"), to deal in
@@ -16,4 +16,4 @@
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
\ No newline at end of file
+SOFTWARE.
diff --git a/third_party/mako/mako/mako/__init__.py b/third_party/mako/mako/mako/__init__.py
index 5ae55011..f105e78 100644
--- a/third_party/mako/mako/mako/__init__.py
+++ b/third_party/mako/mako/mako/__init__.py
@@ -1,8 +1,8 @@
 # mako/__init__.py
-# Copyright 2006-2021 the Mako authors and contributors <see AUTHORS file>
+# Copyright 2006-2022 the Mako authors and contributors <see AUTHORS file>
 #
 # This module is part of Mako and is released under
 # the MIT License: http://www.opensource.org/licenses/mit-license.php
 
 
-__version__ = "1.2.0"
+__version__ = "1.2.1"
diff --git a/third_party/mako/mako/mako/_ast_util.py b/third_party/mako/mako/mako/_ast_util.py
index b861533..9574283 100644
--- a/third_party/mako/mako/mako/_ast_util.py
+++ b/third_party/mako/mako/mako/_ast_util.py
@@ -1,5 +1,5 @@
 # mako/_ast_util.py
-# Copyright 2006-2021 the Mako authors and contributors <see AUTHORS file>
+# Copyright 2006-2022 the Mako authors and contributors <see AUTHORS file>
 #
 # This module is part of Mako and is released under
 # the MIT License: http://www.opensource.org/licenses/mit-license.php
diff --git a/third_party/mako/mako/mako/ast.py b/third_party/mako/mako/mako/ast.py
index f879e8b..a3f3ee3 100644
--- a/third_party/mako/mako/mako/ast.py
+++ b/third_party/mako/mako/mako/ast.py
@@ -1,5 +1,5 @@
 # mako/ast.py
-# Copyright 2006-2021 the Mako authors and contributors <see AUTHORS file>
+# Copyright 2006-2022 the Mako authors and contributors <see AUTHORS file>
 #
 # This module is part of Mako and is released under
 # the MIT License: http://www.opensource.org/licenses/mit-license.php
diff --git a/third_party/mako/mako/mako/cache.py b/third_party/mako/mako/mako/cache.py
index d77be27..db21bb3e 100644
--- a/third_party/mako/mako/mako/cache.py
+++ b/third_party/mako/mako/mako/cache.py
@@ -1,5 +1,5 @@
 # mako/cache.py
-# Copyright 2006-2021 the Mako authors and contributors <see AUTHORS file>
+# Copyright 2006-2022 the Mako authors and contributors <see AUTHORS file>
 #
 # This module is part of Mako and is released under
 # the MIT License: http://www.opensource.org/licenses/mit-license.php
diff --git a/third_party/mako/mako/mako/cmd.py b/third_party/mako/mako/mako/cmd.py
index 7592fb2..93a6733f 100644
--- a/third_party/mako/mako/mako/cmd.py
+++ b/third_party/mako/mako/mako/cmd.py
@@ -1,5 +1,5 @@
 # mako/cmd.py
-# Copyright 2006-2021 the Mako authors and contributors <see AUTHORS file>
+# Copyright 2006-2022 the Mako authors and contributors <see AUTHORS file>
 #
 # This module is part of Mako and is released under
 # the MIT License: http://www.opensource.org/licenses/mit-license.php
diff --git a/third_party/mako/mako/mako/codegen.py b/third_party/mako/mako/mako/codegen.py
index c897f0f..a9dbcb67 100644
--- a/third_party/mako/mako/mako/codegen.py
+++ b/third_party/mako/mako/mako/codegen.py
@@ -1,5 +1,5 @@
 # mako/codegen.py
-# Copyright 2006-2021 the Mako authors and contributors <see AUTHORS file>
+# Copyright 2006-2022 the Mako authors and contributors <see AUTHORS file>
 #
 # This module is part of Mako and is released under
 # the MIT License: http://www.opensource.org/licenses/mit-license.php
diff --git a/third_party/mako/mako/mako/compat.py b/third_party/mako/mako/mako/compat.py
index 68bc03b..48df30d 100644
--- a/third_party/mako/mako/mako/compat.py
+++ b/third_party/mako/mako/mako/compat.py
@@ -1,5 +1,5 @@
 # mako/compat.py
-# Copyright 2006-2021 the Mako authors and contributors <see AUTHORS file>
+# Copyright 2006-2022 the Mako authors and contributors <see AUTHORS file>
 #
 # This module is part of Mako and is released under
 # the MIT License: http://www.opensource.org/licenses/mit-license.php
diff --git a/third_party/mako/mako/mako/exceptions.py b/third_party/mako/mako/mako/exceptions.py
index a0a5fec..31c695fd 100644
--- a/third_party/mako/mako/mako/exceptions.py
+++ b/third_party/mako/mako/mako/exceptions.py
@@ -1,5 +1,5 @@
 # mako/exceptions.py
-# Copyright 2006-2021 the Mako authors and contributors <see AUTHORS file>
+# Copyright 2006-2022 the Mako authors and contributors <see AUTHORS file>
 #
 # This module is part of Mako and is released under
 # the MIT License: http://www.opensource.org/licenses/mit-license.php
diff --git a/third_party/mako/mako/mako/ext/autohandler.py b/third_party/mako/mako/mako/ext/autohandler.py
index e8fdac8..5bcfc28 100644
--- a/third_party/mako/mako/mako/ext/autohandler.py
+++ b/third_party/mako/mako/mako/ext/autohandler.py
@@ -1,5 +1,5 @@
 # ext/autohandler.py
-# Copyright 2006-2021 the Mako authors and contributors <see AUTHORS file>
+# Copyright 2006-2022 the Mako authors and contributors <see AUTHORS file>
 #
 # This module is part of Mako and is released under
 # the MIT License: http://www.opensource.org/licenses/mit-license.php
diff --git a/third_party/mako/mako/mako/ext/babelplugin.py b/third_party/mako/mako/mako/ext/babelplugin.py
index f015ec2..907d0b80 100644
--- a/third_party/mako/mako/mako/ext/babelplugin.py
+++ b/third_party/mako/mako/mako/ext/babelplugin.py
@@ -1,5 +1,5 @@
 # ext/babelplugin.py
-# Copyright 2006-2021 the Mako authors and contributors <see AUTHORS file>
+# Copyright 2006-2022 the Mako authors and contributors <see AUTHORS file>
 #
 # This module is part of Mako and is released under
 # the MIT License: http://www.opensource.org/licenses/mit-license.php
diff --git a/third_party/mako/mako/mako/ext/beaker_cache.py b/third_party/mako/mako/mako/ext/beaker_cache.py
index a40b09c..9aa35b0 100644
--- a/third_party/mako/mako/mako/ext/beaker_cache.py
+++ b/third_party/mako/mako/mako/ext/beaker_cache.py
@@ -1,5 +1,5 @@
 # ext/beaker_cache.py
-# Copyright 2006-2021 the Mako authors and contributors <see AUTHORS file>
+# Copyright 2006-2022 the Mako authors and contributors <see AUTHORS file>
 #
 # This module is part of Mako and is released under
 # the MIT License: http://www.opensource.org/licenses/mit-license.php
diff --git a/third_party/mako/mako/mako/ext/extract.py b/third_party/mako/mako/mako/ext/extract.py
index 74d067d..9d33ee1 100644
--- a/third_party/mako/mako/mako/ext/extract.py
+++ b/third_party/mako/mako/mako/ext/extract.py
@@ -1,5 +1,5 @@
 # ext/extract.py
-# Copyright 2006-2021 the Mako authors and contributors <see AUTHORS file>
+# Copyright 2006-2022 the Mako authors and contributors <see AUTHORS file>
 #
 # This module is part of Mako and is released under
 # the MIT License: http://www.opensource.org/licenses/mit-license.php
diff --git a/third_party/mako/mako/mako/ext/linguaplugin.py b/third_party/mako/mako/mako/ext/linguaplugin.py
index 4cce626..efb04c7 100644
--- a/third_party/mako/mako/mako/ext/linguaplugin.py
+++ b/third_party/mako/mako/mako/ext/linguaplugin.py
@@ -1,5 +1,5 @@
 # ext/linguaplugin.py
-# Copyright 2006-2021 the Mako authors and contributors <see AUTHORS file>
+# Copyright 2006-2022 the Mako authors and contributors <see AUTHORS file>
 #
 # This module is part of Mako and is released under
 # the MIT License: http://www.opensource.org/licenses/mit-license.php
diff --git a/third_party/mako/mako/mako/ext/preprocessors.py b/third_party/mako/mako/mako/ext/preprocessors.py
index 6855eeb..80403ec 100644
--- a/third_party/mako/mako/mako/ext/preprocessors.py
+++ b/third_party/mako/mako/mako/ext/preprocessors.py
@@ -1,5 +1,5 @@
 # ext/preprocessors.py
-# Copyright 2006-2021 the Mako authors and contributors <see AUTHORS file>
+# Copyright 2006-2022 the Mako authors and contributors <see AUTHORS file>
 #
 # This module is part of Mako and is released under
 # the MIT License: http://www.opensource.org/licenses/mit-license.php
diff --git a/third_party/mako/mako/mako/ext/pygmentplugin.py b/third_party/mako/mako/mako/ext/pygmentplugin.py
index 38d6a71..9acbf4cd6 100644
--- a/third_party/mako/mako/mako/ext/pygmentplugin.py
+++ b/third_party/mako/mako/mako/ext/pygmentplugin.py
@@ -1,5 +1,5 @@
 # ext/pygmentplugin.py
-# Copyright 2006-2021 the Mako authors and contributors <see AUTHORS file>
+# Copyright 2006-2022 the Mako authors and contributors <see AUTHORS file>
 #
 # This module is part of Mako and is released under
 # the MIT License: http://www.opensource.org/licenses/mit-license.php
diff --git a/third_party/mako/mako/mako/ext/turbogears.py b/third_party/mako/mako/mako/ext/turbogears.py
index 413d9f7..2ebf0746 100644
--- a/third_party/mako/mako/mako/ext/turbogears.py
+++ b/third_party/mako/mako/mako/ext/turbogears.py
@@ -1,5 +1,5 @@
 # ext/turbogears.py
-# Copyright 2006-2021 the Mako authors and contributors <see AUTHORS file>
+# Copyright 2006-2022 the Mako authors and contributors <see AUTHORS file>
 #
 # This module is part of Mako and is released under
 # the MIT License: http://www.opensource.org/licenses/mit-license.php
diff --git a/third_party/mako/mako/mako/filters.py b/third_party/mako/mako/mako/filters.py
index 26edd8e..af202f3 100644
--- a/third_party/mako/mako/mako/filters.py
+++ b/third_party/mako/mako/mako/filters.py
@@ -1,5 +1,5 @@
 # mako/filters.py
-# Copyright 2006-2021 the Mako authors and contributors <see AUTHORS file>
+# Copyright 2006-2022 the Mako authors and contributors <see AUTHORS file>
 #
 # This module is part of Mako and is released under
 # the MIT License: http://www.opensource.org/licenses/mit-license.php
diff --git a/third_party/mako/mako/mako/lexer.py b/third_party/mako/mako/mako/lexer.py
index 527c4b51..bfcf286 100644
--- a/third_party/mako/mako/mako/lexer.py
+++ b/third_party/mako/mako/mako/lexer.py
@@ -1,5 +1,5 @@
 # mako/lexer.py
-# Copyright 2006-2021 the Mako authors and contributors <see AUTHORS file>
+# Copyright 2006-2022 the Mako authors and contributors <see AUTHORS file>
 #
 # This module is part of Mako and is released under
 # the MIT License: http://www.opensource.org/licenses/mit-license.php
@@ -74,12 +74,11 @@
             (start, end) = match.span()
             self.match_position = end + 1 if end == start else end
             self.matched_lineno = self.lineno
-            lines = re.findall(r"\n", self.text[mp : self.match_position])
             cp = mp - 1
-            while cp >= 0 and cp < self.textlength and self.text[cp] != "\n":
-                cp -= 1
+            if cp >= 0 and cp < self.textlength:
+                cp = self.text[: cp + 1].rfind("\n")
             self.matched_charpos = mp - cp
-            self.lineno += len(lines)
+            self.lineno += self.text[mp : self.match_position].count("\n")
         return match
 
     def parse_until_text(self, watch_nesting, *text):
diff --git a/third_party/mako/mako/mako/lookup.py b/third_party/mako/mako/mako/lookup.py
index 7afe242..f7410ce 100644
--- a/third_party/mako/mako/mako/lookup.py
+++ b/third_party/mako/mako/mako/lookup.py
@@ -1,5 +1,5 @@
 # mako/lookup.py
-# Copyright 2006-2021 the Mako authors and contributors <see AUTHORS file>
+# Copyright 2006-2022 the Mako authors and contributors <see AUTHORS file>
 #
 # This module is part of Mako and is released under
 # the MIT License: http://www.opensource.org/licenses/mit-license.php
diff --git a/third_party/mako/mako/mako/parsetree.py b/third_party/mako/mako/mako/parsetree.py
index 2135769f..c5a12d1 100644
--- a/third_party/mako/mako/mako/parsetree.py
+++ b/third_party/mako/mako/mako/parsetree.py
@@ -1,5 +1,5 @@
 # mako/parsetree.py
-# Copyright 2006-2021 the Mako authors and contributors <see AUTHORS file>
+# Copyright 2006-2022 the Mako authors and contributors <see AUTHORS file>
 #
 # This module is part of Mako and is released under
 # the MIT License: http://www.opensource.org/licenses/mit-license.php
diff --git a/third_party/mako/mako/mako/pygen.py b/third_party/mako/mako/mako/pygen.py
index 46b0b52..57c69793 100644
--- a/third_party/mako/mako/mako/pygen.py
+++ b/third_party/mako/mako/mako/pygen.py
@@ -1,5 +1,5 @@
 # mako/pygen.py
-# Copyright 2006-2021 the Mako authors and contributors <see AUTHORS file>
+# Copyright 2006-2022 the Mako authors and contributors <see AUTHORS file>
 #
 # This module is part of Mako and is released under
 # the MIT License: http://www.opensource.org/licenses/mit-license.php
@@ -43,6 +43,15 @@
         # source lines
         self.source_map = {}
 
+        self._re_space_comment = re.compile(r"^\s*#")
+        self._re_space = re.compile(r"^\s*$")
+        self._re_indent = re.compile(r":[ \t]*(?:#.*)?$")
+        self._re_compound = re.compile(r"^\s*(if|try|elif|while|for|with)")
+        self._re_indent_keyword = re.compile(
+            r"^\s*(def|class|else|elif|except|finally)"
+        )
+        self._re_unindentor = re.compile(r"^\s*(else|elif|except|finally).*\:")
+
     def _update_lineno(self, num):
         self.lineno += num
 
@@ -86,8 +95,8 @@
 
         if (
             line is None
-            or re.match(r"^\s*#", line)
-            or re.match(r"^\s*$", line)
+            or self._re_space_comment.match(line)
+            or self._re_space.match(line)
         ):
             hastext = False
         else:
@@ -121,12 +130,12 @@
         # note that a line can both decrase (before printing) and
         # then increase (after printing) the indentation level.
 
-        if re.search(r":[ \t]*(?:#.*)?$", line):
+        if self._re_indent.search(line):
             # increment indentation count, and also
             # keep track of what the keyword was that indented us,
             # if it is a python compound statement keyword
             # where we might have to look for an "unindent" keyword
-            match = re.match(r"^\s*(if|try|elif|while|for|with)", line)
+            match = self._re_compound.match(line)
             if match:
                 # its a "compound" keyword, so we will check for "unindentors"
                 indentor = match.group(1)
@@ -137,9 +146,7 @@
                 # its not a "compound" keyword.  but lets also
                 # test for valid Python keywords that might be indenting us,
                 # else assume its a non-indenting line
-                m2 = re.match(
-                    r"^\s*(def|class|else|elif|except|finally)", line
-                )
+                m2 = self._re_indent_keyword.match(line)
                 if m2:
                     self.indent += 1
                     self.indent_detail.append(indentor)
@@ -167,7 +174,7 @@
 
         # if the current line doesnt have one of the "unindentor" keywords,
         # return False
-        match = re.match(r"^\s*(else|elif|except|finally).*\:", line)
+        match = self._re_unindentor.match(line)
         # if True, whitespace matches up, we have a compound indentor,
         # and this line has an unindentor, this
         # is probably good enough
@@ -193,6 +200,9 @@
 
         stripspace is a string of space that will be truncated from the
         start of the line before indenting."""
+        if stripspace == "":
+            # Fast path optimization.
+            return self.indentstring * self.indent + line
 
         return re.sub(
             r"^%s" % stripspace, self.indentstring * self.indent, line
diff --git a/third_party/mako/mako/mako/pyparser.py b/third_party/mako/mako/mako/pyparser.py
index 5c55505..d9b090c 100644
--- a/third_party/mako/mako/mako/pyparser.py
+++ b/third_party/mako/mako/mako/pyparser.py
@@ -1,5 +1,5 @@
 # mako/pyparser.py
-# Copyright 2006-2021 the Mako authors and contributors <see AUTHORS file>
+# Copyright 2006-2022 the Mako authors and contributors <see AUTHORS file>
 #
 # This module is part of Mako and is released under
 # the MIT License: http://www.opensource.org/licenses/mit-license.php
diff --git a/third_party/mako/mako/mako/template.py b/third_party/mako/mako/mako/template.py
index bbbe73c..8c70731 100644
--- a/third_party/mako/mako/mako/template.py
+++ b/third_party/mako/mako/mako/template.py
@@ -1,5 +1,5 @@
 # mako/template.py
-# Copyright 2006-2021 the Mako authors and contributors <see AUTHORS file>
+# Copyright 2006-2022 the Mako authors and contributors <see AUTHORS file>
 #
 # This module is part of Mako and is released under
 # the MIT License: http://www.opensource.org/licenses/mit-license.php
diff --git a/third_party/mako/mako/mako/testing/fixtures.py b/third_party/mako/mako/mako/testing/fixtures.py
index c9379c0..01e9961 100644
--- a/third_party/mako/mako/mako/testing/fixtures.py
+++ b/third_party/mako/mako/mako/testing/fixtures.py
@@ -80,6 +80,16 @@
             output = filters(output)
         eq_(output, expected)
 
+    def indicates_unbound_local_error(self, rendered_output, unbound_var):
+        var = f"&#39;{unbound_var}&#39;"
+        error_msgs = (
+            # < 3.11
+            f"local variable {var} referenced before assignment",
+            # >= 3.11
+            f"cannot access local variable {var} where it is not associated",
+        )
+        return any((msg in rendered_output) for msg in error_msgs)
+
 
 class PlainCacheImpl(CacheImpl):
     """Simple memory cache impl so that tests which
diff --git a/third_party/mako/mako/mako/util.py b/third_party/mako/mako/mako/util.py
index 74c8b9e..5fb683b 100644
--- a/third_party/mako/mako/mako/util.py
+++ b/third_party/mako/mako/mako/util.py
@@ -1,5 +1,5 @@
 # mako/util.py
-# Copyright 2006-2021 the Mako authors and contributors <see AUTHORS file>
+# Copyright 2006-2022 the Mako authors and contributors <see AUTHORS file>
 #
 # This module is part of Mako and is released under
 # the MIT License: http://www.opensource.org/licenses/mit-license.php
diff --git a/third_party/mako/mako/setup.cfg b/third_party/mako/mako/setup.cfg
index b7272db..bcfefa1b 100644
--- a/third_party/mako/mako/setup.cfg
+++ b/third_party/mako/mako/setup.cfg
@@ -8,7 +8,7 @@
 author = Mike Bayer
 author_email = mike@zzzcomputing.com
 license = MIT
-license_file = LICENSE
+license_files = LICENSE
 classifiers = 
 	Development Status :: 5 - Production/Stable
 	License :: OSI Approved :: MIT License
@@ -69,9 +69,12 @@
 tag_date = 0
 
 [tool:pytest]
-addopts = --tb native -v -r fxX -W error
+addopts = --tb native -v -r fxX -p warnings
 python_files = test/*test_*.py
 python_classes = *Test
+filterwarnings = 
+	error::DeprecationWarning:test
+	error::DeprecationWarning:mako
 
 [upload]
 sign = 1
diff --git a/third_party/mako/mako/test/ext/test_babelplugin.py b/third_party/mako/mako/test/ext/test_babelplugin.py
index de3e461..cfe79b6 100644
--- a/third_party/mako/mako/test/ext/test_babelplugin.py
+++ b/third_party/mako/mako/test/ext/test_babelplugin.py
@@ -1,26 +1,35 @@
 import io
 import os
 
-from mako.ext.babelplugin import extract
+import pytest
+
 from mako.testing.assertions import eq_
 from mako.testing.config import config
 from mako.testing.exclusions import requires_babel
 from mako.testing.fixtures import TemplateTest
 
 
+class UsesExtract:
+    @pytest.fixture(scope="class")
+    def extract(self):
+        from mako.ext.babelplugin import extract
+
+        return extract
+
+
 @requires_babel
-class PluginExtractTest:
-    def test_parse_python_expression(self):
+class PluginExtractTest(UsesExtract):
+    def test_parse_python_expression(self, extract):
         input_ = io.BytesIO(b'<p>${_("Message")}</p>')
         messages = list(extract(input_, ["_"], [], {}))
         eq_(messages, [(1, "_", ("Message"), [])])
 
-    def test_python_gettext_call(self):
+    def test_python_gettext_call(self, extract):
         input_ = io.BytesIO(b'<p>${_("Message")}</p>')
         messages = list(extract(input_, ["_"], [], {}))
         eq_(messages, [(1, "_", ("Message"), [])])
 
-    def test_translator_comment(self):
+    def test_translator_comment(self, extract):
         input_ = io.BytesIO(
             b"""
         <p>
@@ -43,8 +52,8 @@
 
 
 @requires_babel
-class MakoExtractTest(TemplateTest):
-    def test_extract(self):
+class MakoExtractTest(UsesExtract, TemplateTest):
+    def test_extract(self, extract):
         with open(
             os.path.join(config.template_base, "gettext.mako")
         ) as mako_tmpl:
@@ -83,7 +92,7 @@
             ]
         eq_(expected, messages)
 
-    def test_extract_utf8(self):
+    def test_extract_utf8(self, extract):
         with open(
             os.path.join(config.template_base, "gettext_utf8.mako"), "rb"
         ) as mako_tmpl:
@@ -92,7 +101,7 @@
             )
             assert message == (1, "_", "K\xf6ln", [])
 
-    def test_extract_cp1251(self):
+    def test_extract_cp1251(self, extract):
         with open(
             os.path.join(config.template_base, "gettext_cp1251.mako"), "rb"
         ) as mako_tmpl:
diff --git a/third_party/mako/mako/test/ext/test_linguaplugin.py b/third_party/mako/mako/test/ext/test_linguaplugin.py
index ae24f67..5aafcff5 100644
--- a/third_party/mako/mako/test/ext/test_linguaplugin.py
+++ b/third_party/mako/mako/test/ext/test_linguaplugin.py
@@ -2,7 +2,6 @@
 
 import pytest
 
-from mako.ext.linguaplugin import LinguaMakoExtractor
 from mako.testing.assertions import eq_
 from mako.testing.config import config
 from mako.testing.exclusions import requires_lingua
@@ -19,11 +18,14 @@
 class MakoExtractTest(TemplateTest):
     @pytest.fixture(autouse=True)
     def register_lingua_extractors(self):
+
         from lingua.extractors import register_extractors
 
         register_extractors()
 
     def test_extract(self):
+        from mako.ext.linguaplugin import LinguaMakoExtractor
+
         plugin = LinguaMakoExtractor({"comment-tags": "TRANSLATOR"})
         messages = list(
             plugin(
diff --git a/third_party/mako/mako/test/test_exceptions.py b/third_party/mako/mako/test/test_exceptions.py
index b1930c5..be573e6 100644
--- a/third_party/mako/mako/test/test_exceptions.py
+++ b/third_party/mako/mako/test/test_exceptions.py
@@ -173,7 +173,7 @@
         )
 
         assert (
-            '<div class="sourceline"><table class="syntax-highlightedtable">'
+            '<table class="syntax-highlightedtable">'
             in l.get_template("foo.html").render_unicode()
         )
 
@@ -274,10 +274,8 @@
         html_error = exceptions.html_error_template().render_unicode(
             error=v, traceback=None
         )
-        assert (
-            "local variable &#39;y&#39; referenced before assignment"
-            in html_error
-        )
+
+        assert self.indicates_unbound_local_error(html_error, "y")
 
     def test_tback_trace_from_py_file(self):
         t = self._file_template("runtimeerr.html")
@@ -287,10 +285,7 @@
         except:
             html_error = exceptions.html_error_template().render_unicode()
 
-        assert (
-            "local variable &#39;y&#39; referenced before assignment"
-            in html_error
-        )
+        assert self.indicates_unbound_local_error(html_error, "y")
 
     def test_code_block_line_number(self):
         l = TemplateLookup()
diff --git a/third_party/mako/mako/test/test_template.py b/third_party/mako/mako/test/test_template.py
index fc1aeca..6e7a309 100644
--- a/third_party/mako/mako/test/test_template.py
+++ b/third_party/mako/mako/test/test_template.py
@@ -940,18 +940,15 @@
     %endfor
 """
         )
-        assert (
-            result_lines(
-                t.render(
-                    y=[
-                        {"test": "one"},
-                        {"foo": "bar"},
-                        {"foo": "bar", "test": "two"},
-                    ]
-                )
+        assert result_lines(
+            t.render(
+                y=[
+                    {"test": "one"},
+                    {"foo": "bar"},
+                    {"foo": "bar", "test": "two"},
+                ]
             )
-            == ["yes x has test", "no x does not have test", "yes x has test"]
-        )
+        ) == ["yes x has test", "no x does not have test", "yes x has test"]
 
     def test_blank_control_1(self):
         self._do_memory_test(
diff --git a/third_party/mako/mako/tox.ini b/third_party/mako/mako/tox.ini
index d8edd3cb..6f7cefd 100644
--- a/third_party/mako/mako/tox.ini
+++ b/third_party/mako/mako/tox.ini
@@ -30,7 +30,7 @@
       pydocstyle<4.0.0
       # used by flake8-rst-docstrings
       pygments
-      black==21.9b0
+      black==22.3.0
 commands =
     flake8 ./mako/ ./test/ setup.py --exclude test/templates,test/foo  {posargs}
     black --check .
diff --git a/third_party/rust/Cargo.lock b/third_party/rust/Cargo.lock
index 6517786..6ed602f5 100644
--- a/third_party/rust/Cargo.lock
+++ b/third_party/rust/Cargo.lock
@@ -237,6 +237,7 @@
  "once_cell",
  "rstest",
  "rustversion",
+ "semver",
  "serde",
  "serde_json_lenient",
  "small_ctor",
diff --git a/third_party/rust/Cargo.toml b/third_party/rust/Cargo.toml
index 94298f11..cbeb8c46 100644
--- a/third_party/rust/Cargo.toml
+++ b/third_party/rust/Cargo.toml
@@ -46,6 +46,7 @@
 clap = "3"
 once_cell = "1"
 rstest = "0.12"
+semver = "1"
 small_ctor = "0.1"
 tempfile = "3"
 
diff --git a/third_party/rust/semver/v1/BUILD.gn b/third_party/rust/semver/v1/BUILD.gn
index c2b1c20..2cea83a 100644
--- a/third_party/rust/semver/v1/BUILD.gn
+++ b/third_party/rust/semver/v1/BUILD.gn
@@ -8,10 +8,6 @@
   crate_name = "semver"
   epoch = "1"
   crate_type = "rlib"
-
-  # Only for usage from third-party crates. Add the crate to
-  # third_party.toml to use it from first-party code.
-  visibility = [ "//third_party/rust/*" ]
   crate_root = "crate/src/lib.rs"
 
   # Unit tests skipped. Generate with --with-tests to include them.
@@ -31,3 +27,25 @@
   build_root = "crate/build.rs"
   build_sources = [ "crate/build.rs" ]
 }
+cargo_crate("test_support") {
+  crate_name = "semver"
+  epoch = "1"
+  crate_type = "rlib"
+  testonly = "true"
+  crate_root = "crate/src/lib.rs"
+  build_native_rust_unit_tests = false
+  sources = [ "crate/src/lib.rs" ]
+  edition = "2018"
+  cargo_pkg_version = "1.0.4"
+  cargo_pkg_authors = "David Tolnay <dtolnay@gmail.com>"
+  cargo_pkg_name = "semver"
+  cargo_pkg_description =
+      "Parser and evaluator for Cargo's flavor of Semantic Versioning"
+  deps = [ "//third_party/rust/serde/v1:lib" ]
+  features = [
+    "serde",
+    "std",
+  ]
+  build_root = "crate/build.rs"
+  build_sources = [ "crate/build.rs" ]
+}
diff --git a/third_party/rust/third_party.toml b/third_party/rust/third_party.toml
index 364e20f..cf67d7c 100644
--- a/third_party/rust/third_party.toml
+++ b/third_party/rust/third_party.toml
@@ -90,5 +90,6 @@
 cargo-platform = "0.1"
 clap = "3"
 once_cell = "1"
+semver = "1"
 tempfile = "3"
 toml = {version = "0.5", features = ["preserve_order"]}
diff --git a/tools/binary_size/print_trybot_sizes.py b/tools/binary_size/print_trybot_sizes.py
index bde0f9e..1f653e7c 100755
--- a/tools/binary_size/print_trybot_sizes.py
+++ b/tools/binary_size/print_trybot_sizes.py
@@ -5,7 +5,9 @@
 """Prints android-binary-size result for a given commit or commit range."""
 
 import argparse
+import collections
 import concurrent.futures
+import csv
 import json
 import os
 import posixpath
@@ -13,20 +15,35 @@
 import subprocess
 import sys
 
+_COMMIT_LIMIT = 200
 _LOG_RE = re.compile(
     r'^commit (\S+).*?'
-    r'^\s*Reviewed-on: (\S+).*?'
-    r'^\s*Cr-Commit-Position:.*?(\d+)', re.DOTALL | re.MULTILINE)
+    r'^Date:\s+(.*?)$.*?'
+    r'^    (\S.*?)$.*?'
+    r'^    Reviewed-on: (\S+).*?'
+    r'^(?!commit)    Cr-Commit-Position:.*?(\d+)', re.DOTALL | re.MULTILINE)
 
 
-def _git_log(rev_list):
-  cmd = ['git', 'log', rev_list]
-  if '..' not in rev_list:
-    cmd += ['-n1']
+_CommitInfo = collections.namedtuple(
+    '_CommitInfo', 'git_hash date subject review_url cr_position')
+
+
+def _git_log(git_log_args):
+  cmd = ['git', 'log']
+
+  # Ensure there's a limit on number of commits.
+  if not any(x.startswith('-n') for x in git_log_args):
+    cmd += [f'-n{_COMMIT_LIMIT}']
+
+  cmd += git_log_args
 
   log_output = subprocess.check_output(cmd, encoding='utf8')
-  for git_hash, review_url, cr_position in _LOG_RE.findall(log_output):
-    yield git_hash, review_url, cr_position
+  ret = [_CommitInfo(*x) for x in _LOG_RE.findall(log_output)]
+
+  if len(ret) == _COMMIT_LIMIT:
+    sys.stderr.write(
+        f'Limiting to {_COMMIT_LIMIT} commits. Use -n## to override\n')
+  return ret
 
 
 def _query_size(review_url):
@@ -68,22 +85,36 @@
 
 def main():
   parser = argparse.ArgumentParser(description=__doc__)
-  parser.add_argument('rev_list')
-  args = parser.parse_args()
+  parser.add_argument('--csv', action='store_true', help='Print as CSV')
+  args, git_log_args = parser.parse_known_args()
 
   # Ensure user has authenticated.
   result = subprocess.run(['bb', 'auth-info'],
                           check=False,
                           stdout=subprocess.DEVNULL)
   if result.returncode:
-    print('First run: bb auth-login')
+    sys.stderr.write('First run: bb auth-login\n')
     sys.exit(1)
 
-  commit_infos = list(_git_log(args.rev_list))
+  commit_infos = _git_log(git_log_args)
+  if not commit_infos:
+    sys.stderr.write('Did not find any commits.\n')
+    sys.exit(1)
+
+  print(f'Fetching bot results for {len(commit_infos)} commits...')
+
+  if args.csv:
+    print_func = csv.writer(sys.stdout).writerow
+  else:
+    print_func = lambda v: print('{:12}{:14}{:12}{:32}{}'.format(*v))
+
+  print_func(('Commit #', 'Git Hash', 'Size', 'Date', 'Subject'))
   with concurrent.futures.ThreadPoolExecutor(max_workers=20) as pool:
-    sizes = [pool.submit(_query_size, url) for _, url, _ in commit_infos]
-    for (git_hash, _, cr_position), size in zip(commit_infos, sizes):
-      print(f'{cr_position}\t{git_hash}\t{size.result()}')
+    sizes = [pool.submit(_query_size, info.review_url) for info in commit_infos]
+    for info, size in zip(commit_infos, sizes):
+      size_str = size.result().replace(' bytes', '').lstrip('+')
+      print_func((info.cr_position, info.git_hash[:12], size_str, info.date,
+                  info.subject))
 
 
 if __name__ == '__main__':
diff --git a/tools/crates/gnrt/BUILD.gn b/tools/crates/gnrt/BUILD.gn
index e0f33bb..00609e6 100644
--- a/tools/crates/gnrt/BUILD.gn
+++ b/tools/crates/gnrt/BUILD.gn
@@ -34,6 +34,7 @@
     # in the first place, but does due to a crates.py bug.
     "//third_party/rust/cargo_platform/v0_1:lib",
     "//third_party/rust/once_cell/v1:lib",
+    "//third_party/rust/semver/v1:lib",
     "//third_party/rust/serde/v1:lib",
     "//third_party/rust/serde_json/v1:lib",
     "//third_party/rust/toml/v0_5:test_support",
diff --git a/tools/crates/gnrt/deps.rs b/tools/crates/gnrt/deps.rs
index 9b3d618..391e370 100644
--- a/tools/crates/gnrt/deps.rs
+++ b/tools/crates/gnrt/deps.rs
@@ -17,7 +17,7 @@
 /// A single transitive third-party dependency. Includes information needed for
 /// generating build files later.
 #[derive(Clone, Debug)]
-pub struct Dependency {
+pub struct ThirdPartyDep {
     /// The package name as used by cargo.
     pub package_name: String,
     /// The normalized name we use in vendored crate paths.
@@ -27,6 +27,13 @@
     /// The epoch derived from the dependency version. Used for our vendored
     /// third-party crates.
     pub epoch: Epoch,
+    /// This package's dependencies. Each element cross-references another
+    /// `ThirdPartyDep` by name and epoch.
+    pub dependencies: Vec<DepOfDep>,
+    /// Same as the above, but for build script deps.
+    pub build_dependencies: Vec<DepOfDep>,
+    /// Same as the above, but for test deps.
+    pub dev_dependencies: Vec<DepOfDep>,
     /// A package can be depended upon in different ways: as a normal
     /// dependency, just for build scripts, or just for tests. `kinds` contains
     /// an entry for each way this package is depended on.
@@ -48,6 +55,19 @@
     pub is_local: bool,
 }
 
+/// A dependency of a `ThirdPartyDep`. Cross-references another `ThirdPartyDep`
+/// entry in the resolved list.
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub struct DepOfDep {
+    /// The normalized name of this dependency.
+    pub normalized_name: NormalizedName,
+    /// The requested epoch of this dependency.
+    pub epoch: Epoch,
+    /// A platform constraint for this dependency, or `None` if it's used on all
+    /// platforms.
+    pub platform: Option<Platform>,
+}
+
 /// Information specific to the dependency kind: for normal, build script, or
 /// test dependencies.
 #[derive(Clone, Debug)]
@@ -100,7 +120,7 @@
 /// package may have multiple crates, each of which corresponds to a single
 /// rustc invocation: e.g. a package may have a lib crate as well as multiple
 /// binary crates.
-pub fn collect_dependencies(metadata: &cargo_metadata::Metadata) -> Vec<Dependency> {
+pub fn collect_dependencies(metadata: &cargo_metadata::Metadata) -> Vec<ThirdPartyDep> {
     // The metadata is split into two parts:
     // 1. A list of packages and associated info: targets (e.g. lib, bin,
     //    tests), source path, etc. This includes all workspace members and all
@@ -204,10 +224,25 @@
         }
 
         dep.version = package.version.clone();
-        dep.epoch = match package.version.major {
-            0 => Epoch::Minor(package.version.minor.try_into().unwrap()),
-            x => Epoch::Major(x.try_into().unwrap()),
-        };
+        dep.epoch = epoch_from_version(&package.version);
+
+        // Collect this package's list of resolved dependencies which will be
+        // needed for build file generation later.
+        for node_dep in iter_node_deps(node) {
+            let dep_pkg = dep_graph.packages.get(node_dep.pkg).unwrap();
+            let dep_of_dep = DepOfDep {
+                normalized_name: NormalizedName::from_crate_name(&dep_pkg.name),
+                epoch: epoch_from_version(&dep_pkg.version),
+                platform: node_dep.target,
+            };
+
+            match node_dep.kind {
+                DependencyKind::Normal => dep.dependencies.push(dep_of_dep),
+                DependencyKind::Build => dep.build_dependencies.push(dep_of_dep),
+                DependencyKind::Development => dep.dev_dependencies.push(dep_of_dep),
+                DependencyKind::Unknown => unreachable!(),
+            }
+        }
 
         // Make sure the package comes from our vendored source. If not, report the
         // error for later.
@@ -233,7 +268,7 @@
     /// The path of package IDs to the current node. For human consumption.
     path: Vec<String>,
     /// The final set of dependencies.
-    dependencies: HashMap<&'a cargo_metadata::PackageId, Dependency>,
+    dependencies: HashMap<&'a cargo_metadata::PackageId, ThirdPartyDep>,
 }
 
 /// Recursively explore a particular node in the dependency graph. Fills data in
@@ -246,13 +281,14 @@
 
     // Helper to insert a placeholder `Dependency` into a map. We fill in the
     // fields later.
-    let init_dep = |path| Dependency {
+    let init_dep = |path| ThirdPartyDep {
         package_name: String::new(),
         normalized_name: NormalizedName::from_crate_name(""),
-        // Dummy value to be filled in later.
         version: Version::new(0, 0, 0),
-        // Dummy value to be filled in later.
         epoch: Epoch::Minor(1),
+        dependencies: Vec::new(),
+        build_dependencies: Vec::new(),
+        dev_dependencies: Vec::new(),
         dependency_kinds: HashMap::new(),
         lib_target: None,
         bin_targets: Vec::new(),
@@ -273,7 +309,7 @@
         explore_node(state, target_node);
 
         // Merge this with the existing entry for the dep.
-        let dep: &mut Dependency =
+        let dep: &mut ThirdPartyDep =
             state.dependencies.entry(dep_edge.pkg).or_insert_with(|| init_dep(state.path.clone()));
         let info: &mut PerKindInfo = dep
             .dependency_kinds
@@ -394,3 +430,10 @@
         }
     }
 }
+
+fn epoch_from_version(version: &cargo_metadata::Version) -> Epoch {
+    match version.major {
+        0 => Epoch::Minor(version.minor.try_into().unwrap()),
+        x => Epoch::Major(x.try_into().unwrap()),
+    }
+}
diff --git a/tools/crates/gnrt/deps_unittest.rs b/tools/crates/gnrt/deps_unittest.rs
index 3b3d2f0..e1e481b5 100644
--- a/tools/crates/gnrt/deps_unittest.rs
+++ b/tools/crates/gnrt/deps_unittest.rs
@@ -2,13 +2,16 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-use rust_gtest_interop::prelude::*;
-
 use gnrt_lib::deps::*;
 
+use std::str::FromStr;
+
+use cargo_platform::Platform;
+use rust_gtest_interop::prelude::*;
+
 #[gtest(DepsTest, CollectDependenciesOnSampleOutput)]
 fn test() {
-    use gnrt_lib::crates::Epoch;
+    use gnrt_lib::crates::{Epoch, NormalizedName};
 
     let metadata: cargo_metadata::Metadata = serde_json::from_str(SAMPLE_CARGO_METADATA).unwrap();
     let mut dependencies = collect_dependencies(&metadata);
@@ -18,6 +21,8 @@
 
     let empty_str_slice: &'static [&'static str] = &[];
 
+    expect_eq!(dependencies.len(), 12);
+
     expect_eq!(dependencies[0].package_name, "cc");
     expect_eq!(dependencies[0].epoch, Epoch::Major(1));
     expect_eq!(
@@ -59,6 +64,17 @@
         dependencies[5].dependency_kinds.get(&DependencyKind::Normal).unwrap().features,
         &["default", "derive", "serde_derive", "std"]
     );
+    expect_eq!(dependencies[5].dependencies.len(), 1);
+    expect_eq!(dependencies[5].build_dependencies.len(), 0);
+    expect_eq!(dependencies[5].dev_dependencies.len(), 0);
+    expect_eq!(
+        dependencies[5].dependencies[0],
+        DepOfDep {
+            normalized_name: NormalizedName::new("serde_derive").unwrap(),
+            epoch: Epoch::Major(1),
+            platform: None,
+        }
+    );
 
     expect_eq!(dependencies[6].package_name, "serde_derive");
     expect_eq!(dependencies[6].epoch, Epoch::Major(1));
@@ -66,6 +82,33 @@
         dependencies[6].dependency_kinds.get(&DependencyKind::Normal).unwrap().features,
         &["default"]
     );
+    expect_eq!(dependencies[6].dependencies.len(), 3);
+    expect_eq!(dependencies[6].build_dependencies.len(), 0);
+    expect_eq!(dependencies[6].dev_dependencies.len(), 0);
+    expect_eq!(
+        dependencies[6].dependencies[0],
+        DepOfDep {
+            normalized_name: NormalizedName::new("proc_macro2").unwrap(),
+            epoch: Epoch::Major(1),
+            platform: None,
+        }
+    );
+    expect_eq!(
+        dependencies[6].dependencies[1],
+        DepOfDep {
+            normalized_name: NormalizedName::new("quote").unwrap(),
+            epoch: Epoch::Major(1),
+            platform: None,
+        }
+    );
+    expect_eq!(
+        dependencies[6].dependencies[2],
+        DepOfDep {
+            normalized_name: NormalizedName::new("syn").unwrap(),
+            epoch: Epoch::Major(1),
+            platform: None,
+        }
+    );
 
     expect_eq!(dependencies[7].package_name, "syn");
     expect_eq!(dependencies[7].epoch, Epoch::Major(1));
@@ -73,6 +116,33 @@
         dependencies[7].dependency_kinds.get(&DependencyKind::Normal).unwrap().features,
         &["clone-impls", "default", "derive", "parsing", "printing", "proc-macro", "quote"]
     );
+    expect_eq!(dependencies[7].dependencies.len(), 3);
+    expect_eq!(dependencies[7].build_dependencies.len(), 0);
+    expect_eq!(dependencies[7].dev_dependencies.len(), 0);
+    expect_eq!(
+        dependencies[7].dependencies[0],
+        DepOfDep {
+            normalized_name: NormalizedName::new("proc_macro2").unwrap(),
+            epoch: Epoch::Major(1),
+            platform: None,
+        }
+    );
+    expect_eq!(
+        dependencies[7].dependencies[1],
+        DepOfDep {
+            normalized_name: NormalizedName::new("quote").unwrap(),
+            epoch: Epoch::Major(1),
+            platform: None,
+        }
+    );
+    expect_eq!(
+        dependencies[7].dependencies[2],
+        DepOfDep {
+            normalized_name: NormalizedName::new("unicode_ident").unwrap(),
+            epoch: Epoch::Major(1),
+            platform: None,
+        }
+    );
 
     expect_eq!(dependencies[8].package_name, "termcolor");
     expect_eq!(dependencies[8].epoch, Epoch::Major(1));
@@ -80,6 +150,17 @@
         dependencies[8].dependency_kinds.get(&DependencyKind::Normal).unwrap().features,
         empty_str_slice
     );
+    expect_eq!(dependencies[8].dependencies.len(), 1);
+    expect_eq!(dependencies[8].build_dependencies.len(), 0);
+    expect_eq!(dependencies[8].dev_dependencies.len(), 0);
+    expect_eq!(
+        dependencies[8].dependencies[0],
+        DepOfDep {
+            normalized_name: NormalizedName::new("winapi_util").unwrap(),
+            epoch: Epoch::Minor(1),
+            platform: Some(Platform::from_str("cfg(windows)").unwrap()),
+        }
+    );
 
     expect_eq!(dependencies[9].package_name, "unicode-ident");
     expect_eq!(dependencies[9].epoch, Epoch::Major(1));
@@ -87,6 +168,45 @@
         dependencies[9].dependency_kinds.get(&DependencyKind::Normal).unwrap().features,
         empty_str_slice
     );
+
+    expect_eq!(dependencies[10].package_name, "winapi");
+    expect_eq!(dependencies[10].epoch, Epoch::Minor(3));
+    expect_eq!(
+        dependencies[10].dependency_kinds.get(&DependencyKind::Normal).unwrap().features,
+        &[
+            "consoleapi",
+            "errhandlingapi",
+            "fileapi",
+            "minwindef",
+            "processenv",
+            "std",
+            "winbase",
+            "wincon",
+            "winerror",
+            "winnt"
+        ]
+    );
+    expect_eq!(dependencies[10].dependencies.len(), 0);
+    expect_eq!(dependencies[10].build_dependencies.len(), 0);
+    expect_eq!(dependencies[10].dev_dependencies.len(), 0);
+
+    expect_eq!(dependencies[11].package_name, "winapi-util");
+    expect_eq!(dependencies[11].epoch, Epoch::Minor(1));
+    expect_eq!(
+        dependencies[11].dependency_kinds.get(&DependencyKind::Normal).unwrap().features,
+        empty_str_slice
+    );
+    expect_eq!(dependencies[11].dependencies.len(), 1);
+    expect_eq!(dependencies[11].build_dependencies.len(), 0);
+    expect_eq!(dependencies[11].dev_dependencies.len(), 0);
+    expect_eq!(
+        dependencies[11].dependencies[0],
+        DepOfDep {
+            normalized_name: NormalizedName::new("winapi").unwrap(),
+            epoch: Epoch::Minor(3),
+            platform: Some(Platform::from_str("cfg(windows)").unwrap()),
+        }
+    );
 }
 
 // test_metadata.json contains the output of "cargo metadata" run in
diff --git a/tools/crates/gnrt/main.rs b/tools/crates/gnrt/main.rs
index 0562c5e8..a50a38a 100644
--- a/tools/crates/gnrt/main.rs
+++ b/tools/crates/gnrt/main.rs
@@ -78,7 +78,7 @@
     // Compare cargo's dependency resolution with the crates we have on disk.
     let present_crates: HashSet<&ThirdPartyCrate> = crates.iter().collect();
     let mut unused_crates: HashSet<&ThirdPartyCrate> = present_crates.clone();
-    let mut missing_deps: Vec<&deps::Dependency> = Vec::new();
+    let mut missing_deps: Vec<&deps::ThirdPartyDep> = Vec::new();
     for dep in dependencies.iter() {
         let dep_crate = ThirdPartyCrate { name: dep.package_name.clone(), epoch: dep.epoch };
 
diff --git a/tools/crates/gnrt/manifest.rs b/tools/crates/gnrt/manifest.rs
index 17798120..bc39f10 100644
--- a/tools/crates/gnrt/manifest.rs
+++ b/tools/crates/gnrt/manifest.rs
@@ -17,11 +17,16 @@
 /// and the local path.
 pub type CargoPatchSet = BTreeMap<String, CargoPatch>;
 
-/// A crate version in a dependency specification.
+/// A specific crate version.
+pub use semver::Version;
+
+/// A version constraint in a dependency spec. We don't use `semver::VersionReq`
+/// since we only pass it through opaquely from third_party.toml to Cargo.toml.
+/// Parsing it is unnecessary.
 #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
 // From serde's perspective we serialize and deserialize this as a plain string.
 #[serde(transparent)]
-pub struct Version(pub String);
+pub struct VersionConstraint(pub String);
 
 /// Parsed third_party.toml. This is a limited variant of Cargo.toml.
 #[derive(Clone, Debug, Deserialize)]
@@ -72,7 +77,7 @@
 pub enum Dependency {
     /// A dependency of the form `foo = "1.0.11"`: just the package name as key
     /// and the version as value. The sole field is the crate version.
-    Short(Version),
+    Short(VersionConstraint),
     /// A dependency that specifies other fields in the form of `foo = { ... }`
     /// or `[dependencies.foo] ... `.
     Full(FullDependency),
@@ -84,7 +89,7 @@
 pub struct FullDependency {
     /// Version constraint on dependency.
     #[serde(default, skip_serializing_if = "Option::is_none")]
-    pub version: Option<Version>,
+    pub version: Option<VersionConstraint>,
     /// Required features.
     #[serde(default, skip_serializing_if = "Vec::is_empty")]
     pub features: Vec<String>,
@@ -184,7 +189,7 @@
 
     let package = CargoPackage {
         name: "chromium".to_string(),
-        version: Version("0.1.0".to_string()),
+        version: Version::new(0, 1, 0),
         edition: Default::default(),
     };
 
diff --git a/tools/crates/gnrt/manifest_unittest.rs b/tools/crates/gnrt/manifest_unittest.rs
index 2ff35949..45b2fa2c 100644
--- a/tools/crates/gnrt/manifest_unittest.rs
+++ b/tools/crates/gnrt/manifest_unittest.rs
@@ -16,7 +16,7 @@
             "build-script-outputs = [\"stuff.rs\"]\n",
         )),
         Ok(FullDependency {
-            version: Some(Version("1.0.0".to_string())),
+            version: Some(VersionConstraint("1.0.0".to_string())),
             features: vec!["foo".to_string(), "bar".to_string()],
             allow_first_party_usage: false,
             build_script_outputs: vec!["stuff.rs".to_string()],
@@ -29,7 +29,7 @@
             "build-script-outputs = [\"generated.rs\"]\n",
         )),
         Ok(FullDependency {
-            version: Some(Version("3.14.159".to_string())),
+            version: Some(VersionConstraint("3.14.159".to_string())),
             features: vec![],
             allow_first_party_usage: true,
             build_script_outputs: vec!["generated.rs".to_string()],
@@ -57,17 +57,17 @@
 
     expect_eq!(
         manifest.dependency_spec.dependencies.get("cxx"),
-        Some(&Dependency::Short(Version("1".to_string())))
+        Some(&Dependency::Short(VersionConstraint("1".to_string())))
     );
     expect_eq!(
         manifest.dependency_spec.dependencies.get("serde"),
-        Some(&Dependency::Short(Version("1".to_string())))
+        Some(&Dependency::Short(VersionConstraint("1".to_string())))
     );
 
     expect_eq!(
         manifest.dependency_spec.dependencies.get("rustversion"),
         Some(&Dependency::Full(FullDependency {
-            version: Some(Version("1".to_string())),
+            version: Some(VersionConstraint("1".to_string())),
             features: vec![],
             allow_first_party_usage: true,
             build_script_outputs: vec!["version.rs".to_string()],
@@ -77,7 +77,7 @@
     expect_eq!(
         manifest.dependency_spec.dependencies.get("unicode-linebreak"),
         Some(&Dependency::Full(FullDependency {
-            version: Some(Version("0.1".to_string())),
+            version: Some(VersionConstraint("0.1".to_string())),
             features: vec![],
             allow_first_party_usage: false,
             build_script_outputs: vec!["table.rs".to_string()],
@@ -87,7 +87,7 @@
     expect_eq!(
         manifest.dependency_spec.dev_dependencies.get("syn"),
         Some(&Dependency::Full(FullDependency {
-            version: Some(Version("1".to_string())),
+            version: Some(VersionConstraint("1".to_string())),
             features: vec!["full".to_string()],
             allow_first_party_usage: true,
             build_script_outputs: vec![],
@@ -100,7 +100,7 @@
     let manifest = CargoManifest {
         package: CargoPackage {
             name: "chromium".to_string(),
-            version: Version("0.1.0".to_string()),
+            version: Version::new(0, 1, 0),
             edition: Default::default(),
         },
         workspace: None,
diff --git a/tools/metrics/actions/actions.xml b/tools/metrics/actions/actions.xml
index 87e8316..41aacbf7 100644
--- a/tools/metrics/actions/actions.xml
+++ b/tools/metrics/actions/actions.xml
@@ -8139,12 +8139,18 @@
   <owner>rdevlin.cronin@chromium.org</owner>
   <owner>extensions-core@chromium.org</owner>
   <description>
-    The user activated an extension from the Extensions Menu in the toolbar.
-    This is recorded for both keyboard and mouse activation.
+    The user triggered an extension from the Extensions Menu in the toolbar. The
+    action may or may not run, depending on other checks. This is recorded for
+    both keyboard and mouse activation.
   </description>
 </action>
 
 <action name="Extensions.Toolbar.ExtensionActivatedFromRequestAccessButton">
+  <obsolete>
+    This was never fully implemented. Metric changed name to
+    &quot;ExtensionsActivatedFromRequestAccessButton&quot; to better fit
+    description
+  </obsolete>
   <owner>Please list the metric's owners. Add more owner tags as needed.</owner>
   <description>Please enter the description of the metric.</description>
 </action>
@@ -8153,8 +8159,19 @@
   <owner>rdevlin.cronin@chromium.org</owner>
   <owner>extensions-core@chromium.org</owner>
   <description>
-    The user activated an extension from the toolbar. This is recorded for both
-    keyboard and mouse activation.
+    The user triggered an extension from the toolbar. The action may or may not
+    run, depending on other checks. This is recorded for both keyboard and mouse
+    activation.
+  </description>
+</action>
+
+<action name="Extensions.Toolbar.ExtensionsActivatedFromRequestAccessButton">
+  <owner>rdevlin.cronin@chromium.org</owner>
+  <owner>emiliapaz@chromium.org</owner>
+  <description>
+    The user triggered multiple extensions from the request access button. Their
+    actions may or may not run, depending on other checks. This is recorded for
+    both keyboard and mouse activation.
   </description>
 </action>
 
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index af9ec9c..91ca7e5e 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -55623,6 +55623,8 @@
       label="OmniboxSpeculativeServiceWorkerStartOnQueryInput:enabled"/>
   <int value="-1779753607" label="VizDisplayCompositor:disabled"/>
   <int value="-1778993296" label="ContextualSearchMlTapSuppression:disabled"/>
+  <int value="-1778760568"
+      label="OmniboxFocusTriggersContextualWebZeroSuggest:enabled"/>
   <int value="-1778202807" label="DelayAsyncScriptExecution:enabled"/>
   <int value="-1777540083" label="LocationBarModelOptimizations:disabled"/>
   <int value="-1776351704" label="DesktopPWAsOmniboxInstall:disabled"/>
@@ -57071,6 +57073,7 @@
   <int value="-870994173" label="NtpChromeCartModule:enabled"/>
   <int value="-870120067" label="EnableSearchBoxSelection:enabled"/>
   <int value="-868138290" label="CrostiniPortForwarding:disabled"/>
+  <int value="-868041476" label="OmniboxFocusTriggersSRPZeroSuggest:enabled"/>
   <int value="-867087281" label="enable-virtual-keyboard"/>
   <int value="-867085393" label="WebNotesDynamicTemplates:enabled"/>
   <int value="-866993841" label="OfflinePagesCTV2:disabled"/>
@@ -57605,8 +57608,6 @@
   <int value="-526236814" label="WebAssemblyTiering:enabled"/>
   <int value="-523155003" label="PhoneHubCameraRoll:disabled"/>
   <int value="-523030434" label="EnableBackgroundBlur:enabled"/>
-  <int value="-522075903"
-      label="OmniboxOnFocusSuggestionsContextualWeb:enabled"/>
   <int value="-521251533" label="PrivacyGuideAndroid:enabled"/>
   <int value="-520221221" label="UserDataSnapshot:enabled"/>
   <int value="-520004021" label="WebXRHitTest:disabled"/>
@@ -58258,8 +58259,6 @@
   <int value="-77789682" label="SharingPreferVapid:disabled"/>
   <int value="-77713912" label="EnableKeyboardBacklightToggle:enabled"/>
   <int value="-77328559" label="IncognitoReauthenticationForAndroid:enabled"/>
-  <int value="-77230883"
-      label="OmniboxOnFocusSuggestionsContextualWebAllowSRP:enabled"/>
   <int value="-77084779" label="ExperimentalFlingAnimation:enabled"/>
   <int value="-76631048" label="disable-offline-auto-reload-visible-only"/>
   <int value="-76445689" label="WasmCodeCache:enabled"/>
@@ -59966,8 +59965,6 @@
   <int value="1026169964" label="MicMuteNotifications:enabled"/>
   <int value="1026981579" label="TLS13HardeningForLocalAnchors:enabled"/>
   <int value="1027252926" label="SyncSupportSecondaryAccount:enabled"/>
-  <int value="1028817487"
-      label="OmniboxOnFocusSuggestionsContextualWeb:disabled"/>
   <int value="1030608602" label="AutofillAssistantProactiveHelp:enabled"/>
   <int value="1031239808" label="ForceMajorVersion100InUserAgent:disabled"/>
   <int value="1031281564"
@@ -59975,6 +59972,8 @@
   <int value="1033148287" label="NTPShortcuts:disabled"/>
   <int value="1033412163" label="OmniboxDisplayTitleForCurrentUrl:enabled"/>
   <int value="1033597574" label="disable-layer-squashing"/>
+  <int value="1034347979"
+      label="OmniboxFocusTriggersContextualWebZeroSuggest:disabled"/>
   <int value="1036068554" label="enable-android-pay-integration-v2"/>
   <int value="1036501658" label="UseClientConfigIPH:disabled"/>
   <int value="1036565901" label="AssistPersonalInfo:enabled"/>
@@ -60993,8 +60992,6 @@
       label="ThrottleDisplayNoneAndVisibilityHiddenCrossOriginIframes:enabled"/>
   <int value="1706682238" label="ContextualSearchTranslations:disabled"/>
   <int value="1707283026" label="SyncPseudoUSSExtensions:disabled"/>
-  <int value="1707873180"
-      label="OmniboxOnFocusSuggestionsContextualWebAllowSRP:disabled"/>
   <int value="1708118086" label="TextFragmentAnchor:disabled"/>
   <int value="1709255374" label="DragAndDropAndroid:enabled"/>
   <int value="1710630380"
@@ -61301,6 +61298,7 @@
   <int value="1910240042" label="enable-experimental-fullscreen-exit-ui"/>
   <int value="1910562940" label="SharedHighlightingRefinedBlocklist:disabled"/>
   <int value="1911002680" label="TFLiteLanguageDetectionEnabled:enabled"/>
+  <int value="1911623289" label="OmniboxFocusTriggersSRPZeroSuggest:disabled"/>
   <int value="1913263516" label="OculusVR:enabled"/>
   <int value="1913298816" label="OverlayScrollbar:enabled"/>
   <int value="1913926782" label="ChromeModernAlternateCardLayout:disabled"/>
@@ -72401,6 +72399,7 @@
   <int value="1114" label="Privacy: Snooping Protection"/>
   <int value="1115" label="Privacy: Lock On Leave"/>
   <int value="1116" label="Privacy: Camera Software Switch"/>
+  <int value="1117" label="Privacy: Microphone Software Switch"/>
   <int value="1200" label="Add Language"/>
   <int value="1201" label="Show Input Options In Shelf"/>
   <int value="1202" label="Show Personal Information Suggestions"/>
@@ -73859,6 +73858,7 @@
   <int value="4" label="Unlock account-store to generate password"/>
   <int value="5" label="Re-Signin to unlock account-stored passwords"/>
   <int value="6" label="WebAuthn credential"/>
+  <int value="7" label="Sign in with another WebAuthn device"/>
 </enum>
 
 <enum name="PasswordDropdownState">
@@ -82617,6 +82617,7 @@
   <int value="120" label="IDC_UNFOLLOW"/>
   <int value="121" label="IDC_CONTENT_CONTEXT_AUTOFILL_CUSTOM_FIRST"/>
   <int value="122" label="IDC_CONTENT_CONTEXT_RUN_PDF_OCR"/>
+  <int value="123" label="IDC_CONTENT_CONTEXT_PARTIAL_TRANSLATE"/>
 </enum>
 
 <enum name="ReopenTabPromoStepAtDismissal">
@@ -86828,6 +86829,8 @@
   <int value="12" label="Text tool: color changed"/>
   <int value="13" label="Draw tool: size changed"/>
   <int value="14" label="Draw tool: color changed"/>
+  <int value="15" label="Shape tool selected"/>
+  <int value="16" label="Shape tool: color changed"/>
 </enum>
 
 <enum name="SharingScreenshotFallbackAction">
diff --git a/tools/metrics/histograms/metadata/histogram_suffixes_list.xml b/tools/metrics/histograms/metadata/histogram_suffixes_list.xml
index 83804078..804d098e 100644
--- a/tools/metrics/histograms/metadata/histogram_suffixes_list.xml
+++ b/tools/metrics/histograms/metadata/histogram_suffixes_list.xml
@@ -6133,14 +6133,25 @@
 <histogram_suffixes name="OmniboxProviderTime" separator=".">
   <suffix name="Bookmark" label=""/>
   <suffix name="Builtin" label=""/>
+  <suffix name="Clipboard" label=""/>
   <suffix name="Contact" label=""/>
+  <suffix name="Document" label=""/>
   <suffix name="ExtensionApp" label=""/>
+  <suffix name="HistoryCluster" label=""/>
   <suffix name="HistoryContents" label=""/>
+  <suffix name="HistoryFuzzy" label=""/>
   <suffix name="HistoryQuick" label=""/>
   <suffix name="HistoryURL" label=""/>
   <suffix name="Keyword" label=""/>
+  <suffix name="LocalHistoryZeroSuggest" label=""/>
+  <suffix name="MostVisitedSites" label=""/>
+  <suffix name="OnDeviceHead" label=""/>
+  <suffix name="OpenTab" label=""/>
+  <suffix name="QueryTile" label=""/>
   <suffix name="Search" label=""/>
   <suffix name="Shortcuts" label=""/>
+  <suffix name="VerbatimMatch" label=""/>
+  <suffix name="VoiceSuggest" label=""/>
   <suffix name="ZeroSuggest" label=""/>
   <affected-histogram name="Omnibox.ProviderTime"/>
   <affected-histogram name="Omnibox.ProviderTime2"/>
diff --git a/tools/metrics/histograms/metadata/optimization/histograms.xml b/tools/metrics/histograms/metadata/optimization/histograms.xml
index 53a96791..8b6f449 100644
--- a/tools/metrics/histograms/metadata/optimization/histograms.xml
+++ b/tools/metrics/histograms/metadata/optimization/histograms.xml
@@ -125,7 +125,7 @@
 </variants>
 
 <histogram name="OptimizationGuide.ApplyDecision.{OptimizationType}"
-    enum="OptimizationGuideOptimizationTypeDecision" expires_after="M106">
+    enum="OptimizationGuideOptimizationTypeDecision" expires_after="M109">
   <owner>sophiechang@chromium.org</owner>
   <owner>mcrouse@chromium.org</owner>
   <summary>
@@ -136,7 +136,7 @@
 </histogram>
 
 <histogram name="OptimizationGuide.ApplyDecisionAsync.{OptimizationType}"
-    enum="OptimizationGuideOptimizationTypeDecision" expires_after="M106">
+    enum="OptimizationGuideOptimizationTypeDecision" expires_after="M109">
   <owner>sophiechang@chromium.org</owner>
   <owner>mcrouse@chromium.org</owner>
   <summary>
@@ -184,7 +184,7 @@
 </histogram>
 
 <histogram name="OptimizationGuide.HintCache.HintType.Loaded"
-    enum="HintCacheStoreEntryType" expires_after="M106">
+    enum="HintCacheStoreEntryType" expires_after="M109">
   <owner>mcrouse@chromium.org</owner>
   <owner>sophiechang@chromium.org</owner>
   <summary>
@@ -216,7 +216,7 @@
 
 <histogram
     name="OptimizationGuide.HintsFetcher.GetHintsRequest.ActiveRequestCanceled.{RequestContext}"
-    units="counts" expires_after="M106">
+    units="counts" expires_after="M109">
   <owner>sophiechang@chromium.org</owner>
   <owner>mcrouse@chromium.org</owner>
   <summary>
@@ -228,7 +228,7 @@
 
 <histogram
     name="OptimizationGuide.HintsFetcher.GetHintsRequest.DroppedHosts.{RequestContext}"
-    units="counts" expires_after="M106">
+    units="counts" expires_after="M109">
   <owner>sophiechang@chromium.org</owner>
   <owner>mcrouse@chromium.org</owner>
   <summary>
@@ -241,7 +241,7 @@
 
 <histogram
     name="OptimizationGuide.HintsFetcher.GetHintsRequest.DroppedUrls.{RequestContext}"
-    units="counts" expires_after="M106">
+    units="counts" expires_after="M109">
   <owner>sophiechang@chromium.org</owner>
   <owner>mcrouse@chromium.org</owner>
   <summary>
@@ -254,7 +254,7 @@
 
 <histogram
     name="OptimizationGuide.HintsFetcher.GetHintsRequest.FetchLatency.{RequestContext}"
-    units="ms" expires_after="M106">
+    units="ms" expires_after="M109">
   <owner>sophiechang@chromium.org</owner>
   <owner>mcrouse@chromium.org</owner>
   <summary>
@@ -316,7 +316,7 @@
 </histogram>
 
 <histogram name="OptimizationGuide.HintsFetcher.RequestStatus.{RequestContext}"
-    enum="OptimizationGuideHintsFetcherRequestStatus" expires_after="M106">
+    enum="OptimizationGuideHintsFetcherRequestStatus" expires_after="M109">
   <owner>mcrouse@chromium.org</owner>
   <owner>sophiechang@chromium.org</owner>
   <summary>
@@ -340,7 +340,7 @@
 </histogram>
 
 <histogram name="OptimizationGuide.HintsManager.ConcurrentBatchUpdateFetches"
-    units="counts" expires_after="M106">
+    units="counts" expires_after="M109">
   <owner>sophiechang@chromium.org</owner>
   <owner>mcrouse@chromium.org</owner>
   <summary>
@@ -352,7 +352,7 @@
 
 <histogram
     name="OptimizationGuide.HintsManager.ConcurrentPageNavigationFetches"
-    units="counts" expires_after="M106">
+    units="counts" expires_after="M109">
   <owner>sophiechang@chromium.org</owner>
   <owner>mcrouse@chromium.org</owner>
   <summary>
@@ -364,7 +364,7 @@
 
 <histogram
     name="OptimizationGuide.HintsManager.PageNavigationHintsReturnedBeforeDataFlushed"
-    enum="BooleanStored" expires_after="M106">
+    enum="BooleanStored" expires_after="M109">
   <owner>sophiechang@chromium.org</owner>
   <owner>mcrouse@chromium.org</owner>
   <summary>
@@ -388,7 +388,7 @@
 </histogram>
 
 <histogram name="OptimizationGuide.IsPredictionModelValid.{OptimizationTarget}"
-    enum="BooleanValid" expires_after="M106">
+    enum="BooleanValid" expires_after="M109">
   <owner>mcrouse@chromium.org</owner>
   <owner>sophiechang@chromium.org</owner>
   <summary>
@@ -400,7 +400,7 @@
 </histogram>
 
 <histogram name="OptimizationGuide.MetadataFetchValidation.Result"
-    enum="Boolean" expires_after="M106">
+    enum="Boolean" expires_after="M109">
   <owner>robertogden@chromium.org</owner>
   <owner>chrome-intelligence-core@google.com</owner>
   <summary>
@@ -411,7 +411,7 @@
 
 <histogram
     name="OptimizationGuide.ModelExecutor.DidTimeout.{OptimizationTarget}"
-    enum="Boolean" expires_after="M106">
+    enum="Boolean" expires_after="M109">
   <owner>robertogden@chromium.org</owner>
   <owner>chrome-intelligence-core@google.com</owner>
   <summary>
@@ -423,7 +423,7 @@
 
 <histogram
     name="OptimizationGuide.ModelExecutor.ExecutionLatency.{OptimizationTarget}"
-    units="ms" expires_after="M106">
+    units="ms" expires_after="M109">
   <owner>mcrouse@chromium.org</owner>
   <owner>chrome-intelligence-core@google.com</owner>
   <summary>
@@ -436,7 +436,7 @@
 
 <histogram
     name="OptimizationGuide.ModelExecutor.ExecutionStatus.{OptimizationTarget}"
-    enum="OptimizationGuideExecutionStatus" expires_after="M106">
+    enum="OptimizationGuideExecutionStatus" expires_after="M109">
   <owner>robertogden@chromium.org</owner>
   <owner>chrome-intelligence-core@google.com</owner>
   <summary>
@@ -449,7 +449,7 @@
 
 <histogram
     name="OptimizationGuide.ModelExecutor.ExecutionThreadTime.{OptimizationTarget}"
-    units="ms" expires_after="M106">
+    units="ms" expires_after="M109">
   <owner>sophiechang@chromium.org</owner>
   <owner>chrome-intelligence-core@google.com</owner>
   <summary>
@@ -464,7 +464,7 @@
 
 <histogram
     name="OptimizationGuide.ModelExecutor.ModelAvailableToLoad.{OptimizationTarget}"
-    enum="BooleanAvailable" expires_after="M106">
+    enum="BooleanAvailable" expires_after="M109">
   <owner>robertogden@chromium.org</owner>
   <owner>chrome-intelligence-core@google.com</owner>
   <summary>
@@ -481,7 +481,7 @@
 
 <histogram
     name="OptimizationGuide.ModelExecutor.ModelLoadingDuration2.{OptimizationTarget}"
-    units="ms" expires_after="M106">
+    units="ms" expires_after="M109">
   <owner>robertogden@chromium.org</owner>
   <owner>chrome-intelligence-core@google.com</owner>
   <summary>
@@ -494,7 +494,7 @@
 
 <histogram
     name="OptimizationGuide.ModelExecutor.TaskExecutionLatency.{OptimizationTarget}"
-    units="ms" expires_after="M106">
+    units="ms" expires_after="M109">
   <owner>mcrouse@chromium.org</owner>
   <owner>chrome-intelligence-core@google.com</owner>
   <summary>
@@ -507,7 +507,7 @@
 
 <histogram
     name="OptimizationGuide.ModelExecutor.TaskSchedulingLatency.{OptimizationTarget}"
-    units="ms" expires_after="M106">
+    units="ms" expires_after="M109">
   <owner>robertogden@chromium.org</owner>
   <owner>chrome-intelligence-core@google.com</owner>
   <summary>
@@ -520,7 +520,7 @@
 
 <histogram
     name="OptimizationGuide.ModelExecutor.TimeSincePreviousRun.{OptimizationTarget}"
-    units="ms" expires_after="M106">
+    units="ms" expires_after="M109">
   <owner>robertogden@chromium.org</owner>
   <owner>chrome-intelligence-core@google.com</owner>
   <summary>
@@ -531,7 +531,7 @@
 </histogram>
 
 <histogram name="OptimizationGuide.ModelFilesVerified.{OptimizationTarget}"
-    enum="BooleanValid" expires_after="M106">
+    enum="BooleanValid" expires_after="M109">
   <owner>robertogden@chromium.org</owner>
   <owner>chrome-intelligence-core@google.com</owner>
   <summary>
@@ -546,7 +546,7 @@
 
 <histogram
     name="OptimizationGuide.ModelHandler.HandlerCreated.{OptimizationTarget}"
-    enum="BooleanCreated" expires_after="M106">
+    enum="BooleanCreated" expires_after="M109">
   <owner>sophiechang@chromium.org</owner>
   <owner>chrome-intelligence-core@google.com</owner>
   <summary>
@@ -559,7 +559,7 @@
 
 <histogram
     name="OptimizationGuide.ModelHandler.HandlerCreatedToModelAvailable.{OptimizationTarget}"
-    units="ms" expires_after="M106">
+    units="ms" expires_after="M109">
   <owner>sophiechang@chromium.org</owner>
   <owner>chrome-intelligence-core@google.com</owner>
   <summary>
@@ -571,7 +571,7 @@
 </histogram>
 
 <histogram name="OptimizationGuide.OptimizationFilterStatus.{OptimizationType}"
-    enum="OptimizationGuideOptimizationFilterStatus" expires_after="M106">
+    enum="OptimizationGuideOptimizationFilterStatus" expires_after="M109">
   <owner>mcrouse@chromium.org</owner>
   <owner>sophiechang@chromium.org</owner>
   <summary>
@@ -607,7 +607,7 @@
 
 <histogram
     name="OptimizationGuide.PageContentAnnotations.BatchRequestedSize.{AnnotationType}"
-    units="counts" expires_after="M106">
+    units="counts" expires_after="M109">
   <owner>robertogden@chromium.org</owner>
   <owner>chrome-intelligence-core@google.com</owner>
   <summary>
@@ -619,7 +619,7 @@
 
 <histogram
     name="OptimizationGuide.PageContentAnnotations.BatchSuccess.{AnnotationType}"
-    enum="BooleanSuccess" expires_after="M106">
+    enum="BooleanSuccess" expires_after="M109">
   <owner>robertogden@chromium.org</owner>
   <owner>chrome-intelligence-core@google.com</owner>
   <summary>
@@ -632,7 +632,7 @@
 
 <histogram
     name="OptimizationGuide.PageContentAnnotations.JobExecutionTime.{AnnotationType}"
-    units="ms" expires_after="M106">
+    units="ms" expires_after="M109">
   <owner>robertogden@chromium.org</owner>
   <owner>chrome-intelligence-core@google.com</owner>
   <summary>
@@ -644,7 +644,7 @@
 
 <histogram
     name="OptimizationGuide.PageContentAnnotations.JobScheduleTime.{AnnotationType}"
-    units="ms" expires_after="M106">
+    units="ms" expires_after="M109">
   <owner>robertogden@chromium.org</owner>
   <owner>chrome-intelligence-core@google.com</owner>
   <summary>
@@ -657,7 +657,7 @@
 
 <histogram
     name="OptimizationGuide.PageContentAnnotations.TemplateURLServiceLoadedAtNavigationFinish"
-    enum="BooleanLoaded" expires_after="M106">
+    enum="BooleanLoaded" expires_after="M109">
   <owner>sophiechang@chromium.org</owner>
   <owner>chrome-intelligence-core@google.com</owner>
   <summary>
@@ -668,7 +668,7 @@
 
 <histogram
     name="OptimizationGuide.PageContentAnnotationsService.ContentAnnotated"
-    enum="BooleanAnnotated" expires_after="M106">
+    enum="BooleanAnnotated" expires_after="M109">
   <owner>sophiechang@chromium.org</owner>
   <owner>mcrouse@chromium.org</owner>
   <summary>
@@ -695,7 +695,7 @@
 
 <histogram
     name="OptimizationGuide.PageContentAnnotationsService.ContentAnnotationsStorageMinMagnitudeForVisitNotFound.{PageContentAnnotationsStorageType}"
-    units="ms" expires_after="M106">
+    units="ms" expires_after="M109">
   <owner>sophiechang@chromium.org</owner>
   <owner>chrome-intelligence-core@google.com</owner>
   <summary>
@@ -726,7 +726,7 @@
 <histogram
     name="OptimizationGuide.PageContentAnnotationsService.ContentAnnotationsStorageStatus.{PageContentAnnotationsStorageType}"
     enum="OptimizationGuidePageContentAnnotationsStorageStatus"
-    expires_after="M106">
+    expires_after="M109">
   <owner>sophiechang@chromium.org</owner>
   <owner>mcrouse@chromium.org</owner>
   <summary>
@@ -844,7 +844,7 @@
 
 <histogram
     name="OptimizationGuide.PageTextDump.FrameDumpLength.{PageTextDumpEvent}"
-    units="bytes" expires_after="M106">
+    units="bytes" expires_after="M109">
   <owner>robertogden@chromium.org</owner>
   <owner>chrome-intelligence-core@google.com</owner>
   <summary>
@@ -857,7 +857,7 @@
 
 <histogram
     name="OptimizationGuide.PageTextDump.OutstandingRequests.DidFinishLoad"
-    units="count" expires_after="M106">
+    units="count" expires_after="M109">
   <owner>robertogden@chromium.org</owner>
   <owner>chrome-intelligence-core@google.com</owner>
   <summary>
@@ -867,7 +867,7 @@
 
 <histogram
     name="OptimizationGuide.PageTextDump.TimeUntilFrameDisconnected.{PageTextDumpEvent}"
-    units="ms" expires_after="M106">
+    units="ms" expires_after="M109">
   <owner>robertogden@chromium.org</owner>
   <owner>chrome-intelligence-core@google.com</owner>
   <summary>
@@ -880,7 +880,7 @@
 
 <histogram
     name="OptimizationGuide.PageTextDump.TimeUntilFrameDumpCompleted.{PageTextDumpEvent}"
-    units="ms" expires_after="M106">
+    units="ms" expires_after="M109">
   <owner>robertogden@chromium.org</owner>
   <owner>chrome-intelligence-core@google.com</owner>
   <summary>
@@ -916,7 +916,7 @@
 </histogram>
 
 <histogram name="OptimizationGuide.PageTopicsOverrideList.UsedOverride"
-    enum="Boolean" expires_after="M106">
+    enum="Boolean" expires_after="M109">
   <owner>robertogden@chromium.org</owner>
   <owner>chrome-intelligence-core@google.com</owner>
   <summary>
@@ -938,7 +938,7 @@
 
 <histogram
     name="OptimizationGuide.PredictionManager.FirstModelFetchSinceServiceInit"
-    units="ms" expires_after="M106">
+    units="ms" expires_after="M109">
   <owner>rajendrant@chromium.org</owner>
   <owner>chrome-intelligence-core@google.com</owner>
   <summary>
@@ -964,7 +964,7 @@
 
 <histogram
     name="OptimizationGuide.PredictionManager.IsDownloadUrlValid.{OptimizationTarget}"
-    enum="BooleanValid" expires_after="M106">
+    enum="BooleanValid" expires_after="M109">
   <owner>sophiechang@chromium.org</owner>
   <owner>mcrouse@chromium.org</owner>
   <summary>
@@ -976,7 +976,7 @@
 
 <histogram
     name="OptimizationGuide.PredictionManager.ModelAvailableAtRegistration.{OptimizationTarget}"
-    enum="BooleanAvailable" expires_after="M106">
+    enum="BooleanAvailable" expires_after="M109">
   <owner>sophiechang@chromium.org</owner>
   <owner>mcrouse@chromium.org</owner>
   <summary>
@@ -990,7 +990,7 @@
 
 <histogram
     name="OptimizationGuide.PredictionManager.ModelDeliveryEvents.{OptimizationTarget}"
-    enum="OptimizationGuideModelDeliveryEvent" expires_after="M106">
+    enum="OptimizationGuideModelDeliveryEvent" expires_after="M109">
   <owner>rajendrant@chromium.org</owner>
   <owner>chrome-intelligence-core@google.com</owner>
   <summary>
@@ -1020,7 +1020,7 @@
 
 <histogram
     name="OptimizationGuide.PredictionManager.RegistrationTimeSinceServiceInit.{OptimizationTarget}"
-    units="ms" expires_after="M106">
+    units="ms" expires_after="M109">
   <owner>rajendrant@chromium.org</owner>
   <owner>chrome-intelligence-core@google.com</owner>
   <summary>
@@ -1034,7 +1034,7 @@
 
 <histogram
     name="OptimizationGuide.PredictionModelDownloadManager.DownloadStartLatency.{OptimizationTarget}"
-    units="ms" expires_after="M106">
+    units="ms" expires_after="M109">
   <owner>rajendrant@chromium.org</owner>
   <owner>chrome-intelligence-core@google.com</owner>
   <summary>
@@ -1071,7 +1071,7 @@
 
 <histogram
     name="OptimizationGuide.PredictionModelDownloadManager.ReplaceFileError.{OptimizationTarget}"
-    enum="PlatformFileError" expires_after="M106">
+    enum="PlatformFileError" expires_after="M109">
   <owner>mcrouse@chromium.org</owner>
   <owner>sophiechang@chromium.org</owner>
   <summary>
@@ -1085,7 +1085,7 @@
 
 <histogram
     name="OptimizationGuide.PredictionModelDownloadManager.State.{OptimizationTarget}"
-    enum="OptimizationGuidePredictionModelDownloadState" expires_after="M106">
+    enum="OptimizationGuidePredictionModelDownloadState" expires_after="M109">
   <owner>rajendrant@chromium.org</owner>
   <owner>sophiechang@chromium.org</owner>
   <summary>
@@ -1096,7 +1096,7 @@
 </histogram>
 
 <histogram name="OptimizationGuide.PredictionModelExpired.{OptimizationTarget}"
-    enum="BooleanExpired" expires_after="M106">
+    enum="BooleanExpired" expires_after="M109">
   <owner>sophiechang@chromium.org</owner>
   <owner>chrome-intelligence-core@google.com</owner>
   <summary>
@@ -1110,7 +1110,7 @@
 
 <histogram
     name="OptimizationGuide.PredictionModelExpiredVersion.{OptimizationTarget}"
-    units="version number" expires_after="M106">
+    units="version number" expires_after="M109">
   <owner>sophiechang@chromium.org</owner>
   <owner>chrome-intelligence-core@google.com</owner>
   <summary>
@@ -1138,7 +1138,7 @@
 
 <histogram
     name="OptimizationGuide.PredictionModelFetcher.GetModelsResponse.NetErrorCode.{OptimizationTarget}"
-    enum="NetErrorCodes" expires_after="M106">
+    enum="NetErrorCodes" expires_after="M109">
   <owner>mcrouse@chromium.org</owner>
   <owner>sophiechang@chromium.org</owner>
   <summary>
@@ -1168,7 +1168,7 @@
 
 <histogram
     name="OptimizationGuide.PredictionModelFetcher.GetModelsResponse.Status.{OptimizationTarget}"
-    enum="HttpResponseCode" expires_after="M106">
+    enum="HttpResponseCode" expires_after="M109">
   <owner>mcrouse@chromium.org</owner>
   <owner>sophiechang@chromium.org</owner>
   <summary>
@@ -1184,7 +1184,7 @@
 
 <histogram
     name="OptimizationGuide.PredictionModelLoadedVersion.{OptimizationTarget}"
-    units="version number" expires_after="M106">
+    units="version number" expires_after="M109">
   <owner>mcrouse@chromium.org</owner>
   <owner>sophiechang@chromium.org</owner>
   <summary>
@@ -1211,7 +1211,7 @@
 
 <histogram
     name="OptimizationGuide.PredictionModelUpdateVersion.{OptimizationTarget}"
-    units="version number" expires_after="M106">
+    units="version number" expires_after="M109">
   <owner>mcrouse@chromium.org</owner>
   <owner>sophiechang@chromium.org</owner>
   <summary>
@@ -1223,7 +1223,7 @@
 
 <histogram
     name="OptimizationGuide.PredictionModelValidationLatency.{OptimizationTarget}"
-    units="ms" expires_after="M106">
+    units="ms" expires_after="M109">
   <owner>mcrouse@chromium.org</owner>
   <owner>sophiechang@chromium.org</owner>
   <summary>
@@ -1247,7 +1247,7 @@
 </histogram>
 
 <histogram name="OptimizationGuide.ProcessingComponentAtShutdown"
-    enum="BooleanYesNo" expires_after="M106">
+    enum="BooleanYesNo" expires_after="M109">
   <owner>mcrouse@chromium.org</owner>
   <owner>sophiechang@chromium.org</owner>
   <summary>
@@ -1257,7 +1257,7 @@
 </histogram>
 
 <histogram name="OptimizationGuide.PushNotifications.CachedNotificationCount"
-    units="count" expires_after="M106">
+    units="count" expires_after="M109">
   <owner>robertogden@chromium.org</owner>
   <owner>chrome-intelligence-core@google.com</owner>
   <summary>
@@ -1269,7 +1269,7 @@
 
 <histogram
     name="OptimizationGuide.PushNotifications.CachedNotificationsHandledSuccessfully"
-    enum="BooleanSuccess" expires_after="M106">
+    enum="BooleanSuccess" expires_after="M109">
   <owner>robertogden@chromium.org</owner>
   <owner>chrome-intelligence-core@google.com</owner>
   <summary>
@@ -1279,7 +1279,7 @@
 </histogram>
 
 <histogram name="OptimizationGuide.PushNotifications.DidOverflow"
-    enum="Boolean" expires_after="M106">
+    enum="Boolean" expires_after="M109">
   <owner>robertogden@chromium.org</owner>
   <owner>chrome-intelligence-core@google.com</owner>
   <summary>
@@ -1289,7 +1289,7 @@
 </histogram>
 
 <histogram name="OptimizationGuide.PushNotifications.GotPushNotification"
-    enum="Boolean" expires_after="M106">
+    enum="Boolean" expires_after="M109">
   <owner>robertogden@chromium.org</owner>
   <owner>chrome-intelligence-core@google.com</owner>
   <summary>Records a true sample for every incoming push notification.</summary>
@@ -1297,7 +1297,7 @@
 
 <histogram
     name="OptimizationGuide.PushNotifications.PushNotificationHandledSuccessfully"
-    enum="BooleanSuccess" expires_after="M106">
+    enum="BooleanSuccess" expires_after="M109">
   <owner>robertogden@chromium.org</owner>
   <owner>chrome-intelligence-core@google.com</owner>
   <summary>
@@ -1307,7 +1307,7 @@
 </histogram>
 
 <histogram name="OptimizationGuide.PushNotifications.ReadCacheResult"
-    enum="OptimizationGuideReadCacheResult" expires_after="M106">
+    enum="OptimizationGuideReadCacheResult" expires_after="M109">
   <owner>robertogden@chromium.org</owner>
   <owner>chrome-intelligence-core@google.com</owner>
   <summary>
@@ -1318,7 +1318,7 @@
 </histogram>
 
 <histogram name="OptimizationGuide.PushNotifications.ReceivedNotificationType"
-    enum="OptimizationType" expires_after="M106">
+    enum="OptimizationType" expires_after="M109">
   <owner>robertogden@chromium.org</owner>
   <owner>chrome-intelligence-core@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/others/histograms.xml b/tools/metrics/histograms/metadata/others/histograms.xml
index c53ddd0..1324653 100644
--- a/tools/metrics/histograms/metadata/others/histograms.xml
+++ b/tools/metrics/histograms/metadata/others/histograms.xml
@@ -3067,6 +3067,17 @@
   </summary>
 </histogram>
 
+<histogram name="ContextMenu.iOS.LensWebUploadTime" units="ms"
+    expires_after="2023-06-28">
+  <owner>hujasonx@google.com</owner>
+  <owner>lens-in-bling-team@google.com</owner>
+  <summary>
+    Logs the time it takes to upload image data to the Lens Web servers.
+    Recorded only when the Lens feature is triggered via the context menu entry
+    from an image long-press.
+  </summary>
+</histogram>
+
 <histogram name="ContextMenu.LensChip.Event" enum="LensChipEvent"
     expires_after="2022-08-07">
   <owner>yusuyoutube@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/safe_browsing/histograms.xml b/tools/metrics/histograms/metadata/safe_browsing/histograms.xml
index 1b8f1b5..4c80b530 100644
--- a/tools/metrics/histograms/metadata/safe_browsing/histograms.xml
+++ b/tools/metrics/histograms/metadata/safe_browsing/histograms.xml
@@ -1962,30 +1962,6 @@
 </histogram>
 
 <histogram
-    name="SafeBrowsing.TailoredSecurity.ConsentedDesktopModalDisabledOutcome"
-    enum="SafeBrowsingTailoredSecurityOutcome" expires_after="2022-10-01">
-  <owner>jacastro@chromium.org</owner>
-  <owner>chrome-safebrowsing-alerts@google.com</owner>
-  <summary>
-    Records the result of displaying the Chrome enhanced protection
-    opt-in/disabled modal to sync users. It is logged once each time Chrome is
-    informed that the account level enhanced protection bit has been disabled.
-  </summary>
-</histogram>
-
-<histogram
-    name="SafeBrowsing.TailoredSecurity.ConsentedDesktopModalEnabledOutcome"
-    enum="SafeBrowsingTailoredSecurityOutcome" expires_after="2022-10-01">
-  <owner>jacastro@chromium.org</owner>
-  <owner>chrome-safebrowsing-alerts@google.com</owner>
-  <summary>
-    Records the result of displaying the Chrome enhanced protection
-    opt-in/enabled modal to sync users. It is logged once each time Chrome is
-    informed that the account level enhanced protection bit has been enabled.
-  </summary>
-</histogram>
-
-<histogram
     name="SafeBrowsing.TailoredSecurity.SyncPromptEnabledNotificationResult"
     enum="SafeBrowsingTailoredSecurityNotificationResult"
     expires_after="2022-12-11">
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps.json b/tools/perf/core/perfetto_binary_roller/binary_deps.json
index f1de773..f6108d61 100644
--- a/tools/perf/core/perfetto_binary_roller/binary_deps.json
+++ b/tools/perf/core/perfetto_binary_roller/binary_deps.json
@@ -5,8 +5,8 @@
             "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux_arm64/49b4b5dcbc312d8d2c3751cf29238b8efeb4e494/trace_processor_shell"
         },
         "win": {
-            "hash": "01626d926c76d26714e5e6b77f7a18dabae5ca63",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/cea074c65731ceb4cbb1985388634c16b4c74479/trace_processor_shell.exe"
+            "hash": "f60387aa15d5811d6ed7e87be7f219d6bad07cac",
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/7bcfdf24e8e973d85a646811ddd6751e8dc8f543/trace_processor_shell.exe"
         },
         "linux_arm": {
             "hash": "58893933be305d3bfe0a72ebebcacde2ac3ca893",
@@ -21,8 +21,8 @@
             "full_remote_path": "perfetto-luci-artifacts/v25.0/mac-arm64/trace_processor_shell"
         },
         "linux": {
-            "hash": "852ba165715611eb30ce6f6fb146cb669083177e",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/32aab6ae7c710c16e750c37918c197e4d40537ec/trace_processor_shell"
+            "hash": "009194c96975079693b010e48a8604f18eeb7a5a",
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/7bcfdf24e8e973d85a646811ddd6751e8dc8f543/trace_processor_shell"
         }
     },
     "power_profile.sql": {
diff --git a/tools/typescript/definitions/passwords_private.d.ts b/tools/typescript/definitions/passwords_private.d.ts
index 8835d5058..9fc015cd 100644
--- a/tools/typescript/definitions/passwords_private.d.ts
+++ b/tools/typescript/definitions/passwords_private.d.ts
@@ -82,6 +82,7 @@
         detailedOrigin: string;
         isAndroidCredential: boolean;
         changePasswordUrl?: string;
+        hasStartableScript: boolean;
         signonRealm: string;
         username: string;
         password?: string;
diff --git a/tools/vscode/cpp.json5 b/tools/vscode/cpp.json
similarity index 100%
rename from tools/vscode/cpp.json5
rename to tools/vscode/cpp.json
diff --git a/tools/vscode/keybindings.json5 b/tools/vscode/keybindings.json
similarity index 100%
rename from tools/vscode/keybindings.json5
rename to tools/vscode/keybindings.json
diff --git a/tools/vscode/launch.json5 b/tools/vscode/launch.json
similarity index 100%
rename from tools/vscode/launch.json5
rename to tools/vscode/launch.json
diff --git a/tools/vscode/settings.json5 b/tools/vscode/settings.json
similarity index 100%
rename from tools/vscode/settings.json5
rename to tools/vscode/settings.json
diff --git a/tools/vscode/tasks.json5 b/tools/vscode/tasks.json
similarity index 87%
rename from tools/vscode/tasks.json5
rename to tools/vscode/tasks.json
index 704a2837..36506984 100644
--- a/tools/vscode/tasks.json5
+++ b/tools/vscode/tasks.json
@@ -9,6 +9,7 @@
   "runner": "terminal",
   // The default problem matcher matches build output, which is useful for most tasks.
   "problemMatcher": [
+    // Matches output from clang.
     {
       "owner": "cpp",
       "fileLocation": ["relative", "${config:chrome.outputDir}"],
@@ -25,6 +26,37 @@
         "file": 1, "line": 2, "column": 3, "severity": 4, "message": 5
       }
     },
+    // Matches output from clang-cl / msvc.
+    {
+      "owner": "cpp",
+      "fileLocation": [
+        "relative",
+        "${config:chrome.outputDir}"
+      ],
+      "pattern": {
+        "regexp": "^(gen/.*)\\((\\d+),(\\d+)\\):\\s+(warning|\\w*\\s?error):\\s+(.*)$",
+        "file": 1,
+        "line": 2,
+        "column": 3,
+        "severity": 4,
+        "message": 5
+      }
+    },
+    {
+      "owner": "cpp",
+      "fileLocation": [
+        "relative",
+        "${workspaceRoot}"
+      ],
+      "pattern": {
+        "regexp": "^../../(.*)\\((\\d+),(\\d+)\\):\\s+(warning|\\w*\\s?error):\\s+(.*)$",
+        "file": 1,
+        "line": 2,
+        "column": 3,
+        "severity": 4,
+        "message": 5
+      }
+    },
     {
       "owner": "cpp",
       "fileLocation": ["relative", "${config:chrome.outputDir}"],
diff --git a/ui/accessibility/BUILD.gn b/ui/accessibility/BUILD.gn
index 6402c7f..d5efc86 100644
--- a/ui/accessibility/BUILD.gn
+++ b/ui/accessibility/BUILD.gn
@@ -304,8 +304,6 @@
     "//ui/gfx:test_support",
   ]
 
-  data_deps = [ "//testing/buildbot/filters:accessibility_unittests_filters" ]
-
   if (is_fuchsia) {
     sources += [
       "platform/fuchsia/accessibility_bridge_fuchsia_unittest.cc",
diff --git a/ui/accessibility/ax_language_detection.h b/ui/accessibility/ax_language_detection.h
index eded0ff3..61ec4a9c 100644
--- a/ui/accessibility/ax_language_detection.h
+++ b/ui/accessibility/ax_language_detection.h
@@ -7,11 +7,11 @@
 
 #include <memory>
 #include <string>
-#include <unordered_map>
-#include <unordered_set>
 #include <utility>
 #include <vector>
 
+#include "base/containers/flat_map.h"
+#include "base/containers/flat_set.h"
 #include "base/memory/raw_ptr.h"
 #include "third_party/cld_3/src/src/nnet_language_identifier.h"
 #include "ui/accessibility/ax_enums.mojom-forward.h"
@@ -150,7 +150,7 @@
   friend class AXLanguageDetectionTestFixture;
 
   // Store a count of the occurrences of a given language.
-  std::unordered_map<std::string, int> lang_counts_;
+  base::flat_map<std::string, int> lang_counts_;
 
   // Cache of last calculated top language results.
   // A vector of pairs of (score, language) sorted by descending score.
@@ -201,7 +201,7 @@
 
   // Set of top language detected for every node, used to generate the unique
   // number of detected languages metric (LangsPerPage).
-  std::unordered_set<std::string> unique_top_lang_detected_;
+  base::flat_set<std::string> unique_top_lang_detected_;
 };
 
 // AXLanguageDetectionObserver is registered as a change observer on an AXTree
diff --git a/ui/accessibility/ax_language_detection_unittest.cc b/ui/accessibility/ax_language_detection_unittest.cc
index 6b4bc53..f7091ae 100644
--- a/ui/accessibility/ax_language_detection_unittest.cc
+++ b/ui/accessibility/ax_language_detection_unittest.cc
@@ -10,6 +10,7 @@
 #include <memory>
 
 #include "base/command_line.h"
+#include "base/containers/flat_set.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -103,7 +104,7 @@
     return tree.language_detection_manager->lang_info_stats_.count_overridden_;
   }
 
-  const std::unordered_set<std::string>& unique_top_lang_detected(
+  const base::flat_set<std::string>& unique_top_lang_detected(
       AXTree& tree) const {
     return tree.language_detection_manager->lang_info_stats_
         .unique_top_lang_detected_;
@@ -672,8 +673,8 @@
   // There should be 4 unique languages (de, en, fr, es).
   {
     const auto& top_lang = unique_top_lang_detected(tree);
-    const std::unordered_set<std::string> expected_top_lang = {"de", "en", "es",
-                                                               "fr"};
+    const base::flat_set<std::string> expected_top_lang = {"de", "en", "es",
+                                                           "fr"};
     EXPECT_EQ(top_lang, expected_top_lang);
   }
   histograms.ExpectUniqueSample("Accessibility.LanguageDetection.LangsPerPage",
@@ -1182,7 +1183,7 @@
   // There should be 2 unique languages (fr, es).
   {
     auto top_lang = unique_top_lang_detected(tree);
-    const std::unordered_set<std::string> expected_top_lang = {"es", "fr"};
+    const base::flat_set<std::string> expected_top_lang = {"es", "fr"};
     EXPECT_EQ(top_lang, expected_top_lang);
   }
   // There should be a single (unique, 1) value for '2' unique languages.
diff --git a/ui/accessibility/ax_tree.h b/ui/accessibility/ax_tree.h
index 3c3d0e9..d65e9a03 100644
--- a/ui/accessibility/ax_tree.h
+++ b/ui/accessibility/ax_tree.h
@@ -11,9 +11,9 @@
 #include <memory>
 #include <set>
 #include <string>
-#include <unordered_map>
 #include <vector>
 
+#include "base/containers/flat_map.h"
 #include "base/memory/raw_ptr.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/observer_list.h"
@@ -388,9 +388,9 @@
 
   base::ObserverList<AXTreeObserver> observers_;
   raw_ptr<AXNode, DanglingUntriaged> root_ = nullptr;
-  std::unordered_map<AXNodeID, std::unique_ptr<AXNode>> id_map_;
   std::string error_;
   AXTreeData data_;
+  base::flat_map<AXNodeID, std::unique_ptr<AXNode>> id_map_;
 
   // Map from an int attribute (if IsNodeIdIntAttribute is true) to
   // a reverse mapping from target nodes to source nodes.
@@ -403,7 +403,7 @@
 
   // Map from node ID to cached table info, if the given node is a table.
   // Invalidated every time the tree is updated.
-  mutable std::unordered_map<AXNodeID, std::unique_ptr<AXTableInfo>>
+  mutable base::flat_map<AXNodeID, std::unique_ptr<AXTableInfo>>
       table_info_map_;
 
   // The next negative node ID to use for internal nodes.
@@ -462,7 +462,7 @@
   // objects.
   // All other objects will map to default-constructed OrderedSetInfo objects.
   // Invalidated every time the tree is updated.
-  mutable std::unordered_map<AXNodeID, NodeSetSizePosInSetInfo>
+  mutable base::flat_map<AXNodeID, NodeSetSizePosInSetInfo>
       node_set_size_pos_in_set_info_map_;
 
   // Indicates if the tree is updating.
diff --git a/ui/accessibility/ax_tree_manager_base.cc b/ui/accessibility/ax_tree_manager_base.cc
index 5b452d8..461ad3aa 100644
--- a/ui/accessibility/ax_tree_manager_base.cc
+++ b/ui/accessibility/ax_tree_manager_base.cc
@@ -25,10 +25,9 @@
 }
 
 // static
-std::unordered_map<AXTreeID, AXTreeManagerBase*, AXTreeIDHash>&
+base::flat_map<AXTreeID, AXTreeManagerBase*>&
 AXTreeManagerBase::GetTreeManagerMapInstance() {
-  static base::NoDestructor<
-      std::unordered_map<AXTreeID, AXTreeManagerBase*, AXTreeIDHash>>
+  static base::NoDestructor<base::flat_map<AXTreeID, AXTreeManagerBase*>>
       map_instance;
   return *map_instance;
 }
diff --git a/ui/accessibility/ax_tree_manager_base.h b/ui/accessibility/ax_tree_manager_base.h
index b42429d..ac733dba 100644
--- a/ui/accessibility/ax_tree_manager_base.h
+++ b/ui/accessibility/ax_tree_manager_base.h
@@ -6,8 +6,8 @@
 #define UI_ACCESSIBILITY_AX_TREE_MANAGER_BASE_H_
 
 #include <memory>
-#include <unordered_map>
 
+#include "base/containers/flat_map.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "ui/accessibility/ax_export.h"
 #include "ui/accessibility/ax_node_data.h"
@@ -122,7 +122,7 @@
   AXTreeManagerBase* DetachChildTree(AXNode& host_node);
 
  private:
-  static std::unordered_map<AXTreeID, AXTreeManagerBase*, AXTreeIDHash>&
+  static base::flat_map<AXTreeID, AXTreeManagerBase*>&
   GetTreeManagerMapInstance();
 
   std::unique_ptr<AXTree> tree_;
diff --git a/ui/accessibility/ax_tree_manager_map.h b/ui/accessibility/ax_tree_manager_map.h
index aff21f0..f09c6389 100644
--- a/ui/accessibility/ax_tree_manager_map.h
+++ b/ui/accessibility/ax_tree_manager_map.h
@@ -5,15 +5,14 @@
 #ifndef UI_ACCESSIBILITY_AX_TREE_MANAGER_MAP_H_
 #define UI_ACCESSIBILITY_AX_TREE_MANAGER_MAP_H_
 
-#include <unordered_map>
-
+#include "base/containers/flat_map.h"
 #include "ui/accessibility/ax_tree_id.h"
 #include "ui/accessibility/ax_tree_manager.h"
 
 namespace ui {
 
 // This class manages AXTreeManager instances. It is a singleton wrapper
-// around a std::unordered_map. AXTreeID's are used as the key for the map.
+// around a base::flat_map. AXTreeID's are used as the key for the map.
 // Since AXTreeID's might refer to AXTreeIDUnknown, callers should not expect
 // AXTreeIDUnknown to map to a particular AXTreeManager.
 class AX_EXPORT AXTreeManagerMap {
@@ -33,7 +32,7 @@
   AXTreeManager* GetManagerForChildTree(const AXNode& parent_node);
 
  private:
-  std::unordered_map<AXTreeID, AXTreeManager*, AXTreeIDHash> map_;
+  base::flat_map<AXTreeID, AXTreeManager*> map_;
 };
 
 }  // namespace ui
diff --git a/ui/accessibility/ax_tree_serializer.h b/ui/accessibility/ax_tree_serializer.h
index e7c2dd8..9d927ec6 100644
--- a/ui/accessibility/ax_tree_serializer.h
+++ b/ui/accessibility/ax_tree_serializer.h
@@ -13,7 +13,6 @@
 #include <memory>
 #include <ostream>
 #include <set>
-#include <unordered_set>
 #include <vector>
 
 #include "base/debug/crash_logging.h"
diff --git a/ui/accessibility/platform/ax_fragment_root_win.cc b/ui/accessibility/platform/ax_fragment_root_win.cc
index 7cae2b1..34f8ee4 100644
--- a/ui/accessibility/platform/ax_fragment_root_win.cc
+++ b/ui/accessibility/platform/ax_fragment_root_win.cc
@@ -4,8 +4,7 @@
 
 #include "ui/accessibility/platform/ax_fragment_root_win.h"
 
-#include <unordered_map>
-
+#include "base/containers/flat_map.h"
 #include "base/no_destructor.h"
 #include "base/strings/string_number_conversions.h"
 #include "ui/accessibility/platform/ax_fragment_root_delegate_win.h"
@@ -252,7 +251,7 @@
   }
 
  private:
-  std::unordered_map<gfx::AcceleratedWidget, AXFragmentRootWin*> map_;
+  base::flat_map<gfx::AcceleratedWidget, AXFragmentRootWin*> map_;
 };
 
 AXFragmentRootWin::AXFragmentRootWin(gfx::AcceleratedWidget widget,
diff --git a/ui/accessibility/platform/ax_platform_node_base.cc b/ui/accessibility/platform/ax_platform_node_base.cc
index 9faae5d..cc83364 100644
--- a/ui/accessibility/platform/ax_platform_node_base.cc
+++ b/ui/accessibility/platform/ax_platform_node_base.cc
@@ -10,8 +10,8 @@
 #include <set>
 #include <sstream>
 #include <string>
-#include <unordered_map>
 
+#include "base/containers/flat_map.h"
 #include "base/no_destructor.h"
 #include "base/numerics/checked_math.h"
 #include "base/strings/string_number_conversions.h"
@@ -66,7 +66,7 @@
 const char16_t AXPlatformNodeBase::kEmbeddedCharacter = u'\xfffc';
 
 // Map from each AXPlatformNode's unique id to its instance.
-using UniqueIdMap = std::unordered_map<int32_t, AXPlatformNode*>;
+using UniqueIdMap = base::flat_map<int32_t, AXPlatformNode*>;
 base::LazyInstance<UniqueIdMap>::Leaky g_unique_id_map =
     LAZY_INSTANCE_INITIALIZER;
 
diff --git a/ui/accessibility/platform/ax_platform_node_win.cc b/ui/accessibility/platform/ax_platform_node_win.cc
index 0cd1985..d904890b 100644
--- a/ui/accessibility/platform/ax_platform_node_win.cc
+++ b/ui/accessibility/platform/ax_platform_node_win.cc
@@ -11,10 +11,10 @@
 #include <map>
 #include <set>
 #include <string>
-#include <unordered_set>
 #include <utility>
 #include <vector>
 
+#include "base/containers/flat_set.h"
 #include "base/json/json_writer.h"
 #include "base/lazy_instance.h"
 #include "base/metrics/histogram_functions.h"
@@ -208,7 +208,7 @@
 
 namespace {
 
-typedef std::unordered_set<AXPlatformNodeWin*> AXPlatformNodeWinSet;
+typedef base::flat_set<AXPlatformNodeWin*> AXPlatformNodeWinSet;
 // Set of all AXPlatformNodeWin objects that were the target of an
 // alert event.
 base::LazyInstance<AXPlatformNodeWinSet>::Leaky g_alert_targets =
diff --git a/ui/accessibility/platform/ax_unique_id.cc b/ui/accessibility/platform/ax_unique_id.cc
index cfb433a..0835330 100644
--- a/ui/accessibility/platform/ax_unique_id.cc
+++ b/ui/accessibility/platform/ax_unique_id.cc
@@ -5,9 +5,9 @@
 #include "ui/accessibility/platform/ax_unique_id.h"
 
 #include <memory>
-#include <unordered_set>
 
 #include "base/containers/contains.h"
+#include "base/containers/flat_set.h"
 #include "base/lazy_instance.h"
 #include "base/logging.h"
 
@@ -15,7 +15,7 @@
 
 namespace {
 
-base::LazyInstance<std::unordered_set<int32_t>>::Leaky g_assigned_ids =
+base::LazyInstance<base::flat_set<int32_t>>::Leaky g_assigned_ids =
     LAZY_INSTANCE_INITIALIZER;
 
 }  // namespace
diff --git a/ui/android/junit/src/org/chromium/ui/base/MimeTypeUtilsTest.java b/ui/android/junit/src/org/chromium/ui/base/MimeTypeUtilsTest.java
index 753116a4..8596f246 100644
--- a/ui/android/junit/src/org/chromium/ui/base/MimeTypeUtilsTest.java
+++ b/ui/android/junit/src/org/chromium/ui/base/MimeTypeUtilsTest.java
@@ -30,6 +30,7 @@
  * Tests for {@link MimeTypeUtils}, verifying behavior across OS versions.
  */
 @RunWith(BaseRobolectricTestRunner.class)
+@SuppressWarnings("DoNotMock") // Mocking GURL
 public class MimeTypeUtilsTest {
     @Mock
     private GURL mMockedUrl;
diff --git a/ui/base/cocoa/nsmenuitem_additions.mm b/ui/base/cocoa/nsmenuitem_additions.mm
index 7471b453..e2a67ba 100644
--- a/ui/base/cocoa/nsmenuitem_additions.mm
+++ b/ui/base/cocoa/nsmenuitem_additions.mm
@@ -31,8 +31,10 @@
       NSEventModifierFlagCommand | NSEventModifierFlagControl |
       NSEventModifierFlagOption | NSEventModifierFlagShift;
 
-  // If `event` isn't a function key press we can simply return the mask.
-  if (([event modifierFlags] & NSEventModifierFlagFunction) == 0)
+  // If `event` isn't a function key press or it's not a character key press
+  // (e.g. it's a flags change), we can simply return the mask.
+  if (([event modifierFlags] & NSEventModifierFlagFunction) == 0 ||
+      [event type] != NSEventTypeKeyDown)
     return eventModifierMask;
 
   // "Up arrow", home, and other "function" key events include
diff --git a/ui/base/cursor/cursor_theme_manager.cc b/ui/base/cursor/cursor_theme_manager.cc
index c790164..28552fb 100644
--- a/ui/base/cursor/cursor_theme_manager.cc
+++ b/ui/base/cursor/cursor_theme_manager.cc
@@ -4,8 +4,6 @@
 
 #include "ui/base/cursor/cursor_theme_manager.h"
 
-#include "base/check.h"
-#include "base/check_op.h"
 #include "base/observer_list.h"
 
 namespace ui {
@@ -16,16 +14,18 @@
 
 }
 
-CursorThemeManager::~CursorThemeManager() {
-  DCHECK_EQ(g_instance, this);
-  g_instance = nullptr;
-}
+CursorThemeManager::~CursorThemeManager() = default;
 
 // static
 CursorThemeManager* CursorThemeManager::GetInstance() {
   return g_instance;
 }
 
+// static
+void CursorThemeManager::SetInstance(CursorThemeManager* instance) {
+  g_instance = instance;
+}
+
 void CursorThemeManager::AddObserver(CursorThemeManagerObserver* observer) {
   cursor_theme_observers_.AddObserver(observer);
   std::string name = GetCursorThemeName();
@@ -40,9 +40,6 @@
   cursor_theme_observers_.RemoveObserver(observer);
 }
 
-CursorThemeManager::CursorThemeManager() {
-  DCHECK(!g_instance);
-  g_instance = this;
-}
+CursorThemeManager::CursorThemeManager() = default;
 
 }  // namespace ui
diff --git a/ui/base/cursor/cursor_theme_manager.h b/ui/base/cursor/cursor_theme_manager.h
index 42a4dac..40f96b9 100644
--- a/ui/base/cursor/cursor_theme_manager.h
+++ b/ui/base/cursor/cursor_theme_manager.h
@@ -21,6 +21,8 @@
 
   static CursorThemeManager* GetInstance();
 
+  static void SetInstance(CursorThemeManager* instance);
+
   // Adds |observer| and makes initial OnCursorThemNameChanged() and/or
   // OnCursorThemeSizeChanged() calls if the respective settings were set.
   void AddObserver(CursorThemeManagerObserver* observer);
diff --git a/ui/gfx/animation/animation_settings_provider_linux.cc b/ui/gfx/animation/animation_settings_provider_linux.cc
index e4d4c6c..96a10c7 100644
--- a/ui/gfx/animation/animation_settings_provider_linux.cc
+++ b/ui/gfx/animation/animation_settings_provider_linux.cc
@@ -4,8 +4,6 @@
 
 #include "ui/gfx/animation/animation_settings_provider_linux.h"
 
-#include "base/check_op.h"
-
 namespace gfx {
 
 // static
@@ -17,14 +15,14 @@
   return instance_;
 }
 
-AnimationSettingsProviderLinux::AnimationSettingsProviderLinux() {
-  DCHECK(!instance_);
-  instance_ = this;
+// static
+void AnimationSettingsProviderLinux::SetInstance(
+    AnimationSettingsProviderLinux* instance) {
+  instance_ = instance;
 }
 
-AnimationSettingsProviderLinux::~AnimationSettingsProviderLinux() {
-  DCHECK_EQ(instance_, this);
-  instance_ = nullptr;
-}
+AnimationSettingsProviderLinux::AnimationSettingsProviderLinux() = default;
+
+AnimationSettingsProviderLinux::~AnimationSettingsProviderLinux() = default;
 
 }  // namespace gfx
diff --git a/ui/gfx/animation/animation_settings_provider_linux.h b/ui/gfx/animation/animation_settings_provider_linux.h
index 8664544..36a28b8 100644
--- a/ui/gfx/animation/animation_settings_provider_linux.h
+++ b/ui/gfx/animation/animation_settings_provider_linux.h
@@ -23,6 +23,8 @@
 
   static AnimationSettingsProviderLinux* GetInstance();
 
+  static void SetInstance(AnimationSettingsProviderLinux* instance);
+
  protected:
   AnimationSettingsProviderLinux();
 
diff --git a/ui/qt/BUILD.gn b/ui/qt/BUILD.gn
index ff341e2..1c67c69 100644
--- a/ui/qt/BUILD.gn
+++ b/ui/qt/BUILD.gn
@@ -90,6 +90,7 @@
     "//base",
     "//printing/buildflags",
     "//skia",
+    "//ui/base/ime/linux",
     "//ui/color",
     "//ui/color:mixers",
     "//ui/gfx",
diff --git a/ui/qt/DEPS b/ui/qt/DEPS
index 4dd7006..819fddb 100644
--- a/ui/qt/DEPS
+++ b/ui/qt/DEPS
@@ -2,6 +2,7 @@
   "+chrome/browser/themes/theme_properties.h",
   "+printing",
   "+third_party/skia",
+  "+ui/base",
   "+ui/color",
   "+ui/gfx",
   "+ui/native_theme",
diff --git a/ui/qt/qt_ui.cc b/ui/qt/qt_ui.cc
index 979bd552..9bd43c48 100644
--- a/ui/qt/qt_ui.cc
+++ b/ui/qt/qt_ui.cc
@@ -15,6 +15,7 @@
 #include "base/time/time.h"
 #include "chrome/browser/themes/theme_properties.h"  // nogncheck
 #include "third_party/skia/include/core/SkBitmap.h"
+#include "ui/base/ime/linux/linux_input_method_context.h"
 #include "ui/color/color_mixer.h"
 #include "ui/color/color_provider.h"
 #include "ui/color/color_recipe.h"
@@ -31,6 +32,7 @@
 #include "ui/qt/qt_interface.h"
 #include "ui/shell_dialogs/select_file_policy.h"
 #include "ui/views/controls/button/label_button_border.h"
+#include "ui/views/linux_ui/linux_ui.h"
 
 namespace qt {
 
@@ -113,14 +115,16 @@
   raw_ptr<QtInterface> const shim_;
 };
 
-QtUi::QtUi() = default;
+QtUi::QtUi(std::unique_ptr<views::LinuxUI> fallback_linux_ui)
+    : fallback_linux_ui_(std::move(fallback_linux_ui)) {}
 
 QtUi::~QtUi() = default;
 
 std::unique_ptr<ui::LinuxInputMethodContext> QtUi::CreateInputMethodContext(
     ui::LinuxInputMethodContextDelegate* delegate) const {
-  NOTIMPLEMENTED_LOG_ONCE();
-  return nullptr;
+  return fallback_linux_ui_
+             ? fallback_linux_ui_->CreateInputMethodContext(delegate)
+             : nullptr;
 }
 
 gfx::FontRenderParams QtUi::GetDefaultFontRenderParams() const {
@@ -147,8 +151,9 @@
 ui::SelectFileDialog* QtUi::CreateSelectFileDialog(
     ui::SelectFileDialog::Listener* listener,
     std::unique_ptr<ui::SelectFilePolicy> policy) const {
-  NOTIMPLEMENTED_LOG_ONCE();
-  return nullptr;
+  return fallback_linux_ui_ ? fallback_linux_ui_->CreateSelectFileDialog(
+                                  listener, std::move(policy))
+                            : nullptr;
 }
 
 bool QtUi::Initialize() {
@@ -254,8 +259,8 @@
 }
 
 bool QtUi::PreferDarkTheme() const {
-  NOTIMPLEMENTED_LOG_ONCE();
-  return false;
+  return color_utils::IsDark(
+      shim_->GetColor(ColorType::kWindowBg, ColorState::kNormal));
 }
 
 bool QtUi::AnimationsEnabled() const {
@@ -273,8 +278,8 @@
 }
 
 base::flat_map<std::string, std::string> QtUi::GetKeyboardLayoutMap() {
-  NOTIMPLEMENTED_LOG_ONCE();
-  return {};
+  return fallback_linux_ui_ ? fallback_linux_ui_->GetKeyboardLayoutMap()
+                            : base::flat_map<std::string, std::string>{};
 }
 
 std::string QtUi::GetCursorThemeName() {
@@ -316,13 +321,13 @@
 #if BUILDFLAG(ENABLE_PRINTING)
 printing::PrintDialogLinuxInterface* QtUi::CreatePrintDialog(
     printing::PrintingContextLinux* context) {
-  NOTIMPLEMENTED_LOG_ONCE();
-  return nullptr;
+  return fallback_linux_ui_ ? fallback_linux_ui_->CreatePrintDialog(context)
+                            : nullptr;
 }
 
 gfx::Size QtUi::GetPdfPaperSize(printing::PrintingContextLinux* context) {
-  NOTIMPLEMENTED_LOG_ONCE();
-  return gfx::Size();
+  return fallback_linux_ui_ ? fallback_linux_ui_->GetPdfPaperSize(context)
+                            : gfx::Size();
 }
 #endif
 
@@ -496,8 +501,9 @@
   }
 }
 
-std::unique_ptr<views::LinuxUI> CreateQtUi() {
-  return std::make_unique<QtUi>();
+std::unique_ptr<views::LinuxUI> CreateQtUi(
+    std::unique_ptr<views::LinuxUI> fallback_linux_ui) {
+  return std::make_unique<QtUi>(std::move(fallback_linux_ui));
 }
 
 }  // namespace qt
diff --git a/ui/qt/qt_ui.h b/ui/qt/qt_ui.h
index b3a3a0b..4b751e4 100644
--- a/ui/qt/qt_ui.h
+++ b/ui/qt/qt_ui.h
@@ -24,7 +24,7 @@
 // Interface to QT desktop features.
 class QtUi : public views::LinuxUI, QtInterface::Delegate {
  public:
-  QtUi();
+  explicit QtUi(std::unique_ptr<views::LinuxUI> fallback_linux_uik);
 
   QtUi(const QtUi&) = delete;
   QtUi& operator=(const QtUi&) = delete;
@@ -97,6 +97,10 @@
 
   absl::optional<SkColor> GetColor(int id, bool use_custom_frame) const;
 
+  // TODO(https://crbug.com/1317782): This is a fallback for any unimplemented
+  // functionality in the QT backend and should eventually be removed.
+  std::unique_ptr<views::LinuxUI> fallback_linux_ui_;
+
   // QT modifies argc and argv, and they must be kept alive while
   // `shim_` is alive.
   CmdLineArgs cmd_line_;
@@ -116,7 +120,8 @@
 
 // This should be the only symbol exported from this component.
 COMPONENT_EXPORT(QT)
-std::unique_ptr<views::LinuxUI> CreateQtUi();
+std::unique_ptr<views::LinuxUI> CreateQtUi(
+    std::unique_ptr<views::LinuxUI> fallback_linux_ui);
 
 }  // namespace qt
 
diff --git a/ui/views/linux_ui/linux_ui.cc b/ui/views/linux_ui/linux_ui.cc
index cc93710f..f5a54ff 100644
--- a/ui/views/linux_ui/linux_ui.cc
+++ b/ui/views/linux_ui/linux_ui.cc
@@ -34,6 +34,8 @@
 #if BUILDFLAG(IS_LINUX) && BUILDFLAG(ENABLE_PRINTING)
   printing::PrintingContextLinuxDelegate::SetInstance(g_linux_ui);
 #endif
+  ui::CursorThemeManager::SetInstance(g_linux_ui);
+  gfx::AnimationSettingsProviderLinux::SetInstance(g_linux_ui);
 
   // Do not set IME instance for ozone as we delegate creating the input method
   // to OzonePlatforms instead. If this is set, OzonePlatform never sets a
diff --git a/ui/views/linux_ui/linux_ui_factory.cc b/ui/views/linux_ui/linux_ui_factory.cc
index 9651a38..2cebfc0 100644
--- a/ui/views/linux_ui/linux_ui_factory.cc
+++ b/ui/views/linux_ui/linux_ui_factory.cc
@@ -4,6 +4,8 @@
 
 #include "ui/views/linux_ui/linux_ui_factory.h"
 
+#include <utility>
+
 #include "ui/base/buildflags.h"
 #include "ui/views/linux_ui/linux_ui.h"
 
@@ -18,10 +20,17 @@
   // TODO(thomasanderson): LinuxUI backend should be chosen depending on the
   // environment.
 #if BUILDFLAG(USE_QT)
-  auto qt_ui = qt::CreateQtUi();
-  if (qt_ui->Initialize())
-    return qt_ui;
-  qt_ui.reset();  // Reset to prevent 2 active LinuxUI instances.
+  {
+    std::unique_ptr<views::LinuxUI> fallback_linux_ui;
+#if BUILDFLAG(USE_GTK)
+    fallback_linux_ui = BuildGtkUi();
+    if (!fallback_linux_ui->Initialize())
+      fallback_linux_ui.reset();
+#endif
+    auto qt_ui = qt::CreateQtUi(std::move(fallback_linux_ui));
+    if (qt_ui->Initialize())
+      return qt_ui;
+  }
 #endif
 #if BUILDFLAG(USE_GTK)
   {
diff --git a/ui/webui/resources/cr_elements/chromeos/os_cr_elements.gni b/ui/webui/resources/cr_elements/chromeos/os_cr_elements.gni
deleted file mode 100644
index 65aefff7..0000000
--- a/ui/webui/resources/cr_elements/chromeos/os_cr_elements.gni
+++ /dev/null
@@ -1,11 +0,0 @@
-# Copyright 2020 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import("//third_party/closure_compiler/compile_js.gni")
-
-cr_elements_chromeos_namespace_rewrites = [
-  "cr.png.convertImageSequenceToPng|convertImageSequenceToPng",
-  "cr.png.isEncodedPngDataUrlAnimated|isEncodedPngDataUrlAnimated",
-  "cr.ui.Action|Action",
-]
diff --git a/url/BUILD.gn b/url/BUILD.gn
index 7997d4d..8bfd023 100644
--- a/url/BUILD.gn
+++ b/url/BUILD.gn
@@ -143,6 +143,7 @@
       "//base:base_java",
       "//base:jni_java",
       "//build/android:build_java",
+      "//third_party/android_deps:com_google_errorprone_error_prone_annotations_java",
       "//third_party/androidx:androidx_annotation_annotation_java",
     ]
     annotation_processor_deps = [ "//base/android/jni_generator:jni_processor" ]
diff --git a/url/android/java/src/org/chromium/url/GURL.java b/url/android/java/src/org/chromium/url/GURL.java
index 52142c6..2a885630 100644
--- a/url/android/java/src/org/chromium/url/GURL.java
+++ b/url/android/java/src/org/chromium/url/GURL.java
@@ -10,6 +10,8 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
+import com.google.errorprone.annotations.DoNotMock;
+
 import org.chromium.base.Log;
 import org.chromium.base.ThreadUtils;
 import org.chromium.base.annotations.CalledByNative;
@@ -36,6 +38,7 @@
  */
 @JNINamespace("url")
 @MainDex
+@DoNotMock("Create a real instance instead. For Robolectric, see JUnitTestGURLs.java")
 public class GURL {
     private static final String TAG = "GURL";
     /* package */ static final int SERIALIZER_VERSION = 1;