diff --git a/DEPS b/DEPS
index 58831d9..7845b43 100644
--- a/DEPS
+++ b/DEPS
@@ -253,7 +253,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Skia
   # and whatever else without interference from each other.
-  'skia_revision': 'e1880aed8f81fd9f7cdea2439e7d6d724738647c',
+  'skia_revision': 'b17e7aafc11e4daa177c8f61288b8e60319e97d1',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
@@ -320,7 +320,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling catapult
   # and whatever else without interference from each other.
-  'catapult_revision': '63a615bbdb57ad0a2e794ed800b826dddcef2713',
+  'catapult_revision': '0c2e8d34dde04fedf933d3b39d803acfe01d7554',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
@@ -328,7 +328,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling devtools-frontend
   # and whatever else without interference from each other.
-  'devtools_frontend_revision': '5f3a22081f93b61250671a1ce7eea84045a981b9',
+  'devtools_frontend_revision': '1a45cd5a54887f7263c938c23c7e63769f8e6713',
   # 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.
@@ -368,7 +368,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'dawn_revision': '5f881cbca0e92057ad86dc2a93e8b48fafeda17a',
+  'dawn_revision': '0146d3e31d86d6fb6959d3066d14fd92f32048b5',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -396,7 +396,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling nearby
   # and whatever else without interference from each other.
-  'nearby_revision': '9239fb0767a790416cc5faf055f5169bce89e741',
+  'nearby_revision': '0702409047f372c5ec7b7566ec8a86500caa8972',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling securemessage
   # and whatever else without interference from each other.
@@ -704,16 +704,16 @@
     Var('chromium_git') + '/external/github.com/toji/webvr.info.git' + '@' + 'c58ae99b9ff9e2aa4c524633519570bf33536248',
 
   'src/docs/website': {
-    'url': Var('chromium_git') + '/website.git' + '@' + '0853531c05252b359fc3bf8affad61530105f2bf',
+    'url': Var('chromium_git') + '/website.git' + '@' + 'dc2fb879a69f339640ef770cda63683dc0b281c7',
   },
 
   'src/ios/third_party/earl_grey2/src': {
-      'url': Var('chromium_git') + '/external/github.com/google/EarlGrey.git' + '@' + 'd772a0e8eb889fb184e3bd74a1d0cd868d94605c',
+      'url': Var('chromium_git') + '/external/github.com/google/EarlGrey.git' + '@' + 'e3cb1fbaef52fa8f8071b053398cecbf861577a3',
       'condition': 'checkout_ios',
   },
 
     'src/ios/third_party/edo/src': {
-      'url': Var('chromium_git') + '/external/github.com/google/eDistantObject.git' + '@' + 'b043e0b7c2f8d1c59b116529abb97d5e87102b83',
+      'url': Var('chromium_git') + '/external/github.com/google/eDistantObject.git' + '@' + 'f7dea1a5bdc745493aeffece692a4883e85c0e78',
       'condition': 'checkout_ios',
   },
 
@@ -892,7 +892,7 @@
     'packages': [
       {
           'package': 'chromium/third_party/androidx',
-          'version': 'Hn2dB6ZTIa1GhUTs1KqxEzKA_xgWAAeP8xJN3GTY17cC',
+          'version': 'GSeF5aW_033IJ7WfJr-L0ydknZykCwvytFFCrJfLMNMC',
       },
     ],
     'condition': 'checkout_android',
@@ -1120,7 +1120,7 @@
   },
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '34816458276bf9a528f306f0bc26f33dfdb428c8',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '79b4bab70c8da5d2a3770d445648f053a66a96cc',
 
   'src/third_party/devtools-frontend/src':
     Var('chromium_git') + '/devtools/devtools-frontend' + '@' + Var('devtools_frontend_revision'),
@@ -1486,7 +1486,7 @@
     Var('chromium_git') + '/external/github.com/cisco/openh264' + '@' + 'b52786888ddce9d6bc06b7825ba9bffc65924e0c',
 
   'src/third_party/openscreen/src':
-    Var('chromium_git') + '/openscreen' + '@' + 'ef872b4586b5d62840fdcc1b76ce7c977dcff104',
+    Var('chromium_git') + '/openscreen' + '@' + '05e42cebb5b8a256923e81057f9c434429ae2c97',
 
   'src/third_party/openxr/src': {
     'url': Var('chromium_git') + '/external/github.com/KhronosGroup/OpenXR-SDK' + '@' + 'bf21ccb1007bb531b45d9978919a56ea5059c245',
@@ -1503,7 +1503,7 @@
   },
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' + 'e26fd0a8a5e96b82f71b3c86240d8c8c0429245e',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' + 'bce4286eebf41c7e7f6689684dea989c0b010fe1',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '6f3e5028eb65d0b4c5fdd792106ac4c84eee1eb3',
@@ -1581,7 +1581,7 @@
       'packages': [
           {
               'package': 'fuchsia/third_party/android/aemu/release/linux-amd64',
-              'version': 'p_ehnYla4xhPaxDnGkBMOVRzCWuby9Pbm7OknbRYhrwC'
+              'version': 'fketj7I7hlpIgx7ROcCNT4oiEA8wbPaX8DmlHdfDw_4C'
           },
       ],
       'condition': 'host_os == "linux" and checkout_fuchsia',
@@ -1794,7 +1794,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@276ba242765462c7ee95b522886e7ac305caee04',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@7004c06997dd9ab988878b74b7631dc04bdff75a',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/ash/app_list/app_list_presenter_unittest.cc b/ash/app_list/app_list_presenter_unittest.cc
index d72f9a0..34da200 100644
--- a/ash/app_list/app_list_presenter_unittest.cc
+++ b/ash/app_list/app_list_presenter_unittest.cc
@@ -72,6 +72,7 @@
 #include "ash/shell.h"
 #include "ash/system/unified/unified_system_tray.h"
 #include "ash/test/ash_test_base.h"
+#include "ash/test/layer_animation_stopped_waiter.h"
 #include "ash/test/test_window_builder.h"
 #include "ash/wallpaper/wallpaper_controller_test_api.h"
 #include "ash/wm/mru_window_tracker.h"
@@ -806,6 +807,75 @@
   EXPECT_FALSE(AppListIsInFolderView());
 }
 
+// Tests that folder item view does not animate out and in after folder is
+// closed (and the folder item location in apps grid did not change while the
+// folder was shown).
+TEST_P(ProductivityLauncherTest, FolderItemViewNotAnimatingAfterClosingFolder) {
+  app_list_test_model_->PopulateApps(2);
+  AppListFolderItem* const folder_item =
+      app_list_test_model_->CreateAndPopulateFolderWithApps(3);
+  const std::string folder_id = folder_item->id();
+  app_list_test_model_->PopulateApps(3);
+
+  // Setup tablet/clamshell mode and show launcher.
+  EnableTabletMode(tablet_mode_param());
+  EnsureLauncherShown();
+  SetupGridTestApi();
+
+  ui::ScopedAnimationDurationScaleMode scope_duration(
+      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
+
+  // Cache the initial folder item bounds.
+  const gfx::Rect original_folder_item_bounds =
+      apps_grid_view_->GetItemViewAt(2)->GetBoundsInScreen();
+
+  grid_test_api_->PressItemAt(2);
+  EXPECT_TRUE(AppListIsInFolderView());
+  GetAppListTestHelper()->WaitForFolderAnimation();
+  AppListFolderView* const folder_view = GetFolderView();
+
+  AppListItemView* const folder_item_view = apps_grid_view_->GetItemViewAt(2);
+  ASSERT_TRUE(folder_item_view);
+  ASSERT_TRUE(folder_item_view->is_folder());
+  EXPECT_EQ(original_folder_item_bounds, folder_item_view->GetBoundsInScreen());
+
+  // Close the folder view.
+  ui::test::EventGenerator* event_generator = GetEventGenerator();
+  event_generator->MoveMouseTo(
+      GetFolderView()->GetBoundsInScreen().right_center() +
+      gfx::Vector2d(10, 0));
+  event_generator->ClickLeftButton();
+
+  EXPECT_TRUE(folder_view->IsAnimationRunning());
+  EXPECT_EQ(original_folder_item_bounds, folder_item_view->GetBoundsInScreen());
+
+  base::RunLoop folder_animation_waiter;
+  // Once folder completes hiding, the folder item view should be moved to
+  // target location.
+  folder_view->SetAnimationDoneTestCallback(base::BindLambdaForTesting([&]() {
+    folder_animation_waiter.Quit();
+
+    EXPECT_EQ(original_folder_item_bounds,
+              folder_item_view->GetBoundsInScreen());
+
+    // The folder item position did not change, so the item view should not
+    // start fading out when the folder view hides.
+    EXPECT_FALSE(folder_item_view->layer());
+  }));
+  folder_animation_waiter.Run();
+
+  EXPECT_FALSE(AppListIsInFolderView());
+
+  // Verify that the folder is visible, and positioned in its final bounds.
+  EXPECT_EQ(original_folder_item_bounds, folder_item_view->GetBoundsInScreen());
+
+  // No item layers are expected to be created.
+  for (int i = 0; i < apps_grid_view_->view_model()->view_size(); ++i) {
+    views::View* item_view = apps_grid_view_->view_model()->view_at(i);
+    EXPECT_FALSE(item_view->layer()) << "at " << i;
+  }
+}
+
 // Tests that folder view bounds do not change if an item gets added to app list
 // model while the folder view is visible (even if it changes the folder item
 // view position in the root apps grid).
@@ -875,16 +945,50 @@
   EXPECT_TRUE(folder_view->IsAnimationRunning());
   EXPECT_EQ(original_folder_item_bounds, folder_item_view->GetBoundsInScreen());
 
+  base::RunLoop folder_animation_waiter;
   // Once folder completes hiding, the folder item view should be moved to
   // target location.
-  GetAppListTestHelper()->WaitForFolderAnimation();
-  apps_grid_view_->GetWidget()->LayoutRootViewIfNecessary();
-  grid_test_api_->WaitForItemMoveAnimationDone();
+  folder_view->SetAnimationDoneTestCallback(base::BindLambdaForTesting([&]() {
+    folder_animation_waiter.Quit();
+
+    EXPECT_EQ(original_folder_item_bounds,
+              folder_item_view->GetBoundsInScreen());
+
+    // The folder item should start fading out in it's current position.
+    ASSERT_TRUE(folder_item_view->layer());
+    EXPECT_EQ(0.0f, folder_item_view->layer()->GetTargetOpacity());
+  }));
+
+  folder_animation_waiter.Run();
+
   EXPECT_FALSE(AppListIsInFolderView());
 
+  // Wait for the folder item to fade out.
+  if (folder_item_view->layer()) {
+    LayerAnimationStoppedWaiter animation_waiter;
+    animation_waiter.Wait(folder_item_view->layer());
+  }
+
+  grid_test_api_->WaitForItemMoveAnimationDone();
+
+  // Make sure the folder item view fade in animation is done.
+  if (folder_item_view->layer()) {
+    LayerAnimationStoppedWaiter animation_waiter;
+    animation_waiter.Wait(folder_item_view->layer());
+  }
+
+  // Verify that the folder is visible, and positioned in its final bounds.
   EXPECT_EQ(final_folder_item_bounds, folder_item_view->GetBoundsInScreen());
+  EXPECT_FALSE(folder_item_view->layer());
+
   EXPECT_EQ(original_folder_item_bounds,
             apps_grid_view_->GetItemViewAt(2)->GetBoundsInScreen());
+
+  // Verify that item view layers have been deleted.
+  for (int i = 0; i < apps_grid_view_->view_model()->view_size(); ++i) {
+    views::View* item_view = apps_grid_view_->view_model()->view_at(i);
+    EXPECT_FALSE(item_view->layer()) << "at " << i;
+  }
 }
 
 // Tests that folder view bounds do not change if position of the original
@@ -956,17 +1060,41 @@
   EXPECT_TRUE(folder_view->IsAnimationRunning());
   EXPECT_EQ(original_folder_item_bounds, folder_item_view->GetBoundsInScreen());
 
+  base::RunLoop folder_animation_waiter;
   // Once folder completes hiding, the folder item view should be moved to
   // target location.
-  GetAppListTestHelper()->WaitForFolderAnimation();
-  apps_grid_view_->GetWidget()->LayoutRootViewIfNecessary();
-  grid_test_api_->WaitForItemMoveAnimationDone();
+  folder_view->SetAnimationDoneTestCallback(base::BindLambdaForTesting([&]() {
+    folder_animation_waiter.Quit();
+
+    EXPECT_EQ(original_folder_item_bounds,
+              folder_item_view->GetBoundsInScreen());
+
+    // The folder item should start fading out in it's current position.
+    ASSERT_TRUE(folder_item_view->layer());
+    EXPECT_EQ(0.0f, folder_item_view->layer()->GetTargetOpacity());
+  }));
+
+  folder_animation_waiter.Run();
+
   EXPECT_FALSE(AppListIsInFolderView());
 
-  EXPECT_EQ(final_folder_item_bounds, folder_item_view->GetBoundsInScreen());
+  // Wait for the folder item to fade out.
+  if (folder_item_view->layer()) {
+    LayerAnimationStoppedWaiter animation_waiter;
+    animation_waiter.Wait(folder_item_view->layer());
+  }
 
-  EXPECT_EQ(original_folder_item_bounds,
-            apps_grid_view_->GetItemViewAt(2)->GetBoundsInScreen());
+  grid_test_api_->WaitForItemMoveAnimationDone();
+
+  // Make sure the folder item view fade in animation is done.
+  if (folder_item_view->layer()) {
+    LayerAnimationStoppedWaiter animation_waiter;
+    animation_waiter.Wait(folder_item_view->layer());
+  }
+
+  // Verify that the folder is visible, and positioned in its final bounds.
+  EXPECT_EQ(final_folder_item_bounds, folder_item_view->GetBoundsInScreen());
+  EXPECT_FALSE(folder_item_view->layer());
 
   // The item at slot 1 should be remain in place.
   EXPECT_EQ(original_item_1_bounds,
@@ -974,6 +1102,12 @@
   // The item at slot 2 in the model should move into original folder item slot.
   EXPECT_EQ(original_folder_item_bounds,
             apps_grid_view_->GetItemViewAt(2)->GetBoundsInScreen());
+
+  // Verify that item view layers have been deleted.
+  for (int i = 0; i < apps_grid_view_->view_model()->view_size(); ++i) {
+    views::View* item_view = apps_grid_view_->view_model()->view_at(i);
+    EXPECT_FALSE(item_view->layer()) << "at " << i;
+  }
 }
 
 // Tests that folder view bounds do not change if position of the original
@@ -1045,17 +1179,41 @@
   EXPECT_TRUE(folder_view->IsAnimationRunning());
   EXPECT_EQ(original_folder_item_bounds, folder_item_view->GetBoundsInScreen());
 
+  base::RunLoop folder_animation_waiter;
   // Once folder completes hiding, the folder item view should be moved to
   // target location.
-  GetAppListTestHelper()->WaitForFolderAnimation();
-  apps_grid_view_->GetWidget()->LayoutRootViewIfNecessary();
-  grid_test_api_->WaitForItemMoveAnimationDone();
+  folder_view->SetAnimationDoneTestCallback(base::BindLambdaForTesting([&]() {
+    folder_animation_waiter.Quit();
+
+    EXPECT_EQ(original_folder_item_bounds,
+              folder_item_view->GetBoundsInScreen());
+
+    // The folder item should start fading out in it's current position.
+    ASSERT_TRUE(folder_item_view->layer());
+    EXPECT_EQ(0.0f, folder_item_view->layer()->GetTargetOpacity());
+  }));
+
+  folder_animation_waiter.Run();
+
   EXPECT_FALSE(AppListIsInFolderView());
 
-  EXPECT_EQ(final_folder_item_bounds, folder_item_view->GetBoundsInScreen());
+  // Wait for the folder item to fade out.
+  if (folder_item_view->layer()) {
+    LayerAnimationStoppedWaiter animation_waiter;
+    animation_waiter.Wait(folder_item_view->layer());
+  }
 
-  EXPECT_EQ(original_folder_item_bounds,
-            apps_grid_view_->GetItemViewAt(2)->GetBoundsInScreen());
+  grid_test_api_->WaitForItemMoveAnimationDone();
+
+  // Make sure the folder item view fade in animation is done.
+  if (folder_item_view->layer()) {
+    LayerAnimationStoppedWaiter animation_waiter;
+    animation_waiter.Wait(folder_item_view->layer());
+  }
+
+  // Verify that the folder is visible, and positioned in its final bounds.
+  EXPECT_EQ(final_folder_item_bounds, folder_item_view->GetBoundsInScreen());
+  EXPECT_FALSE(folder_item_view->layer());
 
   // The item at slot 2 in the model should move into original folder item slot.
   EXPECT_EQ(original_folder_item_bounds,
@@ -1063,6 +1221,12 @@
   // The item at slot 3 in the model should move into new position.
   EXPECT_EQ(original_item_3_bounds,
             apps_grid_view_->GetItemViewAt(3)->GetBoundsInScreen());
+
+  // Verify that item view layers have been deleted.
+  for (int i = 0; i < apps_grid_view_->view_model()->view_size(); ++i) {
+    views::View* item_view = apps_grid_view_->view_model()->view_at(i);
+    EXPECT_FALSE(item_view->layer()) << "at " << i;
+  }
 }
 
 // Tests that folder item deletion during folder view hide animation is handled
@@ -1140,11 +1304,18 @@
             apps_grid_view_->GetItemViewAt(2)->GetBoundsInScreen());
   EXPECT_EQ(original_item_3_bounds,
             apps_grid_view_->GetItemViewAt(3)->GetBoundsInScreen());
+
+  // Verify that item view layers have been deleted.
+  for (int i = 0; i < apps_grid_view_->view_model()->view_size(); ++i) {
+    views::View* item_view = apps_grid_view_->view_model()->view_at(i);
+    EXPECT_FALSE(item_view->layer()) << "at " << i;
+  }
 }
 
 // Tests that folder item deletion just after folder gets hidden (while item
 // bounds are still animating to final positions) gets handled well.
-TEST_P(ProductivityLauncherTest, ReorderedFolderItemDeletionAfterFolderClose) {
+TEST_P(ProductivityLauncherTest,
+       ReorderedFolderItemDeletionDuringFolderItemFadeOut) {
   app_list_test_model_->PopulateApps(2);
   AppListFolderItem* const folder_item =
       app_list_test_model_->CreateAndPopulateFolderWithApps(3);
@@ -1204,11 +1375,22 @@
                                gfx::Vector2d(10, 0));
   event_generator->ClickLeftButton();
 
-  GetAppListTestHelper()->WaitForFolderAnimation();
+  EXPECT_TRUE(folder_view->IsAnimationRunning());
+  EXPECT_EQ(original_folder_item_bounds, folder_item_view->GetBoundsInScreen());
 
-  // Delete the folder item while items are animating into their final
-  // positions.
-  DeleteFolderItemChildren(folder_item);
+  base::RunLoop folder_animation_waiter;
+  // Once folder completes hiding, the folder item view should be moved to
+  // target location.
+  folder_view->SetAnimationDoneTestCallback(base::BindLambdaForTesting([&]() {
+    folder_animation_waiter.Quit();
+
+    // Delete the folder item while items are animating into their final
+    // positions.
+    DeleteFolderItemChildren(folder_item);
+  }));
+
+  folder_animation_waiter.Run();
+
   apps_grid_view_->GetWidget()->LayoutRootViewIfNecessary();
   grid_test_api_->WaitForItemMoveAnimationDone();
   EXPECT_FALSE(AppListIsInFolderView());
@@ -1220,6 +1402,109 @@
             apps_grid_view_->GetItemViewAt(2)->GetBoundsInScreen());
   EXPECT_EQ(original_item_3_bounds,
             apps_grid_view_->GetItemViewAt(3)->GetBoundsInScreen());
+
+  // Verify that item view layers have been deleted.
+  for (int i = 0; i < apps_grid_view_->view_model()->view_size(); ++i) {
+    views::View* item_view = apps_grid_view_->view_model()->view_at(i);
+    EXPECT_FALSE(item_view->layer()) << "at " << i;
+  }
+}
+
+// Tests that folder item deletion just after folder gets hidden (while item
+// bounds are still animating to final positions) gets handled well.
+TEST_P(ProductivityLauncherTest,
+       ReorderedFolderItemDeletionAfterFolderItemFadeOut) {
+  app_list_test_model_->PopulateApps(2);
+  AppListFolderItem* const folder_item =
+      app_list_test_model_->CreateAndPopulateFolderWithApps(3);
+  const std::string folder_id = folder_item->id();
+  app_list_test_model_->PopulateApps(3);
+
+  // Setup tablet/clamshell mode and show launcher.
+  EnableTabletMode(tablet_mode_param());
+  EnsureLauncherShown();
+  SetupGridTestApi();
+
+  ui::ScopedAnimationDurationScaleMode scope_duration(
+      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
+
+  grid_test_api_->PressItemAt(2);
+  EXPECT_TRUE(AppListIsInFolderView());
+  GetAppListTestHelper()->WaitForFolderAnimation();
+  AppListFolderView* folder_view = GetFolderView();
+
+  // Cache the initial folder bounds.
+  const gfx::Rect folder_bounds = folder_view->GetBoundsInScreen();
+  const gfx::Rect original_folder_item_bounds =
+      apps_grid_view_->GetItemViewAt(2)->GetBoundsInScreen();
+  const gfx::Rect original_item_1_bounds =
+      apps_grid_view_->GetItemViewAt(1)->GetBoundsInScreen();
+  const gfx::Rect original_item_3_bounds =
+      apps_grid_view_->GetItemViewAt(3)->GetBoundsInScreen();
+
+  // Move the folder item to the last position in the model.
+  app_list_test_model_->RequestPositionUpdate(
+      folder_id,
+      app_list_test_model_->top_level_item_list()
+          ->item_at(0)
+          ->position()
+          .CreateBefore(),
+      RequestPositionUpdateReason::kMoveItem);
+
+  // Verify that the folder view location did not actually change.
+  EXPECT_EQ(folder_bounds, folder_view->GetBoundsInScreen());
+
+  AppListItemView* const folder_item_view = apps_grid_view_->GetItemViewAt(0);
+  ASSERT_TRUE(folder_item_view);
+  ASSERT_TRUE(folder_item_view->is_folder());
+  EXPECT_EQ(original_folder_item_bounds, folder_item_view->GetBoundsInScreen());
+
+  // The item at slot 3 in the model did not change, so it should remain in
+  // place.
+  EXPECT_EQ(original_item_3_bounds,
+            apps_grid_view_->GetItemViewAt(3)->GetBoundsInScreen());
+  // The item at slot 2 in the model should remain in the old position (slot 1).
+  EXPECT_EQ(original_item_1_bounds,
+            apps_grid_view_->GetItemViewAt(2)->GetBoundsInScreen());
+
+  // Close the folder view.
+  ui::test::EventGenerator* event_generator = GetEventGenerator();
+  event_generator->MoveMouseTo(folder_view->GetBoundsInScreen().right_center() +
+                               gfx::Vector2d(10, 0));
+  event_generator->ClickLeftButton();
+
+  EXPECT_TRUE(folder_view->IsAnimationRunning());
+  EXPECT_EQ(original_folder_item_bounds, folder_item_view->GetBoundsInScreen());
+
+  GetAppListTestHelper()->WaitForFolderAnimation();
+
+  // Wait for the folder item to fade out.
+  if (folder_item_view->layer()) {
+    LayerAnimationStoppedWaiter animation_waiter;
+    animation_waiter.Wait(folder_item_view->layer());
+  }
+
+  // Delete the folder item while items are animating into their final
+  // positions.
+  DeleteFolderItemChildren(folder_item);
+  apps_grid_view_->GetWidget()->LayoutRootViewIfNecessary();
+
+  grid_test_api_->WaitForItemMoveAnimationDone();
+  EXPECT_FALSE(AppListIsInFolderView());
+
+  // Verify remaining items are moved into correct slots.
+  EXPECT_EQ(original_item_1_bounds,
+            apps_grid_view_->GetItemViewAt(1)->GetBoundsInScreen());
+  EXPECT_EQ(original_folder_item_bounds,
+            apps_grid_view_->GetItemViewAt(2)->GetBoundsInScreen());
+  EXPECT_EQ(original_item_3_bounds,
+            apps_grid_view_->GetItemViewAt(3)->GetBoundsInScreen());
+
+  // Verify that item view layers have been deleted.
+  for (int i = 0; i < apps_grid_view_->view_model()->view_size(); ++i) {
+    views::View* item_view = apps_grid_view_->view_model()->view_at(i);
+    EXPECT_FALSE(item_view->layer()) << "at " << i;
+  }
 }
 
 // Tests that folder item deletion while the folder is shown gets handled well.
diff --git a/ash/app_list/views/apps_grid_view.cc b/ash/app_list/views/apps_grid_view.cc
index 58bc456..7713d41 100644
--- a/ash/app_list/views/apps_grid_view.cc
+++ b/ash/app_list/views/apps_grid_view.cc
@@ -56,6 +56,7 @@
 #include "ui/display/screen.h"
 #include "ui/events/event.h"
 #include "ui/gfx/animation/animation.h"
+#include "ui/gfx/geometry/transform_util.h"
 #include "ui/gfx/geometry/vector2d.h"
 #include "ui/gfx/geometry/vector2d_conversions.h"
 #include "ui/strings/grit/ui_strings.h"
@@ -107,6 +108,24 @@
 // The time duration of the fade out animation used for apps grid reorder.
 constexpr base::TimeDelta kFadeOutAnimationDuration = base::Milliseconds(100);
 
+// Constants for folder item view relocation animation - the animation runs
+// after closing a folder view if the shown folder item view location within the
+// apps grid changed while the folder view was open.
+// The folder view animates in the old folder item location, then the folder
+// item view animates out at the old location, other items move into their
+// correct spot, and after a delay, the folder item view animates into its new
+// location.
+//
+// The duration of the folder item view fade out animation.
+constexpr base::TimeDelta kFolderItemFadeOutDuration = base::Milliseconds(100);
+
+// The duraction of the folder item view fade in animation.
+constexpr base::TimeDelta kFolderItemFadeInDuration = base::Milliseconds(300);
+
+// The delay for starting the folder item view fade in after the item view was
+// faded out.
+constexpr base::TimeDelta kFolderItemFadeInDelay = base::Milliseconds(300);
+
 // RowMoveAnimationDelegate is used when moving an item into a different row.
 // Before running the animation, the item's layer is re-created and kept in
 // the original position, then the item is moved to just before its target
@@ -343,7 +362,7 @@
   bounds_animator_ = std::make_unique<views::BoundsAnimator>(
       items_container_, /*use_transforms=*/true);
   bounds_animator_->AddObserver(this);
-
+  bounds_animator_->SetAnimationDuration(base::Milliseconds(300));
   if (features::IsProductivityLauncherEnabled()) {
     GetViewAccessibility().OverrideRole(ax::mojom::Role::kGroup);
     GetViewAccessibility().OverrideName(
@@ -885,16 +904,92 @@
 }
 
 void AppsGridView::FolderHidden(const std::string& item_id) {
-  if (open_folder_info_ && open_folder_info_->item_id == item_id) {
-    open_folder_info_.reset();
-    AnimateToIdealBounds();
-    // Drag updates get throttled while folder is closing during reparent drag -
-    // handle cached drag update if reparent drag is in progress.
-    if (IsDraggingForReparentInRootLevelGridView()) {
-      MaybeStartCardifiedView();
-      UpdateDrag(drag_pointer_, last_drag_point_);
+  if (!open_folder_info_ || open_folder_info_->item_id != item_id)
+    return;
+
+  // Find the folder item location in the app list model to determine whether
+  // the item view location changed while the folder was closed (in which case
+  // the folder location change should be animated).
+  AppListItemView* item_view = nullptr;
+  int model_index = -1;
+  for (int i = 0; i < view_model_.view_size(); ++i) {
+    AppListItemView* view = view_model_.view_at(i);
+    if (view == drag_view_)
+      continue;
+
+    ++model_index;
+    if (view->item()->id() == item_id) {
+      item_view = view;
+      break;
     }
   }
+
+  // If the item view is gone, or the location in the grid did not change,
+  // the folder item should not be animated - immediately update apps grid state
+  // for folder hide.
+  if (!item_view || view_structure_.GetIndexFromModelIndex(model_index) ==
+                        open_folder_info_->grid_index) {
+    open_folder_info_.reset();
+    OnFolderHideAnimationDone();
+    return;
+  }
+
+  // When folder animates out, remaining items will animate to their ideal
+  // bounds - ensure their layers are created (and marked not to fill bounds
+  // opaquely).
+  for (int i = 0; i < view_model_.view_size(); ++i)
+    view_model_.view_at(i)->EnsureLayer();
+
+  // Animate the folder item view out from its original location.
+  reordering_folder_view_ = item_view;
+  views::AnimationBuilder animation;
+  animation.OnEnded(base::BindOnce(&AppsGridView::AnimateFolderItemViewIn,
+                                   weak_factory_.GetWeakPtr()));
+  animation.OnAborted(base::BindOnce(&AppsGridView::AnimateFolderItemViewIn,
+                                     weak_factory_.GetWeakPtr()));
+
+  gfx::Transform scale;
+  scale.Scale(0.5, 0.5);
+  scale = gfx::TransformAboutPivot(item_view->GetLocalBounds().CenterPoint(),
+                                   scale);
+  animation.Once()
+      .SetDuration(kFolderItemFadeOutDuration)
+      .SetTransform(item_view->layer(), scale, gfx::Tween::FAST_OUT_LINEAR_IN)
+      .SetOpacity(item_view->layer(), 0.0f, gfx::Tween::FAST_OUT_LINEAR_IN);
+}
+
+void AppsGridView::AnimateFolderItemViewIn() {
+  // Once folder item view fades out, animate remaining items into their target
+  // location, and schedule the folder item view fade-in (note that
+  // `AnimateToIdealBounds()` updates `reordering_folder_view_` bounds without
+  // animation).
+  open_folder_info_.reset();
+  AnimateToIdealBounds();
+
+  if (!reordering_folder_view_)
+    return;
+
+  views::AnimationBuilder()
+      .OnEnded(base::BindOnce(&AppsGridView::OnFolderHideAnimationDone,
+                              weak_factory_.GetWeakPtr()))
+      .OnAborted(base::BindOnce(&AppsGridView::OnFolderHideAnimationDone,
+                                weak_factory_.GetWeakPtr()))
+      .Once()
+      .At(kFolderItemFadeInDelay)
+      .SetDuration(kFolderItemFadeInDuration)
+      .SetTransform(reordering_folder_view_.value()->layer(), gfx::Transform(),
+                    gfx::Tween::ACCEL_LIN_DECEL_100_3)
+      .SetOpacity(reordering_folder_view_.value()->layer(), 1.0f,
+                  gfx::Tween::ACCEL_LIN_DECEL_100_3);
+}
+
+void AppsGridView::OnFolderHideAnimationDone() {
+  reordering_folder_view_.reset();
+  OnBoundsAnimatorDone(nullptr);
+  if (IsDraggingForReparentInRootLevelGridView()) {
+    MaybeStartCardifiedView();
+    UpdateDrag(drag_pointer_, last_drag_point_);
+  }
 }
 
 bool AppsGridView::IsDragging() const {
@@ -1022,6 +1117,9 @@
         last_ghost_view_ = nullptr;
     }
 
+    if (reordering_folder_view_ && *reordering_folder_view_ == details.child)
+      reordering_folder_view_.reset();
+
     bounds_animator_->StopAnimatingView(details.child);
   }
 }
@@ -1311,8 +1409,9 @@
     const gfx::Rect& current = view->bounds();
     const bool current_visible = visible_bounds.Intersects(current);
     const bool target_visible = visible_bounds.Intersects(target);
-    const bool visible =
-        !IsViewHiddenForDrag(view) && (current_visible || target_visible);
+    const bool visible = !IsViewHiddenForFolderReorder(view) &&
+                         !IsViewHiddenForDrag(view) &&
+                         (current_visible || target_visible);
 
     const int y_diff = target.y() - current.y();
     const int tile_size_height =
@@ -2554,6 +2653,11 @@
   if (IsUnderReorderAnimation())
     return true;
 
+  // Folder position is changing after folder closure - this involves animating
+  // folder item view layer out and in, and changing other view's bounds.
+  if (reordering_folder_view_)
+    return true;
+
   return false;
 }
 
@@ -2601,6 +2705,10 @@
   return drag_view_hider_ && drag_view_hider_->drag_view() == view;
 }
 
+bool AppsGridView::IsViewHiddenForFolderReorder(const views::View* view) const {
+  return reordering_folder_view_ && *reordering_folder_view_ == view;
+}
+
 bool AppsGridView::IsUnderReorderAnimation() const {
   return reorder_animation_status_ != AppListReorderAnimationStatus::kEmpty;
 }
diff --git a/ash/app_list/views/apps_grid_view.h b/ash/app_list/views/apps_grid_view.h
index 96287e5..6eed801e 100644
--- a/ash/app_list/views/apps_grid_view.h
+++ b/ash/app_list/views/apps_grid_view.h
@@ -300,6 +300,11 @@
   // example, the drag view for which a drag icon proxy has been created).
   bool IsViewHiddenForDrag(const views::View* view) const;
 
+  // Whether `view` is the folder view that is animating out and in as part of
+  // folder reorder animation that runs after folder is closed if the folder
+  // position within the grid changed.
+  bool IsViewHiddenForFolderReorder(const views::View* view) const;
+
   // Returns true if the apps grid is under the reorder animation process. This
   // function is public for testing.
   bool IsUnderReorderAnimation() const;
@@ -906,6 +911,17 @@
   // `item_id` is the folder items app list model ID.
   void FolderHidden(const std::string& item_id);
 
+  // When folder item view position in the grid changes while the folder view
+  // was shown for the item, the folder item view animates out in old location,
+  // and animate in in the new location upon folder view closure. This methods
+  // schedules folder item fade-in animation, and schedule bounds animations for
+  // other item views in the grid.
+  void AnimateFolderItemViewIn();
+
+  // Called when the folder view closes, and optional folder item view "position
+  // change" animation completes.
+  void OnFolderHideAnimationDone();
+
   class ScopedModelUpdate;
 
   AppListModel* model_ = nullptr;         // Owned by AppListView.
@@ -1051,6 +1067,11 @@
   // added by sync, or the folder item moves as a result of folder rename).
   absl::optional<OpenFolderInfo> open_folder_info_;
 
+  // Folder item view that is being animated into it's target position. The
+  // animation runs after a folder gets closed if the folder intended position
+  // in the grid changed while the folder was open.
+  absl::optional<views::View*> reordering_folder_view_;
+
   std::unique_ptr<AppsGridContextMenu> context_menu_;
 
   // Indicates the current reorder animation.
diff --git a/ash/app_list/views/paged_apps_grid_view.cc b/ash/app_list/views/paged_apps_grid_view.cc
index 0af2747..c15d326 100644
--- a/ash/app_list/views/paged_apps_grid_view.cc
+++ b/ash/app_list/views/paged_apps_grid_view.cc
@@ -313,7 +313,8 @@
     // opacity (the layers will be deleted when they are no longer needed).
     if (ItemViewsRequireLayers()) {
       for (const auto& entry : view_model()->entries()) {
-        if (!IsViewHiddenForDrag(entry.view) && entry.view->layer())
+        if (!IsViewHiddenForDrag(entry.view) &&
+            !IsViewHiddenForFolderReorder(entry.view) && entry.view->layer())
           entry.view->layer()->SetOpacity(1.0f);
       }
       return;
diff --git a/ash/constants/ash_features.cc b/ash/constants/ash_features.cc
index 43f3753..920b17d 100644
--- a/ash/constants/ash_features.cc
+++ b/ash/constants/ash_features.cc
@@ -621,7 +621,7 @@
 // flag controls the first tier, whose support is very good.
 // https://crbug.com/1216245
 const base::Feature kFilesArchivemount{"FilesArchivemount",
-                                       base::FEATURE_DISABLED_BY_DEFAULT};
+                                       base::FEATURE_ENABLED_BY_DEFAULT};
 
 // Enables mounting various archive formats (in two tiers) in Files App. This
 // flag controls the second tier, whose support is more experimental.
@@ -1479,6 +1479,10 @@
   return base::FeatureList::IsEnabled(kAllowAmbientEQ);
 }
 
+bool IsAmbientModeAnimationEnabled() {
+  return base::FeatureList::IsEnabled(kAmbientModeAnimationFeature);
+}
+
 bool IsAmbientModeDevUseProdEnabled() {
   return base::FeatureList::IsEnabled(kAmbientModeDevUseProdFeature);
 }
diff --git a/ash/constants/ash_features.h b/ash/constants/ash_features.h
index 0790e1b4..7f4680f7a 100644
--- a/ash/constants/ash_features.h
+++ b/ash/constants/ash_features.h
@@ -565,6 +565,7 @@
 COMPONENT_EXPORT(ASH_CONSTANTS) bool DoWindowsFollowCursor();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsAdjustSplitViewForVKEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsAllowAmbientEQEnabled();
+COMPONENT_EXPORT(ASH_CONSTANTS) bool IsAmbientModeAnimationEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsAmbientModeDevUseProdEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsAmbientModeEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsAmbientModePhotoPreviewEnabled();
diff --git a/ash/webui/eche_app_ui/eche_recent_app_click_handler_unittest.cc b/ash/webui/eche_app_ui/eche_recent_app_click_handler_unittest.cc
index 6da1e7d5..21bc649 100644
--- a/ash/webui/eche_app_ui/eche_recent_app_click_handler_unittest.cc
+++ b/ash/webui/eche_app_ui/eche_recent_app_click_handler_unittest.cc
@@ -72,7 +72,7 @@
       const absl::optional<std::u16string>& title,
       const absl::optional<std::u16string>& message,
       std::unique_ptr<LaunchAppHelper::NotificationInfo> info) {
-    // Do nothing.
+    num_notifications_shown_++;
   }
 
   void FakeCloseEcheAppFunction() {
@@ -105,21 +105,31 @@
         ->FetchRecentAppMetadataList();
   }
 
+  void SetAppLaunchProhibitedReason(
+      LaunchAppHelper::AppLaunchProhibitedReason reason) {
+    launch_app_helper_->SetAppLaunchProhibitedReason(reason);
+  }
+
+  void reset() { num_notifications_shown_ = 0; }
+
   const std::string& get_package_name() { return package_name_; }
 
   const std::u16string& get_visible_name() { return visible_name_; }
 
   int64_t get_user_id() { return user_id_; }
 
+  size_t num_notifications_shown() { return num_notifications_shown_; }
+
  private:
   phonehub::FakePhoneHubManager fake_phone_hub_manager_;
   base::test::ScopedFeatureList scoped_feature_list_;
   FakeFeatureStatusProvider fake_feature_status_provider_;
-  std::unique_ptr<LaunchAppHelper> launch_app_helper_;
+  std::unique_ptr<FakeLaunchAppHelper> launch_app_helper_;
   std::unique_ptr<EcheRecentAppClickHandler> handler_;
   std::string package_name_;
   std::u16string visible_name_;
   int64_t user_id_;
+  size_t num_notifications_shown_ = 0;
 };
 
 TEST_F(EcheRecentAppClickHandlerTest, StatusChangeTransitions) {
@@ -184,5 +194,26 @@
   EXPECT_EQ(fake_app_metadata.user_id, app_metadata[0].user_id);
 }
 
+TEST_F(EcheRecentAppClickHandlerTest,
+       HandleRecentAppClickWithProhibitedReason) {
+  const int64_t user_id = 1;
+  const char16_t app_visible_name[] = u"Fake App";
+  const char package_name[] = "com.fakeapp";
+  auto fake_app_metadata = phonehub::Notification::AppMetadata(
+      app_visible_name, package_name, gfx::Image(),
+      /*icon_color=*/absl::nullopt, /*icon_is_monochrome=*/true, user_id);
+
+  SetAppLaunchProhibitedReason(
+      LaunchAppHelper::AppLaunchProhibitedReason::kDisabledByScreenLock);
+  RecentAppClicked(fake_app_metadata);
+  EXPECT_EQ(num_notifications_shown(), 1u);
+
+  reset();
+  SetAppLaunchProhibitedReason(
+      LaunchAppHelper::AppLaunchProhibitedReason::kDisabledByPhone);
+  RecentAppClicked(fake_app_metadata);
+  EXPECT_EQ(num_notifications_shown(), 1u);
+}
+
 }  // namespace eche_app
 }  // namespace ash
diff --git a/ash/webui/personalization_app/mojom/BUILD.gn b/ash/webui/personalization_app/mojom/BUILD.gn
index 2e69726..1608227 100644
--- a/ash/webui/personalization_app/mojom/BUILD.gn
+++ b/ash/webui/personalization_app/mojom/BUILD.gn
@@ -50,6 +50,10 @@
           cpp = "ash::default_user_image::DefaultUserImage"
         },
         {
+          mojom = "ash.personalization_app.mojom.AnimationTheme"
+          cpp = "ash::AmbientAnimationTheme"
+        },
+        {
           mojom = "ash.personalization_app.mojom.TopicSource"
           cpp = "ash::AmbientModeTopicSource"
         },
diff --git a/ash/webui/personalization_app/mojom/personalization_app.mojom b/ash/webui/personalization_app/mojom/personalization_app.mojom
index 7e244eb..81a41fe 100644
--- a/ash/webui/personalization_app/mojom/personalization_app.mojom
+++ b/ash/webui/personalization_app/mojom/personalization_app.mojom
@@ -368,6 +368,14 @@
   SelectLastExternalUserImage();
 };
 
+// The animation theme for screen saver. Should be kept in sync with
+// |ash::AmbientAnimationTheme|.
+enum AnimationTheme {
+  kSlideshow = 0,
+  kFeelTheBreeze = 1,
+  kFloatOnBy = 2,
+};
+
 // The source of the screensaver images come from.
 enum TopicSource {
   // Google Photos.
@@ -411,6 +419,9 @@
   // Retrieves information whether ambient mode is enabled.
   OnAmbientModeEnabledChanged(bool ambient_mode_enabled);
 
+  // Notifies the JS side the current state of AnimationTheme.
+  OnAnimationThemeChanged(AnimationTheme animation_theme);
+
   // Notifies the JS side about the current state of TopicSource.
   OnTopicSourceChanged(TopicSource topic_source);
 
@@ -433,6 +444,9 @@
   // Binds a listener to start receiving updates on ambient mode pref changes.
   SetAmbientObserver(pending_remote<AmbientObserver> observer);
 
+  // Sets the ambient animation theme.
+  SetAnimationTheme(AnimationTheme animation_theme);
+
   // Sets the ambient mode topic source.
   SetTopicSource(TopicSource topic_source);
 
diff --git a/ash/webui/personalization_app/mojom/personalization_app_mojom_traits.cc b/ash/webui/personalization_app/mojom/personalization_app_mojom_traits.cc
index 28a1339..3670f2d 100644
--- a/ash/webui/personalization_app/mojom/personalization_app_mojom_traits.cc
+++ b/ash/webui/personalization_app/mojom/personalization_app_mojom_traits.cc
@@ -7,6 +7,7 @@
 #include <string>
 #include <vector>
 
+#include "ash/public/cpp/ambient/ambient_animation_theme.h"
 #include "ash/public/cpp/ambient/common/ambient_settings.h"
 #include "ash/public/cpp/default_user_image.h"
 #include "ash/public/cpp/personalization_app/user_display_info.h"
@@ -29,6 +30,7 @@
 using MojomWallpaperLayout = ash::personalization_app::mojom::WallpaperLayout;
 using MojomWallpaperType = ash::personalization_app::mojom::WallpaperType;
 using MojomOnlineImageType = ash::personalization_app::mojom::OnlineImageType;
+using MojomAnimationTheme = ash::personalization_app::mojom::AnimationTheme;
 using MojomTopicSource = ash::personalization_app::mojom::TopicSource;
 using MojomTemperatureUnit = ash::personalization_app::mojom::TemperatureUnit;
 
@@ -299,6 +301,37 @@
   return data.ReadTitle(&out->title) && data.ReadUrl(&out->url);
 }
 
+MojomAnimationTheme
+EnumTraits<MojomAnimationTheme, ash::AmbientAnimationTheme>::ToMojom(
+    ash::AmbientAnimationTheme input) {
+  switch (input) {
+    case ash::AmbientAnimationTheme::kSlideshow:
+      return MojomAnimationTheme::kSlideshow;
+    case ash::AmbientAnimationTheme::kFeelTheBreeze:
+      return MojomAnimationTheme::kFeelTheBreeze;
+    case ash::AmbientAnimationTheme::kFloatOnBy:
+      return MojomAnimationTheme::kFloatOnBy;
+  }
+}
+
+bool EnumTraits<MojomAnimationTheme, ash::AmbientAnimationTheme>::FromMojom(
+    MojomAnimationTheme input,
+    ash::AmbientAnimationTheme* output) {
+  switch (input) {
+    case MojomAnimationTheme::kSlideshow:
+      *output = ash::AmbientAnimationTheme::kSlideshow;
+      return true;
+    case MojomAnimationTheme::kFeelTheBreeze:
+      *output = ash::AmbientAnimationTheme::kFeelTheBreeze;
+      return true;
+    case MojomAnimationTheme::kFloatOnBy:
+      *output = ash::AmbientAnimationTheme::kFloatOnBy;
+      return true;
+  }
+  NOTREACHED();
+  return false;
+}
+
 // TODO (b/220933864): remove ash::AmbientModeTopicSource and
 // ash::AmbientModeTemperatureUnit enums.
 MojomTopicSource
diff --git a/ash/webui/personalization_app/mojom/personalization_app_mojom_traits.h b/ash/webui/personalization_app/mojom/personalization_app_mojom_traits.h
index 8efaa29..5271369 100644
--- a/ash/webui/personalization_app/mojom/personalization_app_mojom_traits.h
+++ b/ash/webui/personalization_app/mojom/personalization_app_mojom_traits.h
@@ -8,6 +8,7 @@
 #include <string>
 #include <vector>
 
+#include "ash/public/cpp/ambient/ambient_animation_theme.h"
 #include "ash/public/cpp/ambient/common/ambient_settings.h"
 #include "ash/public/cpp/default_user_image.h"
 #include "ash/public/cpp/personalization_app/user_display_info.h"
@@ -104,6 +105,15 @@
 };
 
 template <>
+struct EnumTraits<ash::personalization_app::mojom::AnimationTheme,
+                  ash::AmbientAnimationTheme> {
+  using MojomAnimationTheme = ::ash::personalization_app::mojom::AnimationTheme;
+  static MojomAnimationTheme ToMojom(ash::AmbientAnimationTheme input);
+  static bool FromMojom(MojomAnimationTheme input,
+                        ash::AmbientAnimationTheme* output);
+};
+
+template <>
 struct EnumTraits<ash::personalization_app::mojom::TopicSource,
                   ash::AmbientModeTopicSource> {
   using MojomTopicSource = ::ash::personalization_app::mojom::TopicSource;
diff --git a/ash/webui/personalization_app/personalization_app_ui.cc b/ash/webui/personalization_app/personalization_app_ui.cc
index 56ad6ab..373654ad 100644
--- a/ash/webui/personalization_app/personalization_app_ui.cc
+++ b/ash/webui/personalization_app/personalization_app_ui.cc
@@ -137,6 +137,16 @@
        IDS_PERONSONALIZATION_APP_AMBIENT_MODE_LAST_ART_ALBUM_MESSAGE},
       {"ambientModeArtAlbumDialogCloseButtonLabel",
        IDS_PERONSONALIZATION_APP_AMBIENT_MODE_ART_ALBUM_DIALOG_CLOSE_BUTTON_LABEL},
+      {"ambientModeAnimationTitle",
+       IDS_PERSONALIZATION_APP_AMBIENT_MODE_ANIMATION_TITLE},
+      {"ambientModeAnimationSlideshowLabel",
+       IDS_PERSONALIZATION_APP_AMBIENT_MODE_ANIMATION_SLIDESHOW_LABEL},
+      {"ambientModeAnimationFeelTheBreezeLabel",
+       IDS_PERSONALIZATION_APP_AMBIENT_MODE_ANIMATION_FEEL_THE_BREEZE_LABEL},
+      {"ambientModeAnimationFloatOnByLabel",
+       IDS_PERSONALIZATION_APP_AMBIENT_MODE_ANIMATION_FLOAT_ON_BY_LABEL},
+      {"ambientModeZeroStateMessage",
+       IDS_PERSONALIZATION_APP_AMBIENT_MODE_ZERO_STATE_MESSAGE},
 
       // Google Photos strings
       {"googlePhotosLabel", IDS_PERSONALIZATION_APP_GOOGLE_PHOTOS},
@@ -176,6 +186,9 @@
 
   source->AddBoolean("isDarkLightModeEnabled",
                      features::IsDarkLightModeEnabled());
+
+  source->AddBoolean("isAmbientModeAnimationEnabled",
+                     features::IsAmbientModeAnimationEnabled());
 }
 
 }  // namespace
diff --git a/ash/webui/personalization_app/resources/BUILD.gn b/ash/webui/personalization_app/resources/BUILD.gn
index 0e31474..8ee34af4 100644
--- a/ash/webui/personalization_app/resources/BUILD.gn
+++ b/ash/webui/personalization_app/resources/BUILD.gn
@@ -66,16 +66,18 @@
   "common/icons.ts",
   "common/styles.ts",
 
-  "trusted/ambient/album_item_element.ts",
   "trusted/ambient/album_list_element.ts",
   "trusted/ambient/albums_subpage_element.ts",
   "trusted/ambient/art_album_dialog_element.ts",
   "trusted/ambient/ambient_preview_element.ts",
   "trusted/ambient/ambient_subpage_element.ts",
   "trusted/ambient/ambient_weather_element.ts",
+  "trusted/ambient/animation_theme_item_element.ts",
+  "trusted/ambient/animation_theme_list_element.ts",
   "trusted/ambient/toggle_row_element.ts",
   "trusted/ambient/topic_source_item_element.ts",
   "trusted/ambient/topic_source_list_element.ts",
+  "trusted/ambient/zero_state_element.ts",
   "trusted/personalization_main_element.ts",
   "trusted/personalization_router_element.ts",
   "trusted/personalization_theme_element.ts",
@@ -108,9 +110,14 @@
 static_resource_files = [
   "icon_192.png",
 
+  "common/ambient_mode_disabled.svg",
+  "common/ambient_mode_disabled_dark.svg",
   "common/base.css",
+  "common/feel_the_breeze.png",
+  "common/float_on_by.png",
   "common/google_photos.svg",
   "common/no_images.svg",
+  "common/slideshow.png",
 
   "trusted/index.html",
 ]
diff --git a/ash/webui/personalization_app/resources/common/ambient_mode_disabled.svg b/ash/webui/personalization_app/resources/common/ambient_mode_disabled.svg
new file mode 100644
index 0000000..e6819dea
--- /dev/null
+++ b/ash/webui/personalization_app/resources/common/ambient_mode_disabled.svg
@@ -0,0 +1,89 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 viewBox="0 0 240 188" style="enable-background:new 0 0 240 188;" xml:space="preserve">
+<style type="text/css">
+	.st0{fill:#202124;}
+	.st1{fill:#404D64;}
+	.st2{fill:#1E3A5F;}
+	.st3{fill:#FCC934;}
+	.st4{fill:#5EF1F2;}
+	.st5{fill:none;}
+	.st6{fill:#F882FF;}
+	.st7{fill:#5184EB;}
+	.st8{fill:#669DF6;}
+	.st9{fill:#5BB974;}
+	.st10{fill:#EA4335;}
+	.st11{fill:none;stroke:#231F20;stroke-width:0.3;stroke-miterlimit:10;}
+	.st12{fill:#D2E3FC;}
+	.st13{fill:#4285F4;}
+	.st14{font-family:'GoogleSans-Regular';}
+	.st15{font-size:14px;}
+	.st16{fill:#8AB4F8;}
+	.st17{fill:#FBBC04;}
+	.st18{fill:#30E2EA;}
+	.st19{fill:#EE5FFA;}
+	.st20{fill:#FFFFFF;}
+	.st21{fill:#34A853;}
+</style>
+<g id="BG">
+</g>
+<g id="Layer_1">
+	<g>
+		<path class="st12" d="M202.4,155.3H40.8c-3.6,0-6.5-2.9-6.5-6.5V44.6c0-3.6,2.9-6.5,6.5-6.5h161.6c3.6,0,6.5,2.9,6.5,6.5v104.2
+			C208.9,152.4,206,155.3,202.4,155.3z"/>
+		<g>
+			<text transform="matrix(1 0 0 1 42.7482 148.5264)" class="st13 st14 st15">10:20</text>
+			<path class="st16" d="M176,148.8h-16c-2.4,0-4.3-1.9-4.3-4.3l0,0c0-2.4,1.9-4.3,4.3-4.3h16c2.4,0,4.3,1.9,4.3,4.3l0,0
+				C180.3,146.9,178.4,148.8,176,148.8z"/>
+			<path class="st16" d="M198.7,47.6h-21.5c-0.8,0-1.5-0.7-1.5-1.5l0,0c0-0.8,0.7-1.5,1.5-1.5h21.5c0.8,0,1.5,0.7,1.5,1.5l0,0
+				C200.2,46.9,199.5,47.6,198.7,47.6z"/>
+			<path class="st16" d="M195.9,148.8h-8.5c-2.4,0-4.3-1.9-4.3-4.3l0,0c0-2.4,1.9-4.3,4.3-4.3h8.5c2.4,0,4.3,1.9,4.3,4.3l0,0
+				C200.2,146.9,198.3,148.8,195.9,148.8z"/>
+		</g>
+	</g>
+	<path class="st17" d="M103.1,113.5l1-2.2c0.2-0.4,0.7-0.6,1.1-0.4l2.2,1c0.4,0.2,0.6,0.7,0.4,1.1l-1,2.2c-0.2,0.4-0.7,0.6-1.1,0.4
+		l-2.2-1C103.1,114.4,102.9,113.9,103.1,113.5z"/>
+	<path class="st18" d="M89.1,81.2l1-2.2c0.2-0.4,0.7-0.6,1.1-0.4l2.2,1c0.4,0.2,0.6,0.7,0.4,1.1l-1,2.2c-0.2,0.4-0.7,0.6-1.1,0.4
+		l-2.2-1C89.1,82.1,88.9,81.7,89.1,81.2z"/>
+	<path class="st5" d="M92.9,102.2c0.6-0.2,1.3-0.5,1.8-0.9c0.6-0.5,1.2-1,1.8-1.6c0.2-0.2,0.5-0.5,0.7-0.7c1.7-1.6,3.9-2.4,6.2-2.3
+		l2.4,0.1c0,0,0.1,0,0.1,0c0.7,0.1,1.3,0.1,2,0.1c1.5-0.1,2.8-0.6,4.1-1.4c3.5-2.3,4.9-6.9,3.1-10.8c-1-2.2-2.8-3.9-5.1-4.6
+		c-2.3-0.8-4.7-0.6-6.8,0.5c-1,0.6-2,1.3-2.7,2.2c-0.4,0.5-0.8,1.1-1.1,1.7c0,0,0,0.1,0,0.1l-1.3,2c-1.1,1.8-2.8,3-5,3.8
+		c-0.6,0.2-1.3,0.4-1.9,0.5c-1.3,0.3-2.4,0.6-3.4,1.2C91,94.4,92.9,98.2,92.9,102.2z"/>
+	<path class="st19" d="M116.6,83.9c-1.2-2.6-3.4-4.6-6.1-5.5c-2.7-0.9-5.7-0.7-8.2,0.7c-1.2,0.7-2.3,1.6-3.2,2.7c0,0,0,0,0,0
+		c-0.5,0.6-0.9,1.3-1.2,2c-0.9,1.4-1.1,1.8-1.2,2l0,0c-0.9,1.4-2.3,2.5-4.1,3.1c-0.6,0.2-1.2,0.3-1.8,0.5c-1.6,0.4-3.2,0.8-4.5,1.9
+		c0.5,0.3,1.1,0.6,1.5,1c1-0.6,2.1-0.9,3.4-1.2c0.6-0.2,1.3-0.3,1.9-0.5c2.2-0.7,3.9-2,5-3.8l1.3-2c0,0,0-0.1,0-0.1
+		c0.3-0.6,0.6-1.2,1.1-1.7c0.7-0.9,1.6-1.7,2.7-2.2c2.1-1.1,4.6-1.3,6.8-0.5c2.3,0.8,4.1,2.4,5.1,4.6c1.7,3.8,0.4,8.5-3.1,10.8
+		c-1.2,0.8-2.6,1.3-4.1,1.4c-0.7,0.1-1.3,0-2-0.1c0,0-0.1,0-0.1,0l-2.4-0.1c-2.3-0.1-4.5,0.7-6.2,2.3c-0.2,0.2-0.5,0.4-0.7,0.7
+		c-0.6,0.6-1.2,1.2-1.8,1.6c-0.6,0.4-1.2,0.7-1.8,0.9c0,0.6-0.1,1.2-0.1,1.8c1.1-0.2,2.1-0.7,3-1.4c0.7-0.5,1.4-1.2,2-1.8
+		c0.2-0.2,0.5-0.4,0.7-0.7c1.4-1.3,3.2-2,5-1.9l2.3,0.1c0.8,0.1,1.6,0.1,2.3,0.1c1.7-0.1,3.4-0.7,4.8-1.7
+		C117.1,94.1,118.7,88.5,116.6,83.9z M98.7,84.1L98.7,84.1L98.7,84.1z"/>
+	<path class="st7" d="M86.4,93.4c-1.2,1.7-1.4,3.9-0.5,5.8c0,0.1,0.1,0.2,0.1,0.3c0.8,1.5,2.2,2.5,3.8,2.9c1,0.2,2.1,0.2,3.1-0.1
+		c0-4-1.8-7.8-5.1-10.2C87.3,92.4,86.8,92.8,86.4,93.4z"/>
+	<path class="st13" d="M89.5,104c-2.1-0.5-3.9-1.8-4.9-3.7c-0.1-0.1-0.1-0.2-0.2-0.4c-1.1-2.4-0.9-5.3,0.7-7.4
+		c0.4-0.5,0.8-1,1.2-1.3c-0.4-0.2-0.8-0.4-1.3-0.6c-6.4-2.5-13.6,0.7-16,7.1s0.7,13.6,7.1,16c6.4,2.5,13.6-0.7,16-7.1
+		c0.3-0.9,0.6-1.7,0.7-2.6c-0.5,0.1-1.1,0.2-1.6,0.2C90.6,104.2,90,104.1,89.5,104z"/>
+	<path class="st20" d="M92.9,102.2c-1,0.3-2,0.4-3.1,0.1c-1.7-0.4-3-1.4-3.8-2.9c-0.1-0.1-0.1-0.2-0.1-0.3c-0.9-1.9-0.7-4.1,0.5-5.8
+		c0.4-0.6,0.9-1,1.4-1.3c-0.5-0.4-1-0.7-1.5-1c-0.4,0.4-0.9,0.8-1.2,1.3c-1.5,2.2-1.8,5-0.7,7.4c0.1,0.1,0.1,0.2,0.2,0.4
+		c1,1.9,2.8,3.3,4.9,3.7c0.5,0.1,1.1,0.2,1.6,0.2c0.6,0,1.1-0.1,1.6-0.2C92.8,103.4,92.9,102.8,92.9,102.2z"/>
+	<circle class="st21" cx="119.4" cy="104.8" r="3.5"/>
+	<circle class="st10" cx="107.3" cy="64.1" r="4.3"/>
+	<g>
+		<path class="st20" d="M138.3,128.4c-0.1,0-0.2,0-0.3-0.1l-6.6-1.4c-1.3-0.3-2.4-1.3-2.9-2.5l-2.1-6.4c-0.4-1.3-0.1-2.7,0.8-3.7
+			l4.5-5c0.9-1,2.3-1.5,3.6-1.2l6.6,1.4c1.3,0.3,2.4,1.3,2.9,2.5l2.1,6.4c0.4,1.3,0.1,2.7-0.8,3.7l-4.5,5
+			C140.7,128.1,139.5,128.6,138.3,128.4z M134.7,109.7c-0.7-0.1-1.4,0.2-1.9,0.7l-4.5,5c-0.5,0.6-0.7,1.4-0.4,2.1l2.1,6.4
+			c0.2,0.7,0.9,1.3,1.6,1.4l6.6,1.4c0.8,0.2,1.5-0.1,2.1-0.7l4.5-5c0.5-0.6,0.7-1.4,0.4-2.1l-2.1-6.4c-0.2-0.7-0.9-1.3-1.6-1.4
+			c0,0,0,0,0,0l-6.6-1.4C134.9,109.8,134.8,109.7,134.7,109.7z"/>
+	</g>
+	<g>
+		<path class="st5" d="M144.9,85l-1,5.3l5.1,4.6c3.6,3.2,9.1,2.9,12.3-0.7c1.5-1.7,2.3-4,2.2-6.3c-0.1-2.3-1.1-4.4-2.9-6L144,67.1
+			c-1.7-1.5-4-2.3-6.3-2.2c-2.3,0.1-4.4,1.1-6,2.9c-3.2,3.6-2.9,9.1,0.7,12.3l6,5.3l5-1.7C144.3,83.4,145.1,84.1,144.9,85z"/>
+		<path class="st21" d="M161.7,80.7l-16.6-14.8c-2.1-1.8-4.7-2.8-7.5-2.6c-2.8,0.1-5.3,1.4-7.1,3.4c-3.8,4.2-3.5,10.8,0.8,14.6
+			l5.3,4.7l1.8-0.6l-6-5.3c-3.6-3.2-3.9-8.7-0.7-12.3c1.5-1.7,3.7-2.7,6-2.9c2.3-0.1,4.5,0.7,6.3,2.2L160.6,82
+			c1.7,1.5,2.7,3.7,2.9,6c0.1,2.3-0.7,4.5-2.2,6.3c-3.2,3.6-8.7,3.9-12.3,0.7l-5.1-4.6l-0.4,1.9l4.4,3.9c2,1.8,4.4,2.6,6.9,2.6
+			c2.8,0,5.7-1.2,7.7-3.4C166.3,91.1,166,84.5,161.7,80.7z"/>
+		<path class="st17" d="M125.2,89.9c-0.9,0.3-1.1,1.4-0.4,2l14.5,12.6c0.7,0.6,1.7,0.2,1.9-0.7l2.3-11.7l-6.9-6.2L125.2,89.9z"/>
+		<path class="st17" d="M144.9,85c0.2-0.9-0.7-1.6-1.5-1.3l-5,1.7l5.5,4.9L144.9,85z"/>
+		<polygon class="st10" points="138.4,85.4 136.6,86 143.5,92.2 143.9,90.3 		"/>
+	</g>
+</g>
+</svg>
diff --git a/ash/webui/personalization_app/resources/common/ambient_mode_disabled_dark.svg b/ash/webui/personalization_app/resources/common/ambient_mode_disabled_dark.svg
new file mode 100644
index 0000000..b964466
--- /dev/null
+++ b/ash/webui/personalization_app/resources/common/ambient_mode_disabled_dark.svg
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 viewBox="0 0 240 188" style="enable-background:new 0 0 240 188;" xml:space="preserve">
+<style type="text/css">
+	.st0{fill:#404D64;}
+	.st1{fill:#669DF6;}
+	.st2{font-family:'GoogleSans-Regular';}
+	.st3{font-size:14px;}
+	.st4{fill:#1E3A5F;}
+	.st5{fill:#FCC934;}
+	.st6{fill:#5EF1F2;}
+	.st7{fill:none;}
+	.st8{fill:#F882FF;}
+	.st9{fill:#5184EB;}
+	.st10{fill:#202124;}
+	.st11{fill:#5BB974;}
+	.st12{fill:#EA4335;}
+	.st13{fill:none;stroke:#231F20;stroke-width:0.3;stroke-miterlimit:10;}
+	.st14{fill:#D2E3FC;}
+	.st15{fill:#8AB4F8;}
+	.st16{fill:#FBBC04;}
+	.st17{fill:#30E2EA;}
+	.st18{fill:#EE5FFA;}
+	.st19{fill:#4285F4;}
+	.st20{fill:#FFFFFF;}
+	.st21{fill:#34A853;}
+</style>
+<g id="BG">
+</g>
+<g id="Layer_1">
+	<path class="st0" d="M202.4,155.3H40.8c-3.6,0-6.5-2.9-6.5-6.5V44.6c0-3.6,2.9-6.5,6.5-6.5h161.6c3.6,0,6.5,2.9,6.5,6.5v104.2
+		C208.9,152.4,206,155.3,202.4,155.3z"/>
+	<text transform="matrix(1 0 0 1 42.7482 148.5264)" class="st1 st2 st3">10:20</text>
+	<path class="st4" d="M176,148.8h-16c-2.4,0-4.3-1.9-4.3-4.3l0,0c0-2.4,1.9-4.3,4.3-4.3h16c2.4,0,4.3,1.9,4.3,4.3l0,0
+		C180.3,146.9,178.4,148.8,176,148.8z"/>
+	<path class="st4" d="M198.7,47.6h-21.5c-0.8,0-1.5-0.7-1.5-1.5l0,0c0-0.8,0.7-1.5,1.5-1.5h21.5c0.8,0,1.5,0.7,1.5,1.5l0,0
+		C200.2,46.9,199.5,47.6,198.7,47.6z"/>
+	<path class="st4" d="M195.9,148.8h-8.5c-2.4,0-4.3-1.9-4.3-4.3l0,0c0-2.4,1.9-4.3,4.3-4.3h8.5c2.4,0,4.3,1.9,4.3,4.3l0,0
+		C200.2,146.9,198.3,148.8,195.9,148.8z"/>
+	<path class="st5" d="M103.1,113.5l1-2.2c0.2-0.4,0.7-0.6,1.1-0.4l2.2,1c0.4,0.2,0.6,0.7,0.4,1.1l-1,2.2c-0.2,0.4-0.7,0.6-1.1,0.4
+		l-2.2-1C103.1,114.4,102.9,113.9,103.1,113.5z"/>
+	<path class="st6" d="M89.1,81.2l1-2.2c0.2-0.4,0.7-0.6,1.1-0.4l2.2,1c0.4,0.2,0.6,0.7,0.4,1.1l-1,2.2c-0.2,0.4-0.7,0.6-1.1,0.4
+		l-2.2-1C89.1,82.1,88.9,81.7,89.1,81.2z"/>
+	<path class="st7" d="M92.9,102.2c0.6-0.2,1.3-0.5,1.8-0.9c0.6-0.5,1.2-1,1.8-1.6c0.2-0.2,0.5-0.5,0.7-0.7c1.7-1.6,3.9-2.4,6.2-2.3
+		l2.4,0.1c0,0,0.1,0,0.1,0c0.7,0.1,1.3,0.1,2,0.1c1.5-0.1,2.8-0.6,4.1-1.4c3.5-2.3,4.9-6.9,3.1-10.8c-1-2.2-2.8-3.9-5.1-4.6
+		c-2.3-0.8-4.7-0.6-6.8,0.5c-1,0.6-2,1.3-2.7,2.2c-0.4,0.5-0.8,1.1-1.1,1.7c0,0,0,0.1,0,0.1l-1.3,2c-1.1,1.8-2.8,3-5,3.8
+		c-0.6,0.2-1.3,0.4-1.9,0.5c-1.3,0.3-2.4,0.6-3.4,1.2C91,94.4,92.9,98.2,92.9,102.2z"/>
+	<path class="st8" d="M116.6,83.9c-1.2-2.6-3.4-4.6-6.1-5.5c-2.7-0.9-5.7-0.7-8.2,0.7c-1.2,0.7-2.3,1.6-3.2,2.7c0,0,0,0,0,0
+		c-0.5,0.6-0.9,1.3-1.2,2c-0.9,1.4-1.1,1.8-1.2,2l0,0c-0.9,1.4-2.3,2.5-4.1,3.1c-0.6,0.2-1.2,0.3-1.8,0.5c-1.6,0.4-3.2,0.8-4.5,1.9
+		c0.5,0.3,1.1,0.6,1.5,1c1-0.6,2.1-0.9,3.4-1.2c0.6-0.2,1.3-0.3,1.9-0.5c2.2-0.7,3.9-2,5-3.8l1.3-2c0,0,0-0.1,0-0.1
+		c0.3-0.6,0.6-1.2,1.1-1.7c0.7-0.9,1.6-1.7,2.7-2.2c2.1-1.1,4.6-1.3,6.8-0.5c2.3,0.8,4.1,2.4,5.1,4.6c1.7,3.8,0.4,8.5-3.1,10.8
+		c-1.2,0.8-2.6,1.3-4.1,1.4c-0.7,0.1-1.3,0-2-0.1c0,0-0.1,0-0.1,0l-2.4-0.1c-2.3-0.1-4.5,0.7-6.2,2.3c-0.2,0.2-0.5,0.4-0.7,0.7
+		c-0.6,0.6-1.2,1.2-1.8,1.6c-0.6,0.4-1.2,0.7-1.8,0.9c0,0.6-0.1,1.2-0.1,1.8c1.1-0.2,2.1-0.7,3-1.4c0.7-0.5,1.4-1.2,2-1.8
+		c0.2-0.2,0.5-0.4,0.7-0.7c1.4-1.3,3.2-2,5-1.9l2.3,0.1c0.8,0.1,1.6,0.1,2.3,0.1c1.7-0.1,3.4-0.7,4.8-1.7
+		C117.1,94.1,118.7,88.5,116.6,83.9z M98.7,84.1L98.7,84.1L98.7,84.1z"/>
+	<path class="st9" d="M86.4,93.4c-1.2,1.7-1.4,3.9-0.5,5.8c0,0.1,0.1,0.2,0.1,0.3c0.8,1.5,2.2,2.5,3.8,2.9c1,0.2,2.1,0.2,3.1-0.1
+		c0-4-1.8-7.8-5.1-10.2C87.3,92.4,86.8,92.8,86.4,93.4z"/>
+	<path class="st1" d="M89.5,104c-2.1-0.5-3.9-1.8-4.9-3.7c-0.1-0.1-0.1-0.2-0.2-0.4c-1.1-2.4-0.9-5.3,0.7-7.4c0.4-0.5,0.8-1,1.2-1.3
+		c-0.4-0.2-0.8-0.4-1.3-0.6c-6.4-2.5-13.6,0.7-16,7.1s0.7,13.6,7.1,16c6.4,2.5,13.6-0.7,16-7.1c0.3-0.9,0.6-1.7,0.7-2.6
+		c-0.5,0.1-1.1,0.2-1.6,0.2C90.6,104.2,90,104.1,89.5,104z"/>
+	<path class="st10" d="M92.9,102.2c-1,0.3-2,0.4-3.1,0.1c-1.7-0.4-3-1.4-3.8-2.9c-0.1-0.1-0.1-0.2-0.1-0.3c-0.9-1.9-0.7-4.1,0.5-5.8
+		c0.4-0.6,0.9-1,1.4-1.3c-0.5-0.4-1-0.7-1.5-1c-0.4,0.4-0.9,0.8-1.2,1.3c-1.5,2.2-1.8,5-0.7,7.4c0.1,0.1,0.1,0.2,0.2,0.4
+		c1,1.9,2.8,3.3,4.9,3.7c0.5,0.1,1.1,0.2,1.6,0.2c0.6,0,1.1-0.1,1.6-0.2C92.8,103.4,92.9,102.8,92.9,102.2z"/>
+	<circle class="st11" cx="119.4" cy="104.8" r="3.5"/>
+	<circle class="st12" cx="107.3" cy="64.1" r="4.3"/>
+	<g>
+		<path class="st10" d="M138.3,128.4c-0.1,0-0.2,0-0.3-0.1l-6.6-1.4c-1.3-0.3-2.4-1.3-2.9-2.5l-2.1-6.4c-0.4-1.3-0.1-2.7,0.8-3.7
+			l4.5-5c0.9-1,2.3-1.5,3.6-1.2l6.6,1.4c1.3,0.3,2.4,1.3,2.9,2.5l2.1,6.4c0.4,1.3,0.1,2.7-0.8,3.7l-4.5,5
+			C140.7,128.1,139.5,128.6,138.3,128.4z M134.7,109.7c-0.7-0.1-1.4,0.2-1.9,0.7l-4.5,5c-0.5,0.6-0.7,1.4-0.4,2.1l2.1,6.4
+			c0.2,0.7,0.9,1.3,1.6,1.4l6.6,1.4c0.8,0.2,1.5-0.1,2.1-0.7l4.5-5c0.5-0.6,0.7-1.4,0.4-2.1l-2.1-6.4c-0.2-0.7-0.9-1.3-1.6-1.4
+			c0,0,0,0,0,0l-6.6-1.4C134.9,109.8,134.8,109.7,134.7,109.7z"/>
+	</g>
+	<g>
+		<path class="st7" d="M144.9,85l-1,5.3l5.1,4.6c3.6,3.2,9.1,2.9,12.3-0.7c1.5-1.7,2.3-4,2.2-6.3c-0.1-2.3-1.1-4.4-2.9-6L144,67.1
+			c-1.7-1.5-4-2.3-6.3-2.2c-2.3,0.1-4.4,1.1-6,2.9c-3.2,3.6-2.9,9.1,0.7,12.3l6,5.3l5-1.7C144.3,83.4,145.1,84.1,144.9,85z"/>
+		<path class="st11" d="M161.7,80.7l-16.6-14.8c-2.1-1.8-4.7-2.8-7.5-2.6c-2.8,0.1-5.3,1.4-7.1,3.4c-3.8,4.2-3.5,10.8,0.8,14.6
+			l5.3,4.7l1.8-0.6l-6-5.3c-3.6-3.2-3.9-8.7-0.7-12.3c1.5-1.7,3.7-2.7,6-2.9c2.3-0.1,4.5,0.7,6.3,2.2L160.6,82
+			c1.7,1.5,2.7,3.7,2.9,6c0.1,2.3-0.7,4.5-2.2,6.3c-3.2,3.6-8.7,3.9-12.3,0.7l-5.1-4.6l-0.4,1.9l4.4,3.9c2,1.8,4.4,2.6,6.9,2.6
+			c2.8,0,5.7-1.2,7.7-3.4C166.3,91.1,166,84.5,161.7,80.7z"/>
+		<path class="st5" d="M125.2,89.9c-0.9,0.3-1.1,1.4-0.4,2l14.5,12.6c0.7,0.6,1.7,0.2,1.9-0.7l2.3-11.7l-6.9-6.2L125.2,89.9z"/>
+		<path class="st5" d="M144.9,85c0.2-0.9-0.7-1.6-1.5-1.3l-5,1.7l5.5,4.9L144.9,85z"/>
+		<polygon class="st12" points="138.4,85.4 136.6,86 143.5,92.2 143.9,90.3 		"/>
+	</g>
+</g>
+</svg>
diff --git a/ash/webui/personalization_app/resources/common/feel_the_breeze.png b/ash/webui/personalization_app/resources/common/feel_the_breeze.png
new file mode 100644
index 0000000..0ee49d7
--- /dev/null
+++ b/ash/webui/personalization_app/resources/common/feel_the_breeze.png
Binary files differ
diff --git a/ash/webui/personalization_app/resources/common/float_on_by.png b/ash/webui/personalization_app/resources/common/float_on_by.png
new file mode 100644
index 0000000..ce246b5
--- /dev/null
+++ b/ash/webui/personalization_app/resources/common/float_on_by.png
Binary files differ
diff --git a/ash/webui/personalization_app/resources/common/slideshow.png b/ash/webui/personalization_app/resources/common/slideshow.png
new file mode 100644
index 0000000..239ed6e
--- /dev/null
+++ b/ash/webui/personalization_app/resources/common/slideshow.png
Binary files differ
diff --git a/ash/webui/personalization_app/resources/trusted/ambient/album_item_element.html b/ash/webui/personalization_app/resources/trusted/ambient/album_item_element.html
deleted file mode 100644
index 3f5901e..0000000
--- a/ash/webui/personalization_app/resources/trusted/ambient/album_item_element.html
+++ /dev/null
@@ -1,119 +0,0 @@
-<style include="cr-shared-style">
-  :host(:not([disabled])) {
-    cursor: default;
-    display: block;
-    outline: none;
-  }
-
-  #albumContainer {
-    /* For a grid iron-list the list template item must have both a fixed
-       width and height. */
-    height: 236px;
-    line-height: 20px;
-  }
-
-  #albumContainer.personal-album {
-    margin: 8px 0;
-    width: 184px;
-  }
-
-  #albumContainer.art-album {
-    margin: 8px auto;
-    width: 280px;
-  }
-
-  #albumTitle {
-    color: var(--cros-text-color-primary);
-  }
-
-  #albumDescription {
-    color: var(--cros-text-color-secondary);
-  }
-
-  #imageContainer {
-    background-color: var(--cros-highlight-color);
-    border-radius: 8px;
-    display: block;
-    margin: 0 12px;
-    position: relative;
-  }
-
-  #imageContainer.personal-album {
-    height: 160px;
-    width: 160px;
-  }
-
-  #imageContainer.art-album {
-    height: 160px;
-    width: 256px;
-  }
-
-  #albumInfo {
-    margin: 16px 12px 0 12px;
-  }
-
-  #image {
-    border-color: var(--cros-separator-color);
-    border-radius: 8px;
-    display: block;
-    height: 160px;
-    object-fit: cover;
-    position: absolute;
-    transform: scale(1.0);
-    transition: transform 240ms;
-  }
-
-  #image.personal-album {
-    width: 160px;
-  }
-
-  #image.art-album {
-    width: 256px;
-  }
-
-  .check {
-    display: block;
-    position: absolute;
-    z-index: 1;
-  }
-
-  .check.personal-album {
-    left: 134px;
-    top: 6px;
-  }
-
-  .check.art-album {
-    left: 230px;
-    top: 0;
-  }
-
-  :host([checked]) #image {
-    transition: transform 240ms;
-  }
-
-  :host([checked]) #image.personal-album {
-    transform: scale(0.8);
-  }
-
-  :host([checked]) #image.art-album {
-    transform: scale(0.875);
-  }
-</style>
-<div id="albumContainer" class$="[[computeClass_(topicSource)]]">
-  <div id="imageContainer" class$="[[computeClass_(topicSource)]]"
-      aria-hidden="true">
-    <img id="image" class$="[[computeClass_(topicSource)]]" actionable
-        is="cr-auto-img" auto-src="[[album.url.url]]" with-cookies
-        on-click="onImageClick_">
-    <iron-icon icon="personalization:checkmark"
-        class$="check [[computeClass_(topicSource)]]"
-        hidden="[[!album.checked]]">
-    </iron-icon>
-  </div>
-
-  <div id="albumInfo" class$="[[computeClass_(topicSource)]]"
-      aria-hidden="true">
-    <div>[[album.title]]</div>
-    <div class="cr-secondary-text">[[itemDescription_]]</div>
-  </div>
-</div>
diff --git a/ash/webui/personalization_app/resources/trusted/ambient/album_item_element.ts b/ash/webui/personalization_app/resources/trusted/ambient/album_item_element.ts
deleted file mode 100644
index b26c255..0000000
--- a/ash/webui/personalization_app/resources/trusted/ambient/album_item_element.ts
+++ /dev/null
@@ -1,139 +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.
-
-/**
- * @fileoverview The element for displaying an album.
- */
-
-import 'chrome://resources/cr_elements/cr_auto_img/cr_auto_img.js';
-import 'chrome://resources/cr_elements/shared_vars_css.m.js';
-
-import {html} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
-
-import {isSelectionEvent} from '../../common/utils.js';
-import {AmbientModeAlbum, TopicSource} from '../personalization_app.mojom-webui.js';
-import {WithPersonalizationStore} from '../personalization_store.js';
-import {getPhotoCount, isRecentHighlightsAlbum} from '../utils.js';
-
-export type AlbumSelectedChangedEvent = CustomEvent<{album: AmbientModeAlbum}>;
-
-declare global {
-  interface HTMLElementEventMap {
-    'album_selected_changed': AlbumSelectedChangedEvent;
-  }
-}
-
-export interface AlbumItem {
-  $: {image: HTMLElement;};
-}
-
-export class AlbumItem extends WithPersonalizationStore {
-  static get is() {
-    return 'album-item';
-  }
-
-  static get template() {
-    return html`{__html_template__}`;
-  }
-
-  static get properties() {
-    return {
-      album: {
-        type: AmbientModeAlbum,
-        value: null,
-      },
-      topicSource: TopicSource,
-      checked: {
-        type: Boolean,
-        value: false,
-        reflectToAttribute: true,
-        notify: true,
-      },
-      ariaLabel: {
-        type: String,
-        computed: 'computeAriaLabel_(album, checked, topicSource)',
-        reflectToAttribute: true,
-      },
-      itemDescription_: {
-        type: String,
-        computed: 'computeItemDescription_(album.*, topicSource)',
-      },
-    };
-  }
-
-  album: AmbientModeAlbum|null;
-  topicSource: TopicSource;
-  checked: boolean;
-  ariaLabel: string;
-  private itemDescription_: string;
-
-  ready() {
-    super.ready();
-
-    this.addEventListener('keydown', this.onItemSelected_.bind(this));
-  }
-
-  private computeClass_(): string {
-    return this.topicSource === TopicSource.kGooglePhotos ? 'personal-album' :
-                                                            'art-album';
-  }
-
-  private computeItemDescription_(): string {
-    if (!this.album) {
-      return '';
-    }
-
-    if (this.topicSource === TopicSource.kGooglePhotos) {
-      if (isRecentHighlightsAlbum(this.album)) {
-        return this.i18n('ambientModeAlbumsSubpageRecentHighlightsDesc');
-      }
-      return getPhotoCount(this.album);
-    }
-
-    if (this.topicSource === TopicSource.kArtGallery) {
-      return this.album.description;
-    }
-
-    return '';
-  }
-
-  private computeAriaLabel_(): string {
-    if (!this.album) {
-      return '';
-    }
-
-    if (this.album.checked) {
-      return this.i18n(
-          'ambientModeAlbumsSubpageAlbumSelected', this.album.title,
-          this.itemDescription_);
-    }
-
-    return this.i18n(
-        'ambientModeAlbumsSubpageAlbumUnselected', this.album.title,
-        this.itemDescription_);
-  }
-
-  private onImageClick_(event: Event) {
-    this.onItemSelected_(event);
-  }
-
-  private onItemSelected_(event: Event) {
-    if (!this.album) {
-      return;
-    }
-
-    if (!isSelectionEvent(event)) {
-      return;
-    }
-
-    event.preventDefault();
-    event.stopPropagation();
-    this.checked = !this.checked;
-    this.dispatchEvent(new CustomEvent(
-        'album_selected_changed',
-        {bubbles: true, composed: true, detail: {album: this.album}}));
-  }
-}
-
-customElements.define(AlbumItem.is, AlbumItem);
diff --git a/ash/webui/personalization_app/resources/trusted/ambient/album_list_element.html b/ash/webui/personalization_app/resources/trusted/ambient/album_list_element.html
index 4fd6ac67..9caa3dd5 100644
--- a/ash/webui/personalization_app/resources/trusted/ambient/album_list_element.html
+++ b/ash/webui/personalization_app/resources/trusted/ambient/album_list_element.html
@@ -1,6 +1,6 @@
-<style include="cr-shared-style">
+<style include="trusted-style common-style">
   :host {
-    display: block;
+    overflow: hidden
   }
 
   paper-spinner-lite {
@@ -10,15 +10,23 @@
     width: 28px;
   }
 
-  iron-list > :focus {
-    background-color: var(--cr-focused-item-color);
+  iron-list {
+    height: 100%;
+    width: 100%;
   }
 </style>
-<iron-list scroll-target="document" grid items="[[albums]]">
+
+<iron-list id="grid" items="[[albums]]" as="album" grid>
   <template>
-    <album-item album="[[item]]" checked="{{item.checked}}"
-        topic-source="[[topicSource]]" tabindex$="[[tabIndex]]"
-        role="checkbox">
-    </album-item>
+    <wallpaper-grid-item
+      class="album"
+      image-src="[[album.url.url]]"
+      on-click="onAlbumSelected_"
+      on-keypress="onAlbumSelected_"
+      primary-text="[[album.title]]"
+      secondary-text="[[getSecondaryText_(album, topicSource)]]"
+      selected$="[[isAlbumSelected_(album, albums)]]"
+      tabindex$="[[tabIndex]]">
+    </wallpaper-grid-item>
   </template>
 </iron-list>
diff --git a/ash/webui/personalization_app/resources/trusted/ambient/album_list_element.ts b/ash/webui/personalization_app/resources/trusted/ambient/album_list_element.ts
index 83dae4a..a530e0d02 100644
--- a/ash/webui/personalization_app/resources/trusted/ambient/album_list_element.ts
+++ b/ash/webui/personalization_app/resources/trusted/ambient/album_list_element.ts
@@ -6,16 +6,31 @@
  * @fileoverview The element for displaying a list of albums.
  */
 
-import 'chrome://resources/cr_elements/shared_style_css.m.js';
-import 'chrome://resources/cr_elements/shared_vars_css.m.js';
 import 'chrome://resources/polymer/v3_0/iron-list/iron-list.js';
-import './album_item_element.js';
+import '../../common/styles.js';
 
-import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {assert} from 'chrome://resources/js/assert.m.js';
+import {IronListElement} from 'chrome://resources/polymer/v3_0/iron-list/iron-list.js';
+import {afterNextRender, html} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
+import {getCountText, isSelectionEvent} from '../../common/utils.js';
 import {AmbientModeAlbum, TopicSource} from '../personalization_app.mojom-webui.js';
+import {WithPersonalizationStore} from '../personalization_store.js';
+import {isRecentHighlightsAlbum} from '../utils.js';
 
-export class AlbumList extends PolymerElement {
+export interface AlbumList {
+  $: {grid: IronListElement;};
+}
+
+export type AlbumSelectedChangedEvent = CustomEvent<{album: AmbientModeAlbum}>;
+
+declare global {
+  interface HTMLElementEventMap {
+    'album_selected_changed': AlbumSelectedChangedEvent;
+  }
+}
+
+export class AlbumList extends WithPersonalizationStore {
   static get is() {
     return 'album-list';
   }
@@ -36,6 +51,48 @@
 
   topicSource: TopicSource;
   albums: AmbientModeAlbum[]|null;
+
+  override ready() {
+    super.ready();
+    /** When element is ready, force rendering iron-list */
+    afterNextRender(this, () => this.$.grid.fire('iron-resize'));
+  }
+
+  /** Invoked on selection of an album. */
+  private onAlbumSelected_(e: Event&{model: {album: AmbientModeAlbum}}) {
+    assert(e.model.album);
+    if (isSelectionEvent(e)) {
+      e.model.album.checked = !e.model.album.checked;
+      this.dispatchEvent(new CustomEvent(
+          'album_selected_changed',
+          {bubbles: true, composed: true, detail: {album: e.model.album}}));
+    }
+  }
+
+  private isAlbumSelected_(
+      changedAlbum: AmbientModeAlbum|null,
+      albums: AmbientModeAlbum[]|null): boolean {
+    const album = albums!.find(album => album.id === changedAlbum?.id);
+    return album?.checked || false;
+  }
+
+  /** Returns the secondary text to display for the specified |album|. */
+  private getSecondaryText_(
+      album: AmbientModeAlbum|null, topicSource: TopicSource): string {
+    if (!album) {
+      return '';
+    }
+    if (topicSource === TopicSource.kGooglePhotos) {
+      if (isRecentHighlightsAlbum(album)) {
+        return this.i18n('ambientModeAlbumsSubpageRecentHighlightsDesc');
+      }
+      return getCountText(album.numberOfPhotos);
+    }
+    if (this.topicSource === TopicSource.kArtGallery) {
+      return album.description;
+    }
+    return '';
+  }
 }
 
 customElements.define(AlbumList.is, AlbumList);
diff --git a/ash/webui/personalization_app/resources/trusted/ambient/albums_subpage_element.html b/ash/webui/personalization_app/resources/trusted/ambient/albums_subpage_element.html
index 1e2ac9f5..a141b26 100644
--- a/ash/webui/personalization_app/resources/trusted/ambient/albums_subpage_element.html
+++ b/ash/webui/personalization_app/resources/trusted/ambient/albums_subpage_element.html
@@ -1,8 +1,8 @@
 <style include="cr-shared-style">
   #pageDescription {
     display: flex;
+    margin-inline-start: 8px;
     min-height: 32px;
-    padding: 0 var(--cr-section-padding);
   }
   paper-spinner-lite {
     display: block;
diff --git a/ash/webui/personalization_app/resources/trusted/ambient/albums_subpage_element.ts b/ash/webui/personalization_app/resources/trusted/ambient/albums_subpage_element.ts
index 8df577a2..3334862 100644
--- a/ash/webui/personalization_app/resources/trusted/ambient/albums_subpage_element.ts
+++ b/ash/webui/personalization_app/resources/trusted/ambient/albums_subpage_element.ts
@@ -22,7 +22,7 @@
 import {Paths, PersonalizationRouter} from '../personalization_router_element.js';
 import {WithPersonalizationStore} from '../personalization_store.js';
 
-import {AlbumSelectedChangedEvent} from './album_item_element.js';
+import {AlbumSelectedChangedEvent} from './album_list_element.js';
 import {setAlbumSelected} from './ambient_controller.js';
 import {getAmbientProvider} from './ambient_interface_provider.js';
 
@@ -62,7 +62,7 @@
 
   private showArtAlbumDialog_: boolean;
 
-  ready() {
+  override ready() {
     super.ready();
     this.addEventListener(
         'album_selected_changed', this.onAlbumSelectedChanged_.bind(this));
diff --git a/ash/webui/personalization_app/resources/trusted/ambient/ambient_actions.ts b/ash/webui/personalization_app/resources/trusted/ambient/ambient_actions.ts
index d8bd9ef..4c1e1ab5 100644
--- a/ash/webui/personalization_app/resources/trusted/ambient/ambient_actions.ts
+++ b/ash/webui/personalization_app/resources/trusted/ambient/ambient_actions.ts
@@ -4,7 +4,7 @@
 
 import {Action} from 'chrome://resources/js/cr/ui/store.js';
 
-import {AmbientModeAlbum, TemperatureUnit, TopicSource} from '../personalization_app.mojom-webui.js';
+import {AmbientModeAlbum, AnimationTheme, TemperatureUnit, TopicSource} from '../personalization_app.mojom-webui.js';
 
 /**
  * @fileoverview Defines the actions to change ambient state.
@@ -14,12 +14,14 @@
   SET_ALBUMS = 'set_albums',
   SET_ALBUM_SELECTED = 'set_album_selected',
   SET_AMBIENT_MODE_ENABLED = 'set_ambient_mode_enabled',
+  SET_ANIMATION_THEME = 'set_animation_theme',
   SET_TEMPERATURE_UNIT = 'set_temperature_unit',
   SET_TOPIC_SOURCE = 'set_topic_source',
 }
 
-export type AmbientActions = SetAlbumsAction|SetAlbumSelectedAction|
-    SetAmbientModeEnabledAction|SetTopicSourceAction|SetTemperatureUnitAction;
+export type AmbientActions =
+    SetAlbumsAction|SetAlbumSelectedAction|SetAmbientModeEnabledAction|
+    SetAnimationThemeAction|SetTopicSourceAction|SetTemperatureUnitAction;
 
 export type SetAlbumsAction = Action&{
   name: AmbientActionName.SET_ALBUMS;
@@ -35,6 +37,11 @@
   enabled: boolean;
 };
 
+export type SetAnimationThemeAction = Action&{
+  name: AmbientActionName.SET_ANIMATION_THEME;
+  animationTheme: AnimationTheme;
+}
+
 export type SetTemperatureUnitAction = Action&{
   name: AmbientActionName.SET_TEMPERATURE_UNIT;
   temperatureUnit: TemperatureUnit;
@@ -65,6 +72,14 @@
 }
 
 /**
+ * Sets the current value of the animation theme.
+ */
+export function setAnimationThemeAction(animationTheme: AnimationTheme):
+    SetAnimationThemeAction {
+  return {name: AmbientActionName.SET_ANIMATION_THEME, animationTheme};
+}
+
+/**
  * Sets the current value of the topic source.
  */
 export function setTopicSourceAction(topicSource: TopicSource):
diff --git a/ash/webui/personalization_app/resources/trusted/ambient/ambient_controller.ts b/ash/webui/personalization_app/resources/trusted/ambient/ambient_controller.ts
index 82134d7..6474fc8 100644
--- a/ash/webui/personalization_app/resources/trusted/ambient/ambient_controller.ts
+++ b/ash/webui/personalization_app/resources/trusted/ambient/ambient_controller.ts
@@ -2,10 +2,10 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import {AmbientModeAlbum, AmbientProviderInterface, TemperatureUnit, TopicSource} from '../personalization_app.mojom-webui.js';
+import {AmbientModeAlbum, AmbientProviderInterface, AnimationTheme, TemperatureUnit, TopicSource} from '../personalization_app.mojom-webui.js';
 import {PersonalizationStore} from '../personalization_store.js';
 
-import {setAlbumSelectedAction, setAmbientModeEnabledAction, setTemperatureUnitAction, setTopicSourceAction} from './ambient_actions.js';
+import {setAlbumSelectedAction, setAmbientModeEnabledAction, setAnimationThemeAction, setTemperatureUnitAction, setTopicSourceAction} from './ambient_actions.js';
 
 /**
  * @fileoverview contains all of the functions to interact with ambient mode
@@ -24,6 +24,15 @@
   store.dispatch(setAmbientModeEnabledAction(ambientModeEnabled));
 }
 
+// Set the animation theme.
+export function setAnimationTheme(
+    animationTheme: AnimationTheme, provider: AmbientProviderInterface,
+    store: PersonalizationStore): void {
+  provider.setAnimationTheme(animationTheme);
+
+  store.dispatch(setAnimationThemeAction(animationTheme));
+}
+
 // Set ambient mode topic source.
 export function setTopicSource(
     topicSource: TopicSource, provider: AmbientProviderInterface,
diff --git a/ash/webui/personalization_app/resources/trusted/ambient/ambient_observer.ts b/ash/webui/personalization_app/resources/trusted/ambient/ambient_observer.ts
index 0777558d1..2d36a883 100644
--- a/ash/webui/personalization_app/resources/trusted/ambient/ambient_observer.ts
+++ b/ash/webui/personalization_app/resources/trusted/ambient/ambient_observer.ts
@@ -2,10 +2,10 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import {AmbientModeAlbum, AmbientObserverInterface, AmbientObserverReceiver, AmbientProviderInterface, TemperatureUnit, TopicSource} from '../personalization_app.mojom-webui.js';
+import {AmbientModeAlbum, AmbientObserverInterface, AmbientObserverReceiver, AmbientProviderInterface, AnimationTheme, TemperatureUnit, TopicSource} from '../personalization_app.mojom-webui.js';
 import {PersonalizationStore} from '../personalization_store.js';
 
-import {setAlbumsAction, setAmbientModeEnabledAction, setTemperatureUnitAction, setTopicSourceAction} from './ambient_actions.js';
+import {setAlbumsAction, setAmbientModeEnabledAction, setAnimationThemeAction, setTemperatureUnitAction, setTopicSourceAction} from './ambient_actions.js';
 import {getAmbientProvider} from './ambient_interface_provider.js';
 
 /** @fileoverview listens for updates on color mode changes. */
@@ -43,6 +43,11 @@
     store.dispatch(setAmbientModeEnabledAction(ambientModeEnabled));
   }
 
+  onAnimationThemeChanged(animationTheme: AnimationTheme): void {
+    const store = PersonalizationStore.getInstance();
+    store.dispatch(setAnimationThemeAction(animationTheme));
+  }
+
   onTopicSourceChanged(topicSource: TopicSource) {
     const store = PersonalizationStore.getInstance();
     store.dispatch(setTopicSourceAction(topicSource));
diff --git a/ash/webui/personalization_app/resources/trusted/ambient/ambient_preview_element.html b/ash/webui/personalization_app/resources/trusted/ambient/ambient_preview_element.html
index f423663..6f6e7a71 100644
--- a/ash/webui/personalization_app/resources/trusted/ambient/ambient_preview_element.html
+++ b/ash/webui/personalization_app/resources/trusted/ambient/ambient_preview_element.html
@@ -80,7 +80,7 @@
   </template>
   <template is="dom-if" if="[[previewAlbum_]]">
     <div id="imageContainer" class="preview-image-container" aria-hidden="true">
-      <img class="preview-image" is="cr-auto-img" 
+      <img class="preview-image" is="cr-auto-img"
           auto-src="[[getPreviewImage_(previewAlbum_)]]" with-cookies>
     </div>
     <h2 id="textContainer" class="preview-text-container album-info-mainpage album-info-subpage">
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 32bd6b1a..308058d 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
@@ -49,12 +49,12 @@
   private topicSource_: TopicSource|null;
   private previewAlbum_: AmbientModeAlbum|null;
 
-  ready() {
+  override ready() {
     super.ready();
     AmbientObserver.initAmbientObserverIfNeeded();
   }
 
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
     this.watch('albums_', state => state.ambient.albums);
     this.watch('topicSource_', state => state.ambient.topicSource);
diff --git a/ash/webui/personalization_app/resources/trusted/ambient/ambient_reducers.ts b/ash/webui/personalization_app/resources/trusted/ambient/ambient_reducers.ts
index 4e22619..abbf841 100644
--- a/ash/webui/personalization_app/resources/trusted/ambient/ambient_reducers.ts
+++ b/ash/webui/personalization_app/resources/trusted/ambient/ambient_reducers.ts
@@ -39,6 +39,17 @@
   }
 }
 
+export function animationThemeReducer(
+    state: AmbientState['animationTheme'], action: Actions,
+    _: PersonalizationState): AmbientState['animationTheme'] {
+  switch (action.name) {
+    case AmbientActionName.SET_ANIMATION_THEME:
+      return action.animationTheme;
+    default:
+      return state;
+  }
+}
+
 export function temperatureUnitReducer(
     state: AmbientState['temperatureUnit'], action: Actions,
     _: PersonalizationState): AmbientState['temperatureUnit'] {
@@ -65,6 +76,7 @@
     {[K in keyof AmbientState]: ReducerFunction<AmbientState[K]>} = {
       albums: albumsReducer,
       ambientModeEnabled: ambientModeEnabledReducer,
+      animationTheme: animationThemeReducer,
       temperatureUnit: temperatureUnitReducer,
       topicSource: topicSourceReducer,
     };
diff --git a/ash/webui/personalization_app/resources/trusted/ambient/ambient_state.ts b/ash/webui/personalization_app/resources/trusted/ambient/ambient_state.ts
index 4b653c6..e30dec7 100644
--- a/ash/webui/personalization_app/resources/trusted/ambient/ambient_state.ts
+++ b/ash/webui/personalization_app/resources/trusted/ambient/ambient_state.ts
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import {AmbientModeAlbum, TemperatureUnit, TopicSource} from '../personalization_app.mojom-webui.js';
+import {AmbientModeAlbum, AnimationTheme, TemperatureUnit, TopicSource} from '../personalization_app.mojom-webui.js';
 
 /**
  * Stores ambient related states.
@@ -10,6 +10,7 @@
 export interface AmbientState {
   albums: AmbientModeAlbum[]|null;
   ambientModeEnabled: boolean|null;
+  animationTheme: AnimationTheme|null;
   temperatureUnit: TemperatureUnit|null;
   topicSource: TopicSource|null;
 }
@@ -18,6 +19,7 @@
   return {
     albums: null,
     ambientModeEnabled: null,
+    animationTheme: null,
     temperatureUnit: null,
     topicSource: null,
   };
diff --git a/ash/webui/personalization_app/resources/trusted/ambient/ambient_subpage_element.html b/ash/webui/personalization_app/resources/trusted/ambient/ambient_subpage_element.html
index 565382c5..4b4c5472 100644
--- a/ash/webui/personalization_app/resources/trusted/ambient/ambient_subpage_element.html
+++ b/ash/webui/personalization_app/resources/trusted/ambient/ambient_subpage_element.html
@@ -1,20 +1,23 @@
 <style>
-  #mainSettings[hidden] {
-    display: none;
-  }
   #mainSettings {
     display: grid;
     grid-template-areas:
-      '. . toggles . .'
-      '. . spinner . .'
-      '. . preview . .'
-      '. . sources . .'
-      '. . weather . .';
-    grid-template-columns: 1fr 24px minmax(568px, 920px) 24px 1fr;
+      '. . toggles   . .'
+      '. . zeroState . .'
+      '. . spinner   . .'
+      '. . preview   . .'
+      '. . animation . .'
+      '. . sources   . .'
+      '. . weather   . .';
+    grid-template-columns: 1fr 16px minmax(568px, 920px) 16px 1fr;
     grid-template-rows: auto;
   }
   toggle-row {
     grid-area: toggles;
+    margin: 0 8px;
+  }
+  ambient-zero-state {
+    grid-area: zeroState;
   }
   paper-spinner-lite {
     grid-area: spinner;
@@ -24,13 +27,30 @@
   }
   ambient-preview {
     grid-area: preview;
-    margin-top: 20px;
+    margin: 20px 8px 0 8px;
+  }
+  animation-theme-list {
+    grid-area: animation;
   }
   topic-source-list {
     grid-area: sources;
+    margin: 0 8px;
   }
   ambient-weather-unit {
     grid-area: weather;
+    margin: 0 8px;
+  }
+  #albumsSubpage {
+    display: grid;
+    grid-template-areas:
+      '. . albums-subpage . .';
+    grid-template-columns: 1fr 16px minmax(568px, 920px) 16px 1fr;
+    grid-template-rows: auto;
+    position: relative;
+    width: 100%;
+  }
+  albums-subpage {
+    grid-area: albums-subpage;
   }
 </style>
 <div id="container">
@@ -39,29 +59,40 @@
       <toggle-row checked="[[ambientModeEnabled_]]"
           on-click="onClickAmbientModeButton_" on-change="onToggleStateChanged_">
       </toggle-row>
+      <template is="dom-if" if="[[ambientModeEnabled_]]">
+        <template is="dom-if" if="[[loadingSettings_]]">
+          <!-- TODO(b/217311018): Add loading state -->
+          <paper-spinner-lite active></paper-spinner-lite>
+        </template>
 
-      <template is="dom-if" if="[[loadingSettings_]]">
-        <!-- TODO(b/217311018): Add loading state -->
-        <paper-spinner-lite active></paper-spinner-lite>
+        <template is="dom-if" if="[[!loadingSettings_]]">
+          <ambient-preview></ambient-preview>
+          <template is="dom-if" if="[[isAmbientModeAnimationEnabled_()]]">
+            <animation-theme-list selected-animation-theme="[[animationTheme_]]"
+                disabled$="[[disabled_]]">
+            </animation-theme-list>
+          </template>
+          <topic-source-list selected-topic-source="[[topicSource_]]"
+              has-google-photos-albums="[[hasGooglePhotosAlbums_(albums_)]]"
+              disabled="[[disabled_]]">
+          </topic-source-list>
+          <ambient-weather-unit
+              selected-temperature-unit="[[temperatureUnitToString_(temperatureUnit_)]]"
+              disabled="[[disabled_]]">
+          </ambient-weather-unit>
+        </template>
       </template>
-
-      <template is="dom-if" if="[[!loadingSettings_]]">
-        <ambient-preview></ambient-preview>
-        <topic-source-list selected-topic-source="[[topicSource_]]"
-            has-google-photos-albums="[[hasGooglePhotosAlbums_(albums_)]]"
-            disabled="[[disabled_]]">
-        </topic-source-list>
-        <ambient-weather-unit
-            selected-temperature-unit="[[temperatureUnitToString_(temperatureUnit_)]]"
-            disabled="[[disabled_]]">
-        </ambient-weather-unit>
+      <template is="dom-if" if="[[!ambientModeEnabled_]]">
+        <ambient-zero-state id="zeroState"></ambient-zero-state>
       </template>
     </div>
   </template>
   <template is="dom-if" if="[[shouldShowAlbums_(path, ambientModeEnabled_)]]">
-    <albums-subpage disabled="[[disabled_]]" path="[[path]]"
-        topic-source="[[getTopicSource_(queryParams)]]"
-        albums="[[getAlbums_(albums_, queryParams)]]">
-    </albums-subpage>
+    <div id="albumsSubpage">
+      <albums-subpage disabled="[[disabled_]]" path="[[path]]"
+          topic-source="[[getTopicSource_(queryParams)]]"
+          albums="[[getAlbums_(albums_, queryParams)]]">
+      </albums-subpage>
+    </div>
   </template>
 </div>
diff --git a/ash/webui/personalization_app/resources/trusted/ambient/ambient_subpage_element.ts b/ash/webui/personalization_app/resources/trusted/ambient/ambient_subpage_element.ts
index 75855e94..5ba0dd5 100644
--- a/ash/webui/personalization_app/resources/trusted/ambient/ambient_subpage_element.ts
+++ b/ash/webui/personalization_app/resources/trusted/ambient/ambient_subpage_element.ts
@@ -11,12 +11,14 @@
 import './albums_subpage_element.js';
 import './ambient_weather_element.js';
 import './ambient_preview_element.js';
+import './animation_theme_list_element.js';
 import './toggle_row_element.js';
 import './topic_source_list_element.js';
 
+import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
 import {html} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
-import {AmbientModeAlbum, TemperatureUnit, TopicSource} from '../personalization_app.mojom-webui.js';
+import {AmbientModeAlbum, AnimationTheme, TemperatureUnit, TopicSource} from '../personalization_app.mojom-webui.js';
 import {Paths} from '../personalization_router_element.js';
 import {WithPersonalizationStore} from '../personalization_store.js';
 
@@ -61,16 +63,19 @@
   queryParams: Record<string, string>;
   private albums_: AmbientModeAlbum[]|null = null;
   private ambientModeEnabled_: boolean|null = null;
+  private animationTheme_: AnimationTheme|null = null;
   private temperatureUnit_: TemperatureUnit|null = null;
   private topicSource_: TopicSource|null = null;
 
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
     AmbientObserver.initAmbientObserverIfNeeded();
     this.watch<AmbientSubpage['albums_']>(
         'albums_', state => state.ambient.albums);
     this.watch<AmbientSubpage['ambientModeEnabled_']>(
         'ambientModeEnabled_', state => state.ambient.ambientModeEnabled);
+    this.watch<AmbientSubpage['animationTheme_']>(
+        'animationTheme_', state => state.ambient.animationTheme);
     this.watch<AmbientSubpage['temperatureUnit_']>(
         'temperatureUnit_', state => state.ambient.temperatureUnit);
     this.watch<AmbientSubpage['topicSource_']>(
@@ -78,6 +83,10 @@
     this.updateFromStore();
   }
 
+  private isAmbientModeAnimationEnabled_(): boolean {
+    return loadTimeData.getBoolean('isAmbientModeAnimationEnabled');
+  }
+
   private onClickAmbientModeButton_(event: Event) {
     event.stopPropagation();
     this.setAmbientModeEnabled_(!this.ambientModeEnabled_);
diff --git a/ash/webui/personalization_app/resources/trusted/ambient/animation_theme_item_element.html b/ash/webui/personalization_app/resources/trusted/ambient/animation_theme_item_element.html
new file mode 100644
index 0000000..3345513
--- /dev/null
+++ b/ash/webui/personalization_app/resources/trusted/ambient/animation_theme_item_element.html
@@ -0,0 +1,116 @@
+<style include="common-style">
+  :host {
+    box-sizing: border-box;
+    cursor: pointer;
+    overflow: hidden;
+    padding: calc(var(--personalization-app-grid-item-spacing) / 2);
+    /* Subtract 0.34px to fix subpixel rounding issues with iron-list. This
+     * ensures all grid items in a row add up to at least 1px smaller than the
+     * parent width. */
+    width: calc(100% / 3 - 0.34px);
+  }
+
+  @media(min-width: 720px) {
+    :host {
+      /* Subtract 0.25px to fix subpixel rounding issues with iron-list. This
+       * ensures all grid items in a row add up to at least 1px smaller than the
+       * parent width. */
+      width: calc(100% / 4 - 0.25px) !important;
+    }
+  }
+
+  :host([disabled]) {
+    cursor: initial;
+    opacity: var(--cros-disabled-opacity);
+    pointer-events: none;
+    user-select: none;
+  }
+
+  :host(:focus-visible) {
+    outline: none;
+  }
+
+  .item {
+    align-items: center;
+    background-color: rgba(0, 0, 0, 0.12);
+    border-radius: var(--personalization-app-grid-item-border-radius);
+    box-sizing: border-box;
+    display: flex;
+    flex-direction: column;
+    height: 156px;
+    justify-content: center;
+    overflow: hidden;
+    position: relative;
+    width: 100%;
+  }
+
+  :host(:focus-visible) .item {
+    outline: 2px solid var(--cros-focus-ring-color);
+  }
+
+  .item[aria-selected] {
+    background-color: rgba(
+      var(--cros-color-prominent-rgb),
+      var(--personalization-app-second-tone-opacity));
+    border-radius:
+      calc(var(--personalization-app-grid-item-border-radius) + 4px);
+  }
+
+  img {
+    border-radius: var(--personalization-app-grid-item-border-radius);
+    height: 100%;
+    object-fit: cover;
+    width: 100%;
+  }
+
+  .item[aria-selected] img {
+    animation-duration:  200ms;
+    animation-fill-mode: forwards;
+    animation-name: img-resize;
+    animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1.0);
+  }
+
+  @keyframes img-resize {
+    100% {
+      height: calc(100% - 8px);
+      width: calc(100% - 8px);
+    }
+  }
+
+  .text {
+    color: var(--cros-text-color-secondary);
+    display: flex;
+    flex-direction: column;
+    font: var(--cros-body-2-font);
+    margin: 8px 0 0 0;
+  }
+
+  iron-icon {
+    --iron-icon-height: 20px;
+    --iron-icon-width: 20px;
+    animation-duration: 200ms;
+    animation-name: iron-icon-scale;
+    animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1.0);
+    left: 8px;
+    position: absolute;
+    top: 8px;
+  }
+
+  .item:not([aria-selected]) iron-icon {
+    display: none;
+  }
+
+  @keyframes iron-icon-scale {
+    from {
+      transform: scale(0);
+    }
+    to {
+      transform: scale(1);
+    }
+  }
+</style>
+<div class="item" aria-selected$="[[checked]]">
+  <img is="cr-auto-img" auto-src="[[imgSrc_]]"></img>
+  <iron-icon icon="personalization:checkmark"></iron-icon>
+</div>
+<p class="text" title$="[[itemDescription_]]">[[itemDescription_]]</p>
diff --git a/ash/webui/personalization_app/resources/trusted/ambient/animation_theme_item_element.ts b/ash/webui/personalization_app/resources/trusted/ambient/animation_theme_item_element.ts
new file mode 100644
index 0000000..e690da5
--- /dev/null
+++ b/ash/webui/personalization_app/resources/trusted/ambient/animation_theme_item_element.ts
@@ -0,0 +1,107 @@
+// 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.
+
+/**
+ * @fileoverview The element for displaying an animation theme.
+ */
+
+import '../../common/styles.js';
+
+import 'chrome://resources/cr_elements/cr_auto_img/cr_auto_img.js';
+import 'chrome://resources/cr_elements/shared_vars_css.m.js';
+import {assertNotReached} from 'chrome://resources/js/assert_ts.js';
+
+import {html} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {isSelectionEvent} from '../../common/utils.js';
+import {AnimationTheme} from '../personalization_app.mojom-webui.js';
+import {WithPersonalizationStore} from '../personalization_store.js';
+import {setAnimationTheme} from './ambient_controller.js';
+import {getAmbientProvider} from './ambient_interface_provider.js';
+
+export class AnimationThemeItem extends WithPersonalizationStore {
+  static get is() {
+    return 'animation-theme-item';
+  }
+
+  static get template() {
+    return html`{__html_template__}`;
+  }
+
+  static get properties() {
+    return {
+      animationTheme: AnimationTheme,
+      checked: {
+        type: Boolean,
+        value: false,
+        reflectToAttribute: true,
+        notify: true,
+      },
+      itemDescription_: {
+        type: String,
+        computed: 'computeItemDescription_(animationTheme)',
+      },
+      imgSrc_: {
+        type: String,
+        computed: 'computeImgSrc_(animationTheme)',
+      },
+    };
+  }
+
+  animationTheme: AnimationTheme;
+  checked: boolean;
+  private itemDescription_: string;
+  private imgSrc_: string;
+
+  override ready() {
+    super.ready();
+
+    this.addEventListener('click', this.onItemSelected_.bind(this));
+    this.addEventListener('keydown', this.onItemSelected_.bind(this));
+  }
+
+  /** Compute the animation theme description. */
+  private computeItemDescription_(): string {
+    switch (this.animationTheme) {
+      case AnimationTheme.kSlideshow:
+        return this.i18n('ambientModeAnimationSlideshowLabel');
+      case AnimationTheme.kFeelTheBreeze:
+        return this.i18n('ambientModeAnimationFeelTheBreezeLabel');
+      case AnimationTheme.kFloatOnBy:
+        return this.i18n('ambientModeAnimationFloatOnByLabel');
+      default:
+        assertNotReached(
+            'Invalid AnimationTheme value: ${this.animationTheme}');
+    }
+  }
+
+  /** Return the display image for animation theme option. */
+  private computeImgSrc_(animationTheme: AnimationThemeItem['animationTheme']):
+      string {
+    switch (animationTheme) {
+      case AnimationTheme.kSlideshow:
+        return 'chrome://personalization/common/slideshow.png';
+      case AnimationTheme.kFeelTheBreeze:
+        return 'chrome://personalization/common/feel_the_breeze.png';
+      case AnimationTheme.kFloatOnBy:
+        return 'chrome://personalization/common/float_on_by.png';
+      default:
+        assertNotReached('invalid animation theme value.');
+    }
+  }
+
+  /** Invoked when item is selected. */
+  private onItemSelected_(event: Event) {
+    if (!isSelectionEvent(event)) {
+      return;
+    }
+
+    event.preventDefault();
+    event.stopPropagation();
+    setAnimationTheme(
+        this.animationTheme, getAmbientProvider(), this.getStore());
+  }
+}
+
+customElements.define(AnimationThemeItem.is, AnimationThemeItem);
diff --git a/ash/webui/personalization_app/resources/trusted/ambient/animation_theme_list_element.html b/ash/webui/personalization_app/resources/trusted/ambient/animation_theme_list_element.html
new file mode 100644
index 0000000..c9c8feb
--- /dev/null
+++ b/ash/webui/personalization_app/resources/trusted/ambient/animation_theme_list_element.html
@@ -0,0 +1,28 @@
+<style>
+  #animationTitle {
+    color: var(--cros-text-color-primary);
+    font: var(--personalization-app-label-font);
+    margin: 34px 8px 16px 8px;
+  }
+  #animationThemeList {
+    display: flex;
+    flex-direction: row;
+  }
+  iron-list {
+    height: 100%;
+    width: 100%;
+  }
+</style>
+
+<h3 id="animationTitle" aria-hidden="true">
+  $i18n{ambientModeAnimationTitle}
+</h3>
+<iron-list id="grid" items="[[animationThemes]]" as="theme" grid>
+  <template>
+    <animation-theme-item tabindex$="[[tabIndex]]"
+        animation-theme="[[theme]]"
+        disabled$="[[disabled]]"
+        checked="[[isSelected_(theme, selectedAnimationTheme)]]">
+      </animation-theme-item>
+  </template>
+</iron-list>
diff --git a/ash/webui/personalization_app/resources/trusted/ambient/animation_theme_list_element.ts b/ash/webui/personalization_app/resources/trusted/ambient/animation_theme_list_element.ts
new file mode 100644
index 0000000..974e8a2
--- /dev/null
+++ b/ash/webui/personalization_app/resources/trusted/ambient/animation_theme_list_element.ts
@@ -0,0 +1,50 @@
+// 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.
+
+/**
+ * @fileoverview The element for displaying a list of animation themes.
+ */
+import './animation_theme_item_element.js';
+
+import {html} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {AnimationTheme} from '../personalization_app.mojom-webui.js';
+import {WithPersonalizationStore} from '../personalization_store.js';
+
+export class AnimationThemeList extends WithPersonalizationStore {
+  static get is() {
+    return 'animation-theme-list';
+  }
+
+  static get template() {
+    return html`{__html_template__}`;
+  }
+
+  static get properties() {
+    return {
+      animationThemes: {
+        type: Array,
+        value: [
+          AnimationTheme.kSlideshow, AnimationTheme.kFeelTheBreeze,
+          AnimationTheme.kFloatOnBy
+        ],
+      },
+
+      selectedAnimationTheme: AnimationTheme,
+
+      disabled: Boolean,
+    };
+  }
+
+  animationThemes: Array<AnimationTheme>;
+  disabled: boolean;
+  private selectedAnimationTheme: AnimationTheme;
+
+  private isSelected_(
+      animationTheme: AnimationTheme, selectedAnimationTheme: AnimationTheme) {
+    return animationTheme === selectedAnimationTheme;
+  }
+}
+
+customElements.define(AnimationThemeList.is, AnimationThemeList);
diff --git a/ash/webui/personalization_app/resources/trusted/ambient/toggle_row_element.ts b/ash/webui/personalization_app/resources/trusted/ambient/toggle_row_element.ts
index df978db..0fb8a58 100644
--- a/ash/webui/personalization_app/resources/trusted/ambient/toggle_row_element.ts
+++ b/ash/webui/personalization_app/resources/trusted/ambient/toggle_row_element.ts
@@ -34,7 +34,7 @@
   }
 
   checked: boolean;
-  ariaLabel: string;
+  override ariaLabel: string;
 
   private getAriaLabel_(): string {
     return this.i18n(this.checked ? 'ambientModeOn' : 'ambientModeOff');
diff --git a/ash/webui/personalization_app/resources/trusted/ambient/topic_source_item_element.ts b/ash/webui/personalization_app/resources/trusted/ambient/topic_source_item_element.ts
index e54cd9a9..8407b4d 100644
--- a/ash/webui/personalization_app/resources/trusted/ambient/topic_source_item_element.ts
+++ b/ash/webui/personalization_app/resources/trusted/ambient/topic_source_item_element.ts
@@ -62,9 +62,9 @@
   checked: boolean;
   topicSource: TopicSource;
   hasGooglePhotosAlbums: boolean|null;
-  ariaLabel: string;
+  override ariaLabel: string;
 
-  ready() {
+  override ready() {
     super.ready();
 
     this.addEventListener('click', this.onItemSelected_.bind(this));
diff --git a/ash/webui/personalization_app/resources/trusted/ambient/zero_state_element.html b/ash/webui/personalization_app/resources/trusted/ambient/zero_state_element.html
new file mode 100644
index 0000000..ee73bfe
--- /dev/null
+++ b/ash/webui/personalization_app/resources/trusted/ambient/zero_state_element.html
@@ -0,0 +1,27 @@
+<style include="trusted-style common-style">
+  :host {
+    align-items: center;
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    margin: 34px 0;
+    overflow: hidden;
+  }
+
+  div {
+    color: var(--cros-text-color-secondary);
+    font: var(--cros-body-1-font);
+    max-width: 236px;
+    text-align: center;
+  }
+
+  img {
+    width: 260px;
+  }
+</style>
+
+<iron-media-query query="(prefers-color-scheme: dark)"
+    query-matches="{{isDarkModeActive_}}">
+</iron-media-query>
+<img src="[[getImageSource_(isDarkModeActive_)]]" aria-hidden="true">
+<div>$i18n{ambientModeZeroStateMessage}</div>
diff --git a/ash/webui/personalization_app/resources/trusted/ambient/zero_state_element.ts b/ash/webui/personalization_app/resources/trusted/ambient/zero_state_element.ts
new file mode 100644
index 0000000..f8305847
--- /dev/null
+++ b/ash/webui/personalization_app/resources/trusted/ambient/zero_state_element.ts
@@ -0,0 +1,48 @@
+// 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.
+
+/**
+ * @fileoverview Polymer element that displays the Ambient zero state.
+ */
+
+import '../../common/styles.js';
+
+import 'chrome://resources/polymer/v3_0/iron-media-query/iron-media-query.js';
+import {html} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {WithPersonalizationStore} from '../personalization_store.js';
+
+export class AmbientZeroState extends WithPersonalizationStore {
+  static get is() {
+    return 'ambient-zero-state';
+  }
+
+  static get template() {
+    return html`{__html_template__}`;
+  }
+
+  static get properties() {
+    return {
+      isDarkModeActive_: {
+        type: Boolean,
+        value: false,
+      },
+    };
+  }
+
+  /** Whether the page is being rendered in dark mode. */
+  private isDarkModeActive_: boolean;
+
+  /**
+   * Returns the image source based on whether the page is being
+   * rendered in dark mode.
+   */
+  private getImageSource_() {
+    return this.isDarkModeActive_ ?
+        'chrome://personalization/common/ambient_mode_disabled_dark.svg' :
+        'chrome://personalization/common/ambient_mode_disabled.svg';
+  }
+}
+
+customElements.define(AmbientZeroState.is, AmbientZeroState);
diff --git a/ash/webui/personalization_app/resources/trusted/personalization_app.ts b/ash/webui/personalization_app/resources/trusted/personalization_app.ts
index 3c9a936..cf71e740f 100644
--- a/ash/webui/personalization_app/resources/trusted/personalization_app.ts
+++ b/ash/webui/personalization_app/resources/trusted/personalization_app.ts
@@ -9,9 +9,10 @@
  */
 
 import '/strings.m.js';
-import './ambient/album_item_element.js';
 import './ambient/album_list_element.js';
 import './ambient/albums_subpage_element.js';
+import './ambient/animation_theme_item_element.js';
+import './ambient/animation_theme_list_element.js';
 import './ambient/art_album_dialog_element.js';
 import './ambient/ambient_preview_element.js';
 import './ambient/ambient_subpage_element.js';
@@ -19,6 +20,7 @@
 import './ambient/toggle_row_element.js';
 import './ambient/topic_source_item_element.js';
 import './ambient/topic_source_list_element.js';
+import './ambient/zero_state_element.js';
 import './personalization_router_element.js';
 import './personalization_test_api.js';
 import './personalization_toast_element.js';
diff --git a/ash/webui/personalization_app/resources/trusted/personalization_breadcrumb_element.ts b/ash/webui/personalization_app/resources/trusted/personalization_breadcrumb_element.ts
index 108f1d9..94bcaef 100644
--- a/ash/webui/personalization_app/resources/trusted/personalization_breadcrumb_element.ts
+++ b/ash/webui/personalization_app/resources/trusted/personalization_breadcrumb_element.ts
@@ -100,7 +100,7 @@
   private googlePhotosAlbums_: GooglePhotosAlbum[]|null;
   private showBackButton_: boolean;
 
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
     this.watch('collections_', state => state.wallpaper.backdrop.collections);
     this.watch(
diff --git a/ash/webui/personalization_app/resources/trusted/personalization_router_element.ts b/ash/webui/personalization_app/resources/trusted/personalization_router_element.ts
index cb054de..011852f 100644
--- a/ash/webui/personalization_app/resources/trusted/personalization_router_element.ts
+++ b/ash/webui/personalization_app/resources/trusted/personalization_router_element.ts
@@ -77,7 +77,7 @@
     window.location.replace(Paths.Ambient);
   }
 
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
     // Force the user onto the wallpaper subpage if personalization hub feature
     // is not enabled, and the user is not already on a wallpaper page.
diff --git a/ash/webui/personalization_app/resources/trusted/personalization_theme_element.ts b/ash/webui/personalization_app/resources/trusted/personalization_theme_element.ts
index 75afae7..7c7bd04 100644
--- a/ash/webui/personalization_app/resources/trusted/personalization_theme_element.ts
+++ b/ash/webui/personalization_app/resources/trusted/personalization_theme_element.ts
@@ -35,7 +35,7 @@
 
   private darkModeEnabled_: boolean;
 
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
     ThemeObserver.initThemeObserverIfNeeded();
     this.watch<PersonalizationThemeElement['darkModeEnabled_']>(
diff --git a/ash/webui/personalization_app/resources/trusted/personalization_toast_element.ts b/ash/webui/personalization_app/resources/trusted/personalization_toast_element.ts
index 5b78016..c1baf66d 100644
--- a/ash/webui/personalization_app/resources/trusted/personalization_toast_element.ts
+++ b/ash/webui/personalization_app/resources/trusted/personalization_toast_element.ts
@@ -42,7 +42,7 @@
   private isLoading_: boolean;
   private showError_: boolean;
 
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
     this.watch('error_', state => state.error);
     this.watch(
diff --git a/ash/webui/personalization_app/resources/trusted/user/avatar_camera_element.ts b/ash/webui/personalization_app/resources/trusted/user/avatar_camera_element.ts
index a9a687b..7e47467 100644
--- a/ash/webui/personalization_app/resources/trusted/user/avatar_camera_element.ts
+++ b/ash/webui/personalization_app/resources/trusted/user/avatar_camera_element.ts
@@ -107,12 +107,12 @@
   private pngBinary_: Uint8Array|null;
   private previewBlobUrl_: string|null;
 
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
     this.startCamera_();
   }
 
-  disconnectedCallback() {
+  override disconnectedCallback() {
     super.disconnectedCallback();
     this.stopCamera_();
   }
diff --git a/ash/webui/personalization_app/resources/trusted/user/avatar_list_element.ts b/ash/webui/personalization_app/resources/trusted/user/avatar_list_element.ts
index 1fc7cf2..59e82f5 100644
--- a/ash/webui/personalization_app/resources/trusted/user/avatar_list_element.ts
+++ b/ash/webui/personalization_app/resources/trusted/user/avatar_list_element.ts
@@ -63,7 +63,7 @@
   private image_: UserImage|null;
   private lastExternalUserImageUrl_: Url|null;
 
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
     this.watch<AvatarList['defaultUserImages_']>(
         'defaultUserImages_', state => state.user.defaultUserImages);
diff --git a/ash/webui/personalization_app/resources/trusted/user/user_preview_element.ts b/ash/webui/personalization_app/resources/trusted/user/user_preview_element.ts
index 9317c66..8e152a1 100644
--- a/ash/webui/personalization_app/resources/trusted/user/user_preview_element.ts
+++ b/ash/webui/personalization_app/resources/trusted/user/user_preview_element.ts
@@ -50,7 +50,7 @@
   private info_: UserInfo|null;
   private imageUrl_: Url|null;
 
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
     UserImageObserver.initUserImageObserverIfNeeded();
     this.watch<UserPreview['info_']>('info_', state => state.user.info);
diff --git a/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_albums_element.ts b/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_albums_element.ts
index 3793008..66e59d2b 100644
--- a/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_albums_element.ts
+++ b/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_albums_element.ts
@@ -47,7 +47,7 @@
   }
 
   /** Whether or not this element is currently hidden. */
-  hidden: boolean;
+  override hidden: boolean;
 
   /** The list of albums. */
   private albums_: GooglePhotosAlbum[]|null|undefined;
@@ -55,7 +55,7 @@
   /** Whether the list of albums is currently loading. */
   private albumsLoading_: boolean;
 
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
 
     this.watch<GooglePhotosAlbums['albums_']>(
diff --git a/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_collection_element.ts b/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_collection_element.ts
index 26098a2..e7c1d569 100644
--- a/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_collection_element.ts
+++ b/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_collection_element.ts
@@ -69,7 +69,7 @@
   albumId: string|undefined;
 
   /** Whether or not this element is currently hidden. */
-  hidden: boolean;
+  override hidden: boolean;
 
   /** The list of albums. */
   private albums_: GooglePhotosAlbum[]|null|undefined;
@@ -84,7 +84,7 @@
   private wallpaperProvider_: WallpaperProviderInterface =
       getWallpaperProvider();
 
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
 
     this.watch<GooglePhotosCollection['albums_']>(
diff --git a/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_photos_by_album_id_element.ts b/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_photos_by_album_id_element.ts
index e347a11..bed9538 100644
--- a/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_photos_by_album_id_element.ts
+++ b/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_photos_by_album_id_element.ts
@@ -66,7 +66,7 @@
   albumId: string|undefined;
 
   /** Whether or not this element is currently hidden. */
-  hidden: boolean;
+  override hidden: boolean;
 
   /** The list of photos for the currently selected album id. */
   private album_: GooglePhotosPhoto[]|null|undefined;
@@ -87,7 +87,7 @@
   private wallpaperProvider_: WallpaperProviderInterface =
       getWallpaperProvider();
 
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
 
     this.watch<GooglePhotosPhotosByAlbumId['currentSelected_']>(
diff --git a/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_photos_element.ts b/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_photos_element.ts
index 437bf23..b5e3b13 100644
--- a/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_photos_element.ts
+++ b/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_photos_element.ts
@@ -87,7 +87,7 @@
   }
 
   /** Whether or not this element is currently hidden. */
-  hidden: boolean;
+  override hidden: boolean;
 
   /** The currently selected wallpaper. */
   private currentSelected_: CurrentWallpaper|null;
@@ -123,7 +123,7 @@
   private wallpaperProvider_: WallpaperProviderInterface =
       getWallpaperProvider();
 
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
 
     this.addEventListener('iron-resize', this.onResized_.bind(this));
diff --git a/ash/webui/personalization_app/resources/trusted/wallpaper/local_images_element.ts b/ash/webui/personalization_app/resources/trusted/wallpaper/local_images_element.ts
index 3c4a67ed..831a289 100644
--- a/ash/webui/personalization_app/resources/trusted/wallpaper/local_images_element.ts
+++ b/ash/webui/personalization_app/resources/trusted/wallpaper/local_images_element.ts
@@ -73,7 +73,7 @@
     return ['onImageLoaded_(imageData_, imageDataLoading_)'];
   }
 
-  public hidden: boolean;
+  override hidden: boolean;
 
   private wallpaperProvider_: WallpaperProviderInterface;
   private images_: FilePath[]|null;
@@ -88,7 +88,7 @@
     this.wallpaperProvider_ = getWallpaperProvider();
   }
 
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
     this.watch<LocalImages['images_']>(
         'images_', state => state.wallpaper.local.images);
diff --git a/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_collections_element.ts b/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_collections_element.ts
index c14faba..d739b91f 100644
--- a/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_collections_element.ts
+++ b/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_collections_element.ts
@@ -106,7 +106,7 @@
     };
   }
 
-  hidden: boolean;
+  override hidden: boolean;
   private collections_: WallpaperCollection[];
   private collectionsLoading_: boolean;
   private googlePhotos_: GooglePhotosPhoto[]|null;
@@ -142,7 +142,7 @@
     this.didSendLocalImageData_ = false;
   }
 
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
     this.watch('collections_', state => state.wallpaper.backdrop.collections);
     this.watch(
diff --git a/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_fullscreen_element.ts b/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_fullscreen_element.ts
index 288c3b8..2e52fb5d 100644
--- a/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_fullscreen_element.ts
+++ b/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_fullscreen_element.ts
@@ -78,7 +78,7 @@
   }
 
   /** Add override when tsc is updated to 4.3+. */
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
     this.$.container.addEventListener(
         'fullscreenchange', this.onFullscreenChange_.bind(this));
diff --git a/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_images_element.ts b/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_images_element.ts
index 87153e7..c9c544d9 100644
--- a/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_images_element.ts
+++ b/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_images_element.ts
@@ -174,7 +174,7 @@
     };
   }
 
-  hidden: boolean;
+  override hidden: boolean;
   collectionId: string;
   private collections_: WallpaperCollection[]|null;
   private collectionsLoading_: boolean;
@@ -192,7 +192,7 @@
     ];
   }
 
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
     this.watch<WallpaperImages['images_']>(
         'images_', state => state.wallpaper.backdrop.images);
diff --git a/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_preview_element.ts b/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_preview_element.ts
index 42c80af..a29d0ba 100644
--- a/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_preview_element.ts
+++ b/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_preview_element.ts
@@ -56,7 +56,7 @@
     this.wallpaperProvider_ = getWallpaperProvider();
   }
 
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
     this.watch('image_', state => state.wallpaper.currentSelected);
     this.watch(
diff --git a/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_selected_element.ts b/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_selected_element.ts
index c6f8af7..d29882d 100644
--- a/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_selected_element.ts
+++ b/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_selected_element.ts
@@ -152,7 +152,7 @@
     this.wallpaperProvider_ = getWallpaperProvider();
   }
 
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
     this.watch('error_', state => state.error);
     this.watch('image_', state => state.wallpaper.currentSelected);
diff --git a/ash/webui/personalization_app/resources/tsconfig_base.json b/ash/webui/personalization_app/resources/tsconfig_base.json
index 3e71f763..1802bc0f 100644
--- a/ash/webui/personalization_app/resources/tsconfig_base.json
+++ b/ash/webui/personalization_app/resources/tsconfig_base.json
@@ -2,6 +2,7 @@
   "extends": "../../../../tools/typescript/tsconfig_base.json",
   "compilerOptions": {
     "allowJs": true,
+    "noImplicitOverride": true,
     "noUncheckedIndexedAccess": false,
     "noUnusedLocals": false,
     "strictPropertyInitialization": false
diff --git a/ash/webui/personalization_app/test/fake_personalization_app_ambient_provider.h b/ash/webui/personalization_app/test/fake_personalization_app_ambient_provider.h
index 6ab9c17..b497211 100644
--- a/ash/webui/personalization_app/test/fake_personalization_app_ambient_provider.h
+++ b/ash/webui/personalization_app/test/fake_personalization_app_ambient_provider.h
@@ -5,6 +5,7 @@
 #ifndef ASH_WEBUI_PERSONALIZATION_APP_TEST_FAKE_PERSONALIZATION_APP_AMBIENT_PROVIDER_H_
 #define ASH_WEBUI_PERSONALIZATION_APP_TEST_FAKE_PERSONALIZATION_APP_AMBIENT_PROVIDER_H_
 
+#include "ash/public/cpp/ambient/ambient_animation_theme.h"
 #include "ash/webui/personalization_app/personalization_app_ambient_provider.h"
 
 #include <stdint.h>
@@ -43,6 +44,7 @@
       mojo::PendingRemote<ash::personalization_app::mojom::AmbientObserver>
           observer) override {}
   void SetAmbientModeEnabled(bool enabled) override {}
+  void SetAnimationTheme(ash::AmbientAnimationTheme animation_theme) override {}
   void SetTopicSource(ash::AmbientModeTopicSource topic_source) override {}
   void SetTemperatureUnit(
       ash::AmbientModeTemperatureUnit temperature_unit) override {}
diff --git a/base/threading/scoped_blocking_call.cc b/base/threading/scoped_blocking_call.cc
index d112c85..a0f1462 100644
--- a/base/threading/scoped_blocking_call.cc
+++ b/base/threading/scoped_blocking_call.cc
@@ -44,10 +44,9 @@
   internal::AssertBlockingAllowed();
   TRACE_EVENT_BEGIN(
       "base", "ScopedBlockingCall", [&](perfetto::EventContext ctx) {
-        perfetto::protos::pbzero::SourceLocation* source_location_data =
-            ctx.event()->set_source_location();
-        source_location_data->set_file_name(from_here.file_name());
-        source_location_data->set_function_name(from_here.function_name());
+        ctx.event()->set_source_location_iid(
+            base::trace_event::InternedSourceLocation::Get(
+                &ctx, base::trace_event::TraceSourceLocation(from_here)));
       });
 
 #if DCHECK_IS_ON()
diff --git a/base/threading/thread_restrictions.h b/base/threading/thread_restrictions.h
index 6470d87e..79c2a4ba2f 100644
--- a/base/threading/thread_restrictions.h
+++ b/base/threading/thread_restrictions.h
@@ -122,11 +122,11 @@
 class CookieManager;
 class ScopedAllowInitGLBindings;
 class VizCompositorThreadRunnerWebView;
-}
+}  // namespace android_webview
 namespace ash {
 class MojoUtils;
 class BrowserDataMigrator;
-}
+}  // namespace ash
 namespace audio {
 class OutputDevice;
 }
@@ -146,7 +146,7 @@
 namespace cc {
 class CompletionEvent;
 class TileTaskManagerImpl;
-}
+}  // namespace cc
 namespace chrome {
 #if BUILDFLAG(IS_MAC)
 void DeveloperIDCertificateReauthorizeInApp();
@@ -161,12 +161,12 @@
 class StatisticsProviderImpl;
 bool IsCoreSchedulingAvailable();
 int NumberOfPhysicalCores();
-}
-}
+}  // namespace system
+}  // namespace chromeos
 namespace chrome_cleaner {
 class ResetShortcutsComponent;
 class SystemReportComponent;
-}
+}  // namespace chrome_cleaner
 namespace content {
 class BrowserGpuChannelHostFactory;
 class BrowserMainLoop;
@@ -213,7 +213,7 @@
 namespace disk_cache {
 class BackendImpl;
 class InFlightIO;
-}
+}  // namespace disk_cache
 namespace enterprise_connectors {
 class LinuxKeyRotationCommand;
 }  // namespace enterprise_connectors
@@ -243,14 +243,14 @@
 class BlockingUrlProtocol;
 class FileVideoCaptureDeviceFactory;
 class PaintCanvasVideoRenderer;
-}
+}  // namespace media
 namespace memory_instrumentation {
 class OSMetrics;
 }
 namespace metrics {
 class AndroidMetricsServiceClient;
 class CleanExitBeacon;
-}
+}  // namespace metrics
 namespace midi {
 class TaskService;  // https://crbug.com/796830
 }
@@ -264,6 +264,10 @@
 class ScopedIPCSupport;
 }
 }  // namespace mojo
+namespace optimization_guide {
+template <class OutputType, class... InputTypes>
+class TFLiteModelExecutor;
+}
 namespace printing {
 class LocalPrinterHandlerDefault;
 #if BUILDFLAG(IS_MAC)
@@ -272,14 +276,14 @@
 class PrintBackendServiceManager;
 class PrintJobWorker;
 class PrinterQuery;
-}
+}  // namespace printing
 namespace rlz_lib {
 class FinancialPing;
 }
 namespace syncer {
 class GetLocalChangesRequest;
 class HttpBridge;
-}
+}  // namespace syncer
 namespace ui {
 class DrmThreadProxy;
 }
@@ -288,7 +292,7 @@
 class ContentBrowserClientImpl;
 class ProfileImpl;
 class WebLayerPathProvider;
-}
+}  // namespace weblayer
 namespace net {
 class MultiThreadedCertVerifierScopedAllowBaseSyncPrimitives;
 class MultiThreadedProxyResolverScopedAllowJoinOnIO;
@@ -297,7 +301,7 @@
 namespace internal {
 class AddressTrackerLinux;
 }
-}
+}  // namespace net
 
 namespace proxy_resolver {
 class ScopedAllowThreadJoinForProxyResolverV8Tracing;
@@ -313,8 +317,8 @@
 namespace protocol {
 class ScopedAllowSyncPrimitivesForWebRtcTransport;
 class ScopedAllowThreadJoinForWebRtcTransport;
-}
-}
+}  // namespace protocol
+}  // namespace remoting
 
 namespace service_manager {
 class ServiceProcessLauncher;
@@ -339,7 +343,7 @@
 namespace web {
 class WebMainLoop;
 class WebSubThread;
-}
+}  // namespace web
 
 namespace webrtc {
 class DesktopConfigurationMonitor;
@@ -367,7 +371,7 @@
 class GetAppOutputScopedAllowBaseSyncPrimitives;
 class JobTaskSource;
 class TaskTracker;
-}
+}  // namespace internal
 
 class AdjustOOMScoreHelper;
 class FileDescriptorWatcher;
@@ -586,6 +590,8 @@
   friend class media::BlockingUrlProtocol;
   friend class mojo::core::ScopedIPCSupport;
   friend class net::MultiThreadedCertVerifierScopedAllowBaseSyncPrimitives;
+  template <class OutputType, class... InputTypes>
+  friend class optimization_guide::TFLiteModelExecutor;
   friend class rlz_lib::FinancialPing;
   friend class shell_integration_linux::
       LaunchXdgUtilityScopedAllowBaseSyncPrimitives;
@@ -594,7 +600,7 @@
   friend class webrtc::DesktopConfigurationMonitor;
 
   // Usage that should be fixed:
-  friend class ::NativeBackendKWallet;            // http://crbug.com/125331
+  friend class ::NativeBackendKWallet;  // http://crbug.com/125331
   friend class ::chromeos::system::
       StatisticsProviderImpl;                      // http://crbug.com/125385
   friend class blink::VideoFrameResourceProvider;  // http://crbug.com/878070
@@ -678,7 +684,7 @@
   friend class net::
       MultiThreadedProxyResolverScopedAllowJoinOnIO;  // http://crbug.com/69710
   friend class net::NetworkChangeNotifierMac;         // http://crbug.com/125097
-  friend class printing::PrinterQuery;                 // http://crbug.com/66082
+  friend class printing::PrinterQuery;                // http://crbug.com/66082
   friend class proxy_resolver::
       ScopedAllowThreadJoinForProxyResolverV8Tracing;  // http://crbug.com/69710
   friend class remoting::AutoThread;  // https://crbug.com/944316
@@ -688,7 +694,7 @@
       ScopedAllowThreadJoinForWebRtcTransport;  // http://crbug.com/660081
   // Not used in production yet, https://crbug.com/844078.
   friend class service_manager::ServiceProcessLauncher;
-  friend class ui::WindowResizeHelperMac;  // http://crbug.com/902829
+  friend class ui::WindowResizeHelperMac;    // http://crbug.com/902829
   friend class content::TextInputClientMac;  // http://crbug.com/121917
 
   ScopedAllowBaseSyncPrimitivesOutsideBlockingScope(
diff --git a/base/tracing/protos/chrome_track_event.proto b/base/tracing/protos/chrome_track_event.proto
index c759269..08f37a7 100644
--- a/base/tracing/protos/chrome_track_event.proto
+++ b/base/tracing/protos/chrome_track_event.proto
@@ -542,6 +542,7 @@
     CACHE_CONTROL_NO_STORE_HTTP_ONLY_COOKIE_MODIFIED = 51;
     NO_RESPONSE_HEAD = 52;
     ACTIVATION_NAVIGATION_DISALLOWED_FOR_BUG_1234857 = 53;
+    ERROR_DOCUMENT = 54;
   }
 
   optional BackForwardCacheNotRestoredReason
@@ -710,9 +711,17 @@
   optional RemoteHungProcessTerminateReason remote_process_terminate_reason = 2;
 }
 
+enum DeviceThermalState {
+  DEVICE_THERMAL_STATE_UNKNOWN = 0;
+  DEVICE_THERMAL_STATE_NOMINAL = 1;
+  DEVICE_THERMAL_STATE_FAIR = 2;
+  DEVICE_THERMAL_STATE_SERIOUS = 3;
+  DEVICE_THERMAL_STATE_CRITICAL = 4;
+}
+
 message ChromeTrackEvent {
   // Extension range for Chrome: 1000-1999
-  // Next ID: 1036
+  // Next ID: 1037
   extend TrackEvent {
     optional ChromeAppState chrome_app_state = 1000;
 
@@ -790,5 +799,7 @@
     optional SiteInstanceGroup site_instance_group = 1034;
 
     optional BrowsingContextState browsing_context_state = 1035;
+
+    optional DeviceThermalState device_thermal_state = 1036;
   }
 }
diff --git a/build/fuchsia/linux_internal.sdk.sha1 b/build/fuchsia/linux_internal.sdk.sha1
index 10e41d6..253e464 100644
--- a/build/fuchsia/linux_internal.sdk.sha1
+++ b/build/fuchsia/linux_internal.sdk.sha1
@@ -1 +1 @@
-7.20220314.3.3
+7.20220315.1.1
diff --git a/chrome/VERSION b/chrome/VERSION
index f2a4b14..6a6053a0 100644
--- a/chrome/VERSION
+++ b/chrome/VERSION
@@ -1,4 +1,4 @@
 MAJOR=101
 MINOR=0
-BUILD=4945
+BUILD=4946
 PATCH=0
diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn
index 112e27c..a8d48a9 100644
--- a/chrome/android/BUILD.gn
+++ b/chrome/android/BUILD.gn
@@ -3231,6 +3231,14 @@
   ]
 }
 
+_common_smoke_test_args = [
+  "--enable-breakpad-dump",
+  "--use-apk-under-test-flags-file",
+
+  # FRE is too flakey on some old devices https://crbug.com/1289733
+  "--disable-fre",
+]
+
 instrumentation_test_runner("chrome_public_smoke_test") {
   apk_under_test = ":chrome_public_apk"
   android_test_apk = ":chrome_smoke_test_apk"
@@ -3238,10 +3246,8 @@
   if (!is_java_debug) {
     proguard_mapping_path = "$root_build_dir/apks/ChromePublic.apk.mapping"
   }
-  extra_args = [
-    "--enable-breakpad-dump",
-    "--use-apk-under-test-flags-file",
-  ]
+
+  extra_args = _common_smoke_test_args
 }
 
 # Public webview targets don't work with non-public sdks.
@@ -3263,10 +3269,7 @@
   android_test_apk = ":chrome_smoke_test_apk"
   android_test_apk_name = "ChromeSmokeTest"
   never_incremental = true
-  extra_args = [
-    "--enable-breakpad-dump",
-    "--use-apk-under-test-flags-file",
-  ]
+  extra_args = _common_smoke_test_args
 }
 
 android_test_apk("chrome_bundle_smoke_test_apk") {
diff --git a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedSurfaceCoordinator.java b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedSurfaceCoordinator.java
index 6cc7843..ee66625 100644
--- a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedSurfaceCoordinator.java
+++ b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedSurfaceCoordinator.java
@@ -86,7 +86,7 @@
                    BackToTopBubbleScrollListener.ResultHandler, SurfaceCoordinator,
                    FeedAutoplaySettingsDelegate, FeedReliabilityLoggingSignals {
     private static final String TAG = "FeedSurfaceCoordinator";
-    private static final long DELAY_FEED_HEADER_IPH_MS = 5;
+    private static final long DELAY_FEED_HEADER_IPH_MS = 50;
 
     protected final Activity mActivity;
     private final SnackbarManager mSnackbarManager;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/feature_guide/notifications/FeatureNotificationUtils.java b/chrome/android/java/src/org/chromium/chrome/browser/feature_guide/notifications/FeatureNotificationUtils.java
index dcdf9ab..4beac37 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/feature_guide/notifications/FeatureNotificationUtils.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/feature_guide/notifications/FeatureNotificationUtils.java
@@ -5,6 +5,7 @@
 package org.chromium.chrome.browser.feature_guide.notifications;
 
 import android.content.Intent;
+import android.os.Handler;
 import android.text.TextUtils;
 
 import androidx.annotation.StringRes;
@@ -20,6 +21,7 @@
 import org.chromium.chrome.browser.ui.default_browser_promo.DefaultBrowserPromoUtils;
 import org.chromium.components.browser_ui.notifications.NotificationManagerProxy;
 import org.chromium.components.browser_ui.notifications.NotificationManagerProxyImpl;
+import org.chromium.components.browser_ui.widget.textbubble.TextBubble;
 import org.chromium.components.feature_engagement.FeatureConstants;
 import org.chromium.components.feature_engagement.Tracker;
 import org.chromium.components.prefs.PrefService;
@@ -51,9 +53,13 @@
                 IntentUtils.safeGetIntExtra(intent, EXTRA_FEATURE_TYPE, FeatureType.INVALID);
         if (featureType == FeatureType.INVALID) return;
 
-        Tracker tracker = TrackerFactory.getTrackerForProfile(Profile.getLastUsedRegularProfile());
-        tracker.setPriorityNotification(
-                FeatureNotificationUtils.getIPHFeatureForNotificationFeatureType(featureType));
+        TextBubble.dismissBubbles();
+        new Handler().post(() -> {
+            Tracker tracker =
+                    TrackerFactory.getTrackerForProfile(Profile.getLastUsedRegularProfile());
+            tracker.setPriorityNotification(
+                    FeatureNotificationUtils.getIPHFeatureForNotificationFeatureType(featureType));
+        });
     }
 
     /**
diff --git a/chrome/android/javatests/AndroidManifest.xml b/chrome/android/javatests/AndroidManifest.xml
index d9674618..3acc81b7 100644
--- a/chrome/android/javatests/AndroidManifest.xml
+++ b/chrome/android/javatests/AndroidManifest.xml
@@ -45,15 +45,6 @@
             </intent-filter>
         </activity>
 
-        <activity android:name="FakeActivity" android:exported="true" >
-            <intent-filter>
-                <action android:name="android.intent.action.VIEW" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <data android:scheme="externalappscheme" />
-            </intent-filter>
-        </activity>
-
         <activity android:name="org.chromium.chrome.browser.sync.SyncTestRule$DummyKeyRetrievalActivity"
             android:exported="true"/>
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/externalnav/UrlOverridingTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/externalnav/UrlOverridingTest.java
index e72a92a..b42602b9 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/externalnav/UrlOverridingTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/externalnav/UrlOverridingTest.java
@@ -73,7 +73,6 @@
 import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.content_public.browser.test.util.TouchCommon;
 import org.chromium.net.test.EmbeddedTestServer;
-import org.chromium.net.test.util.TestWebServer;
 import org.chromium.ui.base.PageTransition;
 import org.chromium.url.GURL;
 
@@ -231,7 +230,7 @@
     public void setUp() throws Exception {
         IntentFilter filter = new IntentFilter(Intent.ACTION_VIEW);
         filter.addCategory(Intent.CATEGORY_BROWSABLE);
-        filter.addDataScheme("externalappscheme");
+        filter.addDataScheme("market");
         mActivityMonitor = InstrumentationRegistry.getInstrumentation().addMonitor(
                 filter, new Instrumentation.ActivityResult(Activity.RESULT_OK, null), true);
         mTestServer = mActivityTestRule.getTestServer();
@@ -840,26 +839,4 @@
                        Mockito.timeout(CriteriaHelper.DEFAULT_MAX_TIME_TO_POLL).times(1))
                 .clear();
     }
-
-    @Test
-    @LargeTest
-    public void testServerRedirectionFromIntent() throws Exception {
-        TestWebServer webServer = TestWebServer.start();
-        final String redirectTargetUrl = "intent://test/#Intent;scheme=externalappscheme;end";
-        final String redirectUrl = webServer.setRedirect("/302.html", redirectTargetUrl);
-
-        Context context = ContextUtils.getApplicationContext();
-        Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(redirectUrl));
-        intent.setClassName(context, ChromeLauncherActivity.class.getName());
-        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-
-        ChromeTabbedActivity activity = ApplicationTestUtils.waitForActivityWithClass(
-                ChromeTabbedActivity.class, Stage.CREATED, () -> context.startActivity(intent));
-        mActivityTestRule.setActivity(activity);
-
-        CriteriaHelper.pollUiThread(() -> {
-            Criteria.checkThat(mActivityMonitor.getHits(), Matchers.is(1));
-        }, 10000L, CriteriaHelper.DEFAULT_POLLING_INTERVAL);
-        ApplicationTestUtils.waitForActivityState(activity, Stage.DESTROYED);
-    }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/test/smoke/ChromeSmokeTest.java b/chrome/android/javatests/src/org/chromium/chrome/test/smoke/ChromeSmokeTest.java
index 758eb8c..2ad5231 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/test/smoke/ChromeSmokeTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/test/smoke/ChromeSmokeTest.java
@@ -14,15 +14,18 @@
 
 import org.hamcrest.Matchers;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.RuleChain;
+import org.junit.rules.TestRule;
 import org.junit.runner.RunWith;
 
 import org.chromium.base.test.BaseJUnit4ClassRunner;
 import org.chromium.base.test.util.Criteria;
 import org.chromium.base.test.util.CriteriaHelper;
-import org.chromium.base.test.util.DisabledTest;
 import org.chromium.chrome.R;
 import org.chromium.chrome.test.pagecontroller.rules.ChromeUiApplicationTestRule;
+import org.chromium.chrome.test.pagecontroller.rules.ChromeUiAutomatorTestRule;
 import org.chromium.chrome.test.pagecontroller.utils.IUi2Locator;
 import org.chromium.chrome.test.pagecontroller.utils.Ui2Locators;
 import org.chromium.chrome.test.pagecontroller.utils.UiAutomatorUtils;
@@ -43,6 +46,10 @@
     public static final long TIMEOUT_MS = 20000L;
     public static final long UI_CHECK_INTERVAL = 1000L;
     private String mPackageName;
+    public ChromeUiAutomatorTestRule mRule = new ChromeUiAutomatorTestRule();
+    public ChromeUiApplicationTestRule mChromeUiRule = new ChromeUiApplicationTestRule();
+    @Rule
+    public final TestRule mChain = RuleChain.outerRule(mChromeUiRule).around(mRule);
 
     private static Runnable toNotSatisfiedRunnable(
             Callable<Boolean> criteria, String failureReason) {
@@ -86,9 +93,8 @@
         IUi2Locator signinSkipButton = Ui2Locators.withAnyResEntry(R.id.signin_fre_dismiss_button);
         IUi2Locator signinContinueButton =
                 Ui2Locators.withAnyResEntry(R.id.signin_fre_continue_button);
-
-        // Used in DataReductionProxyFirstRunFragment FRE page.
-        IUi2Locator dataSaverPromoNextButton = Ui2Locators.withAnyResEntry(R.id.next_button);
+        IUi2Locator signinProgressSpinner =
+                Ui2Locators.withAnyResEntry(R.id.signin_fre_progress_spinner);
 
         // Used in DefaultSearchEngineFirstRunFragment FRE page.
         IUi2Locator defaultSearchEngineNextButton =
@@ -110,8 +116,8 @@
                 termsAcceptButton,
                 signinSkipButton,
                 signinContinueButton,
+                signinProgressSpinner,
                 noAddAccountButton,
-                dataSaverPromoNextButton,
                 defaultSearchEngineNextButton,
                 urlBar,
         };
@@ -138,9 +144,8 @@
                 // Sometimes there is only the continue button (eg: when signin is
                 // disabled.)
                 UiAutomatorUtils.getInstance().click(signinContinueButton);
-            } else if (uiLocatorHelper.isOnScreen(dataSaverPromoNextButton)) {
-                // Just press next on Data saver promo.
-                UiAutomatorUtils.getInstance().click(dataSaverPromoNextButton);
+            } else if (uiLocatorHelper.isOnScreen(signinProgressSpinner)) {
+                // Do nothing and wait.
             } else if (uiLocatorHelper.isOnScreen(defaultSearchEngineNextButton)) {
                 // Just press next on choosing the default SE.
                 UiAutomatorUtils.getInstance().click(defaultSearchEngineNextButton);
@@ -160,7 +165,6 @@
     }
 
     @Test
-    @DisabledTest(message = "https://crbug.com/1289733")
     public void testHello() {
         Context context = InstrumentationRegistry.getContext();
         final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(DATA_URL));
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index f28e0102..9ee7487 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -8357,6 +8357,10 @@
      flag_descriptions::kWebSQLAccessDescription, kOsAll,
      FEATURE_VALUE_TYPE(blink::features::kWebSQLAccess)},
 
+    {"omit-cors-client-cert", flag_descriptions::kOmitCorsClientCertName,
+     flag_descriptions::kOmitCorsClientCertDescription, kOsAll,
+     FEATURE_VALUE_TYPE(network::features::kOmitCorsClientCert)},
+
     // NOTE: Adding a new flag requires adding a corresponding entry to enum
     // "LoginCustomFlags" in tools/metrics/histograms/enums.xml. See "Flag
     // Histograms" in tools/metrics/histograms/README.md (run the
diff --git a/chrome/browser/apps/app_service/browser_app_instance_registry.cc b/chrome/browser/apps/app_service/browser_app_instance_registry.cc
index 3d53f7e2..a84fd85 100644
--- a/chrome/browser/apps/app_service/browser_app_instance_registry.cc
+++ b/chrome/browser/apps/app_service/browser_app_instance_registry.cc
@@ -349,8 +349,7 @@
     aura::Window* window) {
   DCHECK(window);
   auto* instance = GetInstance(lacros_window_instances_, update.id);
-  DCHECK(instance);
-  if (instance->MaybeUpdate(update.is_active)) {
+  if (instance && instance->MaybeUpdate(update.is_active)) {
     for (auto& observer : observers_) {
       observer.OnBrowserWindowUpdated(*instance);
     }
@@ -362,9 +361,10 @@
     aura::Window* window) {
   DCHECK(window);
   auto instance = PopInstanceIfExists(lacros_window_instances_, update.id);
-  DCHECK(instance);
-  for (auto& observer : observers_) {
-    observer.OnBrowserWindowRemoved(*instance);
+  if (instance) {
+    for (auto& observer : observers_) {
+      observer.OnBrowserWindowRemoved(*instance);
+    }
   }
 }
 
@@ -386,11 +386,10 @@
     aura::Window* window) {
   DCHECK(window);
   BrowserAppInstance* instance = GetInstance(lacros_app_instances_, update.id);
-  DCHECK(instance);
-  if (instance->MaybeUpdate(window, update.title, update.is_browser_active,
-                            update.is_web_contents_active,
-                            update.browser_session_id,
-                            update.restored_browser_session_id)) {
+  if (instance && instance->MaybeUpdate(
+                      window, update.title, update.is_browser_active,
+                      update.is_web_contents_active, update.browser_session_id,
+                      update.restored_browser_session_id)) {
     for (auto& observer : observers_) {
       observer.OnBrowserAppUpdated(*instance);
     }
@@ -402,9 +401,10 @@
     aura::Window* window) {
   DCHECK(window);
   auto instance = PopInstanceIfExists(lacros_app_instances_, update.id);
-  DCHECK(instance);
-  for (auto& observer : observers_) {
-    observer.OnBrowserAppRemoved(*instance);
+  if (instance) {
+    for (auto& observer : observers_) {
+      observer.OnBrowserAppRemoved(*instance);
+    }
   }
 }
 
diff --git a/chrome/browser/ash/crosapi/browser_data_migrator.cc b/chrome/browser/ash/crosapi/browser_data_migrator.cc
index 2fce8ffc..b47b9612 100644
--- a/chrome/browser/ash/crosapi/browser_data_migrator.cc
+++ b/chrome/browser/ash/crosapi/browser_data_migrator.cc
@@ -13,12 +13,17 @@
 #include "base/command_line.h"
 #include "base/feature_list.h"
 #include "base/memory/scoped_refptr.h"
+#include "base/path_service.h"
+#include "base/task/task_traits.h"
+#include "base/task/thread_pool.h"
 #include "base/threading/thread_restrictions.h"
 #include "base/values.h"
 #include "chrome/browser/ash/crosapi/browser_util.h"
 #include "chrome/browser/ash/crosapi/copy_migrator.h"
 #include "chrome/browser/ash/crosapi/move_migrator.h"
+#include "chrome/browser/ash/profiles/profile_helper.h"
 #include "chrome/browser/lifetime/application_lifetime.h"
+#include "chrome/common/chrome_paths.h"
 #include "chromeos/dbus/session_manager/session_manager_client.h"
 #include "components/prefs/pref_registry_simple.h"
 #include "components/prefs/pref_service.h"
@@ -33,6 +38,30 @@
 const char kBrowserDataMigrationForceMigration[] = "force-migration";
 
 base::RepeatingClosure* g_attempt_restart = nullptr;
+
+// Checks if the disk space is enough to run profile migration.
+// Returns the bytes required to be freed. Specifically, on success
+// returns 0.
+uint64_t DiskCheck(const base::FilePath& profile_data_dir) {
+  using browser_data_migrator_util::GetTargetItems;
+  using browser_data_migrator_util::ItemType;
+  using browser_data_migrator_util::TargetItems;
+  TargetItems lacros_items =
+      GetTargetItems(profile_data_dir, ItemType::kLacros);
+  TargetItems need_copy_items =
+      GetTargetItems(profile_data_dir, ItemType::kNeedCopy);
+  TargetItems deletable_items =
+      GetTargetItems(profile_data_dir, ItemType::kDeletable);
+
+  int64_t required_size = need_copy_items.total_size;
+  if (!base::FeatureList::IsEnabled(kLacrosMoveProfileMigration))
+    required_size += lacros_items.total_size;
+  required_size -= deletable_items.total_size;
+
+  return browser_data_migrator_util::ExtraBytesRequiredToBeFreed(
+      required_size, profile_data_dir);
+}
+
 }  // namespace
 
 ScopedRestartAttemptForTesting::ScopedRestartAttemptForTesting(
@@ -73,9 +102,68 @@
     const AccountId& account_id,
     const std::string& user_id_hash,
     crosapi::browser_util::PolicyInitState policy_init_state) {
+  if (!MaybeRestartToMigrateInternal(account_id, user_id_hash,
+                                     policy_init_state)) {
+    return false;
+  }
+  return RestartToMigrate(account_id, user_id_hash,
+                          user_manager::UserManager::Get()->GetLocalState());
+}
+
+void BrowserDataMigratorImpl::MaybeRestartToMigrateWithDiskCheck(
+    const AccountId& account_id,
+    const std::string& user_id_hash,
+    base::OnceCallback<void(bool, const absl::optional<uint64_t>&)> callback) {
+  if (!MaybeRestartToMigrateInternal(
+          account_id, user_id_hash,
+          crosapi::browser_util::PolicyInitState::kAfterInit)) {
+    std::move(callback).Run(false, absl::nullopt);
+    return;
+  }
+
+  base::FilePath user_data_dir;
+  if (!base::PathService::Get(chrome::DIR_USER_DATA, &user_data_dir)) {
+    LOG(DFATAL) << "Could not get the original user data dir path.";
+    std::move(callback).Run(false, absl::nullopt);
+    return;
+  }
+
+  const base::FilePath profile_data_dir =
+      user_data_dir.Append(ProfileHelper::GetUserProfileDir(user_id_hash));
+  base::ThreadPool::PostTaskAndReplyWithResult(
+      FROM_HERE,
+      {base::MayBlock(), base::TaskPriority::USER_VISIBLE,
+       base::TaskShutdownBehavior::BLOCK_SHUTDOWN},
+      base::BindOnce(&DiskCheck, profile_data_dir),
+      base::BindOnce(&BrowserDataMigratorImpl::
+                         MaybeRestartToMigrateWithDiskCheckAfterDiskCheck,
+                     account_id, user_id_hash, std::move(callback)));
+}
+
+void BrowserDataMigratorImpl::MaybeRestartToMigrateWithDiskCheckAfterDiskCheck(
+    const AccountId& account_id,
+    const std::string& user_id_hash,
+    base::OnceCallback<void(bool, const absl::optional<uint64_t>&)> callback,
+    uint64_t required_size) {
+  if (required_size > 0) {
+    LOG(ERROR) << "Failed due to out of disk: " << required_size;
+    std::move(callback).Run(false, required_size);
+    return;
+  }
+
+  bool result =
+      RestartToMigrate(account_id, user_id_hash,
+                       user_manager::UserManager::Get()->GetLocalState());
+  std::move(callback).Run(result, absl::nullopt);
+}
+
+bool BrowserDataMigratorImpl::MaybeRestartToMigrateInternal(
+    const AccountId& account_id,
+    const std::string& user_id_hash,
+    crosapi::browser_util::PolicyInitState policy_init_state) {
   // TODO(crbug.com/1277848): Once `BrowserDataMigrator` stabilises, remove this
   // log message.
-  LOG(WARNING) << "MaybeRestartToMigrate() is called.";
+  LOG(WARNING) << "MaybeRestartToMigrateInternal() is called.";
 
   auto* user_manager = user_manager::UserManager::Get();
   auto* local_state = user_manager->GetLocalState();
@@ -120,7 +208,7 @@
     return false;
   if (force_migration_switch == kBrowserDataMigrationForceMigration) {
     LOG(WARNING) << "`kBrowserDataMigrationForceMigration` switch is present.";
-    return RestartToMigrate(account_id, user_id_hash, local_state);
+    return true;
   }
 
   const user_manager::User* user =
@@ -195,7 +283,7 @@
         << "Restarting to run profile migration since data wipe is required.";
     // If data wipe is required, no need for a further check to determine if
     // lacros data dir exists or not.
-    return RestartToMigrate(account_id, user_id_hash, local_state);
+    return true;
   }
 
   if (crosapi::browser_util::IsProfileMigrationCompletedForUser(local_state,
@@ -206,7 +294,7 @@
     return false;
   }
 
-  return RestartToMigrate(account_id, user_id_hash, local_state);
+  return true;
 }
 
 // static
diff --git a/chrome/browser/ash/crosapi/browser_data_migrator.h b/chrome/browser/ash/crosapi/browser_data_migrator.h
index 80b8d77..bac41b0 100644
--- a/chrome/browser/ash/crosapi/browser_data_migrator.h
+++ b/chrome/browser/ash/crosapi/browser_data_migrator.h
@@ -178,6 +178,19 @@
       const std::string& user_id_hash,
       crosapi::browser_util::PolicyInitState policy_init_state);
 
+  // Very similar to `MaybeRestartToMigrate`, but this checks the disk space in
+  // addition, and reports an error if out of disk space case.
+  // |callback| will be called on completion.
+  // On success, the first argument of the |callback| will be true, and the
+  // second argument should be ignored.
+  // On error, the first argument of the |callback| will be false. If the error
+  // is caused by out-of-disk, the required size to be freed up is passed
+  // to the second argument. Otherwise the second argument is nullopt.
+  static void MaybeRestartToMigrateWithDiskCheck(
+      const AccountId& account_id,
+      const std::string& user_id_hash,
+      base::OnceCallback<void(bool, const absl::optional<uint64_t>&)> callback);
+
   // `BrowserDataMigrator` methods.
   void Migrate(MigrateCallback callback) override;
   void Cancel() override;
@@ -200,6 +213,20 @@
   FRIEND_TEST_ALL_PREFIXES(BrowserDataMigratorRestartTest,
                            MaybeRestartToMigrateWithMigrationStep);
 
+  // The common implementation of `MaybeRestartToMigrate` and
+  // `MaybeRestartToMigrateWithDiskCheck`.
+  static bool MaybeRestartToMigrateInternal(
+      const AccountId& account_id,
+      const std::string& user_id_hash,
+      crosapi::browser_util::PolicyInitState policy_init_state);
+
+  // A part of `MaybeRestartToMigrateWithDiskCheck`, runs after the disk check.
+  static void MaybeRestartToMigrateWithDiskCheckAfterDiskCheck(
+      const AccountId& account_id,
+      const std::string& user_id_hash,
+      base::OnceCallback<void(bool, const absl::optional<uint64_t>&)> callback,
+      uint64_t required_size);
+
   // Sets the value of `kMigrationStep` in Local State.
   static void SetMigrationStep(PrefService* local_state, MigrationStep step);
 
diff --git a/chrome/browser/ash/crosapi/browser_data_migrator_unittest.cc b/chrome/browser/ash/crosapi/browser_data_migrator_unittest.cc
index af8137b39..9af34c6 100644
--- a/chrome/browser/ash/crosapi/browser_data_migrator_unittest.cc
+++ b/chrome/browser/ash/crosapi/browser_data_migrator_unittest.cc
@@ -262,6 +262,7 @@
  private:
   ash::FakeChromeUserManager user_manager_;
   FakeSessionManagerClient session_manager_;
+  base::test::TaskEnvironment task_environment;
 };
 
 TEST_F(BrowserDataMigratorRestartTest, MaybeRestartToMigrateWithMigrationStep) {
@@ -312,4 +313,86 @@
   }
 }
 
+TEST_F(BrowserDataMigratorRestartTest, MaybeRestartToMigrateWithDiskCheck) {
+  bool restart_called = false;
+  ScopedRestartAttemptForTesting scoped_restart_attempt(
+      base::BindLambdaForTesting(
+          [&restart_called]() { restart_called = true; }));
+
+  // If MaybeRestartToMigrate will skip the restarting, WithDiskCheck variation
+  // also skips it.
+  {
+    restart_called = false;
+    base::test::ScopedCommandLine command_line;
+    command_line.GetProcessCommandLine()->AppendSwitchASCII(
+        switches::kForceBrowserDataMigrationForTesting, "force-skip");
+    absl::optional<bool> result;
+    BrowserDataMigratorImpl::MaybeRestartToMigrateWithDiskCheck(
+        AccountId::FromUserEmail("fake@gmail.com"), "abcde",
+        base::BindLambdaForTesting(
+            [&out_result = result](bool result,
+                                   const absl::optional<uint64_t>& size) {
+              out_result = result;
+            }));
+    EXPECT_TRUE(result.has_value());
+    EXPECT_FALSE(result.value());
+    EXPECT_FALSE(restart_called);
+  }
+
+  // If MaybeRestartToMigrate will trigger the restarting, WithDiskCheck
+  // variation will see additional disk size check.
+  {
+    restart_called = false;
+    base::test::ScopedCommandLine command_line;
+    command_line.GetProcessCommandLine()->AppendSwitchASCII(
+        switches::kForceBrowserDataMigrationForTesting, "force-migration");
+    // Inject the behavior that the disk does not have enough space.
+    browser_data_migrator_util::ScopedExtraBytesRequiredToBeFreedForTesting
+        scoped_extra_bytes(1024 * 1024);
+
+    absl::optional<bool> result;
+    absl::optional<uint64_t> out_size;
+    base::RunLoop run_loop;
+    BrowserDataMigratorImpl::MaybeRestartToMigrateWithDiskCheck(
+        AccountId::FromUserEmail("fake@gmail.com"), "abcde",
+        base::BindLambdaForTesting(
+            [&out_result = result, &out_size, &run_loop](
+                bool result, const absl::optional<uint64_t>& size) {
+              run_loop.Quit();
+              out_result = result;
+              out_size = size;
+            }));
+    run_loop.Run();
+    ASSERT_TRUE(result.has_value());
+    EXPECT_FALSE(result.value());
+    EXPECT_EQ(1024 * 1024, out_size);
+    EXPECT_FALSE(restart_called);
+  }
+
+  {
+    restart_called = false;
+    base::test::ScopedCommandLine command_line;
+    command_line.GetProcessCommandLine()->AppendSwitchASCII(
+        switches::kForceBrowserDataMigrationForTesting, "force-migration");
+    // Inject the behavior that the disk has enough space for the migration.
+    browser_data_migrator_util::ScopedExtraBytesRequiredToBeFreedForTesting
+        scoped_extra_bytes(0);
+
+    absl::optional<bool> result;
+    base::RunLoop run_loop;
+    BrowserDataMigratorImpl::MaybeRestartToMigrateWithDiskCheck(
+        AccountId::FromUserEmail("fake@gmail.com"), "abcde",
+        base::BindLambdaForTesting(
+            [&out_result = result, &run_loop](
+                bool result, const absl::optional<uint64_t>& size) {
+              run_loop.Quit();
+              out_result = result;
+            }));
+    run_loop.Run();
+    ASSERT_TRUE(result.has_value());
+    EXPECT_TRUE(result.value());
+    EXPECT_TRUE(restart_called);
+  }
+}
+
 }  // namespace ash
diff --git a/chrome/browser/ash/file_manager/extract_io_task.cc b/chrome/browser/ash/file_manager/extract_io_task.cc
index 34307e5..278621a 100644
--- a/chrome/browser/ash/file_manager/extract_io_task.cc
+++ b/chrome/browser/ash/file_manager/extract_io_task.cc
@@ -37,7 +37,6 @@
 
 void ExtractIOTask::ZipExtractCallback(bool success) {
   progress_.state = success ? State::kSuccess : State::kError;
-  progress_callback_.Run(progress_);
   DCHECK_GT(extractCount_, 0);
   if (--extractCount_ == 0) {
     Complete();
diff --git a/chrome/browser/ash/file_manager/file_manager_browsertest.cc b/chrome/browser/ash/file_manager/file_manager_browsertest.cc
index dec65cd6..29aa6c96 100644
--- a/chrome/browser/ash/file_manager/file_manager_browsertest.cc
+++ b/chrome/browser/ash/file_manager/file_manager_browsertest.cc
@@ -1454,91 +1454,164 @@
 WRAPPED_INSTANTIATE_TEST_SUITE_P(
     FilesTooltip, /* files_tooltip.js */
     FilesAppBrowserTest,
-    ::testing::Values(TestCase("filesTooltipFocus"),
-                      TestCase("filesTooltipLabelChange"),
-                      TestCase("filesTooltipMouseOver"),
-                      TestCase("filesTooltipClickHides"),
-                      TestCase("filesTooltipHidesOnWindowResize"),
-                      TestCase("filesCardTooltipClickHides"),
-                      TestCase("filesTooltipHidesOnDeleteDialogClosed")));
+    ::testing::Values(
+        TestCase("filesTooltipFocus"),
+        TestCase("filesTooltipFocus").FilesSwa(),
+        TestCase("filesTooltipLabelChange"),
+        TestCase("filesTooltipLabelChange").FilesSwa(),
+        TestCase("filesTooltipMouseOver"),
+        TestCase("filesTooltipMouseOver").FilesSwa(),
+        TestCase("filesTooltipClickHides"),
+        TestCase("filesTooltipClickHides").FilesSwa(),
+        TestCase("filesTooltipHidesOnWindowResize"),
+        // TODO(b/189173190): Add SWA OnWindowResize test using window.resizeTo.
+        TestCase("filesCardTooltipClickHides"),
+        TestCase("filesCardTooltipClickHides").FilesSwa(),
+        TestCase("filesTooltipHidesOnDeleteDialogClosed"),
+        TestCase("filesTooltipHidesOnDeleteDialogClosed").FilesSwa()));
 
 WRAPPED_INSTANTIATE_TEST_SUITE_P(
     FileList, /* file_list.js */
     FilesAppBrowserTest,
-    ::testing::Values(TestCase("fileListAriaAttributes"),
-                      TestCase("fileListFocusFirstItem"),
-                      TestCase("fileListSelectLastFocusedItem"),
-                      TestCase("fileListKeyboardSelectionA11y"),
-                      TestCase("fileListMouseSelectionA11y"),
-                      TestCase("fileListDeleteMultipleFiles"),
-                      TestCase("fileListDeleteMultipleFiles").EnableTrash(),
-                      TestCase("fileListRenameSelectedItem"),
-                      TestCase("fileListRenameFromSelectAll")));
+    ::testing::Values(
+        TestCase("fileListAriaAttributes"),
+        TestCase("fileListAriaAttributes").FilesSwa(),
+        TestCase("fileListFocusFirstItem"),
+        TestCase("fileListFocusFirstItem").FilesSwa(),
+        TestCase("fileListSelectLastFocusedItem"),
+        TestCase("fileListSelectLastFocusedItem").FilesSwa(),
+        TestCase("fileListKeyboardSelectionA11y"),
+        TestCase("fileListKeyboardSelectionA11y").FilesSwa(),
+        TestCase("fileListMouseSelectionA11y"),
+        TestCase("fileListMouseSelectionA11y").FilesSwa(),
+        TestCase("fileListDeleteMultipleFiles"),
+        TestCase("fileListDeleteMultipleFiles").FilesSwa(),
+        TestCase("fileListDeleteMultipleFiles").EnableTrash(),
+        TestCase("fileListDeleteMultipleFiles").EnableTrash().FilesSwa(),
+        TestCase("fileListRenameSelectedItem"),
+        TestCase("fileListRenameSelectedItem").FilesSwa(),
+        TestCase("fileListRenameFromSelectAll"),
+        TestCase("fileListRenameFromSelectAll").FilesSwa()));
 
 WRAPPED_INSTANTIATE_TEST_SUITE_P(
     Crostini, /* crostini.js */
     FilesAppBrowserTest,
-    ::testing::Values(TestCase("mountCrostini"),
-                      TestCase("enableDisableCrostini"),
-                      TestCase("sharePathWithCrostini"),
-                      TestCase("pluginVmDirectoryNotSharedErrorDialog"),
-                      TestCase("pluginVmFileOnExternalDriveErrorDialog"),
-                      TestCase("pluginVmFileDropFailErrorDialog")));
+    ::testing::Values(
+        TestCase("mountCrostini"),
+        TestCase("mountCrostini").FilesSwa(),
+        TestCase("enableDisableCrostini"),
+        TestCase("enableDisableCrostini").FilesSwa(),
+        TestCase("sharePathWithCrostini"),
+        TestCase("sharePathWithCrostini").FilesSwa(),
+        TestCase("pluginVmDirectoryNotSharedErrorDialog"),
+        TestCase("pluginVmDirectoryNotSharedErrorDialog").FilesSwa(),
+        TestCase("pluginVmFileOnExternalDriveErrorDialog"),
+        TestCase("pluginVmFileOnExternalDriveErrorDialog").FilesSwa(),
+        TestCase("pluginVmFileDropFailErrorDialog"),
+        TestCase("pluginVmFileDropFailErrorDialog").FilesSwa()));
 
 WRAPPED_INSTANTIATE_TEST_SUITE_P(
     MyFiles, /* my_files.js */
     FilesAppBrowserTest,
     ::testing::Values(
         TestCase("directoryTreeRefresh"),
+        TestCase("directoryTreeRefresh").FilesSwa(),
         TestCase("showMyFiles"),
+        TestCase("showMyFiles").FilesSwa(),
         TestCase("showMyFiles").EnableTrash(),
+        TestCase("showMyFiles").EnableTrash().FilesSwa(),
         TestCase("myFilesDisplaysAndOpensEntries"),
+        TestCase("myFilesDisplaysAndOpensEntries").FilesSwa(),
         TestCase("myFilesFolderRename"),
+        TestCase("myFilesFolderRename").FilesSwa(),
         TestCase("myFilesUpdatesWhenAndroidVolumeMounts").DontMountVolumes(),
         TestCase("myFilesUpdatesWhenAndroidVolumeMounts")
             .DontMountVolumes()
             .FilesSwa(),
         TestCase("myFilesUpdatesChildren"),
+        TestCase("myFilesUpdatesChildren").FilesSwa(),
         TestCase("myFilesAutoExpandOnce"),
-        TestCase("myFilesToolbarDelete")));
+        TestCase("myFilesAutoExpandOnce").FilesSwa(),
+        TestCase("myFilesToolbarDelete"),
+        TestCase("myFilesToolbarDelete").FilesSwa()));
 
 WRAPPED_INSTANTIATE_TEST_SUITE_P(
     InstallLinuxPackageDialog, /* install_linux_package_dialog.js */
     FilesAppBrowserTest,
-    ::testing::Values(TestCase("installLinuxPackageDialog")));
+    ::testing::Values(TestCase("installLinuxPackageDialog"),
+                      TestCase("installLinuxPackageDialog").FilesSwa()));
 
 WRAPPED_INSTANTIATE_TEST_SUITE_P(
     Recents, /* recents.js */
     FilesAppBrowserTest,
     ::testing::Values(
         TestCase("recentsDownloads"),
+        TestCase("recentsDownloads").FilesSwa(),
         TestCase("recentsDownloads").EnableFiltersInRecents(),
+        TestCase("recentsDownloads").EnableFiltersInRecents().FilesSwa(),
         TestCase("recentsDrive"),
+        TestCase("recentsDrive").FilesSwa(),
         TestCase("recentsDrive").EnableFiltersInRecents(),
+        TestCase("recentsDrive").EnableFiltersInRecents().FilesSwa(),
         TestCase("recentsCrostiniNotMounted"),
+        TestCase("recentsCrostiniNotMounted").FilesSwa(),
         TestCase("recentsCrostiniNotMounted").EnableFiltersInRecents(),
+        TestCase("recentsCrostiniNotMounted")
+            .EnableFiltersInRecents()
+            .FilesSwa(),
         TestCase("recentsCrostiniMounted"),
+        TestCase("recentsCrostiniMounted").FilesSwa(),
         TestCase("recentsCrostiniMounted").EnableFiltersInRecents(),
+        TestCase("recentsCrostiniMounted").EnableFiltersInRecents().FilesSwa(),
         TestCase("recentsDownloadsAndDrive"),
+        TestCase("recentsDownloadsAndDrive").FilesSwa(),
         TestCase("recentsDownloadsAndDrive").EnableFiltersInRecents(),
+        TestCase("recentsDownloadsAndDrive")
+            .EnableFiltersInRecents()
+            .FilesSwa(),
         TestCase("recentsDownloadsAndDriveWithOverlap"),
+        TestCase("recentsDownloadsAndDriveWithOverlap").FilesSwa(),
         TestCase("recentsDownloadsAndDriveWithOverlap")
             .EnableFiltersInRecents(),
+        TestCase("recentsDownloadsAndDriveWithOverlap")
+            .EnableFiltersInRecents()
+            .FilesSwa(),
         TestCase("recentsFilterResetToAll").EnableFiltersInRecents(),
+        TestCase("recentsFilterResetToAll").EnableFiltersInRecents().FilesSwa(),
         TestCase("recentsNested"),
+        TestCase("recentsNested").FilesSwa(),
         TestCase("recentsNested").EnableFiltersInRecents(),
+        TestCase("recentsNested").EnableFiltersInRecents().FilesSwa(),
         TestCase("recentAudioDownloads"),
+        TestCase("recentAudioDownloads").FilesSwa(),
         TestCase("recentAudioDownloads").EnableFiltersInRecents(),
+        TestCase("recentAudioDownloads").EnableFiltersInRecents().FilesSwa(),
         TestCase("recentAudioDownloadsAndDrive"),
+        TestCase("recentAudioDownloadsAndDrive").FilesSwa(),
         TestCase("recentAudioDownloadsAndDrive").EnableFiltersInRecents(),
+        TestCase("recentAudioDownloadsAndDrive")
+            .EnableFiltersInRecents()
+            .FilesSwa(),
         TestCase("recentImagesDownloads"),
+        TestCase("recentImagesDownloads").FilesSwa(),
         TestCase("recentImagesDownloads").EnableFiltersInRecents(),
+        TestCase("recentImagesDownloads").EnableFiltersInRecents().FilesSwa(),
         TestCase("recentImagesDownloadsAndDrive"),
+        TestCase("recentImagesDownloadsAndDrive").FilesSwa(),
         TestCase("recentImagesDownloadsAndDrive").EnableFiltersInRecents(),
+        TestCase("recentImagesDownloadsAndDrive")
+            .EnableFiltersInRecents()
+            .FilesSwa(),
         TestCase("recentVideosDownloads"),
+        TestCase("recentVideosDownloads").FilesSwa(),
         TestCase("recentVideosDownloads").EnableFiltersInRecents(),
+        TestCase("recentVideosDownloads").EnableFiltersInRecents().FilesSwa(),
         TestCase("recentVideosDownloadsAndDrive"),
-        TestCase("recentVideosDownloadsAndDrive").EnableFiltersInRecents()));
+        TestCase("recentVideosDownloadsAndDrive").FilesSwa(),
+        TestCase("recentVideosDownloadsAndDrive").EnableFiltersInRecents(),
+        TestCase("recentVideosDownloadsAndDrive")
+            .EnableFiltersInRecents()
+            .FilesSwa()));
 
 WRAPPED_INSTANTIATE_TEST_SUITE_P(
     Metadata, /* metadata.js */
@@ -1549,23 +1622,35 @@
             .EnableGenericDocumentsProvider()
             .FilesSwa(),
         TestCase("metadataDownloads"),
+        TestCase("metadataDownloads").FilesSwa(),
         TestCase("metadataDrive"),
+        TestCase("metadataDrive").FilesSwa(),
         TestCase("metadataTeamDrives"),
-        TestCase("metadataLargeDrive")));
+        TestCase("metadataTeamDrives").FilesSwa(),
+        TestCase("metadataLargeDrive"),
+        TestCase("metadataLargeDrive").FilesSwa()));
 
 WRAPPED_INSTANTIATE_TEST_SUITE_P(
     Search, /* search.js */
     FilesAppBrowserTest,
     ::testing::Values(TestCase("searchDownloadsWithResults"),
+                      TestCase("searchDownloadsWithResults").FilesSwa(),
                       TestCase("searchDownloadsWithNoResults"),
+                      TestCase("searchDownloadsWithNoResults").FilesSwa(),
                       TestCase("searchDownloadsClearSearchKeyDown"),
                       TestCase("searchDownloadsClearSearchKeyDown").FilesSwa(),
                       TestCase("searchDownloadsClearSearch"),
                       TestCase("searchDownloadsClearSearch").FilesSwa(),
                       TestCase("searchHidingViaTab"),
+                      TestCase("searchHidingViaTab").FilesSwa(),
                       TestCase("searchHidingTextEntryField"),
+                      TestCase("searchHidingTextEntryField").FilesSwa(),
                       TestCase("searchButtonToggles"),
-                      TestCase("searchQueryLaunchParam")));
+                      TestCase("searchButtonToggles").FilesSwa(),
+                      TestCase("searchQueryLaunchParam")
+                      // TODO(b/189173190): Enable
+                      // TestCase("searchQueryLaunchParam").FilesSwa()
+                      ));
 
 WRAPPED_INSTANTIATE_TEST_SUITE_P(
     Metrics, /* metrics.js */
@@ -1578,40 +1663,74 @@
 WRAPPED_INSTANTIATE_TEST_SUITE_P(
     Breadcrumbs, /* breadcrumbs.js */
     FilesAppBrowserTest,
-    ::testing::Values(TestCase("breadcrumbsNavigate"),
-                      TestCase("breadcrumbsDownloadsTranslation"),
-                      TestCase("breadcrumbsDownloadsTranslation").FilesSwa(),
-                      TestCase("breadcrumbsRenderShortPath"),
-                      TestCase("breadcrumbsEliderButtonHidden"),
-                      TestCase("breadcrumbsRenderLongPath"),
-                      TestCase("breadcrumbsMainButtonClick"),
-                      TestCase("breadcrumbsMainButtonEnterKey"),
-                      TestCase("breadcrumbsEliderButtonClick"),
-                      TestCase("breadcrumbsEliderButtonKeyboard"),
-                      TestCase("breadcrumbsEliderMenuClickOutside"),
-                      TestCase("breadcrumbsEliderMenuItemClick"),
-                      TestCase("breadcrumbsEliderMenuItemTabLeft"),
-                      TestCase("breadcrumbsEliderMenuItemTabRight"),
-                      TestCase("breadcrumbsDontExceedAvailableViewport")));
+    ::testing::Values(
+        TestCase("breadcrumbsNavigate"),
+        TestCase("breadcrumbsNavigate").FilesSwa(),
+        TestCase("breadcrumbsDownloadsTranslation"),
+        TestCase("breadcrumbsDownloadsTranslation").FilesSwa(),
+        TestCase("breadcrumbsRenderShortPath"),
+        TestCase("breadcrumbsRenderShortPath").FilesSwa(),
+        TestCase("breadcrumbsEliderButtonHidden"),
+        TestCase("breadcrumbsEliderButtonHidden").FilesSwa(),
+        TestCase("breadcrumbsRenderLongPath"),
+        TestCase("breadcrumbsRenderLongPath").FilesSwa(),
+        TestCase("breadcrumbsMainButtonClick"),
+        TestCase("breadcrumbsMainButtonClick").FilesSwa(),
+        TestCase("breadcrumbsMainButtonEnterKey"),
+        TestCase("breadcrumbsMainButtonEnterKey").FilesSwa(),
+        TestCase("breadcrumbsEliderButtonClick"),
+        TestCase("breadcrumbsEliderButtonClick").FilesSwa(),
+        TestCase("breadcrumbsEliderButtonKeyboard"),
+        TestCase("breadcrumbsEliderButtonKeyboard").FilesSwa(),
+        TestCase("breadcrumbsEliderMenuClickOutside"),
+        TestCase("breadcrumbsEliderMenuClickOutside").FilesSwa(),
+        TestCase("breadcrumbsEliderMenuItemClick"),
+        TestCase("breadcrumbsEliderMenuItemClick").FilesSwa(),
+        TestCase("breadcrumbsEliderMenuItemTabLeft"),
+        TestCase("breadcrumbsEliderMenuItemTabLeft").FilesSwa(),
+        TestCase("breadcrumbsEliderMenuItemTabRight"),
+        TestCase("breadcrumbsEliderMenuItemTabRight").FilesSwa()));
 
 WRAPPED_INSTANTIATE_TEST_SUITE_P(
     FormatDialog, /* format_dialog.js */
     FilesAppBrowserTest,
     ::testing::Values(
         TestCase("formatDialog"),
+        TestCase("formatDialog").FilesSwa(),
         TestCase("formatDialogIsModal"),
+        TestCase("formatDialogIsModal").FilesSwa(),
         TestCase("formatDialogEmpty"),
+        TestCase("formatDialogEmpty").FilesSwa(),
         TestCase("formatDialogCancel"),
+        TestCase("formatDialogCancel").FilesSwa(),
         TestCase("formatDialogNameLength"),
+        TestCase("formatDialogNameLength").FilesSwa(),
         TestCase("formatDialogNameInvalid"),
+        TestCase("formatDialogNameInvalid").FilesSwa(),
         TestCase("formatDialogGearMenu"),
+        TestCase("formatDialogGearMenu").FilesSwa(),
         TestCase("formatDialog").EnableSinglePartitionFormat(),
+        TestCase("formatDialog").EnableSinglePartitionFormat().FilesSwa(),
         TestCase("formatDialogIsModal").EnableSinglePartitionFormat(),
+        TestCase("formatDialogIsModal")
+            .EnableSinglePartitionFormat()
+            .FilesSwa(),
         TestCase("formatDialogEmpty").EnableSinglePartitionFormat(),
+        TestCase("formatDialogEmpty").EnableSinglePartitionFormat().FilesSwa(),
         TestCase("formatDialogCancel").EnableSinglePartitionFormat(),
+        TestCase("formatDialogCancel").EnableSinglePartitionFormat().FilesSwa(),
         TestCase("formatDialogNameLength").EnableSinglePartitionFormat(),
+        TestCase("formatDialogNameLength")
+            .EnableSinglePartitionFormat()
+            .FilesSwa(),
         TestCase("formatDialogNameInvalid").EnableSinglePartitionFormat(),
-        TestCase("formatDialogGearMenu").EnableSinglePartitionFormat()));
+        TestCase("formatDialogNameInvalid")
+            .EnableSinglePartitionFormat()
+            .FilesSwa(),
+        TestCase("formatDialogGearMenu").EnableSinglePartitionFormat(),
+        TestCase("formatDialogGearMenu")
+            .EnableSinglePartitionFormat()
+            .FilesSwa()));
 
 WRAPPED_INSTANTIATE_TEST_SUITE_P(
     Trash, /* trash.js */
diff --git a/chrome/browser/ash/file_manager/file_manager_string_util.cc b/chrome/browser/ash/file_manager/file_manager_string_util.cc
index 2cca21a3..6504bee6 100644
--- a/chrome/browser/ash/file_manager/file_manager_string_util.cc
+++ b/chrome/browser/ash/file_manager/file_manager_string_util.cc
@@ -479,8 +479,10 @@
   SET_STRING("FILENAME_LABEL", IDS_FILE_BROWSER_FILENAME_LABEL);
   SET_STRING("FILE_ALREADY_EXISTS", IDS_FILE_BROWSER_FILE_ALREADY_EXISTS);
   SET_STRING("FILE_COPIED", IDS_FILE_BROWSER_FILE_COPIED);
+  SET_STRING("FILE_EXTRACTED", IDS_FILE_BROWSER_FILE_EXTRACTED);
   SET_STRING("FILE_ITEMS", IDS_FILE_BROWSER_FILE_ITEMS);
   SET_STRING("FILE_ITEMS_COPIED", IDS_FILE_BROWSER_FILE_ITEMS_COPIED);
+  SET_STRING("FILE_ITEMS_EXTRACTED", IDS_FILE_BROWSER_FILE_ITEMS_EXTRACTED);
   SET_STRING("FILE_ITEMS_MOVED", IDS_FILE_BROWSER_FILE_ITEMS_MOVED);
   SET_STRING("FILE_MOVED", IDS_FILE_BROWSER_FILE_MOVED);
   SET_STRING("FOLDER_SHARED_WITH_CROSTINI",
diff --git a/chrome/browser/ash/login/ui/login_display_host_webui.cc b/chrome/browser/ash/login/ui/login_display_host_webui.cc
index 5606276..b917a84 100644
--- a/chrome/browser/ash/login/ui/login_display_host_webui.cc
+++ b/chrome/browser/ash/login/ui/login_display_host_webui.cc
@@ -43,6 +43,7 @@
 #include "chrome/browser/ash/login/existing_user_controller.h"
 #include "chrome/browser/ash/login/helper.h"
 #include "chrome/browser/ash/login/login_wizard.h"
+#include "chrome/browser/ash/login/oobe_screen.h"
 #include "chrome/browser/ash/login/startup_utils.h"
 #include "chrome/browser/ash/login/ui/input_events_blocker.h"
 #include "chrome/browser/ash/login/ui/login_display_host_mojo.h"
@@ -594,9 +595,6 @@
 
   // TODO(crbug.com/784495): Make sure this is ported to views.
   if (!login_window_) {
-    TRACE_EVENT_NESTABLE_ASYNC_BEGIN0(
-        "ui", "ShowLoginWebUI",
-        TRACE_ID_WITH_SCOPE(kShowLoginWebUIid, TRACE_ID_GLOBAL(1)));
     TRACE_EVENT_NESTABLE_ASYNC_INSTANT0(
         "ui", "StartSignInScreen",
         TRACE_ID_WITH_SCOPE(kShowLoginWebUIid, TRACE_ID_GLOBAL(1)));
@@ -795,6 +793,18 @@
   ShutdownDisplayHost();
 }
 
+void LoginDisplayHostWebUI::OnCurrentScreenChanged(OobeScreenId current_screen,
+                                                   OobeScreenId new_screen) {
+  if (current_screen == OobeScreen::SCREEN_UNKNOWN) {
+    // First screen shown.
+    session_manager::SessionManager::Get()->NotifyLoginOrLockScreenVisible();
+  }
+}
+
+void LoginDisplayHostWebUI::OnDestroyingOobeUI() {
+  GetOobeUI()->RemoveObserver(this);
+}
+
 bool LoginDisplayHostWebUI::IsOobeUIDialogVisible() const {
   return true;
 }
@@ -832,6 +842,8 @@
   // Subscribe to crash events.
   content::WebContentsObserver::Observe(login_view_->GetWebContents());
   login_view_->LoadURL(url);
+  CHECK(GetOobeUI());
+  GetOobeUI()->AddObserver(this);
 }
 
 void LoginDisplayHostWebUI::ShowWebUI() {
@@ -881,8 +893,6 @@
   login_view_ = new WebUILoginView(WebUILoginView::WebViewSettings(),
                                    weak_factory_.GetWeakPtr());
   login_view_->Init();
-  if (login_view_->webui_visible())
-    OnLoginPromptVisible();
   if (disable_restrictive_proxy_check_for_test_) {
     DisableRestrictiveProxyCheckForTest();
   }
@@ -946,8 +956,10 @@
     return;
 
   OobeUI* oobe_ui = login_view_->GetOobeUI();
-  if (oobe_ui)
+  if (oobe_ui) {
     oobe_ui->signin_screen_handler()->SetDelegate(nullptr);
+    oobe_ui->RemoveObserver(this);
+  }
 
   login_view_ = nullptr;
 }
diff --git a/chrome/browser/ash/login/ui/login_display_host_webui.h b/chrome/browser/ash/login/ui/login_display_host_webui.h
index dc860832..6a39bb9 100644
--- a/chrome/browser/ash/login/ui/login_display_host_webui.h
+++ b/chrome/browser/ash/login/ui/login_display_host_webui.h
@@ -23,6 +23,7 @@
 #include "chrome/browser/ash/login/ui/login_display_host_common.h"
 #include "chrome/browser/ash/login/wizard_controller.h"
 #include "chrome/browser/ui/ash/multi_user/multi_user_window_manager_helper.h"
+#include "chrome/browser/ui/webui/chromeos/login/oobe_ui.h"
 #include "chromeos/dbus/session_manager/session_manager_client.h"
 #include "components/session_manager/core/session_manager.h"
 #include "components/session_manager/core/session_manager_observer.h"
@@ -56,7 +57,8 @@
                               public ui::InputDeviceEventObserver,
                               public views::WidgetRemovalsObserver,
                               public views::WidgetObserver,
-                              public MultiUserWindowManagerObserver {
+                              public MultiUserWindowManagerObserver,
+                              public OobeUI::Observer {
  public:
   LoginDisplayHostWebUI();
 
@@ -156,6 +158,12 @@
   // MultiUserWindowManagerObserver:
   void OnUserSwitchAnimationFinished() override;
 
+  // OobeUI::Observer:
+  void OnCurrentScreenChanged(OobeScreenId current_screen,
+                              OobeScreenId new_screen) override;
+  void OnDestroyingOobeUI() override;
+
+  // LoginDisplayHostCommon:
   bool IsOobeUIDialogVisible() const override;
 
  private:
diff --git a/chrome/browser/ash/login/ui/webui_login_view.h b/chrome/browser/ash/login/ui/webui_login_view.h
index f7a879c..1328985 100644
--- a/chrome/browser/ash/login/ui/webui_login_view.h
+++ b/chrome/browser/ash/login/ui/webui_login_view.h
@@ -117,8 +117,6 @@
 
   void set_is_hidden(bool hidden) { is_hidden_ = hidden; }
 
-  bool webui_visible() const { return webui_visible_; }
-
   // Let suppress emission of this signal.
   void set_should_emit_login_prompt_visible(bool emit) {
     should_emit_login_prompt_visible_ = emit;
diff --git a/chrome/browser/ash/power/auto_screen_brightness/als_reader.cc b/chrome/browser/ash/power/auto_screen_brightness/als_reader.cc
index a4872cb9..2ebd7871 100644
--- a/chrome/browser/ash/power/auto_screen_brightness/als_reader.cc
+++ b/chrome/browser/ash/power/auto_screen_brightness/als_reader.cc
@@ -64,6 +64,9 @@
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(!blocking_task_runner_);
 
+#if BUILDFLAG(USE_IIOSERVICE)
+  provider_ = std::make_unique<LightProviderMojo>(this);
+#else   // !BUILDFLAG(USE_IIOSERVICE)
   blocking_task_runner_ = base::ThreadPool::CreateSequencedTaskRunner(
       {base::TaskPriority::BEST_EFFORT, base::MayBlock(),
        base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN});
@@ -72,6 +75,7 @@
       blocking_task_runner_.get(), FROM_HERE, base::BindOnce(&GetNumAls),
       base::BindOnce(&AlsReader::OnNumAlsRetrieved,
                      weak_ptr_factory_.GetWeakPtr()));
+#endif  // BUILDFLAG(USE_IIOSERVICE)
 }
 
 void AlsReader::AddObserver(Observer* const observer) {
@@ -88,6 +92,7 @@
   observers_.RemoveObserver(observer);
 }
 
+#if !BUILDFLAG(USE_IIOSERVICE)
 void AlsReader::OnNumAlsRetrieved(int num_als) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   if (num_als <= 0) {
@@ -95,15 +100,11 @@
     return;
   }
 
-#if BUILDFLAG(USE_IIOSERVICE)
-  blocking_task_runner_.reset();
-  provider_ = std::make_unique<LightProviderMojo>(this, num_als > 1);
-#else   // !BUILDFLAG(USE_IIOSERVICE)
   auto provider = std::make_unique<AlsFileReader>(this);
   provider->Init(std::move(blocking_task_runner_));
   provider_ = std::move(provider);
-#endif  // BUILDFLAG(USE_IIOSERVICE)
 }
+#endif  // !BUILDFLAG(USE_IIOSERVICE)
 
 void AlsReader::SetLux(int lux) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
diff --git a/chrome/browser/ash/power/auto_screen_brightness/als_reader.h b/chrome/browser/ash/power/auto_screen_brightness/als_reader.h
index b6b5a55..eab147b9 100644
--- a/chrome/browser/ash/power/auto_screen_brightness/als_reader.h
+++ b/chrome/browser/ash/power/auto_screen_brightness/als_reader.h
@@ -11,6 +11,7 @@
 #include "base/observer_list.h"
 #include "base/sequence_checker.h"
 #include "base/task/sequenced_task_runner.h"
+#include "chromeos/components/sensors/buildflags.h"
 
 namespace ash {
 namespace power {
@@ -70,8 +71,10 @@
   friend LightProviderMojo;
   friend LightSamplesObserver;
 
+#if !BUILDFLAG(USE_IIOSERVICE)
   // Called when we've retrieved the number of ALS present.
   void OnNumAlsRetrieved(int num_als);
+#endif  // !BUILDFLAG(USE_IIOSERVICE)
 
   void SetLux(int lux);
   void SetAlsInitStatus(AlsInitStatus status);
diff --git a/chrome/browser/ash/power/auto_screen_brightness/light_provider_mojo.cc b/chrome/browser/ash/power/auto_screen_brightness/light_provider_mojo.cc
index e338add64..b4b30565f 100644
--- a/chrome/browser/ash/power/auto_screen_brightness/light_provider_mojo.cc
+++ b/chrome/browser/ash/power/auto_screen_brightness/light_provider_mojo.cc
@@ -30,10 +30,8 @@
 namespace power {
 namespace auto_screen_brightness {
 
-LightProviderMojo::LightProviderMojo(AlsReader* als_reader,
-                                     bool has_several_light_sensors)
-    : LightProviderInterface(als_reader),
-      has_several_light_sensors_(has_several_light_sensors) {
+LightProviderMojo::LightProviderMojo(AlsReader* als_reader)
+    : LightProviderInterface(als_reader) {
   RegisterSensorClient();
 }
 
@@ -63,15 +61,10 @@
     DCHECK(!light.ignored);
     DCHECK(light.name.has_value());
 
-    if (has_several_light_sensors_) {
-      // The used sensor is cros-ec-light on the lid.
-      DCHECK(light.name.value().compare(kCrosECLightName) == 0);
-      DCHECK(light.on_lid);
+    if (light.name.value().compare(kCrosECLightName) == 0 &&
+        light.on_lid.value_or(false)) {
       return;
     }
-
-    if (light.name.value().compare(kCrosECLightName) == 0)
-      return;
   }
 
   sensor_service_remote_->RegisterNewDevicesObserver(
@@ -228,18 +221,11 @@
 
   light.remote = GetSensorDeviceRemote(id);
 
-  if (has_several_light_sensors_) {
-    light.remote->GetAttributes(
-        std::vector<std::string>{chromeos::sensors::mojom::kDeviceName,
-                                 chromeos::sensors::mojom::kLocation},
-        base::BindOnce(&LightProviderMojo::GetNameLocationCallback,
-                       weak_ptr_factory_.GetWeakPtr(), id));
-  } else {
-    light.remote->GetAttributes(
-        std::vector<std::string>{chromeos::sensors::mojom::kDeviceName},
-        base::BindOnce(&LightProviderMojo::GetNameCallback,
-                       weak_ptr_factory_.GetWeakPtr(), id));
-  }
+  light.remote->GetAttributes(
+      std::vector<std::string>{chromeos::sensors::mojom::kDeviceName,
+                               chromeos::sensors::mojom::kLocation},
+      base::BindOnce(&LightProviderMojo::GetNameLocationCallback,
+                     weak_ptr_factory_.GetWeakPtr(), id));
 }
 
 void LightProviderMojo::GetNameLocationCallback(
@@ -249,9 +235,14 @@
   DCHECK_NE(light_device_id_.value_or(-1), id);
 
   if (light_device_id_.has_value()) {
-    // Already has the cros-ec-light on the lid. Ignoring other light sensors.
-    IgnoreLight(id);
-    return;
+    auto& orig_light = lights_[light_device_id_.value()];
+    if (orig_light.name.has_value() &&
+        orig_light.name.value().compare(kCrosECLightName) == 0 &&
+        orig_light.on_lid.value_or(false)) {
+      // Already has the cros-ec-light on the lid. Ignoring other light sensors.
+      IgnoreLight(id);
+      return;
+    }
   }
 
   if (values.size() < 2) {
@@ -270,63 +261,37 @@
   DCHECK(light.remote.is_bound());
 
   light.name = values[0];
-  if (!light.name.has_value() ||
-      light.name.value().compare(kCrosECLightName) != 0) {
-    LOG(ERROR) << "Not " << kCrosECLightName
-               << ", sensor name: " << light.name.value_or("");
-    IgnoreLight(id);
-    return;
-  }
-
   light.on_lid =
       values[1].has_value() &&
       (values[1].value().compare(chromeos::sensors::mojom::kLocationLid) == 0);
 
-  if (!light.on_lid.value()) {
-    IgnoreLight(id);
-    return;
-  }
-
-  DetermineLightSensor(id);
-  new_devices_observer_.reset();  // Don't need new light sensors anymore.
-}
-
-void LightProviderMojo::GetNameCallback(
-    int32_t id,
-    const std::vector<absl::optional<std::string>>& values) {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  DCHECK_NE(light_device_id_.value_or(-1), id);
-
-  if (light_device_id_.has_value()) {
-    auto& orig_light = lights_[light_device_id_.value()];
-    if (orig_light.name.has_value() &&
-        orig_light.name.value().compare(kCrosECLightName) == 0) {
-      // Already has the cros-ec-light. Ignoring other light sensors.
-      IgnoreLight(id);
-      return;
-    }
-  }
-
-  if (values.empty()) {
-    LOG(ERROR) << "Sensor values doesn't contain the name attribute";
-    IgnoreLight(id);
-    return;
-  }
-
-  if (values.size() != 1) {
-    LOG(WARNING) << "Sensor values contain more than the name attribute. Size: "
-                 << values.size();
-  }
-
-  auto& light = lights_[id];
-  DCHECK(light.remote.is_bound());
-
-  light.name = values[0];
   if (light.name.has_value() &&
       light.name.value().compare(kCrosECLightName) == 0) {
-    // If an acpi-als was chosen, migrate to this cros-ec-light light sensor.
+    if (light.on_lid.value_or(false)) {
+      // If a light sensor was chosen, migrate to this cros-ec-light light
+      // sensor.
+      DetermineLightSensor(id);
+      new_devices_observer_.reset();  // Don't need new light sensors anymore.
+      return;
+    }
+
+    if (light_device_id_.has_value()) {
+      auto& orig_light = lights_[light_device_id_.value()];
+      if (orig_light.name.has_value() &&
+          orig_light.name.value().compare(kCrosECLightName) == 0) {
+        // Already has the cros-ec-light. Ignoring other non-lid cros-ec-light.
+        IgnoreLight(id);
+        return;
+      }
+    }
+
+    // Choose the current non-lid cros-ec-light.
     DetermineLightSensor(id);
-    new_devices_observer_.reset();  // Don't need new light sensors anymore.
+  }
+
+  if (light_device_id_.has_value()) {
+    // Use the current light sensor over the current non cros-ec-light.
+    IgnoreLight(id);
     return;
   }
 
@@ -335,13 +300,7 @@
     LOG(WARNING) << "Unexpected light name: " << light.name.value_or("");
   }
 
-  if (light_device_id_.has_value()) {
-    LOG(WARNING) << "Already have another light sensor with name: "
-                 << lights_[light_device_id_.value()].name.value_or("");
-    IgnoreLight(id);
-    return;
-  }
-
+  // Choose the current acpi-als.
   DetermineLightSensor(id);
 }
 
diff --git a/chrome/browser/ash/power/auto_screen_brightness/light_provider_mojo.h b/chrome/browser/ash/power/auto_screen_brightness/light_provider_mojo.h
index 4a8a076..0101c98 100644
--- a/chrome/browser/ash/power/auto_screen_brightness/light_provider_mojo.h
+++ b/chrome/browser/ash/power/auto_screen_brightness/light_provider_mojo.h
@@ -34,7 +34,7 @@
       public chromeos::sensors::mojom::SensorHalClient,
       public chromeos::sensors::mojom::SensorServiceNewDevicesObserver {
  public:
-  LightProviderMojo(AlsReader* als_reader, bool has_several_light_sensors);
+  explicit LightProviderMojo(AlsReader* als_reader);
   LightProviderMojo(const LightProviderMojo&) = delete;
   LightProviderMojo& operator=(const LightProviderMojo&) = delete;
   ~LightProviderMojo() override;
@@ -97,8 +97,6 @@
   void GetNameLocationCallback(
       int32_t id,
       const std::vector<absl::optional<std::string>>& values);
-  void GetNameCallback(int32_t id,
-                       const std::vector<absl::optional<std::string>>& values);
 
   // Ignores the light with |id| due to some errors of it's attributes.
   void IgnoreLight(int32_t id);
@@ -112,10 +110,6 @@
   void DetermineLightSensor(int32_t id);
   void SetupLightSamplesObserver();
 
-  // Needs cros-ec-light on the lid if true; prefer cros-ec-light than acpi-als
-  // if false.
-  bool has_several_light_sensors_;
-
   // The Mojo channel connecting to Sensor Hal Dispatcher.
   mojo::Receiver<chromeos::sensors::mojom::SensorHalClient> sensor_hal_client_{
       this};
diff --git a/chrome/browser/ash/power/auto_screen_brightness/light_provider_mojo_unittest.cc b/chrome/browser/ash/power/auto_screen_brightness/light_provider_mojo_unittest.cc
index bc4bd0d3..3fd70171 100644
--- a/chrome/browser/ash/power/auto_screen_brightness/light_provider_mojo_unittest.cc
+++ b/chrome/browser/ash/power/auto_screen_brightness/light_provider_mojo_unittest.cc
@@ -27,7 +27,6 @@
 constexpr int32_t kFakeLidLightId = 3;
 
 constexpr int64_t kFakeSampleData = 50;
-const char kWrongLocation[5] = "lidd";
 
 constexpr char kCrosECLightName[] = "cros-ec-light";
 constexpr char kAcpiAlsName[] = "acpi-als";
@@ -50,9 +49,8 @@
     chromeos::sensors::SensorHalDispatcher::Shutdown();
   }
 
-  void SetProvider(bool has_several_light_sensors) {
-    provider_ = std::make_unique<LightProviderMojo>(als_reader_.get(),
-                                                    has_several_light_sensors);
+  void SetProvider() {
+    provider_ = std::make_unique<LightProviderMojo>(als_reader_.get());
   }
 
   void AddDevice(int32_t iio_device_id,
@@ -114,20 +112,8 @@
   base::test::SingleThreadTaskEnvironment task_environment;
 };
 
-TEST_F(LightProviderMojoTest, GetSamplesWithOneSensor) {
-  SetProvider(/*has_several_light_sensors=*/false);
-  AddDevice(kFakeAcpiAlsId, kAcpiAlsName, absl::nullopt);
-
-  StartConnection();
-
-  // Wait until a sample is received.
-  base::RunLoop().RunUntilIdle();
-
-  CheckValues(kFakeAcpiAlsId);
-}
-
 TEST_F(LightProviderMojoTest, AssumingAcpiAlsWithoutDeviceNameWithOneSensor) {
-  SetProvider(/*has_several_light_sensors=*/false);
+  SetProvider();
   AddDevice(kFakeAcpiAlsId, absl::nullopt, absl::nullopt);
 
   StartConnection();
@@ -138,8 +124,8 @@
   CheckValues(kFakeAcpiAlsId);
 }
 
-TEST_F(LightProviderMojoTest, PreferCrosECLightWithOneSensor) {
-  SetProvider(/*has_several_light_sensors=*/false);
+TEST_F(LightProviderMojoTest, PreferCrosECLight) {
+  SetProvider();
   AddDevice(kFakeAcpiAlsId, kAcpiAlsName, absl::nullopt);
   AddDevice(kFakeLidLightId, kCrosECLightName, absl::nullopt);
 
@@ -151,28 +137,8 @@
   CheckValues(kFakeLidLightId);
 }
 
-TEST_F(LightProviderMojoTest, InvalidLocationWithSeveralLightSensors) {
-  SetProvider(/*has_several_light_sensors=*/true);
-  AddDevice(kFakeLidLightId, kCrosECLightName, kWrongLocation);
-
-  StartConnection();
-
-  // Wait until all tasks are done.
-  base::RunLoop().RunUntilIdle();
-
-  // Simulate the timeout so that the initialization fails.
-  TriggerNewDevicesTimeout();
-
-  // Wait until the mojo connection of SensorService is reset.
-  base::RunLoop().RunUntilIdle();
-
-  EXPECT_FALSE(sensor_hal_server_->GetSensorService()->HasReceivers());
-  EXPECT_EQ(fake_observer_.status(),
-            AlsReader::AlsInitStatus::kIncorrectConfig);
-}
-
-TEST_F(LightProviderMojoTest, GetSamplesFromLidLightsSeveralLightSensors) {
-  SetProvider(/*has_several_light_sensors=*/true);
+TEST_F(LightProviderMojoTest, GetSamplesFromLidLights) {
+  SetProvider();
   AddDevice(kFakeAcpiAlsId, kAcpiAlsName, absl::nullopt);
   AddDevice(kFakeBaseLightId, kCrosECLightName,
             chromeos::sensors::mojom::kLocationBase);
@@ -211,9 +177,8 @@
   CheckValues(kFakeLidLightId);
 }
 
-TEST_F(LightProviderMojoTest, PreferLateCrosECLightWithOneSensor) {
-  provider_ = std::make_unique<LightProviderMojo>(als_reader_.get(),
-                                                  /*has_two_sensors=*/false);
+TEST_F(LightProviderMojoTest, PreferLateCrosECLight) {
+  SetProvider();
   StartConnection();
 
   // Wait until all tasks are done.
@@ -244,9 +209,8 @@
   CheckValues(kFakeLidLightId);
 }
 
-TEST_F(LightProviderMojoTest, GetSamplesFromLateLidLightsWithTwoSensors) {
-  provider_ = std::make_unique<LightProviderMojo>(als_reader_.get(),
-                                                  /*has_two_sensors=*/true);
+TEST_F(LightProviderMojoTest, GetSamplesFromLateLidLights) {
+  SetProvider();
   StartConnection();
 
   // Wait until all tasks are done.
@@ -261,7 +225,7 @@
   // Wait until all tasks are done.
   base::RunLoop().RunUntilIdle();
 
-  EXPECT_FALSE(fake_observer_.has_status());
+  CheckValues(kFakeBaseLightId);
 
   AddDevice(kFakeLidLightId, kCrosECLightName,
             chromeos::sensors::mojom::kLocationLid);
@@ -278,8 +242,8 @@
   CheckValues(kFakeLidLightId);
 }
 
-TEST_F(LightProviderMojoTest, DeviceRemovedWithOneSensor) {
-  SetProvider(/*has_several_light_sensors=*/false);
+TEST_F(LightProviderMojoTest, DeviceRemoved) {
+  SetProvider();
   AddDevice(kFakeAcpiAlsId, kAcpiAlsName, absl::nullopt);
   AddDevice(kFakeBaseLightId, kCrosECLightName,
             chromeos::sensors::mojom::kLocationBase);
@@ -291,7 +255,8 @@
   // Wait until a sample is received.
   base::RunLoop().RunUntilIdle();
 
-  CheckValues(kFakeBaseLightId);
+  // cros-ec-light on lid is the highest priority.
+  CheckValues(kFakeLidLightId);
 
   sensor_devices_[kFakeAcpiAlsId]->ClearReceiversWithReason(
       chromeos::sensors::mojom::SensorDeviceDisconnectReason::DEVICE_REMOVED,
@@ -306,11 +271,11 @@
   // Wait until samples are received.
   base::RunLoop().RunUntilIdle();
 
-  sensor_devices_[kFakeBaseLightId]->ClearReceiversWithReason(
+  sensor_devices_[kFakeLidLightId]->ClearReceiversWithReason(
       chromeos::sensors::mojom::SensorDeviceDisconnectReason::DEVICE_REMOVED,
       "Device was removed");
   // Overwrite the lid light sensor in the iioservice.
-  AddDevice(kFakeBaseLightId, "", absl::nullopt);
+  AddDevice(kFakeLidLightId, "", absl::nullopt);
 
   // Wait until the disconnection and LightProviderMojo::ResetStates are done.
   base::RunLoop().RunUntilIdle();
@@ -321,7 +286,7 @@
   // Wait until all tasks are done.
   base::RunLoop().RunUntilIdle();
 
-  CheckValues(kFakeLidLightId);
+  CheckValues(kFakeBaseLightId);
 }
 
 }  // namespace auto_screen_brightness
diff --git a/chrome/browser/ash/web_applications/personalization_app/personalization_app_ambient_provider_impl.cc b/chrome/browser/ash/web_applications/personalization_app/personalization_app_ambient_provider_impl.cc
index c941a9d..0990e07 100644
--- a/chrome/browser/ash/web_applications/personalization_app/personalization_app_ambient_provider_impl.cc
+++ b/chrome/browser/ash/web_applications/personalization_app/personalization_app_ambient_provider_impl.cc
@@ -10,6 +10,7 @@
 #include <vector>
 
 #include "ash/constants/ash_features.h"
+#include "ash/public/cpp/ambient/ambient_animation_theme.h"
 #include "ash/public/cpp/ambient/ambient_client.h"
 #include "ash/public/cpp/ambient/ambient_metrics.h"
 #include "ash/public/cpp/ambient/ambient_prefs.h"
@@ -76,6 +77,11 @@
       base::BindRepeating(
           &PersonalizationAppAmbientProviderImpl::OnAmbientModeEnabledChanged,
           base::Unretained(this)));
+  pref_change_registrar_.Add(
+      ash::ambient::prefs::kAmbientAnimationTheme,
+      base::BindRepeating(
+          &PersonalizationAppAmbientProviderImpl::OnAnimationThemeChanged,
+          base::Unretained(this)));
 }
 
 PersonalizationAppAmbientProviderImpl::
@@ -106,6 +112,9 @@
   // Call it once to get the current ambient mode enabled status.
   OnAmbientModeEnabledChanged();
 
+  // Call it once to get the current animation theme.
+  OnAnimationThemeChanged();
+
   ResetLocalSettings();
   // Will notify WebUI when fetches successfully.
   FetchSettingsAndAlbums();
@@ -118,6 +127,14 @@
   pref_service->SetBoolean(ash::ambient::prefs::kAmbientModeEnabled, enabled);
 }
 
+void PersonalizationAppAmbientProviderImpl::SetAnimationTheme(
+    ash::AmbientAnimationTheme animation_theme) {
+  PrefService* pref_service = profile_->GetPrefs();
+  DCHECK(pref_service);
+  pref_service->SetInteger(ash::ambient::prefs::kAmbientAnimationTheme,
+                           static_cast<int>(animation_theme));
+}
+
 void PersonalizationAppAmbientProviderImpl::SetTopicSource(
     ash::AmbientModeTopicSource topic_source) {
   // If this is an Art gallery album page, will select art gallery topic source.
@@ -212,6 +229,13 @@
   }
 }
 
+void PersonalizationAppAmbientProviderImpl::OnAnimationThemeChanged() {
+  if (!ambient_observer_remote_.is_bound())
+    return;
+
+  ambient_observer_remote_->OnAnimationThemeChanged(GetCurrentAnimationTheme());
+}
+
 void PersonalizationAppAmbientProviderImpl::OnTemperatureUnitChanged() {
   if (!ambient_observer_remote_.is_bound())
     return;
@@ -278,6 +302,14 @@
   return pref_service->GetBoolean(ash::ambient::prefs::kAmbientModeEnabled);
 }
 
+ash::AmbientAnimationTheme
+PersonalizationAppAmbientProviderImpl::GetCurrentAnimationTheme() {
+  PrefService* pref_service = profile_->GetPrefs();
+  DCHECK(pref_service);
+  return static_cast<ash::AmbientAnimationTheme>(
+      pref_service->GetInteger(ash::ambient::prefs::kAmbientAnimationTheme));
+}
+
 void PersonalizationAppAmbientProviderImpl::UpdateSettings() {
   DCHECK(IsAmbientModeEnabled())
       << "Ambient mode must be enabled to update settings";
diff --git a/chrome/browser/ash/web_applications/personalization_app/personalization_app_ambient_provider_impl.h b/chrome/browser/ash/web_applications/personalization_app/personalization_app_ambient_provider_impl.h
index a07e3fea..4020a5e6 100644
--- a/chrome/browser/ash/web_applications/personalization_app/personalization_app_ambient_provider_impl.h
+++ b/chrome/browser/ash/web_applications/personalization_app/personalization_app_ambient_provider_impl.h
@@ -5,6 +5,7 @@
 #ifndef CHROME_BROWSER_ASH_WEB_APPLICATIONS_PERSONALIZATION_APP_PERSONALIZATION_APP_AMBIENT_PROVIDER_IMPL_H_
 #define CHROME_BROWSER_ASH_WEB_APPLICATIONS_PERSONALIZATION_APP_PERSONALIZATION_APP_AMBIENT_PROVIDER_IMPL_H_
 
+#include "ash/public/cpp/ambient/ambient_animation_theme.h"
 #include "ash/public/cpp/ambient/ambient_backend_controller.h"
 #include "ash/public/cpp/ambient/common/ambient_settings.h"
 #include "ash/webui/personalization_app/mojom/personalization_app.mojom.h"
@@ -47,6 +48,7 @@
       mojo::PendingRemote<ash::personalization_app::mojom::AmbientObserver>
           observer) override;
   void SetAmbientModeEnabled(bool enabled) override;
+  void SetAnimationTheme(ash::AmbientAnimationTheme animation_theme) override;
   void SetTopicSource(ash::AmbientModeTopicSource topic_source) override;
   void SetTemperatureUnit(
       ash::AmbientModeTemperatureUnit temperature_unit) override;
@@ -56,6 +58,7 @@
 
   // Notify WebUI the latest values.
   void OnAmbientModeEnabledChanged();
+  void OnAnimationThemeChanged();
   void OnTemperatureUnitChanged();
   void OnTopicSourceChanged();
   void OnAlbumsChanged();
@@ -66,6 +69,8 @@
 
   bool IsAmbientModeEnabled();
 
+  ash::AmbientAnimationTheme GetCurrentAnimationTheme();
+
   // Update the local `settings_` to server.
   void UpdateSettings();
 
diff --git a/chrome/browser/ash/web_applications/personalization_app/personalization_app_ambient_provider_impl_unittest.cc b/chrome/browser/ash/web_applications/personalization_app/personalization_app_ambient_provider_impl_unittest.cc
index 184032d0..5db29fb1 100644
--- a/chrome/browser/ash/web_applications/personalization_app/personalization_app_ambient_provider_impl_unittest.cc
+++ b/chrome/browser/ash/web_applications/personalization_app/personalization_app_ambient_provider_impl_unittest.cc
@@ -10,6 +10,7 @@
 
 #include "ash/ambient/test/ambient_ash_test_helper.h"
 #include "ash/constants/ash_features.h"
+#include "ash/public/cpp/ambient/ambient_animation_theme.h"
 #include "ash/public/cpp/ambient/ambient_prefs.h"
 #include "ash/public/cpp/ambient/common/ambient_settings.h"
 #include "ash/public/cpp/ambient/fake_ambient_backend_controller_impl.h"
@@ -40,6 +41,11 @@
     ambient_mode_enabled_ = ambient_mode_enabled;
   }
 
+  void OnAnimationThemeChanged(
+      ash::AmbientAnimationTheme animation_theme) override {
+    animation_theme_ = animation_theme;
+  }
+
   void OnTopicSourceChanged(ash::AmbientModeTopicSource topic_source) override {
     topic_source_ = topic_source;
   }
@@ -69,6 +75,11 @@
     return ambient_mode_enabled_;
   }
 
+  ash::AmbientAnimationTheme animation_theme() {
+    ambient_observer_receiver_.FlushForTesting();
+    return animation_theme_;
+  }
+
   ash::AmbientModeTopicSource topic_source() {
     ambient_observer_receiver_.FlushForTesting();
     return topic_source_;
@@ -89,6 +100,8 @@
       ambient_observer_receiver_{this};
 
   bool ambient_mode_enabled_ = false;
+  ash::AmbientAnimationTheme animation_theme_ =
+      ash::AmbientAnimationTheme::kSlideshow;
   ash::AmbientModeTopicSource topic_source_ =
       ash::AmbientModeTopicSource::kArtGallery;
   ash::AmbientModeTemperatureUnit temperature_unit_ =
@@ -102,8 +115,10 @@
  public:
   PersonalizationAppAmbientProviderImplTest()
       : profile_manager_(TestingBrowserProcess::GetGlobal()) {
-    scoped_feature_list_.InitAndEnableFeature(
-        ash::features::kPersonalizationHub);
+    scoped_feature_list_.InitWithFeatures(
+        {ash::features::kPersonalizationHub,
+         ash::features::kAmbientModeAnimationFeature},
+        {});
   }
   PersonalizationAppAmbientProviderImplTest(
       const PersonalizationAppAmbientProviderImplTest&) = delete;
@@ -157,6 +172,11 @@
     return test_ambient_observer_.is_ambient_mode_enabled();
   }
 
+  ash::AmbientAnimationTheme ObservedAnimationTheme() {
+    ambient_provider_remote_.FlushForTesting();
+    return test_ambient_observer_.animation_theme();
+  }
+
   ash::AmbientModeTopicSource ObservedTopicSource() {
     ambient_provider_remote_.FlushForTesting();
     return test_ambient_observer_.topic_source();
@@ -182,6 +202,10 @@
                                       enabled);
   }
 
+  void SetAnimationTheme(ash::AmbientAnimationTheme animation_theme) {
+    ambient_provider_->SetAnimationTheme(animation_theme);
+  }
+
   void FetchSettings() { ambient_provider_->FetchSettingsAndAlbums(); }
 
   void UpdateSettings() {
@@ -331,7 +355,7 @@
 }
 
 TEST_F(PersonalizationAppAmbientProviderImplTest,
-       houldCallOnAmbientModeEnabledChanged) {
+       ShouldCallOnAmbientModeEnabledChanged) {
   PrefService* pref_service = profile()->GetPrefs();
   EXPECT_TRUE(pref_service);
   pref_service->SetBoolean(ash::ambient::prefs::kAmbientModeEnabled, false);
@@ -346,6 +370,18 @@
 }
 
 TEST_F(PersonalizationAppAmbientProviderImplTest,
+       ShouldCallOnAnimationThemeChanged) {
+  SetAmbientObserver();
+  ambient_provider_remote().FlushForTesting();
+  SetAnimationTheme(ash::AmbientAnimationTheme::kSlideshow);
+  EXPECT_EQ(ash::AmbientAnimationTheme::kSlideshow, ObservedAnimationTheme());
+
+  SetAnimationTheme(ash::AmbientAnimationTheme::kFeelTheBreeze);
+  EXPECT_EQ(ash::AmbientAnimationTheme::kFeelTheBreeze,
+            ObservedAnimationTheme());
+}
+
+TEST_F(PersonalizationAppAmbientProviderImplTest,
        ShouldCallOnTopicSourceChanged) {
   // Will fetch settings when observer is set.
   SetAmbientObserver();
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index dd603ad..36371a56 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -6046,7 +6046,6 @@
     content::RenderFrameHost* render_frame_host) {
   DCHECK(render_frame_host);
 
-  const GURL& url = render_frame_host->GetLastCommittedOrigin().GetURL();
   content::BrowserContext* browser_context =
       render_frame_host->GetBrowserContext();
   Profile* profile = Profile::FromBrowserContext(browser_context);
@@ -6055,9 +6054,8 @@
   content::PermissionController* permission_controller =
       browser_context->GetPermissionController();
   blink::mojom::PermissionStatus status =
-      permission_controller->GetPermissionStatusForFrame(
-          content::PermissionType::CLIPBOARD_READ_WRITE, render_frame_host,
-          url);
+      permission_controller->GetPermissionStatusForCurrentDocument(
+          content::PermissionType::CLIPBOARD_READ_WRITE, render_frame_host);
 
   // True if this paste is executed from an extension URL with read permission.
   bool is_extension_paste_allowed = false;
@@ -6070,6 +6068,8 @@
   // Until this is implemented, platforms supporting extensions (all  platforms
   // except Android) will essentially no-op here and return true.
   is_content_script_paste_allowed = true;
+  const GURL& url =
+      render_frame_host->GetMainFrame()->GetLastCommittedOrigin().GetURL();
   if (url.SchemeIs(extensions::kExtensionScheme)) {
     auto* process_map = extensions::ProcessMap::Get(profile);
     auto* registry = extensions::ExtensionRegistry::Get(profile);
diff --git a/chrome/browser/chromeos/extensions/file_manager/private_api_file_system.cc b/chrome/browser/chromeos/extensions/file_manager/private_api_file_system.cc
index 43ad46c1..285b8693 100644
--- a/chrome/browser/chromeos/extensions/file_manager/private_api_file_system.cc
+++ b/chrome/browser/chromeos/extensions/file_manager/private_api_file_system.cc
@@ -633,7 +633,7 @@
       volume_manager->FindVolumeById(params->volume_id);
   if (!volume.get())
     return RespondNow(
-        Error("GetSizeStats: volume with ID %s not found", params->volume_id));
+        Error("GetSizeStats: volume with ID * not found", params->volume_id));
 
   if (volume->type() == file_manager::VOLUME_TYPE_MTP) {
     // Resolve storage_name.
@@ -794,7 +794,7 @@
       volume_manager->FindVolumeById(params->volume_id);
   if (!volume)
     return RespondNow(
-        Error("FormatVolume: volume with ID %s not found", params->volume_id));
+        Error("FormatVolume: volume with ID * not found", params->volume_id));
 
   DiskMountManager::GetInstance()->FormatMountedDevice(
       volume->mount_path().AsUTF8Unsafe(),
@@ -855,7 +855,7 @@
       volume_manager->FindVolumeById(params->volume_id);
   if (!volume)
     return RespondNow(
-        Error("RenameVolume: volume with ID %s not found", params->volume_id));
+        Error("RenameVolume: volume with ID * not found", params->volume_id));
 
   DiskMountManager::GetInstance()->RenameMountedDevice(
       volume->mount_path().AsUTF8Unsafe(), params->new_name);
diff --git a/chrome/browser/chromeos/extensions/file_manager/private_api_misc.cc b/chrome/browser/chromeos/extensions/file_manager/private_api_misc.cc
index c9462c89..feb5869a 100644
--- a/chrome/browser/chromeos/extensions/file_manager/private_api_misc.cc
+++ b/chrome/browser/chromeos/extensions/file_manager/private_api_misc.cc
@@ -773,8 +773,8 @@
   base::WeakPtr<Volume> volume =
       volume_manager->FindVolumeById(params->volume_id);
   if (!volume.get())
-    return RespondNow(
-        Error("ConfigureVolume: volume with ID not found.", params->volume_id));
+    return RespondNow(Error("ConfigureVolume: volume with ID * not found.",
+                            params->volume_id));
   if (!volume->configurable())
     return RespondNow(Error("Volume not configurable."));
 
diff --git a/chrome/browser/chromeos/fileapi/recent_disk_source_unittest.cc b/chrome/browser/chromeos/fileapi/recent_disk_source_unittest.cc
index 0b5ac779..b5c9097 100644
--- a/chrome/browser/chromeos/fileapi/recent_disk_source_unittest.cc
+++ b/chrome/browser/chromeos/fileapi/recent_disk_source_unittest.cc
@@ -249,20 +249,27 @@
   ASSERT_TRUE(CreateEmptyFile("4.mp3", base::Time::FromJavaTime(4000)));
   ASSERT_TRUE(CreateEmptyFile("5.gif", base::Time::FromJavaTime(5000)));
   ASSERT_TRUE(CreateEmptyFile("6.webm", base::Time::FromJavaTime(6000)));
+  // RAW images are supported
+  ASSERT_TRUE(CreateEmptyFile("7.dng", base::Time::FromJavaTime(7000)));
+  ASSERT_TRUE(CreateEmptyFile("8.nef", base::Time::FromJavaTime(8000)));
   // Newest
 
   std::vector<RecentFile> files =
-      GetRecentFiles(6, base::Time(), RecentSource::FileType::kImage);
+      GetRecentFiles(8, base::Time(), RecentSource::FileType::kImage);
 
   std::sort(files.begin(), files.end(), RecentFileComparator());
 
-  ASSERT_EQ(3u, files.size());
-  EXPECT_EQ("5.gif", files[0].url().path().BaseName().value());
-  EXPECT_EQ(base::Time::FromJavaTime(5000), files[0].last_modified());
-  EXPECT_EQ("3.png", files[1].url().path().BaseName().value());
-  EXPECT_EQ(base::Time::FromJavaTime(3000), files[1].last_modified());
-  EXPECT_EQ("1.jpg", files[2].url().path().BaseName().value());
-  EXPECT_EQ(base::Time::FromJavaTime(1000), files[2].last_modified());
+  ASSERT_EQ(5u, files.size());
+  EXPECT_EQ("8.nef", files[0].url().path().BaseName().value());
+  EXPECT_EQ(base::Time::FromJavaTime(8000), files[0].last_modified());
+  EXPECT_EQ("7.dng", files[1].url().path().BaseName().value());
+  EXPECT_EQ(base::Time::FromJavaTime(7000), files[1].last_modified());
+  EXPECT_EQ("5.gif", files[2].url().path().BaseName().value());
+  EXPECT_EQ(base::Time::FromJavaTime(5000), files[2].last_modified());
+  EXPECT_EQ("3.png", files[3].url().path().BaseName().value());
+  EXPECT_EQ(base::Time::FromJavaTime(3000), files[3].last_modified());
+  EXPECT_EQ("1.jpg", files[4].url().path().BaseName().value());
+  EXPECT_EQ(base::Time::FromJavaTime(1000), files[4].last_modified());
 }
 
 TEST_F(RecentDiskSourceTest, GetVideoFiles) {
diff --git a/chrome/browser/client_hints/client_hints_browsertest.cc b/chrome/browser/client_hints/client_hints_browsertest.cc
index f5c98a4..709c861 100644
--- a/chrome/browser/client_hints/client_hints_browsertest.cc
+++ b/chrome/browser/client_hints/client_hints_browsertest.cc
@@ -3162,6 +3162,25 @@
   EXPECT_EQ(4, handler.request_count());
 }
 
+IN_PROC_BROWSER_TEST_F(CriticalClientHintsBrowserTest,
+                       CriticalClientHintInsecureRedirect) {
+  net::test_server::EmbeddedTestServer http_server;
+  http_server.AddDefaultHandlers();
+  ASSERT_TRUE(http_server.Start());
+
+  // After the redirect, the second response will have the Critical-CH and will
+  // restart. The final request wil contain the correct headers.
+  GURL url = http_server.GetURL("/server-redirect?" +
+                                critical_ch_ua_full_version_list_url().spec());
+
+  blink::UserAgentMetadata ua = embedder_support::GetUserAgentMetadata();
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
+  const std::string expected_ch_ua_full_version_list =
+      ua.SerializeBrandFullVersionList();
+  EXPECT_THAT(observed_ch_ua_full_version_list(),
+              Optional(Eq(expected_ch_ua_full_version_list)));
+}
+
 class ClientHintsBrowserTestWithEmulatedMedia
     : public DevToolsProtocolTestBase {
  public:
diff --git a/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/sections/SectionHeaderView.java b/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/sections/SectionHeaderView.java
index ef04ca6..f8e6d2c 100644
--- a/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/sections/SectionHeaderView.java
+++ b/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/sections/SectionHeaderView.java
@@ -403,7 +403,6 @@
                 R.string.feature_notification_guide_tooltip_message_ntp_suggestion_card,
                 R.string.feature_notification_guide_tooltip_message_ntp_suggestion_card)
                                       .setAnchorView(mTitleView)
-                                      .setDismissOnTouch(false)
                                       .build());
     }
 
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index e89fffc..0c730cf 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -3367,12 +3367,12 @@
   },
   {
     "name": "files-archivemount",
-    "owners": [ "fdegros", "nigeltao" ],
+    "owners": [ "fdegros", "nigeltao", "msalomao" ],
     "expiry_milestone": 112
   },
   {
     "name": "files-archivemount2",
-    "owners": [ "fdegros", "nigeltao" ],
+    "owners": [ "fdegros", "nigeltao", "msalomao" ],
     "expiry_milestone": 112
   },
   {
@@ -4424,6 +4424,11 @@
     "expiry_milestone": 86
   },
   {
+    "name": "omit-cors-client-cert",
+    "owners": [ "toyoshim", "loading-dev" ],
+    "expiry_milestone": 110
+  },
+  {
     "name": "omnibox-active-search-engines",
     "owners": [ "yoangela", "chrome-omnibox-team@google.com" ],
     "expiry_milestone": 105
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index 6b59512..8be7388 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -1709,6 +1709,13 @@
     "Enable support for using the system notification toasts and notification "
     "center on platforms where these are available.";
 
+const char kOmitCorsClientCertName[] =
+    "Omit TLS client certificates if credential mode disallows";
+const char kOmitCorsClientCertDescription[] =
+    "Strictly conform the Fetch spec to omit TLS client certificates if "
+    "credential mode disallows. Without this flag enabled, Chrome will always "
+    "try sending client certificates regardless of the credential mode.";
+
 const char kOmniboxActiveSearchEnginesName[] =
     "Active Search Engines section on settings page";
 const char kOmniboxActiveSearchEnginesDescription[] =
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index 3511e76c..0c97061 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -980,6 +980,9 @@
 extern const char kOriginAgentClusterDefaultName[];
 extern const char kOriginAgentClusterDefaultDescription[];
 
+extern const char kOmitCorsClientCertName[];
+extern const char kOmitCorsClientCertDescription[];
+
 extern const char kOmniboxActiveSearchEnginesName[];
 extern const char kOmniboxActiveSearchEnginesDescription[];
 
diff --git a/chrome/browser/notifications/persistent_notification_handler.cc b/chrome/browser/notifications/persistent_notification_handler.cc
index 9ba89004..32eedd9 100644
--- a/chrome/browser/notifications/persistent_notification_handler.cc
+++ b/chrome/browser/notifications/persistent_notification_handler.cc
@@ -24,6 +24,7 @@
 #include "content/public/browser/permission_type.h"
 #include "content/public/common/persistent_notification_status.h"
 #include "url/gurl.h"
+#include "url/origin.h"
 
 #if BUILDFLAG(ENABLE_BACKGROUND_MODE)
 #include "chrome/browser/profiles/keep_alive/scoped_profile_keep_alive.h"
@@ -105,8 +106,8 @@
 #endif  // BUILDFLAG(ENABLE_BACKGROUND_MODE)
 
   blink::mojom::PermissionStatus permission_status =
-      profile->GetPermissionController()->GetPermissionStatus(
-          content::PermissionType::NOTIFICATIONS, origin, origin);
+      profile->GetPermissionController()->GetPermissionStatusForServiceWorker(
+          content::PermissionType::NOTIFICATIONS, url::Origin::Create(origin));
 
   // Don't process click events when the |origin| doesn't have permission. This
   // can't be a DCHECK because of potential races with native notifications.
diff --git a/chrome/browser/optimization_guide/prediction/prediction_manager_browsertest.cc b/chrome/browser/optimization_guide/prediction/prediction_manager_browsertest.cc
index 3310b86..3468e36 100644
--- a/chrome/browser/optimization_guide/prediction/prediction_manager_browsertest.cc
+++ b/chrome/browser/optimization_guide/prediction/prediction_manager_browsertest.cc
@@ -32,6 +32,7 @@
 #include "components/optimization_guide/core/optimization_guide_switches.h"
 #include "components/optimization_guide/core/optimization_guide_test_util.h"
 #include "components/optimization_guide/core/prediction_manager.h"
+#include "components/optimization_guide/core/prediction_model_download_manager.h"
 #include "components/optimization_guide/core/store_update_data.h"
 #include "components/optimization_guide/proto/models.pb.h"
 #include "components/prefs/pref_service.h"
@@ -604,6 +605,22 @@
       &histogram_tester,
       "OptimizationGuide.PredictionModelDownloadManager.DownloadStatus", 1);
 
+  histogram_tester.ExpectTotalCount(
+      "OptimizationGuide.PredictionModelDownloadManager.State.PainfulPageLoad",
+      2);
+  histogram_tester.ExpectBucketCount(
+      "OptimizationGuide.PredictionModelDownloadManager.State.PainfulPageLoad",
+      PredictionModelDownloadManager::PredictionModelDownloadState::kRequested,
+      1);
+  histogram_tester.ExpectBucketCount(
+      "OptimizationGuide.PredictionModelDownloadManager.State.PainfulPageLoad",
+      PredictionModelDownloadManager::PredictionModelDownloadState::kStarted,
+      1);
+  histogram_tester.ExpectTotalCount(
+      "OptimizationGuide.PredictionModelDownloadManager.DownloadStartLatency."
+      "PainfulPageLoad",
+      1);
+
   histogram_tester.ExpectUniqueSample(
       "OptimizationGuide.PredictionModelDownloadManager.DownloadStatus",
       PredictionModelDownloadStatus::kFailedCrxVerification, 1);
@@ -747,8 +764,7 @@
 
 // Flaky on multiple ASAN bots. See https://crbug.com/1266318
 #if defined(ADDRESS_SANITIZER)
-#define MAYBE_TestSwitchProfileDoesntCrash \
-  DISABLED_TestSwitchProfileDoesntCrash
+#define MAYBE_TestSwitchProfileDoesntCrash DISABLED_TestSwitchProfileDoesntCrash
 #else
 #define MAYBE_TestSwitchProfileDoesntCrash TestSwitchProfileDoesntCrash
 #endif
diff --git a/chrome/browser/policy/messaging_layer/util/test.cc b/chrome/browser/policy/messaging_layer/util/test.cc
index 76d6d2ec..f5e81fca 100644
--- a/chrome/browser/policy/messaging_layer/util/test.cc
+++ b/chrome/browser/policy/messaging_layer/util/test.cc
@@ -78,22 +78,24 @@
 
 bool RequestContainingRecordMatcher::MatchAndExplain(
     const base::Value::Dict& arg,
-    std::ostream* os) const {
+    MatchResultListener* listener) const {
   const auto* record_list = arg.FindList("encryptedRecord");
   if (record_list == nullptr) {
-    *os << "No key named \"encryptedRecord\" in the argument or the value is "
+    *listener
+        << "No key named \"encryptedRecord\" in the argument or the value is "
            "not a list.";
     return false;
   }
 
   const auto matched_record = base::JSONReader::Read(matched_record_json_);
   if (!matched_record.has_value()) {
-    *os << "The specified record cannot be parsed as a JSON object.";
+    *listener << "The specified record cannot be parsed as a JSON object.";
     return false;
   }
   const auto* matched_record_dict = matched_record->GetIfDict();
   if (matched_record_dict == nullptr) {
-    *os << "The specified record must be a Dict itself because each record "
+    *listener
+        << "The specified record must be a Dict itself because each record "
            "is a Dict.";
     return false;
   }
@@ -112,7 +114,7 @@
     }
   }
 
-  *os << "The specified record is not found in the argument.";
+  *listener << "The specified record is not found in the argument.";
   return false;
 }
 
diff --git a/chrome/browser/policy/messaging_layer/util/test.h b/chrome/browser/policy/messaging_layer/util/test.h
index 347727d..a9bbbcd 100644
--- a/chrome/browser/policy/messaging_layer/util/test.h
+++ b/chrome/browser/policy/messaging_layer/util/test.h
@@ -53,7 +53,8 @@
 
   explicit RequestContainingRecordMatcher(
       base::StringPiece matched_record_json);
-  bool MatchAndExplain(const base::Value::Dict& arg, std::ostream* os) const;
+  bool MatchAndExplain(const base::Value::Dict& arg,
+                       MatchResultListener* os) const;
   void DescribeTo(std::ostream* os) const;
   void DescribeNegationTo(std::ostream* os) const;
 
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/background_test.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/background_test.js
index 93b9fbc..6206e52a 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/background_test.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/background_test.js
@@ -2088,15 +2088,26 @@
 
 TEST_F('ChromeVoxBackgroundTest', 'EventFromUser', function() {
   const site = '<button>ok</button><button>cancel</button>';
-  this.runWithLoadedTree(site, function(root) {
-    const button = root.findAll({role: RoleType.BUTTON})[1];
-    button.addEventListener(EventType.FOCUS, this.newCallback(function(evt) {
-      assertEquals(RoleType.BUTTON, evt.target.role);
-      assertEquals('user', evt.eventFrom);
-      assertEquals('cancel', evt.target.name);
-    }));
+  this.runWithLoadedTree(site, async function(root) {
+    const buttons = root.findAll({role: RoleType.BUTTON});
+    const okButton = buttons[0];
+    const cancelButton = buttons[1];
+
+    await new Promise(r => {
+      if (okButton.state.focused) {
+        r();
+      } else {
+        okButton.addEventListener('focus', r);
+      }
+    });
 
     press(KeyCode.TAB)();
+
+    const evt =
+        await new Promise(r => cancelButton.addEventListener('focus', r));
+    assertEquals(RoleType.BUTTON, evt.target.role);
+    assertEquals('user', evt.eventFrom);
+    assertEquals('cancel', evt.target.name);
   });
 });
 
diff --git a/chrome/browser/resources/chromeos/accessibility/common/testing/e2e_test_base.js b/chrome/browser/resources/chromeos/accessibility/common/testing/e2e_test_base.js
index 6fe84d2..ba82ab8 100644
--- a/chrome/browser/resources/chromeos/accessibility/common/testing/e2e_test_base.js
+++ b/chrome/browser/resources/chromeos/accessibility/common/testing/e2e_test_base.js
@@ -220,23 +220,11 @@
 
                     // This populates the address bar as if we typed the url.
                     focus.setValue(url);
-
-                    // Next, wait until the auto completion shows up.
-                    const clickAutocomplete = () => {
-                      focus.removeEventListener(
-                          'controlsChanged', clickAutocomplete);
-
-                      // The text field relates to the auto complete list box
-                      // via controlledBy. The |controls| node structure here
-                      // nests several levels until the listBoxOption we want.
-                      const autoCompleteListBoxOption =
-                          focus.controls[0].firstChild.firstChild;
-                      assertEquals(
-                          'listBoxOption', autoCompleteListBoxOption.role);
-                      autoCompleteListBoxOption.doDefault();
+                    const onValueChanged = e => {
+                      focus.removeEventListener('valueChanged', onValueChanged);
+                      EventGenerator.sendKeyPress(KeyCode.RETURN);
                     };
-                    focus.addEventListener(
-                        'controlsChanged', clickAutocomplete);
+                    focus.addEventListener('valueChanged', onValueChanged);
                   }
                 });
                 return;
@@ -266,6 +254,10 @@
             if (!hasLacrosChromePath) {
               const createParams = {active: true, url};
               chrome.tabs.create(createParams);
+            } else {
+              chrome.automation.getFocus(f => {
+                listener({target: f});
+              });
             }
           });
     });
diff --git a/chrome/browser/resources/chromeos/login/display_manager.js b/chrome/browser/resources/chromeos/login/display_manager.js
index 52d5e599..2fb0d4e 100644
--- a/chrome/browser/resources/chromeos/login/display_manager.js
+++ b/chrome/browser/resources/chromeos/login/display_manager.js
@@ -349,7 +349,6 @@
           innerContainer.classList.remove('down');
           innerContainer.addEventListener('transitionend', function f(e) {
             innerContainer.removeEventListener('transitionend', f);
-            chrome.send('loginVisible', ['oobe']);
             // Refresh defaultControl. It could have changed.
             let defaultControl = newStep.defaultControl;
             if (defaultControl)
@@ -360,7 +359,6 @@
         } else {
           if (defaultControl)
             defaultControl.focus();
-          chrome.send('loginVisible', ['oobe']);
         }
       }
       this.currentStep_ = nextStepIndex;
diff --git a/chrome/browser/resources/chromeos/login/oobe_polymer3.js b/chrome/browser/resources/chromeos/login/oobe_polymer3.js
index 549f710..4058a92 100644
--- a/chrome/browser/resources/chromeos/login/oobe_polymer3.js
+++ b/chrome/browser/resources/chromeos/login/oobe_polymer3.js
@@ -102,7 +102,4 @@
           initializeDebugger();
       }
     }
-
-    // Make the WebUI visible.
-    chrome.send('loginVisible', ['oobe']);
 })(window);
diff --git a/chrome/browser/resources/chromeos/login/screens/common/gaia_signin.js b/chrome/browser/resources/chromeos/login/screens/common/gaia_signin.js
index ee0f492..b50798a8 100644
--- a/chrome/browser/resources/chromeos/login/screens/common/gaia_signin.js
+++ b/chrome/browser/resources/chromeos/login/screens/common/gaia_signin.js
@@ -539,11 +539,6 @@
   onBeforeShow(data) {
     chrome.send('loginUIStateChanged', ['gaia-signin', true]);
 
-    // Ensure that GAIA signin (or loading UI) is actually visible.
-    window.requestAnimationFrame(function() {
-      chrome.send('loginVisible', ['gaia-loading']);
-    });
-
     // Re-enable navigation in case it was disabled before refresh.
     this.navigationEnabled_ = true;
 
@@ -783,7 +778,6 @@
    */
   onLoginUIVisible_() {
     chrome.send('loginWebuiReady');
-    chrome.send('loginVisible', ['gaia-signin']);
   }
 
   /**
diff --git a/chrome/browser/resources/extensions/BUILD.gn b/chrome/browser/resources/extensions/BUILD.gn
index aa59bce..ed352959 100644
--- a/chrome/browser/resources/extensions/BUILD.gn
+++ b/chrome/browser/resources/extensions/BUILD.gn
@@ -8,6 +8,7 @@
 import("//tools/grit/grit_rule.gni")
 import("//tools/grit/preprocess_if_expr.gni")
 import("//tools/polymer/html_to_js.gni")
+import("//tools/polymer/html_to_wrapper.gni")
 import("//tools/typescript/ts_library.gni")
 import("//ui/webui/resources/tools/generate_grd.gni")
 import("../tools/optimize_webui.gni")
@@ -54,18 +55,25 @@
 preprocess_if_expr("preprocess") {
   in_folder = "."
   out_folder = "$target_gen_dir/$preprocess_folder"
-  in_files = non_web_component_files
+  in_files = ts_files
 }
 
 preprocess_if_expr("preprocess_generated") {
-  public_deps = [ ":web_components" ]
+  public_deps = [
+    ":css_wrapper_files",
+    ":html_wrapper_files",
+  ]
   in_folder = target_gen_dir
   out_folder = "$target_gen_dir/$preprocess_folder"
-  in_files = web_component_files
+  in_files = html_wrapper_files + css_wrapper_files
 }
 
-html_to_js("web_components") {
-  js_files = web_component_files
+html_to_js("css_wrapper_files") {
+  js_files = css_wrapper_files
+}
+
+html_to_wrapper("html_wrapper_files") {
+  in_files = html_files
 }
 
 grit("resources") {
@@ -90,7 +98,7 @@
   out_dir = "$target_gen_dir/tsc"
   composite = true
   tsconfig_base = "tsconfig_base.json"
-  in_files = web_component_files + non_web_component_files
+  in_files = ts_files + css_wrapper_files + html_wrapper_files
   definitions = [
     "//tools/typescript/definitions/activity_log_private.d.ts",
     "//tools/typescript/definitions/chrome_event.d.ts",
diff --git a/chrome/browser/resources/extensions/activity_log/activity_log.ts b/chrome/browser/resources/extensions/activity_log/activity_log.ts
index ca110ee..a2e61ec 100644
--- a/chrome/browser/resources/extensions/activity_log/activity_log.ts
+++ b/chrome/browser/resources/extensions/activity_log/activity_log.ts
@@ -18,10 +18,11 @@
 import {focusWithoutInk} from 'chrome://resources/js/cr/ui/focus_without_ink.m.js';
 import {I18nMixin} from 'chrome://resources/js/i18n_mixin.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
-import {afterNextRender, html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {afterNextRender, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {navigation, Page} from '../navigation_helper.js';
 
+import {getTemplate} from './activity_log.html.js';
 import {ActivityLogDelegate} from './activity_log_history.js';
 
 /**
@@ -63,7 +64,7 @@
   }
 
   static get template() {
-    return html`{__html_template__}`;
+    return getTemplate();
   }
 
   static get properties() {
@@ -97,7 +98,7 @@
   selectedSubpage_: ActivityLogSubpage;
   private tabNames_: string[];
 
-  ready() {
+  override ready() {
     super.ready();
     this.addEventListener('view-enter-start', this.onViewEnterStart_);
     this.addEventListener('view-exit-finish', this.onViewExitFinish_);
diff --git a/chrome/browser/resources/extensions/activity_log/activity_log_history.ts b/chrome/browser/resources/extensions/activity_log/activity_log_history.ts
index 000a67d..a25dcca5 100644
--- a/chrome/browser/resources/extensions/activity_log/activity_log_history.ts
+++ b/chrome/browser/resources/extensions/activity_log/activity_log_history.ts
@@ -11,7 +11,8 @@
 
 import {assert} from 'chrome://resources/js/assert_ts.js';
 import {PromiseResolver} from 'chrome://resources/js/promise_resolver.m.js';
-import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {getTemplate} from './activity_log_history.html.js';
 
 import {ActivityGroup} from './activity_log_history_item.js';
 
@@ -174,7 +175,7 @@
   }
 
   static get template() {
-    return html`{__html_template__}`;
+    return getTemplate();
   }
 
   static get properties() {
@@ -230,7 +231,7 @@
     this.rawActivities_ = '';
   }
 
-  ready() {
+  override ready() {
     super.ready();
     this.addEventListener('delete-activity-log-item', e => this.deleteItem_(e));
   }
@@ -246,7 +247,7 @@
     return this.dataFetchedResolver_!.promise;
   }
 
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
     this.dataFetchedResolver_ = new PromiseResolver();
     this.refreshActivities_();
diff --git a/chrome/browser/resources/extensions/activity_log/activity_log_history_item.ts b/chrome/browser/resources/extensions/activity_log/activity_log_history_item.ts
index 2f5688b..ac4576e 100644
--- a/chrome/browser/resources/extensions/activity_log/activity_log_history_item.ts
+++ b/chrome/browser/resources/extensions/activity_log/activity_log_history_item.ts
@@ -9,7 +9,8 @@
 import '../shared_style.js';
 import '../shared_vars.js';
 
-import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {getTemplate} from './activity_log_history_item.html.js';
 
 export type ActivityGroup = {
   activityIds: Set<string>,
@@ -35,7 +36,7 @@
   }
 
   static get template() {
-    return html`{__html_template__}`;
+    return getTemplate();
   }
 
   static get properties() {
diff --git a/chrome/browser/resources/extensions/activity_log/activity_log_stream.ts b/chrome/browser/resources/extensions/activity_log/activity_log_stream.ts
index 6e0106fb..b32510d 100644
--- a/chrome/browser/resources/extensions/activity_log/activity_log_stream.ts
+++ b/chrome/browser/resources/extensions/activity_log/activity_log_stream.ts
@@ -10,7 +10,8 @@
 
 import {ChromeEvent} from '/tools/typescript/definitions/chrome_event.js';
 import {assert} from 'chrome://resources/js/assert_ts.js';
-import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {getTemplate} from './activity_log_stream.html.js';
 
 import {StreamItem} from './activity_log_stream_item.js';
 
@@ -62,7 +63,7 @@
   }
 
   static get template() {
-    return html`{__html_template__}`;
+    return getTemplate();
   }
 
   static get properties() {
@@ -111,7 +112,7 @@
     this.listenerInstance_ = () => {};
   }
 
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
 
     // Since this component is not restamped, this will only be called once
diff --git a/chrome/browser/resources/extensions/activity_log/activity_log_stream_item.ts b/chrome/browser/resources/extensions/activity_log/activity_log_stream_item.ts
index 51d0e79..afea668 100644
--- a/chrome/browser/resources/extensions/activity_log/activity_log_stream_item.ts
+++ b/chrome/browser/resources/extensions/activity_log/activity_log_stream_item.ts
@@ -8,7 +8,8 @@
 import '../shared_style.js';
 import '../shared_vars.js';
 
-import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {getTemplate} from './activity_log_stream_item.html.js';
 
 export type StreamItem = {
   name?: string,
@@ -52,7 +53,7 @@
   }
 
   static get template() {
-    return html`{__html_template__}`;
+    return getTemplate();
   }
 
   static get properties() {
diff --git a/chrome/browser/resources/extensions/code_section.ts b/chrome/browser/resources/extensions/code_section.ts
index ab58b86b..45168de 100644
--- a/chrome/browser/resources/extensions/code_section.ts
+++ b/chrome/browser/resources/extensions/code_section.ts
@@ -9,7 +9,8 @@
 
 import {I18nMixin} from 'chrome://resources/js/i18n_mixin.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
-import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {getTemplate} from './code_section.html.js';
 
 
 function visibleLineCount(totalCount: number, oppositeCount: number): number {
@@ -29,7 +30,7 @@
   }
 
   static get template() {
-    return html`{__html_template__}`;
+    return getTemplate();
   }
 
   static get properties() {
diff --git a/chrome/browser/resources/extensions/detail_view.ts b/chrome/browser/resources/extensions/detail_view.ts
index afd2f04..c8c2214 100644
--- a/chrome/browser/resources/extensions/detail_view.ts
+++ b/chrome/browser/resources/extensions/detail_view.ts
@@ -29,7 +29,8 @@
 import {CrTooltipIconElement} from 'chrome://resources/cr_elements/policy/cr_tooltip_icon.m.js';
 import {focusWithoutInk} from 'chrome://resources/js/cr/ui/focus_without_ink.m.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
-import {afterNextRender, DomRepeatEvent, html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {afterNextRender, DomRepeatEvent, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {getTemplate} from './detail_view.html.js';
 
 import {ItemDelegate} from './item.js';
 import {ItemMixin} from './item_mixin.js';
@@ -58,7 +59,7 @@
   }
 
   static get template() {
-    return html`{__html_template__}`;
+    return getTemplate();
   }
 
   static get properties() {
@@ -106,11 +107,11 @@
   fromActivityLog: boolean;
   private size_: string;
 
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
   }
 
-  ready() {
+  override ready() {
     super.ready();
     this.addEventListener('view-enter-start', this.onViewEnterStart_);
   }
diff --git a/chrome/browser/resources/extensions/drop_overlay.ts b/chrome/browser/resources/extensions/drop_overlay.ts
index 48c5352..f699ec1a 100644
--- a/chrome/browser/resources/extensions/drop_overlay.ts
+++ b/chrome/browser/resources/extensions/drop_overlay.ts
@@ -8,9 +8,10 @@
 import 'chrome://resources/polymer/v3_0/iron-icon/iron-icon.js';
 
 import {DragWrapper} from 'chrome://resources/js/cr/ui/drag_wrapper.js';
-import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {DragAndDropHandler} from './drag_and_drop_handler.js';
+import {getTemplate} from './drop_overlay.html.js';
 
 class ExtensionsDropOverlayElement extends PolymerElement {
   static get is() {
@@ -18,7 +19,7 @@
   }
 
   static get template() {
-    return html`{__html_template__}`;
+    return getTemplate();
   }
 
   static get properties() {
diff --git a/chrome/browser/resources/extensions/error_page.ts b/chrome/browser/resources/extensions/error_page.ts
index a293d8f..f6ec898 100644
--- a/chrome/browser/resources/extensions/error_page.ts
+++ b/chrome/browser/resources/extensions/error_page.ts
@@ -19,7 +19,8 @@
 import {FocusOutlineManager} from 'chrome://resources/js/cr/ui/focus_outline_manager.m.js';
 import {focusWithoutInk} from 'chrome://resources/js/cr/ui/focus_without_ink.m.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
-import {afterNextRender, DomRepeatEvent, html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {afterNextRender, DomRepeatEvent, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {getTemplate} from './error_page.html.js';
 
 import {navigation, Page} from './navigation_helper.js';
 
@@ -80,6 +81,10 @@
     return 'extensions-error-page';
   }
 
+  static get template() {
+    return getTemplate();
+  }
+
   static get properties() {
     return {
       data: Object,
@@ -125,7 +130,7 @@
   private selectedEntry_: number;
   private selectedStackFrame_: chrome.developerPrivate.StackFrame|null;
 
-  ready() {
+  override ready() {
     super.ready();
     this.addEventListener('view-enter-start', this.onViewEnterStart_);
     FocusOutlineManager.forDocument(document);
@@ -371,10 +376,6 @@
         -1 :
         repeaterEvent.model.index;
   }
-
-  static get template() {
-    return html`{__html_template__}`;
-  }
 }
 
 declare global {
diff --git a/chrome/browser/resources/extensions/extensions.gni b/chrome/browser/resources/extensions/extensions.gni
index 2583eb26..55cac1c 100644
--- a/chrome/browser/resources/extensions/extensions.gni
+++ b/chrome/browser/resources/extensions/extensions.gni
@@ -4,7 +4,6 @@
 
 import("//build/config/chromeos/ui_mode.gni")
 
-# List of files that don't need to be passed to html_to_js().
 non_web_component_files = [
   "drag_and_drop_handler.ts",
   "extensions.ts",
@@ -22,7 +21,7 @@
   non_web_component_files += [ "kiosk_browser_proxy.ts" ]
 }
 
-# List of files that should be passed to html_to_js().
+# Files holding a Polymer element definition and have an equivalent .html file.
 web_component_files = [
   "activity_log/activity_log.ts",
   "activity_log/activity_log_history.ts",
@@ -34,7 +33,6 @@
   "drop_overlay.ts",
   "error_page.ts",
   "host_permissions_toggle_list.ts",
-  "icons.ts",
   "install_warnings_dialog.ts",
   "item.ts",
   "item_list.ts",
@@ -46,8 +44,6 @@
   "pack_dialog_alert.ts",
   "runtime_host_permissions.ts",
   "runtime_hosts_dialog.ts",
-  "shared_style.ts",
-  "shared_vars.ts",
   "shortcut_input.ts",
   "sidebar.ts",
   "site_permissions.ts",
@@ -62,3 +58,23 @@
 if (is_chromeos_ash) {
   web_component_files += [ "kiosk_dialog.ts" ]
 }
+
+# Files that are passed as input to html_to_wrapper().
+html_files = []
+foreach(f, web_component_files) {
+  html_files += [ string_replace(f, ".ts", ".html") ]
+}
+
+# Files that are generated by html_to_wrapper().
+html_wrapper_files = []
+foreach(f, html_files) {
+  html_wrapper_files += [ f + ".ts" ]
+}
+
+ts_files = web_component_files + non_web_component_files
+
+css_wrapper_files = [
+  "icons.ts",
+  "shared_style.ts",
+  "shared_vars.ts",
+]
diff --git a/chrome/browser/resources/extensions/host_permissions_toggle_list.ts b/chrome/browser/resources/extensions/host_permissions_toggle_list.ts
index 3ea92d0..93307db 100644
--- a/chrome/browser/resources/extensions/host_permissions_toggle_list.ts
+++ b/chrome/browser/resources/extensions/host_permissions_toggle_list.ts
@@ -10,7 +10,8 @@
 import './shared_style.js';
 import './strings.m.js';
 
-import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {getTemplate} from './host_permissions_toggle_list.html.js';
 
 import {ItemDelegate} from './item.js';
 import {UserAction} from './item_util.js';
@@ -29,7 +30,7 @@
   }
 
   static get template() {
-    return html`{__html_template__}`;
+    return getTemplate();
   }
 
   static get properties() {
diff --git a/chrome/browser/resources/extensions/install_warnings_dialog.ts b/chrome/browser/resources/extensions/install_warnings_dialog.ts
index 2873d041..a30290e1 100644
--- a/chrome/browser/resources/extensions/install_warnings_dialog.ts
+++ b/chrome/browser/resources/extensions/install_warnings_dialog.ts
@@ -9,7 +9,8 @@
 import './code_section.js';
 
 import {CrDialogElement} from 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js';
-import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {getTemplate} from './install_warnings_dialog.html.js';
 
 interface ExtensionsInstallWarningsDialogElement {
   $: {
@@ -23,7 +24,7 @@
   }
 
   static get template() {
-    return html`{__html_template__}`;
+    return getTemplate();
   }
 
   static get properties() {
@@ -34,7 +35,7 @@
 
   installWarnings: Array<string>;
 
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
     this.$.dialog.showModal();
   }
diff --git a/chrome/browser/resources/extensions/item.ts b/chrome/browser/resources/extensions/item.ts
index 70f4ad54..3d088c2 100644
--- a/chrome/browser/resources/extensions/item.ts
+++ b/chrome/browser/resources/extensions/item.ts
@@ -24,7 +24,8 @@
 import {CrToggleElement} from 'chrome://resources/cr_elements/cr_toggle/cr_toggle.m.js';
 import {assert, assertNotReached} from 'chrome://resources/js/assert_ts.js';
 import {I18nMixin} from 'chrome://resources/js/i18n_mixin.js';
-import {flush, html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {flush, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {getTemplate} from './item.html.js';
 
 import {ItemMixin} from './item_mixin.js';
 import {computeInspectableViewLabel, EnableControl, getEnableControl, getItemSource, getItemSourceString, isEnabled, SourceType, userCanChangeEnablement} from './item_util.js';
@@ -72,7 +73,7 @@
   }
 
   static get template() {
-    return html`{__html_template__}`;
+    return getTemplate();
   }
 
   static get properties() {
diff --git a/chrome/browser/resources/extensions/item_list.ts b/chrome/browser/resources/extensions/item_list.ts
index 4666a42..da86db23 100644
--- a/chrome/browser/resources/extensions/item_list.ts
+++ b/chrome/browser/resources/extensions/item_list.ts
@@ -9,9 +9,10 @@
 import {CrContainerShadowMixin} from 'chrome://resources/cr_elements/cr_container_shadow_mixin.js';
 import {I18nMixin} from 'chrome://resources/js/i18n_mixin.js';
 import {IronA11yAnnouncer} from 'chrome://resources/polymer/v3_0/iron-a11y-announcer/iron-a11y-announcer.js';
-import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {ExtensionsItemElement, ItemDelegate} from './item.js';
+import {getTemplate} from './item_list.html.js';
 
 type Filter = (info: chrome.developerPrivate.ExtensionInfo) => boolean;
 
@@ -24,7 +25,7 @@
   }
 
   static get template() {
-    return html`{__html_template__}`;
+    return getTemplate();
   }
 
   static get properties() {
diff --git a/chrome/browser/resources/extensions/keyboard_shortcuts.ts b/chrome/browser/resources/extensions/keyboard_shortcuts.ts
index f2f6043..302aae7 100644
--- a/chrome/browser/resources/extensions/keyboard_shortcuts.ts
+++ b/chrome/browser/resources/extensions/keyboard_shortcuts.ts
@@ -9,9 +9,10 @@
 import './shortcut_input.js';
 
 import {CrContainerShadowMixin} from 'chrome://resources/cr_elements/cr_container_shadow_mixin.js';
-import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {KeyboardShortcutDelegate} from './keyboard_shortcut_delegate.js';
+import {getTemplate} from './keyboard_shortcuts.html.js';
 
 /** Event interface for dom-repeat. */
 interface RepeaterEvent<T> extends CustomEvent {
@@ -33,7 +34,7 @@
   }
 
   static get template() {
-    return html`{__html_template__}`;
+    return getTemplate();
   }
 
   static get properties() {
@@ -55,7 +56,7 @@
   delegate: KeyboardShortcutDelegate;
   items: Array<chrome.developerPrivate.ExtensionInfo>;
 
-  ready() {
+  override ready() {
     super.ready();
     this.addEventListener('view-enter-start', this.onViewEnter_);
   }
diff --git a/chrome/browser/resources/extensions/kiosk_dialog.ts b/chrome/browser/resources/extensions/kiosk_dialog.ts
index e94da45..a22337a 100644
--- a/chrome/browser/resources/extensions/kiosk_dialog.ts
+++ b/chrome/browser/resources/extensions/kiosk_dialog.ts
@@ -16,9 +16,10 @@
 import {CrInputElement} from 'chrome://resources/cr_elements/cr_input/cr_input.m.js';
 import {assert} from 'chrome://resources/js/assert_ts.js';
 import {WebUIListenerMixin} from 'chrome://resources/js/web_ui_listener_mixin.js';
-import {DomRepeatEvent, html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {DomRepeatEvent, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {KioskApp, KioskAppSettings, KioskBrowserProxy, KioskBrowserProxyImpl} from './kiosk_browser_proxy.js';
+import {getTemplate} from './kiosk_dialog.html.js';
 
 export interface ExtensionsKioskDialogElement {
   $: {
@@ -39,7 +40,7 @@
   }
 
   static get template() {
-    return html`{__html_template__}`;
+    return getTemplate();
   }
 
   static get properties() {
@@ -67,7 +68,7 @@
   private canEditBailout_: boolean;
   private errorAppId_: string|null;
 
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
 
     this.kioskBrowserProxy_.initializeKioskAppSettings()
diff --git a/chrome/browser/resources/extensions/load_error.ts b/chrome/browser/resources/extensions/load_error.ts
index ef946c1..2fdf78e8 100644
--- a/chrome/browser/resources/extensions/load_error.ts
+++ b/chrome/browser/resources/extensions/load_error.ts
@@ -11,9 +11,10 @@
 
 import {CrDialogElement} from 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js';
 import {assert} from 'chrome://resources/js/assert_ts.js';
-import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {ExtensionsCodeSectionElement} from './code_section.js';
+import {getTemplate} from './load_error.html.js';
 
 export interface LoadErrorDelegate {
   /**
@@ -35,7 +36,7 @@
   }
 
   static get template() {
-    return html`{__html_template__}`;
+    return getTemplate();
   }
 
   static get properties() {
diff --git a/chrome/browser/resources/extensions/manager.ts b/chrome/browser/resources/extensions/manager.ts
index 85fa82f..899f38b 100644
--- a/chrome/browser/resources/extensions/manager.ts
+++ b/chrome/browser/resources/extensions/manager.ts
@@ -31,7 +31,7 @@
 import {CrViewManagerElement} from 'chrome://resources/cr_elements/cr_view_manager/cr_view_manager.js';
 import {assert, assertNotReached} from 'chrome://resources/js/assert_ts.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
-import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {ActivityLogExtensionPlaceholder} from './activity_log/activity_log.js';
 import {ExtensionsDetailViewElement} from './detail_view.js';
@@ -39,6 +39,7 @@
 // <if expr="chromeos">
 import {KioskBrowserProxyImpl} from './kiosk_browser_proxy.js';
 // </if>
+import {getTemplate} from './manager.html.js';
 import {Dialog, navigation, Page, PageState} from './navigation_helper.js';
 import {Service} from './service.js';
 
@@ -88,6 +89,10 @@
     return 'extensions-manager';
   }
 
+  static get template() {
+    return getTemplate();
+  }
+
   static get properties() {
     return {
       canLoadUnpacked: {
@@ -247,7 +252,7 @@
     this.navigationListener_ = null;
   }
 
-  ready() {
+  override ready() {
     super.ready();
 
     this.addEventListener('load-error', this.onLoadError_);
@@ -285,7 +290,7 @@
     // </if>
   }
 
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
 
     document.documentElement.classList.remove('loading');
@@ -297,7 +302,7 @@
     });
   }
 
-  disconnectedCallback() {
+  override disconnectedCallback() {
     super.disconnectedCallback();
     assert(this.navigationListener_);
     assert(navigation.removeListener(this.navigationListener_));
@@ -675,10 +680,6 @@
     this.showKioskDialog_ = false;
   }
   // </if>
-
-  static get template() {
-    return html`{__html_template__}`;
-  }
 }
 
 declare global {
diff --git a/chrome/browser/resources/extensions/options_dialog.ts b/chrome/browser/resources/extensions/options_dialog.ts
index f4e27c5..fd953b48 100644
--- a/chrome/browser/resources/extensions/options_dialog.ts
+++ b/chrome/browser/resources/extensions/options_dialog.ts
@@ -6,9 +6,10 @@
 
 import {CrDialogElement} from 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js';
 import {EventTracker} from 'chrome://resources/js/event_tracker.m.js';
-import {Debouncer, html, PolymerElement, timeOut} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {Debouncer, PolymerElement, timeOut} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {navigation, Page} from './navigation_helper.js';
+import {getTemplate} from './options_dialog.html.js';
 
 /**
  * @return A signal that the document is ready. Need to wait for this, otherwise
@@ -48,7 +49,7 @@
   }
 
   static get template() {
-    return html`{__html_template__}`;
+    return getTemplate();
   }
 
   static get properties() {
diff --git a/chrome/browser/resources/extensions/pack_dialog.ts b/chrome/browser/resources/extensions/pack_dialog.ts
index d943462..acb1c590a 100644
--- a/chrome/browser/resources/extensions/pack_dialog.ts
+++ b/chrome/browser/resources/extensions/pack_dialog.ts
@@ -12,7 +12,8 @@
 
 import {CrDialogElement} from 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js';
 import {CrInputElement} from 'chrome://resources/cr_elements/cr_input/cr_input.m.js';
-import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {getTemplate} from './pack_dialog.html.js';
 
 export interface PackDialogDelegate {
   /**
@@ -51,7 +52,7 @@
   }
 
   static get template() {
-    return html`{__html_template__}`;
+    return getTemplate();
   }
 
   static get properties() {
@@ -73,7 +74,7 @@
   private keyFile_: string;
   private lastResponse_: chrome.developerPrivate.PackDirectoryResponse|null;
 
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
     this.$.dialog.showModal();
   }
diff --git a/chrome/browser/resources/extensions/pack_dialog_alert.ts b/chrome/browser/resources/extensions/pack_dialog_alert.ts
index c493d3b..c0b0139 100644
--- a/chrome/browser/resources/extensions/pack_dialog_alert.ts
+++ b/chrome/browser/resources/extensions/pack_dialog_alert.ts
@@ -9,7 +9,8 @@
 import {CrDialogElement} from 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js';
 import {assert, assertNotReached} from 'chrome://resources/js/assert_ts.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
-import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {getTemplate} from './pack_dialog_alert.html.js';
 
 export interface ExtensionsPackDialogAlertElement {
   $: {
@@ -23,7 +24,7 @@
   }
 
   static get template() {
-    return html`{__html_template__}`;
+    return getTemplate();
   }
 
   static get properties() {
@@ -47,7 +48,7 @@
     return this.$.dialog.getNative().returnValue;
   }
 
-  ready() {
+  override ready() {
     super.ready();
 
     // Initialize button label values for initial html binding.
@@ -73,7 +74,7 @@
     }
   }
 
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
     this.$.dialog.showModal();
   }
diff --git a/chrome/browser/resources/extensions/runtime_host_permissions.ts b/chrome/browser/resources/extensions/runtime_host_permissions.ts
index 38cacaa4..150d0913 100644
--- a/chrome/browser/resources/extensions/runtime_host_permissions.ts
+++ b/chrome/browser/resources/extensions/runtime_host_permissions.ts
@@ -23,9 +23,10 @@
 import {CrDialogElement} from 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js';
 import {assert} from 'chrome://resources/js/assert_ts.js';
 import {focusWithoutInk} from 'chrome://resources/js/cr/ui/focus_without_ink.m.js';
-import {DomRepeatEvent, html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {DomRepeatEvent, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {ItemDelegate} from './item.js';
+import {getTemplate} from './runtime_host_permissions.html.js';
 import {getFaviconUrl} from './url_util.js';
 
 export interface ExtensionsRuntimeHostPermissionsElement {
@@ -40,7 +41,7 @@
   }
 
   static get template() {
-    return html`{__html_template__}`;
+    return getTemplate();
   }
 
   static get properties() {
diff --git a/chrome/browser/resources/extensions/runtime_hosts_dialog.ts b/chrome/browser/resources/extensions/runtime_hosts_dialog.ts
index 8786b69..7356dd8 100644
--- a/chrome/browser/resources/extensions/runtime_hosts_dialog.ts
+++ b/chrome/browser/resources/extensions/runtime_hosts_dialog.ts
@@ -12,9 +12,10 @@
 import {CrDialogElement} from 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js';
 import {assert} from 'chrome://resources/js/assert_ts.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
-import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {ItemDelegate} from './item.js';
+import {getTemplate} from './runtime_hosts_dialog.html.js';
 
 // A RegExp to roughly match acceptable patterns entered by the user.
 // exec'ing() this RegExp will match the following groups:
@@ -62,7 +63,7 @@
   }
 
   static get template() {
-    return html`{__html_template__}`;
+    return getTemplate();
   }
 
   static get properties() {
@@ -107,7 +108,7 @@
   private site_: string;
   private inputInvalid_: boolean;
 
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
 
     if (this.currentSite !== null && this.currentSite !== undefined) {
diff --git a/chrome/browser/resources/extensions/shortcut_input.ts b/chrome/browser/resources/extensions/shortcut_input.ts
index aef72348..e51bc19 100644
--- a/chrome/browser/resources/extensions/shortcut_input.ts
+++ b/chrome/browser/resources/extensions/shortcut_input.ts
@@ -12,9 +12,10 @@
 import {assert} from 'chrome://resources/js/assert_ts.js';
 import {I18nMixin} from 'chrome://resources/js/i18n_mixin.js';
 import {IronA11yAnnouncer} from 'chrome://resources/polymer/v3_0/iron-a11y-announcer/iron-a11y-announcer.js';
-import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {KeyboardShortcutDelegate} from './keyboard_shortcut_delegate.js';
+import {getTemplate} from './shortcut_input.html.js';
 import {hasValidModifiers, isValidKeyCode, Key, keystrokeToString} from './shortcut_util.js';
 
 enum ShortcutError {
@@ -42,7 +43,7 @@
   }
 
   static get template() {
-    return html`{__html_template__}`;
+    return getTemplate();
   }
 
   static get properties() {
@@ -97,7 +98,7 @@
   private readonly_: boolean;
   private pendingShortcut_: string;
 
-  ready() {
+  override ready() {
     super.ready();
 
     const node = this.$.input;
diff --git a/chrome/browser/resources/extensions/sidebar.ts b/chrome/browser/resources/extensions/sidebar.ts
index 35880d82..cec1980 100644
--- a/chrome/browser/resources/extensions/sidebar.ts
+++ b/chrome/browser/resources/extensions/sidebar.ts
@@ -8,9 +8,10 @@
 import 'chrome://resources/polymer/v3_0/paper-styles/color.js';
 
 import {IronSelectorElement} from 'chrome://resources/polymer/v3_0/iron-selector/iron-selector.js';
-import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {navigation, Page} from './navigation_helper.js';
+import {getTemplate} from './sidebar.html.js';
 
 export interface ExtensionsSidebarElement {
   $: {
@@ -26,7 +27,7 @@
   }
 
   static get template() {
-    return html`{__html_template__}`;
+    return getTemplate();
   }
 
   static get properties() {
@@ -37,12 +38,12 @@
 
   enableEnhancedSiteControls: boolean;
 
-  ready() {
+  override ready() {
     super.ready();
     this.setAttribute('role', 'navigation');
   }
 
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
 
     const page = navigation.getCurrentPage().page;
diff --git a/chrome/browser/resources/extensions/site_permissions.ts b/chrome/browser/resources/extensions/site_permissions.ts
index 4aa79839..c33379c5c 100644
--- a/chrome/browser/resources/extensions/site_permissions.ts
+++ b/chrome/browser/resources/extensions/site_permissions.ts
@@ -11,9 +11,10 @@
 import './site_permissions_list.js';
 
 import {CrLinkRowElement} from 'chrome://resources/cr_elements/cr_link_row/cr_link_row.js';
-import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {navigation, Page} from './navigation_helper.js';
+import {getTemplate} from './site_permissions.html.js';
 import {SiteSettingsMixin} from './site_settings_mixin.js';
 
 export interface ExtensionsSitePermissionsElement {
@@ -31,7 +32,7 @@
   }
 
   static get template() {
-    return html`{__html_template__}`;
+    return getTemplate();
   }
 
   static get properties() {
diff --git a/chrome/browser/resources/extensions/site_permissions_by_site.ts b/chrome/browser/resources/extensions/site_permissions_by_site.ts
index 3aeb622..97685479 100644
--- a/chrome/browser/resources/extensions/site_permissions_by_site.ts
+++ b/chrome/browser/resources/extensions/site_permissions_by_site.ts
@@ -7,9 +7,10 @@
 import './shared_style.js';
 
 import {CrIconButtonElement} from 'chrome://resources/cr_elements/cr_icon_button/cr_icon_button.m.js';
-import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {navigation, Page} from './navigation_helper.js';
+import {getTemplate} from './site_permissions_by_site.html.js';
 
 export interface ExtensionsSitePermissionsBySiteElement {
   $: {
@@ -23,7 +24,7 @@
   }
 
   static get template() {
-    return html`{__html_template__}`;
+    return getTemplate();
   }
 
   private onCloseButtonClick_() {
diff --git a/chrome/browser/resources/extensions/site_permissions_edit_permissions_dialog.ts b/chrome/browser/resources/extensions/site_permissions_edit_permissions_dialog.ts
index 87a90ef..77e261b1 100644
--- a/chrome/browser/resources/extensions/site_permissions_edit_permissions_dialog.ts
+++ b/chrome/browser/resources/extensions/site_permissions_edit_permissions_dialog.ts
@@ -12,8 +12,9 @@
 import {CrButtonElement} from 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
 import {CrDialogElement} from 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js';
 import {I18nMixin} from 'chrome://resources/js/i18n_mixin.js';
-import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
+import {getTemplate} from './site_permissions_edit_permissions_dialog.html.js';
 import {SiteSettingsDelegate} from './site_settings_mixin.js';
 
 export interface SitePermissionsEditPermissionsDialogElement {
@@ -33,7 +34,7 @@
   }
 
   static get template() {
-    return html`{__html_template__}`;
+    return getTemplate();
   }
 
   static get properties() {
@@ -69,7 +70,7 @@
   site: string;
   private siteSet_: chrome.developerPrivate.UserSiteSet;
 
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
     this.siteSet_ = this.originalSiteSet;
   }
diff --git a/chrome/browser/resources/extensions/site_permissions_edit_url_dialog.ts b/chrome/browser/resources/extensions/site_permissions_edit_url_dialog.ts
index a84fd37..368e6a5 100644
--- a/chrome/browser/resources/extensions/site_permissions_edit_url_dialog.ts
+++ b/chrome/browser/resources/extensions/site_permissions_edit_url_dialog.ts
@@ -12,8 +12,9 @@
 import {CrDialogElement} from 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js';
 import {assert} from 'chrome://resources/js/assert_ts.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
-import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
+import {getTemplate} from './site_permissions_edit_url_dialog.html.js';
 import {SiteSettingsDelegate} from './site_settings_mixin.js';
 
 // A RegExp to roughly match acceptable patterns entered by the user.
@@ -55,7 +56,7 @@
   }
 
   static get template() {
-    return html`{__html_template__}`;
+    return getTemplate();
   }
 
   static get properties() {
@@ -91,7 +92,7 @@
   private site_: string;
   private inputValid_: boolean;
 
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
 
     if (this.siteToEdit !== null) {
diff --git a/chrome/browser/resources/extensions/site_permissions_list.ts b/chrome/browser/resources/extensions/site_permissions_list.ts
index 52bbcde..59abd88d 100644
--- a/chrome/browser/resources/extensions/site_permissions_list.ts
+++ b/chrome/browser/resources/extensions/site_permissions_list.ts
@@ -17,8 +17,9 @@
 import {CrButtonElement} from 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
 import {assert} from 'chrome://resources/js/assert_ts.js';
 import {focusWithoutInk} from 'chrome://resources/js/cr/ui/focus_without_ink.m.js';
-import {DomRepeatEvent, html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {DomRepeatEvent, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
+import {getTemplate} from './site_permissions_list.html.js';
 import {SiteSettingsDelegate} from './site_settings_mixin.js';
 import {getFaviconUrl} from './url_util.js';
 
@@ -35,7 +36,7 @@
   }
 
   static get template() {
-    return html`{__html_template__}`;
+    return getTemplate();
   }
 
   static get properties() {
diff --git a/chrome/browser/resources/extensions/site_settings_mixin.ts b/chrome/browser/resources/extensions/site_settings_mixin.ts
index 783cec1..311170a 100644
--- a/chrome/browser/resources/extensions/site_settings_mixin.ts
+++ b/chrome/browser/resources/extensions/site_settings_mixin.ts
@@ -51,7 +51,7 @@
         protected permittedSites: string[];
         protected restrictedSites: string[];
 
-        ready() {
+        override ready() {
           super.ready();
           if (this.enableEnhancedSiteControls) {
             this.delegate.getUserSiteSettings().then(
diff --git a/chrome/browser/resources/extensions/toggle_row.ts b/chrome/browser/resources/extensions/toggle_row.ts
index 188d2f9..b2add51 100644
--- a/chrome/browser/resources/extensions/toggle_row.ts
+++ b/chrome/browser/resources/extensions/toggle_row.ts
@@ -6,7 +6,8 @@
 import 'chrome://resources/cr_elements/shared_style_css.m.js';
 
 import {CrToggleElement} from 'chrome://resources/cr_elements/cr_toggle/cr_toggle.m.js';
-import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {getTemplate} from './toggle_row.html.js';
 
 
 /**
@@ -29,7 +30,7 @@
   }
 
   static get template() {
-    return html`{__html_template__}`;
+    return getTemplate();
   }
 
   static get properties() {
diff --git a/chrome/browser/resources/extensions/toolbar.ts b/chrome/browser/resources/extensions/toolbar.ts
index 7f20f26..d621516 100644
--- a/chrome/browser/resources/extensions/toolbar.ts
+++ b/chrome/browser/resources/extensions/toolbar.ts
@@ -15,7 +15,8 @@
 import {CrToggleElement} from 'chrome://resources/cr_elements/cr_toggle/cr_toggle.m.js';
 import {I18nMixin} from 'chrome://resources/js/i18n_mixin.js';
 import {listenOnce} from 'chrome://resources/js/util.m.js';
-import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {getTemplate} from './toolbar.html.js';
 
 export interface ToolbarDelegate {
   /**
@@ -53,7 +54,7 @@
   }
 
   static get template() {
-    return html`{__html_template__}`;
+    return getTemplate();
   }
 
   static get properties() {
@@ -103,7 +104,7 @@
   private showPackDialog_: boolean;
   private isUpdating_: boolean;
 
-  ready() {
+  override ready() {
     super.ready();
     this.setAttribute('role', 'banner');
   }
diff --git a/chrome/browser/resources/extensions/tsconfig_base.json b/chrome/browser/resources/extensions/tsconfig_base.json
index afa07315..5a5ce8c 100644
--- a/chrome/browser/resources/extensions/tsconfig_base.json
+++ b/chrome/browser/resources/extensions/tsconfig_base.json
@@ -1,6 +1,7 @@
 {
   "extends": "../../../../tools/typescript/tsconfig_base.json",
   "compilerOptions": {
+    "noImplicitOverride": true,
     "noUncheckedIndexedAccess": false,
     "noUnusedLocals": false,
     "strictPropertyInitialization": false
diff --git a/chrome/browser/resources/settings/autofill_page/password_edit_dialog.ts b/chrome/browser/resources/settings/autofill_page/password_edit_dialog.ts
index 77f015e..bdc770e 100644
--- a/chrome/browser/resources/settings/autofill_page/password_edit_dialog.ts
+++ b/chrome/browser/resources/settings/autofill_page/password_edit_dialog.ts
@@ -317,7 +317,8 @@
 
   private shouldShowNote_(): boolean {
     return this.isPasswordNotesEnabled_ &&
-        this.dialogMode !== PasswordDialogMode.FEDERATED_VIEW;
+        (this.dialogMode === PasswordDialogMode.PASSWORD_VIEW ||
+         this.dialogMode === PasswordDialogMode.EDIT);
   }
 
   /**
diff --git a/chrome/browser/resources/settings/privacy_sandbox/app.html b/chrome/browser/resources/settings/privacy_sandbox/app.html
index edbba59..edbbff6 100644
--- a/chrome/browser/resources/settings/privacy_sandbox/app.html
+++ b/chrome/browser/resources/settings/privacy_sandbox/app.html
@@ -145,7 +145,7 @@
   }
 
   paper-tooltip {
-    --paper-tooltip-background: white;
+    --paper-tooltip-background: var(--cr-card-background-color);
     --paper-tooltip-delay-in: 300;
     --paper-tooltip-duration-in: 300;
     --paper-tooltip-opacity: 1;
@@ -154,13 +154,12 @@
      * third_party)
      */
     --paper-tooltip: {
-      border: 4px solid white;
       border-radius: 4px;
-      box-shadow: 0 0 4px rgba(0, 0, 0, 0.3);
-      font-family: inherit;
-      font-size: 0.875rem; /* 14px */
+      box-shadow: var(--cr-elevation-2);
+      font-family: Roboto, Arial, sans-serif;
+      font-size: inherit;
       font-weight: 400;
-      line-height: normal;
+      line-height: 154%;  /* 20px. */
       margin: 0 4px;
     };
   }
@@ -288,8 +287,7 @@
                   on-focus="onShowTooltip_" on-mouseenter="onShowTooltip_">
               </iron-icon>
               <paper-tooltip id="topicsTooltip" for="topicsTooltipIcon"
-                  hidden="[[!showTopicsTooltip_]]" position="bottom" manual-mode
-                  fit-to-visible-bounds>
+                  position="bottom" manual-mode fit-to-visible-bounds>
                 <div class="dialog-description">
                   $i18n{privacySandboxAdPersonalizationDialogTopicsLearnMore1}
                 </div>
@@ -329,8 +327,7 @@
                   on-focus="onShowTooltip_" on-mouseenter="onShowTooltip_">
               </iron-icon>
               <paper-tooltip id="fledgeTooltip" for="fledgeTooltipIcon"
-                  hidden="[[!showFledgeTooltip_]]" position="top" manual-mode
-                  fit-to-visible-bounds>
+                  position="top" manual-mode fit-to-visible-bounds>
                 <div class="dialog-description">
                   $i18n{privacySandboxAdPersonalizationDialogFledgeLearnMore1}
                 </div>
diff --git a/chrome/browser/resources/settings/privacy_sandbox/app.ts b/chrome/browser/resources/settings/privacy_sandbox/app.ts
index bcab338..89ce5ec 100644
--- a/chrome/browser/resources/settings/privacy_sandbox/app.ts
+++ b/chrome/browser/resources/settings/privacy_sandbox/app.ts
@@ -95,16 +95,6 @@
           return [];
         },
       },
-
-      showTopicsTooltip_: {
-        type: Boolean,
-        value: false,
-      },
-
-      showFledgeTooltip_: {
-        type: Boolean,
-        value: false,
-      },
     };
   }
 
@@ -123,8 +113,6 @@
   private blockedTopics_: Array<PrivacySandboxInterest>;
   private joiningSites_: Array<PrivacySandboxInterest>;
   private blockedSites_: Array<PrivacySandboxInterest>;
-  private showTopicsTooltip_: boolean;
-  private showFledgeTooltip_: boolean;
 
   override ready() {
     super.ready();
@@ -325,16 +313,9 @@
   private onShowTooltip_(e: Event) {
     assert(e.target instanceof HTMLElement);
     const target = e.target! as HTMLElement;
-    let tooltip: PaperTooltipElement;
-    if (target.id === 'topicsTooltipIcon') {
-      tooltip = this.shadowRoot!.querySelector<PaperTooltipElement>(
-          '#topicsTooltip')!;
-      this.showTopicsTooltip_ = true;
-    } else {
-      tooltip = this.shadowRoot!.querySelector<PaperTooltipElement>(
-          '#fledgeTooltip')!;
-      this.showFledgeTooltip_ = true;
-    }
+    const tooltip = this.shadowRoot!.querySelector<PaperTooltipElement>(
+        target.id === 'topicsTooltipIcon' ? '#topicsTooltip' :
+                                            '#fledgeTooltip')!;
 
     const hide = () => {
       tooltip.hide();
@@ -342,8 +323,6 @@
       target.removeEventListener('blur', hide);
       target.removeEventListener('click', hide);
       tooltip.removeEventListener('mouseenter', hide);
-      this.showTopicsTooltip_ = false;
-      this.showFledgeTooltip_ = false;
     };
     target.addEventListener('mouseleave', hide);
     target.addEventListener('blur', hide);
diff --git a/chrome/browser/resources/signin/dice_web_signin_intercept/dice_web_signin_intercept_app.ts b/chrome/browser/resources/signin/dice_web_signin_intercept/dice_web_signin_intercept_app.ts
index 9da14d5..c63bbac 100644
--- a/chrome/browser/resources/signin/dice_web_signin_intercept/dice_web_signin_intercept_app.ts
+++ b/chrome/browser/resources/signin/dice_web_signin_intercept/dice_web_signin_intercept_app.ts
@@ -64,7 +64,7 @@
       DiceWebSigninInterceptBrowserProxy =
           DiceWebSigninInterceptBrowserProxyImpl.getInstance();
 
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
 
     this.addWebUIListener(
diff --git a/chrome/browser/resources/signin/enterprise_profile_welcome/enterprise_profile_welcome_app.ts b/chrome/browser/resources/signin/enterprise_profile_welcome/enterprise_profile_welcome_app.ts
index 2d8e2ed..a6e2838 100644
--- a/chrome/browser/resources/signin/enterprise_profile_welcome/enterprise_profile_welcome_app.ts
+++ b/chrome/browser/resources/signin/enterprise_profile_welcome/enterprise_profile_welcome_app.ts
@@ -97,7 +97,7 @@
       EnterpriseProfileWelcomeBrowserProxy =
           EnterpriseProfileWelcomeBrowserProxyImpl.getInstance();
 
-  ready() {
+  override ready() {
     super.ready();
 
     this.addWebUIListener(
diff --git a/chrome/browser/resources/signin/profile_customization/profile_customization_app.ts b/chrome/browser/resources/signin/profile_customization/profile_customization_app.ts
index 749d77e..1bb0a1bcb 100644
--- a/chrome/browser/resources/signin/profile_customization/profile_customization_app.ts
+++ b/chrome/browser/resources/signin/profile_customization/profile_customization_app.ts
@@ -70,7 +70,7 @@
   private profileCustomizationBrowserProxy_: ProfileCustomizationBrowserProxy =
       ProfileCustomizationBrowserProxyImpl.getInstance();
 
-  ready() {
+  override ready() {
     super.ready();
 
     // profileName_ is only set now, because it triggers a validation of the
diff --git a/chrome/browser/resources/signin/profile_picker/navigation_mixin.ts b/chrome/browser/resources/signin/profile_picker/navigation_mixin.ts
index c4f4d84..72d2b74b 100644
--- a/chrome/browser/resources/signin/profile_picker/navigation_mixin.ts
+++ b/chrome/browser/resources/signin/profile_picker/navigation_mixin.ts
@@ -208,7 +208,7 @@
     <T extends Constructor<PolymerElement>>(superClass: T): T&
     Constructor<NavigationMixinInterface> => {
       class NavigationMixin extends superClass {
-        connectedCallback() {
+        override connectedCallback() {
           super.connectedCallback();
 
           assert(!routeObservers.has(this));
@@ -219,7 +219,7 @@
           this.onRouteChange(history.state.route, history.state.step);
         }
 
-        disconnectedCallback() {
+        override disconnectedCallback() {
           super.disconnectedCallback();
 
           assert(routeObservers.delete(this));
diff --git a/chrome/browser/resources/signin/profile_picker/profile_card.ts b/chrome/browser/resources/signin/profile_picker/profile_card.ts
index 5e53737..d51efa3 100644
--- a/chrome/browser/resources/signin/profile_picker/profile_card.ts
+++ b/chrome/browser/resources/signin/profile_picker/profile_card.ts
@@ -57,7 +57,7 @@
   private manageProfilesBrowserProxy_: ManageProfilesBrowserProxy =
       ManageProfilesBrowserProxyImpl.getInstance();
 
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
     this.addNameInputTooltipListeners_();
     this.addGaiaNameTooltipListeners_();
diff --git a/chrome/browser/resources/signin/profile_picker/profile_card_menu.ts b/chrome/browser/resources/signin/profile_picker/profile_card_menu.ts
index 3be01b45..ddf9e8a 100644
--- a/chrome/browser/resources/signin/profile_picker/profile_card_menu.ts
+++ b/chrome/browser/resources/signin/profile_picker/profile_card_menu.ts
@@ -141,7 +141,7 @@
   private manageProfilesBrowserProxy_: ManageProfilesBrowserProxy =
       ManageProfilesBrowserProxyImpl.getInstance();
 
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
     this.addWebUIListener(
         'profiles-list-changed', () => this.handleProfilesUpdated_());
@@ -152,7 +152,7 @@
         this.handleProfileStatsReceived_.bind(this));
   }
 
-  ready() {
+  override ready() {
     super.ready();
     // <if expr="lacros">
     afterNextRender(this, () => {
diff --git a/chrome/browser/resources/signin/profile_picker/profile_creation_flow/account_selection_lacros.ts b/chrome/browser/resources/signin/profile_picker/profile_creation_flow/account_selection_lacros.ts
index 8dd390e..3251a0f 100644
--- a/chrome/browser/resources/signin/profile_picker/profile_creation_flow/account_selection_lacros.ts
+++ b/chrome/browser/resources/signin/profile_picker/profile_creation_flow/account_selection_lacros.ts
@@ -60,7 +60,7 @@
   profileThemeInfo: AutogeneratedThemeColorInfo;
   private availableAccounts_: Array<AvailableAccount>;
 
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
     this.addWebUIListener(
         'available-accounts-changed',
@@ -69,7 +69,7 @@
     this.manageProfilesBrowserProxy_.getAvailableAccounts();
   }
 
-  ready() {
+  override ready() {
     super.ready();
     this.addEventListener('view-enter-start', this.onViewEnterStart_);
   }
diff --git a/chrome/browser/resources/signin/profile_picker/profile_creation_flow/local_profile_customization.ts b/chrome/browser/resources/signin/profile_picker/profile_creation_flow/local_profile_customization.ts
index 314444cf..cddd64e 100644
--- a/chrome/browser/resources/signin/profile_picker/profile_creation_flow/local_profile_customization.ts
+++ b/chrome/browser/resources/signin/profile_picker/profile_creation_flow/local_profile_customization.ts
@@ -170,7 +170,7 @@
       ManageProfilesBrowserProxyImpl.getInstance();
   private resizeObserver_: ResizeObserver|null = null;
 
-  ready() {
+  override ready() {
     super.ready();
     this.sanityCheck_();
     this.addWebUIListener(
@@ -181,12 +181,12 @@
     this.addEventListener('view-enter-start', this.onViewEnterStart_);
   }
 
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
     this.addResizeObserver_();
   }
 
-  disconnectedCallback() {
+  override disconnectedCallback() {
     super.disconnectedCallback();
     this.resizeObserver_!.disconnect();
   }
diff --git a/chrome/browser/resources/signin/profile_picker/profile_creation_flow/profile_type_choice.ts b/chrome/browser/resources/signin/profile_picker/profile_creation_flow/profile_type_choice.ts
index 6770c42..f1cf7c2 100644
--- a/chrome/browser/resources/signin/profile_picker/profile_creation_flow/profile_type_choice.ts
+++ b/chrome/browser/resources/signin/profile_picker/profile_creation_flow/profile_type_choice.ts
@@ -100,7 +100,7 @@
   // <if expr="lacros">
   private hasAvailableAccounts_: boolean;
 
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
     this.addWebUIListener(
         'available-accounts-changed',
@@ -110,7 +110,7 @@
   }
   // </if>
 
-  ready() {
+  override ready() {
     super.ready();
     this.addWebUIListener(
         'load-signin-finished',
diff --git a/chrome/browser/resources/signin/profile_picker/profile_picker_app.ts b/chrome/browser/resources/signin/profile_picker/profile_picker_app.ts
index bc0e687..c87c01d 100644
--- a/chrome/browser/resources/signin/profile_picker/profile_picker_app.ts
+++ b/chrome/browser/resources/signin/profile_picker/profile_picker_app.ts
@@ -54,12 +54,12 @@
   private manageProfilesBrowserProxy_: ManageProfilesBrowserProxy =
       ManageProfilesBrowserProxyImpl.getInstance();
 
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
     this.setMinimumSize_();
   }
 
-  onRouteChange(route: Routes, step: string) {
+  override onRouteChange(route: Routes, step: string) {
     if (!isProfileCreationAllowed() && route === Routes.NEW_PROFILE) {
       navigateTo(Routes.MAIN);
       return;
diff --git a/chrome/browser/resources/signin/profile_picker/profile_picker_main_view.ts b/chrome/browser/resources/signin/profile_picker/profile_picker_main_view.ts
index cbb42f96..59ffdbc 100644
--- a/chrome/browser/resources/signin/profile_picker/profile_picker_main_view.ts
+++ b/chrome/browser/resources/signin/profile_picker/profile_picker_main_view.ts
@@ -86,7 +86,7 @@
       ManageProfilesBrowserProxyImpl.getInstance();
   private resizeObserver_: ResizeObserver|null = null;
 
-  ready() {
+  override ready() {
     super.ready();
     if (!isGuestModeEnabled()) {
       this.$.browseAsGuestButton.style.display = 'none';
@@ -97,7 +97,7 @@
     }
   }
 
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
     this.addResizeObserver_();
     this.addWebUIListener(
@@ -107,7 +107,7 @@
     this.manageProfilesBrowserProxy_.initializeMainView();
   }
 
-  disconnectedCallback() {
+  override disconnectedCallback() {
     super.disconnectedCallback();
     this.resizeObserver_!.disconnect();
   }
diff --git a/chrome/browser/resources/signin/profile_picker/profile_switch.ts b/chrome/browser/resources/signin/profile_picker/profile_switch.ts
index fa82fcb..9daeda580 100644
--- a/chrome/browser/resources/signin/profile_picker/profile_switch.ts
+++ b/chrome/browser/resources/signin/profile_picker/profile_switch.ts
@@ -47,7 +47,7 @@
   private manageProfilesBrowserProxy_: ManageProfilesBrowserProxy =
       ManageProfilesBrowserProxyImpl.getInstance();
 
-  ready() {
+  override ready() {
     super.ready();
     this.manageProfilesBrowserProxy_.getSwitchProfile().then(profileState => {
       this.profileState_ = profileState;
diff --git a/chrome/browser/resources/signin/profile_picker/tsconfig_base.json b/chrome/browser/resources/signin/profile_picker/tsconfig_base.json
index 1912694..4076440d 100644
--- a/chrome/browser/resources/signin/profile_picker/tsconfig_base.json
+++ b/chrome/browser/resources/signin/profile_picker/tsconfig_base.json
@@ -1,6 +1,7 @@
 {
   "extends": "../../../../../tools/typescript/tsconfig_base.json",
   "compilerOptions": {
+    "noImplicitOverride": true,
     "noUncheckedIndexedAccess": false,
     "noUnusedLocals": false,
     "strictPropertyInitialization": false
diff --git a/chrome/browser/resources/signin/signin_email_confirmation/signin_email_confirmation_app.ts b/chrome/browser/resources/signin/signin_email_confirmation/signin_email_confirmation_app.ts
index 35e3aed..c321e8da 100644
--- a/chrome/browser/resources/signin/signin_email_confirmation/signin_email_confirmation_app.ts
+++ b/chrome/browser/resources/signin/signin_email_confirmation/signin_email_confirmation_app.ts
@@ -29,7 +29,7 @@
     return html`{__html_template__}`;
   }
 
-  ready() {
+  override ready() {
     super.ready();
 
     const args = JSON.parse(chrome.getVariableValue('dialogArguments'));
diff --git a/chrome/browser/resources/signin/signin_error/signin_error_app.ts b/chrome/browser/resources/signin/signin_error/signin_error_app.ts
index f1fb1e6e..a34326d7 100644
--- a/chrome/browser/resources/signin/signin_error/signin_error_app.ts
+++ b/chrome/browser/resources/signin/signin_error/signin_error_app.ts
@@ -71,7 +71,7 @@
   private hideNormalError_: boolean;
   private hideProfileBlockingErrors_: boolean[];
 
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
 
     this.addWebUIListener('switch-button-unavailable', () => {
diff --git a/chrome/browser/resources/signin/signin_reauth/signin_reauth_app.ts b/chrome/browser/resources/signin/signin_reauth/signin_reauth_app.ts
index befe1cf..5544fdc 100644
--- a/chrome/browser/resources/signin/signin_reauth/signin_reauth_app.ts
+++ b/chrome/browser/resources/signin/signin_reauth/signin_reauth_app.ts
@@ -55,7 +55,7 @@
   private signinReauthBrowserProxy_: SigninReauthBrowserProxy =
       SigninReauthBrowserProxyImpl.getInstance();
 
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
 
     this.addWebUIListener(
diff --git a/chrome/browser/resources/signin/sync_confirmation/sync_confirmation_app.ts b/chrome/browser/resources/signin/sync_confirmation/sync_confirmation_app.ts
index 9e2e595b..841e27c 100644
--- a/chrome/browser/resources/signin/sync_confirmation/sync_confirmation_app.ts
+++ b/chrome/browser/resources/signin/sync_confirmation/sync_confirmation_app.ts
@@ -111,7 +111,7 @@
   private syncConfirmationBrowserProxy_: SyncConfirmationBrowserProxy =
       SyncConfirmationBrowserProxyImpl.getInstance();
 
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
 
     this.addWebUIListener(
diff --git a/chrome/browser/resources/signin/sync_confirmation/sync_disabled_confirmation_app.ts b/chrome/browser/resources/signin/sync_confirmation/sync_disabled_confirmation_app.ts
index 84c9ccd7..1117646 100644
--- a/chrome/browser/resources/signin/sync_confirmation/sync_disabled_confirmation_app.ts
+++ b/chrome/browser/resources/signin/sync_confirmation/sync_disabled_confirmation_app.ts
@@ -29,7 +29,7 @@
   private syncConfirmationBrowserProxy_: SyncConfirmationBrowserProxy =
       SyncConfirmationBrowserProxyImpl.getInstance();
 
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
 
     document.addEventListener(
diff --git a/chrome/browser/resources/signin/tsconfig_base.json b/chrome/browser/resources/signin/tsconfig_base.json
index afa07315..5a5ce8c 100644
--- a/chrome/browser/resources/signin/tsconfig_base.json
+++ b/chrome/browser/resources/signin/tsconfig_base.json
@@ -1,6 +1,7 @@
 {
   "extends": "../../../../tools/typescript/tsconfig_base.json",
   "compilerOptions": {
+    "noImplicitOverride": true,
     "noUncheckedIndexedAccess": false,
     "noUnusedLocals": false,
     "strictPropertyInitialization": false
diff --git a/chrome/browser/ui/android/webid/internal/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionBottomSheetContent.java b/chrome/browser/ui/android/webid/internal/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionBottomSheetContent.java
index 8ae4558..345b4c52 100644
--- a/chrome/browser/ui/android/webid/internal/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionBottomSheetContent.java
+++ b/chrome/browser/ui/android/webid/internal/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionBottomSheetContent.java
@@ -87,7 +87,7 @@
 
     @Override
     public boolean swipeToDismissEnabled() {
-        return false;
+        return true;
     }
 
     @Override
diff --git a/chrome/browser/ui/android/webid/internal/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionViewBinder.java b/chrome/browser/ui/android/webid/internal/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionViewBinder.java
index 0cba32ef..736a89af 100644
--- a/chrome/browser/ui/android/webid/internal/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionViewBinder.java
+++ b/chrome/browser/ui/android/webid/internal/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionViewBinder.java
@@ -338,13 +338,28 @@
         if (key == HeaderProperties.FORMATTED_RP_ETLD_PLUS_ONE
                 || key == HeaderProperties.FORMATTED_IDP_ETLD_PLUS_ONE
                 || key == HeaderProperties.TYPE) {
+            Resources resources = view.getResources();
             TextView headerTitleText = view.findViewById(R.id.header_title);
             HeaderProperties.HeaderType headerType = model.get(HeaderProperties.TYPE);
-            String title = computeHeaderTitle(view.getResources(), headerType,
+            String title = computeHeaderTitle(resources, headerType,
                     model.get(HeaderProperties.FORMATTED_RP_ETLD_PLUS_ONE),
                     model.get(HeaderProperties.FORMATTED_IDP_ETLD_PLUS_ONE));
             headerTitleText.setText(title);
 
+            // Make instructions for closing the bottom sheet part of the header's content
+            // description. This is needed because the bottom sheet's content description (which
+            // includes instructions to close the bottom sheet) is not announced when the FedCM
+            // bottom sheet is shown. Don't include instructions for closing the bottom sheet as
+            // part of the "Verifying..." header content description because the bottom sheet
+            // closes itself automatically at the "Verifying..." stage.
+            if (headerType != HeaderProperties.HeaderType.VERIFY) {
+                headerTitleText.setContentDescription(title + ". "
+                        + resources.getString(R.string.bottom_sheet_accessibility_description));
+            } else {
+                // Update the content description in case the view is recycled.
+                headerTitleText.setContentDescription(title);
+            }
+
             if (key == HeaderProperties.TYPE) {
                 boolean progressBarVisible = (headerType == HeaderProperties.HeaderType.VERIFY);
                 view.findViewById(R.id.header_progress_bar)
diff --git a/chrome/browser/ui/color/chrome_color_id.h b/chrome/browser/ui/color/chrome_color_id.h
index 7584668..dea02d4 100644
--- a/chrome/browser/ui/color/chrome_color_id.h
+++ b/chrome/browser/ui/color/chrome_color_id.h
@@ -339,6 +339,8 @@
       ThemeProperties::COLOR_ACCENT_BORDER_INACTIVE) \
     E_CPONLY(kColorCaptionButtonForegroundActive) \
     E_CPONLY(kColorCaptionButtonForegroundInactive) \
+    E_CPONLY(kColorCaptionCloseButtonBackgroundHovered) \
+    E_CPONLY(kColorCaptionCloseButtonForegroundHovered) \
     E_CPONLY(kColorCaptionForegroundActive) \
     E_CPONLY(kColorCaptionForegroundInactive)
 #else
diff --git a/chrome/browser/ui/color/win/native_chrome_color_mixer_win.cc b/chrome/browser/ui/color/win/native_chrome_color_mixer_win.cc
index 5a1471f..d23cbc60 100644
--- a/chrome/browser/ui/color/win/native_chrome_color_mixer_win.cc
+++ b/chrome/browser/ui/color/win/native_chrome_color_mixer_win.cc
@@ -235,6 +235,9 @@
       GetCaptionForegroundColor(kColorWindowControlButtonBackgroundActive);
   mixer[kColorCaptionButtonForegroundInactive] =
       GetCaptionForegroundColor(kColorWindowControlButtonBackgroundInactive);
+  mixer[kColorCaptionCloseButtonBackgroundHovered] = {
+      SkColorSetRGB(0xE8, 0x11, 0x23)};
+  mixer[kColorCaptionCloseButtonForegroundHovered] = {SK_ColorWHITE};
   mixer[kColorCaptionForegroundActive] =
       GetCaptionForegroundColor(ui::kColorFrameActive);
   mixer[kColorCaptionForegroundInactive] =
diff --git a/chrome/browser/ui/thumbnails/thumbnail_tab_helper_interactive_uitest.cc b/chrome/browser/ui/thumbnails/thumbnail_tab_helper_interactive_uitest.cc
index 05b6fae0..782ab14 100644
--- a/chrome/browser/ui/thumbnails/thumbnail_tab_helper_interactive_uitest.cc
+++ b/chrome/browser/ui/thumbnails/thumbnail_tab_helper_interactive_uitest.cc
@@ -180,8 +180,15 @@
 
 // On browser restore, some tabs may not be loaded. Requesting a
 // thumbnail for one of these tabs should trigger load and capture.
+// TODO(crbug.com/1294473): Flaky on Mac.
+#if BUILDFLAG(IS_MAC)
+#define MAYBE_CapturesRestoredTabWhenRequested \
+  DISABLED_CapturesRestoredTabWhenRequested
+#else
+#define MAYBE_CapturesRestoredTabWhenRequested CapturesRestoredTabWhenRequested
+#endif  // BUILDFLAG(IS_MAC)
 IN_PROC_BROWSER_TEST_F(ThumbnailTabHelperInteractiveTest,
-                       CapturesRestoredTabWhenRequested) {
+                       MAYBE_CapturesRestoredTabWhenRequested) {
   ui_test_utils::NavigateToURLWithDisposition(
       browser(), url2_, WindowOpenDisposition::NEW_WINDOW,
       ui_test_utils::BROWSER_TEST_WAIT_FOR_BROWSER);
diff --git a/chrome/browser/ui/views/frame/windows_10_caption_button.cc b/chrome/browser/ui/views/frame/windows_10_caption_button.cc
index 3427a75f..239e280 100644
--- a/chrome/browser/ui/views/frame/windows_10_caption_button.cc
+++ b/chrome/browser/ui/views/frame/windows_10_caption_button.cc
@@ -96,7 +96,8 @@
   SkColor base_color;
   SkAlpha hovered_alpha, pressed_alpha;
   if (button_type_ == VIEW_ID_CLOSE_BUTTON) {
-    base_color = SkColorSetRGB(0xE8, 0x11, 0x23);
+    base_color =
+        GetColorProvider()->GetColor(kColorCaptionCloseButtonBackgroundHovered);
     hovered_alpha = SK_AlphaOPAQUE;
     pressed_alpha = 0x98;
   } else {
@@ -184,6 +185,8 @@
 
 void Windows10CaptionButton::PaintSymbol(gfx::Canvas* canvas) {
   SkColor symbol_color = GetBaseForegroundColor();
+  const SkColor hovered_color =
+      GetColorProvider()->GetColor(kColorCaptionCloseButtonForegroundHovered);
   if (!GetEnabled() ||
       (!frame_view_->ShouldPaintAsActive() && GetState() != STATE_HOVERED &&
        GetState() != STATE_PRESSED)) {
@@ -193,10 +196,10 @@
   } else if (button_type_ == VIEW_ID_CLOSE_BUTTON &&
              hover_animation().is_animating()) {
     symbol_color = gfx::Tween::ColorValueBetween(
-        hover_animation().GetCurrentValue(), symbol_color, SK_ColorWHITE);
+        hover_animation().GetCurrentValue(), symbol_color, hovered_color);
   } else if (button_type_ == VIEW_ID_CLOSE_BUTTON &&
              (GetState() == STATE_HOVERED || GetState() == STATE_PRESSED)) {
-    symbol_color = SK_ColorWHITE;
+    symbol_color = hovered_color;
   }
 
   gfx::ScopedCanvas scoped_canvas(canvas);
@@ -249,7 +252,7 @@
       // When the X is white, the transparent pixels need to be a bit brighter
       // to be visible.
       const float stroke_halo =
-          stroke_width * (symbol_color == SK_ColorWHITE ? 0.1f : 0.05f);
+          stroke_width * (symbol_color == hovered_color ? 0.1f : 0.05f);
       flags.setStrokeWidth(stroke_width + stroke_halo);
 
       // TODO(bsep): This sometimes draws misaligned at fractional device scales
diff --git a/chrome/browser/ui/views/location_bar/location_bar_bubble_delegate_view.cc b/chrome/browser/ui/views/location_bar/location_bar_bubble_delegate_view.cc
index 9526530a..fc8deb3 100644
--- a/chrome/browser/ui/views/location_bar/location_bar_bubble_delegate_view.cc
+++ b/chrome/browser/ui/views/location_bar/location_bar_bubble_delegate_view.cc
@@ -155,9 +155,6 @@
 
 void LocationBarBubbleDelegateView::DidFinishNavigation(
     content::NavigationHandle* navigation_handle) {
-  // TODO(https://crbug.com/1218946): With MPArch there may be multiple main
-  // frames. This caller was converted automatically to the primary main frame
-  // to preserve its semantics. Follow up to confirm correctness.
   if (!close_on_main_frame_origin_navigation_ ||
       !navigation_handle->IsInPrimaryMainFrame() ||
       !navigation_handle->HasCommitted()) {
diff --git a/chrome/browser/ui/views/profiles/signin_view_controller_delegate_views.cc b/chrome/browser/ui/views/profiles/signin_view_controller_delegate_views.cc
index 02972615..ee8d372 100644
--- a/chrome/browser/ui/views/profiles/signin_view_controller_delegate_views.cc
+++ b/chrome/browser/ui/views/profiles/signin_view_controller_delegate_views.cc
@@ -349,10 +349,11 @@
       modal_signin_widget_ = constrained_window::CreateWebModalDialogViews(
           this, host_web_contents);
       if (should_show_close_button_) {
-        GetBubbleFrameView()->SetBubbleBorder(
-            std::make_unique<views::BubbleBorder>(
-                views::BubbleBorder::NONE, views::BubbleBorder::STANDARD_SHADOW,
-                SK_ColorWHITE));
+        auto bubble_border = std::make_unique<views::BubbleBorder>(
+            views::BubbleBorder::NONE, views::BubbleBorder::STANDARD_SHADOW,
+            gfx::kPlaceholderColor);
+        bubble_border->set_use_theme_background_color(true);
+        GetBubbleFrameView()->SetBubbleBorder(std::move(bubble_border));
       }
       constrained_window::ShowModalDialog(
           modal_signin_widget_->GetNativeWindow(), host_web_contents);
diff --git a/chrome/browser/ui/webui/chromeos/login/oobe_ui.h b/chrome/browser/ui/webui/chromeos/login/oobe_ui.h
index b77f478..70906ebd 100644
--- a/chrome/browser/ui/webui/chromeos/login/oobe_ui.h
+++ b/chrome/browser/ui/webui/chromeos/login/oobe_ui.h
@@ -52,7 +52,7 @@
 
   class Observer {
    public:
-    Observer() {}
+    Observer() = default;
 
     Observer(const Observer&) = delete;
 
@@ -62,7 +62,7 @@
     virtual void OnDestroyingOobeUI() = 0;
 
    protected:
-    virtual ~Observer() {}
+    virtual ~Observer() = default;
   };
 
   OobeUI(content::WebUI* web_ui, const GURL& url);
diff --git a/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.cc b/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.cc
index 286375b8..840e967 100644
--- a/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.cc
+++ b/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.cc
@@ -27,7 +27,6 @@
 #include "base/system/sys_info.h"
 #include "base/task/single_thread_task_runner.h"
 #include "base/threading/thread_task_runner_handle.h"
-#include "base/trace_event/trace_event.h"
 #include "chrome/browser/ash/app_mode/kiosk_app_manager.h"
 #include "chrome/browser/ash/language_preferences.h"
 #include "chrome/browser/ash/login/demo_mode/demo_session.h"
@@ -255,7 +254,6 @@
 void SigninScreenHandler::RegisterMessages() {
   AddCallback("launchIncognito", &SigninScreenHandler::HandleLaunchIncognito);
   AddCallback("offlineLogin", &SigninScreenHandler::HandleOfflineLogin);
-  AddCallback("loginVisible", &SigninScreenHandler::HandleLoginVisible);
 
   // TODO(crbug.com/1168114): This is also called by GAIA screen,
   // but might not be needed anymore
@@ -517,21 +515,6 @@
     delegate_->ShowKioskAutolaunchScreen();
 }
 
-void SigninScreenHandler::HandleLoginVisible(const std::string& source) {
-  VLOG(1) << "Login WebUI >> loginVisible, src: " << source << ", "
-          << "webui_visible_: " << webui_visible_;
-  if (!webui_visible_) {
-    // There might be multiple messages from OOBE UI so send notifications after
-    // the first one only.
-    session_manager::SessionManager::Get()->NotifyLoginOrLockScreenVisible();
-    TRACE_EVENT_NESTABLE_ASYNC_END0(
-        "ui", "ShowLoginWebUI",
-        TRACE_ID_WITH_SCOPE(LoginDisplayHostWebUI::kShowLoginWebUIid,
-                            TRACE_ID_GLOBAL(1)));
-  }
-  webui_visible_ = true;
-}
-
 void SigninScreenHandler::HandleLoginUIStateChanged(const std::string& source,
                                                     bool active) {
   VLOG(0) << "Login WebUI >> active: " << active << ", "
diff --git a/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.h b/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.h
index 9974acd..c148dae 100644
--- a/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.h
+++ b/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.h
@@ -138,7 +138,6 @@
   void HandleToggleResetScreen();
   void HandleToggleKioskAutolaunchScreen();
 
-  void HandleLoginVisible(const std::string& source);
   void HandleLoginUIStateChanged(const std::string& source, bool active);
   void HandleShowLoadingTimeoutError();
 
@@ -166,9 +165,6 @@
   // Network state informer used to keep signin screen up.
   scoped_refptr<NetworkStateInformer> network_state_informer_;
 
-  // Set to true once `LOGIN_WEBUI_VISIBLE` notification is observed.
-  bool webui_visible_ = false;
-
   ErrorScreen* error_screen_ = nullptr;
 
   NetworkStateInformer::State last_network_state_ =
diff --git a/chrome/browser/ui/webui/signin/dice_web_signin_intercept_ui.cc b/chrome/browser/ui/webui/signin/dice_web_signin_intercept_ui.cc
index 0b34760..90524ce8 100644
--- a/chrome/browser/ui/webui/signin/dice_web_signin_intercept_ui.cc
+++ b/chrome/browser/ui/webui/signin/dice_web_signin_intercept_ui.cc
@@ -52,7 +52,7 @@
   primary_account.hosted_domain = kNoHostedDomainFound;
 
   return {DiceWebSigninInterceptor::SigninInterceptionType::kMultiUser,
-          intercepted_account, primary_account, SkColors::kMagenta.toSkColor()};
+          intercepted_account, primary_account, SK_ColorMAGENTA};
 }
 
 }  // namespace
diff --git a/chrome/browser/web_applications/app_service/lacros_web_apps_controller_lacros_browsertest.cc b/chrome/browser/web_applications/app_service/lacros_web_apps_controller_lacros_browsertest.cc
new file mode 100644
index 0000000..c41f6d7
--- /dev/null
+++ b/chrome/browser/web_applications/app_service/lacros_web_apps_controller_lacros_browsertest.cc
@@ -0,0 +1,84 @@
+// 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/callback_helpers.h"
+#include "base/logging.h"
+#include "chrome/browser/lacros/browser_test_util.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_list.h"
+#include "chrome/browser/ui/browser_window.h"
+#include "chrome/browser/ui/web_applications/web_app_controller_browsertest.h"
+#include "chrome/browser/web_applications/app_service/lacros_web_apps_controller.h"
+#include "chrome/browser/web_applications/web_app_utils.h"
+#include "chromeos/crosapi/mojom/app_service_types.mojom.h"
+#include "chromeos/crosapi/mojom/test_controller.mojom-test-utils.h"
+#include "chromeos/crosapi/mojom/test_controller.mojom.h"
+#include "chromeos/lacros/lacros_service.h"
+#include "components/services/app_service/public/mojom/types.mojom.h"
+#include "content/public/test/browser_test.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+
+namespace web_app {
+
+using LacrosWebAppsControllerBrowserTest = web_app::WebAppControllerBrowserTest;
+
+// Test that the default context menu for a web app has the correct items.
+IN_PROC_BROWSER_TEST_F(LacrosWebAppsControllerBrowserTest, DefaultContextMenu) {
+  // If ash is does not contain the relevant test controller functionality, then
+  // there's nothing to do for this test.
+  if (chromeos::LacrosService::Get()->GetInterfaceVersion(
+          crosapi::mojom::TestController::Uuid_) <
+      static_cast<int>(crosapi::mojom::TestController::MethodMinVersions::
+                           kDoesItemExistInShelfMinVersion)) {
+    LOG(WARNING) << "Unsupported ash version.";
+    return;
+  }
+
+  ASSERT_TRUE(embedded_test_server()->Start());
+
+  EXPECT_TRUE(IsWebAppsCrosapiEnabled());
+  LacrosWebAppsController lacros_web_apps_controller(profile());
+  lacros_web_apps_controller.Init();
+
+  const AppId app_id =
+      InstallPWA(embedded_test_server()->GetURL("/web_apps/basic.html"));
+
+  // No item should exist in the shelf before the web app is launched.
+  browser_test_util::WaitForShelfItem(app_id, /*exists=*/false);
+
+  crosapi::mojom::LaunchParamsPtr launch_params =
+      crosapi::mojom::LaunchParams::New();
+  launch_params->app_id = app_id;
+  launch_params->launch_source = apps::mojom::LaunchSource::kFromTest;
+  static_cast<crosapi::mojom::AppController&>(lacros_web_apps_controller)
+      .Launch(std::move(launch_params), base::DoNothing());
+
+  // Wait for item to exist in shelf.
+  browser_test_util::WaitForShelfItem(app_id, /*exists=*/true);
+
+  // Get the context menu.
+  crosapi::mojom::TestControllerAsyncWaiter waiter(
+      chromeos::LacrosService::Get()
+          ->GetRemote<crosapi::mojom::TestController>()
+          .get());
+  std::vector<std::string> items;
+  waiter.GetContextMenuForShelfItem(app_id, &items);
+  ASSERT_EQ(5u, items.size());
+  EXPECT_EQ(items[0], "New window");
+  EXPECT_EQ(items[1], "Pin");
+  EXPECT_EQ(items[2], "Close");
+  EXPECT_EQ(items[3], "Uninstall");
+  EXPECT_EQ(items[4], "App info");
+
+  // Close app window.
+  for (auto* browser : *BrowserList::GetInstance()) {
+    if (browser->is_type_app())
+      browser->window()->Close();
+  }
+
+  // Wait for item to stop existing in shelf.
+  browser_test_util::WaitForShelfItem(app_id, /*exists=*/false);
+}
+
+}  // namespace web_app
diff --git a/chrome/build/linux.pgo.txt b/chrome/build/linux.pgo.txt
index 54a65f02..318bb916 100644
--- a/chrome/build/linux.pgo.txt
+++ b/chrome/build/linux.pgo.txt
@@ -1 +1 @@
-chrome-linux-main-1647280729-46955758c68b5d3eac027344281476aa3bd5b881.profdata
+chrome-linux-main-1647323973-125c4ae307471d4e7f863606f71a94bb0b304b87.profdata
diff --git a/chrome/build/mac-arm.pgo.txt b/chrome/build/mac-arm.pgo.txt
index 7266159..798e199 100644
--- a/chrome/build/mac-arm.pgo.txt
+++ b/chrome/build/mac-arm.pgo.txt
@@ -1 +1 @@
-chrome-mac-arm-main-1647302357-e605e3a470d6c1be450e04cc8d355e7542e6a787.profdata
+chrome-mac-arm-main-1647323973-5d6009aee059b83a7aec0d71aff917e80f6e7fd1.profdata
diff --git a/chrome/build/mac.pgo.txt b/chrome/build/mac.pgo.txt
index 3e6b884..8ea9d9d 100644
--- a/chrome/build/mac.pgo.txt
+++ b/chrome/build/mac.pgo.txt
@@ -1 +1 @@
-chrome-mac-main-1647302357-b1d7c9a56e4da5260f62f4ed8df8cccbd9476905.profdata
+chrome-mac-main-1647323973-4f37bc8b068e4e68850206509440ba189ebc4919.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index 970311f7..0d43558 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-main-1647291450-a010c9d9fe7047e6aa656acdc543689945887704.profdata
+chrome-win32-main-1647313056-6f9a56f8b8744567ada1a46a4f4e4d3cf59c41e0.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index 0035aa5b..5a28847d 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-main-1647291450-305c59221bb9bcb7144a62d700e9a145017942ed.profdata
+chrome-win64-main-1647323973-4366a7bfcbbc3f3beb8fabcec31f0da12751a094.profdata
diff --git a/chrome/renderer/cart/commerce_hint_agent_browsertest.cc b/chrome/renderer/cart/commerce_hint_agent_browsertest.cc
index 42e5399..1a91c149 100644
--- a/chrome/renderer/cart/commerce_hint_agent_browsertest.cc
+++ b/chrome/renderer/cart/commerce_hint_agent_browsertest.cc
@@ -468,13 +468,6 @@
 }
 
 IN_PROC_BROWSER_TEST_F(CommerceHintAgentTest, VisitCart) {
-  // The test is flaky with same-site back/forward cache, presumably because it
-  // doesn't expect a RenderView change on same-site navigations.
-  // TODO(https://crbug.com/1302902): Investigate and fix this.
-  content::DisableBackForwardCacheForTesting(
-      web_contents(),
-      content::BackForwardCache::TEST_ASSUMES_NO_RENDER_FRAME_CHANGE);
-
   // Cannot use dummy page with zero products, or the cart would be deleted.
   NavigateToURL("https://www.guitarcenter.com/cart.html");
 
@@ -1003,6 +996,13 @@
 };
 
 IN_PROC_BROWSER_TEST_F(CommerceHintCartPatternTest, VisitCart) {
+  // The test is flaky with same-site back/forward cache, presumably because it
+  // doesn't expect a RenderView change on same-site navigations.
+  // TODO(https://crbug.com/1302902): Investigate and fix this.
+  content::DisableBackForwardCacheForTesting(
+      web_contents(),
+      content::BackForwardCache::TEST_ASSUMES_NO_RENDER_FRAME_CHANGE);
+
   NavigateToURL("https://www.guitarcenter.com/SpecialLoL");
   WaitForUmaCount("Commerce.Carts.VisitCart", 1);
 
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 2ef6f5f..b82cc1eb 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -4425,6 +4425,7 @@
       "../browser/ui/browser_navigator_browsertest_chromeos.cc",
       "../browser/ui/views/location_bar/intent_chip_button_browsertest.cc",
       "../browser/ui/views/profiles/profile_picker_view_browsertest.cc",
+      "../browser/web_applications/app_service/lacros_web_apps_controller_lacros_browsertest.cc",
     ]
 
     deps = [
diff --git a/chrome/test/data/android/url_overriding/navigation_from_bfcache-1.html b/chrome/test/data/android/url_overriding/navigation_from_bfcache-1.html
index 144b715..fbe6323 100644
--- a/chrome/test/data/android/url_overriding/navigation_from_bfcache-1.html
+++ b/chrome/test/data/android/url_overriding/navigation_from_bfcache-1.html
@@ -7,7 +7,7 @@
     <script>
       window.addEventListener('pageshow', (event) => {
         if (event.persisted) {
-          window.location = 'intent://test/#Intent;scheme=externalappscheme;end';
+          window.location = 'intent://test/#Intent;scheme=test;package=com.chrome.test;end';
         }
       });
     </script>
diff --git a/chrome/test/data/android/url_overriding/navigation_from_java_redirection.html b/chrome/test/data/android/url_overriding/navigation_from_java_redirection.html
index 7d790798..e322cee 100644
--- a/chrome/test/data/android/url_overriding/navigation_from_java_redirection.html
+++ b/chrome/test/data/android/url_overriding/navigation_from_java_redirection.html
@@ -2,7 +2,7 @@
 <html>
 <head>
   <script>
-    window.location = 'intent://test/#Intent;scheme=externalappscheme;end';
+    window.location = 'intent://test/#Intent;scheme=test;package=com.chrome.test;end';
   </script>
 </head>
 <body>
diff --git a/chrome/test/data/android/url_overriding/navigation_from_timer.html b/chrome/test/data/android/url_overriding/navigation_from_timer.html
index 6776572ce..f8e2b1b 100644
--- a/chrome/test/data/android/url_overriding/navigation_from_timer.html
+++ b/chrome/test/data/android/url_overriding/navigation_from_timer.html
@@ -3,7 +3,7 @@
 <head>
   <script>
     function openHello() {
-      window.location = 'intent://test/#Intent;scheme=externalappscheme;end';
+      window.location = 'intent://test/#Intent;scheme=test;package=com.chrome.test;end';
     };
 
     setTimeout(openHello, 2000)
diff --git a/chrome/test/data/android/url_overriding/navigation_from_user_gesture.html b/chrome/test/data/android/url_overriding/navigation_from_user_gesture.html
index 92e510d..3d08e06f 100644
--- a/chrome/test/data/android/url_overriding/navigation_from_user_gesture.html
+++ b/chrome/test/data/android/url_overriding/navigation_from_user_gesture.html
@@ -5,7 +5,7 @@
     content="width=device-width, initial-scale=1.0, maximum-scale=1.0" />
   <script>
     function openApp() {
-      window.location = 'intent://test/#Intent;scheme=externalappscheme;end';
+      window.location = 'intent://test/#Intent;scheme=test;package=com.chrome.test;end';
     };
   </script>
 </head>
diff --git a/chrome/test/data/android/url_overriding/navigation_from_xhr_callback.html b/chrome/test/data/android/url_overriding/navigation_from_xhr_callback.html
index c737e8b..07edcffc 100644
--- a/chrome/test/data/android/url_overriding/navigation_from_xhr_callback.html
+++ b/chrome/test/data/android/url_overriding/navigation_from_xhr_callback.html
@@ -7,7 +7,7 @@
     var xmlhttp = new XMLHttpRequest();
 
     function openApp() {
-      window.location = 'intent://test/#Intent;scheme=externalappscheme;end';
+      window.location = 'intent://test/#Intent;scheme=test;package=com.chrome.test;end';
     };
 
     function xhrOnReadyStateChange() {
diff --git a/chrome/test/data/android/url_overriding/navigation_from_xhr_callback_and_long_timeout.html b/chrome/test/data/android/url_overriding/navigation_from_xhr_callback_and_long_timeout.html
index d566c7c..0cd372e 100644
--- a/chrome/test/data/android/url_overriding/navigation_from_xhr_callback_and_long_timeout.html
+++ b/chrome/test/data/android/url_overriding/navigation_from_xhr_callback_and_long_timeout.html
@@ -7,7 +7,7 @@
     var xmlhttp = new XMLHttpRequest();
 
     function openApp() {
-      window.location = 'intent://test/#Intent;scheme=externalappscheme;end';
+      window.location = 'intent://test/#Intent;scheme=test;package=com.chrome.test;end';
     };
 
     function xhrOnReadyStateChange() {
diff --git a/chrome/test/data/android/url_overriding/navigation_from_xhr_callback_and_short_timeout.html b/chrome/test/data/android/url_overriding/navigation_from_xhr_callback_and_short_timeout.html
index 5fbb7ae..b6d8e27 100644
--- a/chrome/test/data/android/url_overriding/navigation_from_xhr_callback_and_short_timeout.html
+++ b/chrome/test/data/android/url_overriding/navigation_from_xhr_callback_and_short_timeout.html
@@ -7,7 +7,7 @@
     var xmlhttp = new XMLHttpRequest();
 
     function openApp() {
-      window.location = 'intent://test/#Intent;scheme=externalappscheme;end';
+      window.location = 'intent://test/#Intent;scheme=test;package=com.chrome.test;end';
     };
 
     function xhrOnReadyStateChange() {
diff --git a/chrome/test/data/android/url_overriding/navigation_with_fallback_url.html b/chrome/test/data/android/url_overriding/navigation_with_fallback_url.html
index ca062a1..f70ca54 100644
--- a/chrome/test/data/android/url_overriding/navigation_with_fallback_url.html
+++ b/chrome/test/data/android/url_overriding/navigation_with_fallback_url.html
@@ -12,7 +12,7 @@
     content="width=device-width, initial-scale=1.0, maximum-scale=1.0" />
   <script>
     function openApp() {
-      location.href = 'intent://test/#Intent;scheme=badscheme;S.browser_fallback_url=PARAM_FALLBACK_URL;end';
+      location.href = 'intent://test/#Intent;scheme=test;package=com.chrome.test;S.browser_fallback_url=PARAM_FALLBACK_URL;end';
     };
   </script>
 </head>
diff --git a/chrome/test/data/android/url_overriding/open_window_from_link_user_gesture.html b/chrome/test/data/android/url_overriding/open_window_from_link_user_gesture.html
index f503106..e7796e9 100644
--- a/chrome/test/data/android/url_overriding/open_window_from_link_user_gesture.html
+++ b/chrome/test/data/android/url_overriding/open_window_from_link_user_gesture.html
@@ -5,7 +5,7 @@
     content="width=device-width, initial-scale=1.0, maximum-scale=1.0" />
 </head>
 <body>
-<a id='link' target='_blank' href='intent://test/#Intent;scheme=externalappscheme;end'>
+<a id='link' target='_blank' href='intent://test/#Intent;scheme=test;package=com.chrome.test;end'>
   Click to open App!!
 </a>
 </body>
diff --git a/chrome/test/data/android/url_overriding/open_window_from_svg_user_gesture.html b/chrome/test/data/android/url_overriding/open_window_from_svg_user_gesture.html
index 0d09a480..7dc0adb 100644
--- a/chrome/test/data/android/url_overriding/open_window_from_svg_user_gesture.html
+++ b/chrome/test/data/android/url_overriding/open_window_from_svg_user_gesture.html
@@ -7,7 +7,7 @@
 <body>
 <div id='link'  width=auto height=auto>
   <svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
-    <a href='intent://test/#Intent;scheme=externalappscheme;end' target='_blank'>
+    <a href='intent://test/#Intent;scheme=test;package=com.chrome.test;end' target='_blank'>
       <circle cx="50" cy="50" r="50"/>
     </a>
   </svg>
diff --git a/chrome/test/data/android/url_overriding/open_window_from_user_gesture.html b/chrome/test/data/android/url_overriding/open_window_from_user_gesture.html
index 81bb0a40..e8136f96 100644
--- a/chrome/test/data/android/url_overriding/open_window_from_user_gesture.html
+++ b/chrome/test/data/android/url_overriding/open_window_from_user_gesture.html
@@ -5,7 +5,7 @@
     content="width=device-width, initial-scale=1.0, maximum-scale=1.0" />
   <script>
     function openApp() {
-      window.open('intent://test/#Intent;scheme=externalappscheme;end');
+      window.open('intent://test/#Intent;scheme=test;package=com.chrome.test;end');
     };
   </script>
 </head>
diff --git a/chrome/test/data/native_messaging/native_hosts/echo.py b/chrome/test/data/native_messaging/native_hosts/echo.py
index d5f760d..fbfc4d5 100755
--- a/chrome/test/data/native_messaging/native_hosts/echo.py
+++ b/chrome/test/data/native_messaging/native_hosts/echo.py
@@ -68,8 +68,8 @@
     return 1
 
   # Verify that the process was started in the correct directory.
-  cwd = os.getcwd()
-  script_path = os.path.dirname(os.path.abspath(sys.argv[0]))
+  cwd = os.path.realpath(os.getcwd())
+  script_path = os.path.dirname(os.path.realpath(sys.argv[0]))
   if cwd.lower() != script_path.lower():
     sys.stderr.write('Native messaging host started in a wrong directory.')
     return 1
diff --git a/chrome/test/data/webui/chromeos/personalization_app/ambient_subpage_element_test.ts b/chrome/test/data/webui/chromeos/personalization_app/ambient_subpage_element_test.ts
index 064aab517..fa335cd 100644
--- a/chrome/test/data/webui/chromeos/personalization_app/ambient_subpage_element_test.ts
+++ b/chrome/test/data/webui/chromeos/personalization_app/ambient_subpage_element_test.ts
@@ -2,16 +2,18 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import {AlbumItem} from 'chrome://personalization/trusted/ambient/album_item_element.js';
 import {AlbumsSubpage} from 'chrome://personalization/trusted/ambient/albums_subpage_element.js';
-import {AmbientActionName, SetAlbumsAction, SetAmbientModeEnabledAction, SetTemperatureUnitAction, SetTopicSourceAction} from 'chrome://personalization/trusted/ambient/ambient_actions.js';
+import {AmbientActionName, SetAlbumsAction, SetAmbientModeEnabledAction, SetAnimationThemeAction, SetTemperatureUnitAction, SetTopicSourceAction} from 'chrome://personalization/trusted/ambient/ambient_actions.js';
 import {AmbientObserver} from 'chrome://personalization/trusted/ambient/ambient_observer.js';
 import {AmbientSubpage} from 'chrome://personalization/trusted/ambient/ambient_subpage_element.js';
+import {AnimationThemeItem} from 'chrome://personalization/trusted/ambient/animation_theme_item_element.js';
 import {TopicSourceItem} from 'chrome://personalization/trusted/ambient/topic_source_item_element.js';
-import {AmbientModeAlbum, TemperatureUnit, TopicSource} from 'chrome://personalization/trusted/personalization_app.mojom-webui.js';
+import {AmbientModeAlbum, AnimationTheme, TemperatureUnit, TopicSource} from 'chrome://personalization/trusted/personalization_app.mojom-webui.js';
 import {Paths, PersonalizationRouter} from 'chrome://personalization/trusted/personalization_router_element.js';
 import {emptyState} from 'chrome://personalization/trusted/personalization_state.js';
+import {WallpaperGridItem} from 'chrome://personalization/trusted/wallpaper/wallpaper_grid_item_element.js';
 import {CrRadioButtonElement} from 'chrome://resources/cr_elements/cr_radio_button/cr_radio_button.m.js';
+import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
 import {assertDeepEquals, assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
 import {TestBrowserProxy} from 'chrome://webui-test/test_browser_proxy.js';
 import {waitAfterNextRender} from 'chrome://webui-test/test_util.js';
@@ -50,12 +52,14 @@
   });
 
   async function displayMainSettings(
-      topicSource: TopicSource|null,
-      temperatureUnit: TemperatureUnit|null): Promise<AmbientSubpage> {
+      topicSource: TopicSource|null, temperatureUnit: TemperatureUnit|null,
+      ambientModeEnabled: boolean,
+      animationTheme = AnimationTheme.kSlideshow): Promise<AmbientSubpage> {
     personalizationStore.data.ambient.albums = ambientProvider.albums;
+    personalizationStore.data.ambient.animationTheme = animationTheme;
     personalizationStore.data.ambient.topicSource = topicSource;
     personalizationStore.data.ambient.temperatureUnit = temperatureUnit;
-    personalizationStore.data.ambient.ambientModeEnabled = false;
+    personalizationStore.data.ambient.ambientModeEnabled = ambientModeEnabled;
     const ambientSubpage =
         initElement(AmbientSubpage, {path: Paths.Ambient, queryParams: {}});
     personalizationStore.notifyObservers();
@@ -64,7 +68,9 @@
   }
 
   test('displays content', async () => {
-    ambientSubpageElement = await displayMainSettings(null, null);
+    ambientSubpageElement = await displayMainSettings(
+        /*topicSource=*/ null, /*temperatureUnit=*/ null,
+        /*ambientModeEnabled=*/ false);
 
     const toggleRow =
         ambientSubpageElement.shadowRoot!.querySelector('toggle-row');
@@ -73,6 +79,10 @@
     assertTrue(!!toggleButton, 'cr-toggle element exists');
     assertFalse(toggleButton!.checked);
 
+    personalizationStore.data.ambient.ambientModeEnabled = true;
+    personalizationStore.notifyObservers();
+    await waitAfterNextRender(ambientSubpageElement);
+
     let spinner =
         ambientSubpageElement.shadowRoot!.querySelector('paper-spinner-lite');
     assertTrue(!!spinner, 'paper-spinner-lite element exists');
@@ -103,7 +113,8 @@
     personalizationStore.expectAction(
         AmbientActionName.SET_AMBIENT_MODE_ENABLED);
     ambientSubpageElement = await displayMainSettings(
-        TopicSource.kArtGallery, TemperatureUnit.kFahrenheit);
+        TopicSource.kArtGallery, TemperatureUnit.kFahrenheit,
+        /*ambientModeEnabled=*/ false);
 
     await ambientProvider.whenCalled('setAmbientObserver');
     ambientProvider.updateAmbientObserver();
@@ -133,10 +144,8 @@
 
   test('sets ambient mode enabled when toggle row clicked', async () => {
     ambientSubpageElement = await displayMainSettings(
-        TopicSource.kArtGallery, TemperatureUnit.kFahrenheit);
-    personalizationStore.data.ambient.ambientModeEnabled = true;
-    personalizationStore.notifyObservers();
-    await waitAfterNextRender(ambientSubpageElement);
+        TopicSource.kArtGallery, TemperatureUnit.kFahrenheit,
+        /*ambientModeEnabled=*/ true);
 
     const toggleRow =
         ambientSubpageElement.shadowRoot!.querySelector('toggle-row');
@@ -169,10 +178,8 @@
 
   test('sets ambient mode enabled when toggle button clicked', async () => {
     ambientSubpageElement = await displayMainSettings(
-        TopicSource.kArtGallery, TemperatureUnit.kFahrenheit);
-    personalizationStore.data.ambient.ambientModeEnabled = true;
-    personalizationStore.notifyObservers();
-    await waitAfterNextRender(ambientSubpageElement);
+        TopicSource.kArtGallery, TemperatureUnit.kFahrenheit,
+        /*ambientModeEnabled=*/ true);
 
     const toggleRow =
         ambientSubpageElement.shadowRoot!.querySelector('toggle-row');
@@ -198,6 +205,60 @@
     assertTrue(action.enabled);
   });
 
+  test('has correct animation theme on load', async () => {
+    personalizationStore.expectAction(AmbientActionName.SET_ANIMATION_THEME);
+    ambientSubpageElement = initElement(AmbientSubpage);
+
+    await ambientProvider.whenCalled('setAmbientObserver');
+    ambientProvider.updateAmbientObserver();
+
+    const action =
+        await personalizationStore.waitForAction(
+            AmbientActionName.SET_ANIMATION_THEME) as SetAnimationThemeAction;
+    assertEquals(AnimationTheme.kSlideshow, action.animationTheme);
+  });
+
+  test(
+      'sets animation theme when animation theme item is clicked', async () => {
+        loadTimeData.overrideValues({isAmbientModeAnimationEnabled: true});
+        ambientSubpageElement = await displayMainSettings(
+            TopicSource.kArtGallery, TemperatureUnit.kFahrenheit,
+            /*ambientModeEnabled=*/ true);
+
+        const animationThemeList =
+            ambientSubpageElement.shadowRoot!.querySelector(
+                'animation-theme-list');
+        assertTrue(!!animationThemeList);
+        const animationThemeItems =
+            animationThemeList!.shadowRoot!.querySelectorAll(
+                'animation-theme-item:not([hidden])');
+        assertEquals(3, animationThemeItems!.length);
+        const slideshow = animationThemeItems[0] as AnimationThemeItem;
+        const feelTheBreeze = animationThemeItems[1] as AnimationThemeItem;
+        assertEquals(AnimationTheme.kSlideshow, slideshow.animationTheme);
+        assertEquals(
+            AnimationTheme.kFeelTheBreeze, feelTheBreeze.animationTheme);
+
+        assertFalse(feelTheBreeze.checked);
+        assertTrue(slideshow.checked);
+
+        personalizationStore.expectAction(
+            AmbientActionName.SET_ANIMATION_THEME);
+        feelTheBreeze!.click();
+        let action = await personalizationStore.waitForAction(
+                         AmbientActionName.SET_ANIMATION_THEME) as
+            SetAnimationThemeAction;
+        assertEquals(AnimationTheme.kFeelTheBreeze, action.animationTheme);
+
+        personalizationStore.expectAction(
+            AmbientActionName.SET_ANIMATION_THEME);
+        slideshow!.click();
+        action = await personalizationStore.waitForAction(
+                     AmbientActionName.SET_ANIMATION_THEME) as
+            SetAnimationThemeAction;
+        assertEquals(AnimationTheme.kSlideshow, action.animationTheme);
+      });
+
   test('has correct topic sources on load', async () => {
     personalizationStore.expectAction(AmbientActionName.SET_TOPIC_SOURCE);
     ambientSubpageElement = initElement(AmbientSubpage);
@@ -213,7 +274,8 @@
 
   test('sets topic source when topic source item clicked', async () => {
     ambientSubpageElement = await displayMainSettings(
-        TopicSource.kArtGallery, TemperatureUnit.kFahrenheit);
+        TopicSource.kArtGallery, TemperatureUnit.kFahrenheit,
+        /*ambientModeEnabled=*/ true);
 
     const topicSourceList =
         ambientSubpageElement.shadowRoot!.querySelector('topic-source-list');
@@ -258,7 +320,8 @@
 
   test('sets temperature unit when temperature unit item clicked', async () => {
     ambientSubpageElement = await displayMainSettings(
-        TopicSource.kArtGallery, TemperatureUnit.kFahrenheit);
+        TopicSource.kArtGallery, TemperatureUnit.kFahrenheit,
+        /*ambientModeEnabled=*/ true);
 
     const weatherUnit =
         ambientSubpageElement.shadowRoot!.querySelector('ambient-weather-unit');
@@ -391,14 +454,9 @@
     const albumList = albumsSubpage.shadowRoot!.querySelector('album-list');
     assertTrue(!!albumList);
 
-    const albums = albumList.shadowRoot!.querySelectorAll<AlbumItem>(
-        'album-item:not([hidden])');
+    const albums = albumList.shadowRoot!.querySelectorAll<WallpaperGridItem>(
+        'wallpaper-grid-item:not([hidden])');
     assertEquals(1, albums.length);
-    assertTrue(!!albums[0]);
-    assertEquals('3', albums[0].album!.id);
-    assertFalse(albums[0].album!.checked);
-    assertEquals(1, albums[0].album!.numberOfPhotos);
-    assertEquals(TopicSource.kGooglePhotos, albums[0].album!.topicSource);
   });
 
   test('has correct albums on Art albums subpage', async () => {
@@ -420,22 +478,12 @@
     const albumList = albumsSubpage.shadowRoot!.querySelector('album-list');
     assertTrue(!!albumList);
 
-    const albums = albumList.shadowRoot!.querySelectorAll<AlbumItem>(
-        'album-item:not([hidden])');
+    const albums = albumList.shadowRoot!.querySelectorAll<WallpaperGridItem>(
+        'wallpaper-grid-item:not([hidden])');
     assertEquals(3, albums.length);
     assertTrue(!!albums[0]);
     assertTrue(!!albums[1]);
     assertTrue(!!albums[2]);
-
-    assertEquals('0', albums[0].album!.id);
-    assertFalse(albums[0].album!.checked);
-    assertEquals(TopicSource.kArtGallery, albums[0].album!.topicSource);
-    assertEquals('1', albums[1].album!.id);
-    assertFalse(albums[1].album!.checked);
-    assertEquals(TopicSource.kArtGallery, albums[1].album!.topicSource);
-    assertEquals('2', albums[2].album!.id);
-    assertTrue(albums[2].album!.checked);
-    assertEquals(TopicSource.kArtGallery, albums[2].album!.topicSource);
   });
 
   test('toggle album selection by clicking', async () => {
@@ -451,26 +499,26 @@
 
     const action = await personalizationStore.waitForAction(
                        AmbientActionName.SET_ALBUMS) as SetAlbumsAction;
-    assertEquals(4, action.albums.length);
+    assertEquals(4, action.albums.length, 'action.albums.length');
 
     const albumsSubpage =
         ambientSubpageElement.shadowRoot!.querySelector('albums-subpage');
-    assertTrue(!!albumsSubpage);
-    assertFalse(albumsSubpage.hidden);
+    assertTrue(!!albumsSubpage, '!!albumsSubpage');
+    assertFalse(albumsSubpage.hidden, 'albumsSubpage.hidden');
     await waitAfterNextRender(albumsSubpage);
 
     const albumList = albumsSubpage.shadowRoot!.querySelector('album-list');
-    assertTrue(!!albumList);
+    assertTrue(!!albumList, '!!albumList');
 
-    const albums = albumList.shadowRoot!.querySelectorAll<AlbumItem>(
-        'album-item:not([hidden])');
+    const albums = albumList.shadowRoot!.querySelectorAll<WallpaperGridItem>(
+        'wallpaper-grid-item:not([hidden])');
     assertEquals(3, albums.length);
     assertTrue(!!albums[0]);
     assertTrue(!!albums[1]);
     assertTrue(!!albums[2]);
-    assertFalse(albums[0].album!.checked);
-    assertFalse(albums[1].album!.checked);
-    assertTrue(albums[2].album!.checked);
+    assertFalse(albums[0].selected);
+    assertFalse(albums[1].selected);
+    assertTrue(albums[2].selected);
     let selectedAlbums = getSelectedAlbums(
         personalizationStore.data.ambient.albums,
         personalizationStore.data.ambient.topicSource);
@@ -478,8 +526,8 @@
     assertEquals('2', selectedAlbums[0]!.title);
 
     personalizationStore.expectAction(AmbientActionName.SET_ALBUM_SELECTED);
-    albums[1].$.image.click();
-    assertTrue(albums[1].album!.checked);
+    albums[1].click();
+    assertTrue(albums[1].selected);
     await personalizationStore.waitForAction(
         AmbientActionName.SET_ALBUM_SELECTED);
     selectedAlbums = getSelectedAlbums(
@@ -514,20 +562,20 @@
     const albumList = albumsSubpage.shadowRoot!.querySelector('album-list');
     assertTrue(!!albumList);
 
-    const albums = albumList.shadowRoot!.querySelectorAll<AlbumItem>(
-        'album-item:not([hidden])');
+    const albums = albumList.shadowRoot!.querySelectorAll<WallpaperGridItem>(
+        'wallpaper-grid-item:not([hidden])');
     assertEquals(3, albums.length);
     assertTrue(!!albums[0]);
     assertTrue(!!albums[1]);
     assertTrue(!!albums[2]);
-    assertFalse(albums[0].album!.checked);
-    assertFalse(albums[1].album!.checked);
-    assertTrue(albums[2].album!.checked);
+    assertFalse(albums[0].selected);
+    assertFalse(albums[1].selected);
+    assertTrue(albums[2].selected);
 
     // Click the last art album item image will not toggle the check and will
     // show a dialog.
-    albums[2].$.image.click();
-    assertTrue(albums[2].album!.checked);
+    albums[2].click();
+    assertTrue(albums[2].selected);
 
     const artAlbumDialog =
         albumsSubpage.shadowRoot!.querySelector('art-album-dialog');
@@ -541,7 +589,8 @@
     personalizationStore.expectAction(AmbientActionName.SET_ALBUMS);
 
     ambientSubpageElement = await displayMainSettings(
-        TopicSource.kArtGallery, TemperatureUnit.kFahrenheit);
+        TopicSource.kArtGallery, TemperatureUnit.kFahrenheit,
+        /*ambientModeEnabled=*/ true);
 
     await ambientProvider.whenCalled('setAmbientObserver');
     ambientProvider.updateAmbientObserver();
@@ -564,4 +613,30 @@
     assertTrue(!!previewAlbumTitle);
     assertEquals('2', previewAlbumTitle.innerText.replace(/\s/g, ''));
   });
+
+  test('displays zero state when ambient mode is disabled', async () => {
+    ambientSubpageElement = await displayMainSettings(
+        TopicSource.kArtGallery, TemperatureUnit.kFahrenheit,
+        /*ambientModeEnabled=*/ false);
+
+    // Preview image should be absent.
+    assertEquals(
+        ambientSubpageElement.shadowRoot!.querySelector('ambient-preview'),
+        null);
+
+    // Topic source list should be absent.
+    assertEquals(
+        ambientSubpageElement.shadowRoot!.querySelector('topic-source-list'),
+        null);
+
+    // Weather unit should be absent.
+    assertEquals(
+        ambientSubpageElement.shadowRoot!.querySelector('ambient-weather-unit'),
+        null);
+
+    // Zero state should be present.
+    const zeroState =
+        ambientSubpageElement.shadowRoot!.querySelector('ambient-zero-state');
+    assertTrue(!!zeroState);
+  });
 }
diff --git a/chrome/test/data/webui/chromeos/personalization_app/test_ambient_interface_provider.ts b/chrome/test/data/webui/chromeos/personalization_app/test_ambient_interface_provider.ts
index bba846f..e597587 100644
--- a/chrome/test/data/webui/chromeos/personalization_app/test_ambient_interface_provider.ts
+++ b/chrome/test/data/webui/chromeos/personalization_app/test_ambient_interface_provider.ts
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import {AmbientModeAlbum, AmbientObserverInterface, AmbientObserverRemote, AmbientProviderInterface, TemperatureUnit, TopicSource} from 'chrome://personalization/trusted/personalization_app.mojom-webui.js';
+import {AmbientModeAlbum, AmbientObserverInterface, AmbientObserverRemote, AmbientProviderInterface, AnimationTheme, TemperatureUnit, TopicSource} from 'chrome://personalization/trusted/personalization_app.mojom-webui.js';
 import {TestBrowserProxy} from 'chrome://webui-test/test_browser_proxy.js';
 
 export class TestAmbientProvider extends TestBrowserProxy implements
@@ -51,6 +51,7 @@
       'isAmbientModeEnabled',
       'setAmbientObserver',
       'setAmbientModeEnabled',
+      'setAnimationTheme',
       'setTopicSource',
       'setTemperatureUnit',
       'setAlbumSelected',
@@ -75,6 +76,8 @@
         /*ambientModeEnabled=*/ true);
 
     this.ambientObserverRemote!.onAlbumsChanged(this.albums);
+    this.ambientObserverRemote!.onAnimationThemeChanged(
+        AnimationTheme.kSlideshow);
     this.ambientObserverRemote!.onTopicSourceChanged(TopicSource.kArtGallery);
     this.ambientObserverRemote!.onTemperatureUnitChanged(
         TemperatureUnit.kFahrenheit);
@@ -84,6 +87,10 @@
     this.methodCalled('setAmbientModeEnabled', ambientModeEnabled);
   }
 
+  setAnimationTheme(animationTheme: AnimationTheme) {
+    this.methodCalled('setAnimationTheme', animationTheme);
+  }
+
   setTopicSource(topic_source: TopicSource) {
     this.methodCalled('setTopicSource', topic_source);
   }
diff --git a/chrome/test/data/webui/settings/password_edit_dialog_test.ts b/chrome/test/data/webui/settings/password_edit_dialog_test.ts
index 033bdc3..20353fa2 100644
--- a/chrome/test/data/webui/settings/password_edit_dialog_test.ts
+++ b/chrome/test/data/webui/settings/password_edit_dialog_test.ts
@@ -705,6 +705,16 @@
         !!passwordDialog.shadowRoot!.querySelector<CrInputElement>('#note'));
   });
 
+  test('addDialogDoesntHaveNotes', async function() {
+    loadTimeData.overrideValues({enablePasswordNotes: true});
+    const existingEntry = createMultiStorePasswordEntry(
+        {url: 'website.com', username: 'username', accountId: 0});
+    const addDialog =
+        elementFactory.createPasswordEditDialog(null, [existingEntry]);
+    assertAddDialogParts(addDialog);
+    assertFalse(!!addDialog.shadowRoot!.querySelector('#note'));
+  });
+
   // <if expr="not chromeos_ash and not chromeos_lacros">
   // On ChromeOS/Lacros the behavior is different (on failure we request token
   // and retry).
diff --git a/chromecast/media/api/BUILD.gn b/chromecast/media/api/BUILD.gn
index c3f7cf1..3c306cf 100644
--- a/chromecast/media/api/BUILD.gn
+++ b/chromecast/media/api/BUILD.gn
@@ -9,6 +9,7 @@
     "cast_audio_decoder.h",
     "cast_audio_demuxer.h",
     "cast_audio_resampler.h",
+    "cast_channel_mixer.h",
     "cast_sounds_manager.h",
     "cma_backend.h",
     "cma_backend_factory.h",
diff --git a/chromecast/media/api/cast_channel_mixer.h b/chromecast/media/api/cast_channel_mixer.h
new file mode 100644
index 0000000..22dd0b6
--- /dev/null
+++ b/chromecast/media/api/cast_channel_mixer.h
@@ -0,0 +1,33 @@
+// 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 CHROMECAST_MEDIA_API_CAST_CHANNEL_MIXER_H_
+#define CHROMECAST_MEDIA_API_CAST_CHANNEL_MIXER_H_
+
+#include <memory>
+
+#include "chromecast/public/media/decoder_config.h"
+
+namespace chromecast {
+namespace media {
+
+// Channel mixer to convert audio between different channel layouts.
+class CastChannelMixer {
+ public:
+  static std::unique_ptr<CastChannelMixer> Create(ChannelLayout input,
+                                                  ChannelLayout output);
+
+  virtual ~CastChannelMixer() = default;
+
+  // Change the channel counts for |input|. |input| should be prepared in planar
+  // float format. The returned value points to an array that is owned by this
+  // class, also in planar float format. The returned data is only valid until
+  // the next call.
+  virtual const float* Transform(const float* input, int num_frames) = 0;
+};
+
+}  // namespace media
+}  // namespace chromecast
+
+#endif  // CHROMECAST_MEDIA_API_CAST_CHANNEL_MIXER_H_
diff --git a/chromecast/media/audio/BUILD.gn b/chromecast/media/audio/BUILD.gn
index 48440ba8..835a030 100644
--- a/chromecast/media/audio/BUILD.gn
+++ b/chromecast/media/audio/BUILD.gn
@@ -100,6 +100,16 @@
   ]
 }
 
+cast_source_set("channel_mixer") {
+  sources = [ "cast_channel_mixer_impl.cc" ]
+  deps = [
+    "//base",
+    "//chromecast/media/api",
+    "//chromecast/media/cma/base",
+    "//media",
+  ]
+}
+
 cast_source_set("audio_renderer") {
   sources = [
     "cast_audio_renderer.cc",
diff --git a/chromecast/media/audio/cast_channel_mixer_impl.cc b/chromecast/media/audio/cast_channel_mixer_impl.cc
new file mode 100644
index 0000000..dcfc376
--- /dev/null
+++ b/chromecast/media/audio/cast_channel_mixer_impl.cc
@@ -0,0 +1,79 @@
+// 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 "chromecast/media/api/cast_channel_mixer.h"
+
+#include "chromecast/media/cma/base/decoder_config_adapter.h"
+#include "media/base/audio_bus.h"
+#include "media/base/channel_layout.h"
+#include "media/base/channel_mixer.h"
+
+namespace chromecast {
+namespace media {
+namespace {
+
+void SetChannelData(::media::AudioBus* bus, float* data, int num_frames) {
+  bus->set_frames(num_frames);
+  for (int channel = 0; channel < bus->channels(); channel++) {
+    bus->SetChannelData(channel, data + num_frames * channel);
+  }
+}
+
+// A wrapper of ::media::ChannelMixer.
+class CastChannelMixerImpl : public CastChannelMixer {
+ public:
+  CastChannelMixerImpl(ChannelLayout input, ChannelLayout output);
+  ~CastChannelMixerImpl() override;
+
+  const float* Transform(const float* input, int num_frames) override;
+
+ private:
+  std::unique_ptr<::media::ChannelMixer> channel_mixer_;
+
+  std::unique_ptr<::media::AudioBus> input_bus_;
+  std::unique_ptr<::media::AudioBus> output_bus_;
+
+  std::vector<float> output_buffer_;
+};
+
+CastChannelMixerImpl::CastChannelMixerImpl(ChannelLayout input,
+                                           ChannelLayout output) {
+  ::media::ChannelLayout input_layout =
+      DecoderConfigAdapter::ToMediaChannelLayout(input);
+  ::media::ChannelLayout output_layout =
+      DecoderConfigAdapter::ToMediaChannelLayout(output);
+  channel_mixer_ =
+      std::make_unique<::media::ChannelMixer>(input_layout, output_layout);
+  input_bus_ = ::media::AudioBus::CreateWrapper(
+      ::media::ChannelLayoutToChannelCount(input_layout));
+  output_bus_ = ::media::AudioBus::CreateWrapper(
+      ::media::ChannelLayoutToChannelCount(output_layout));
+}
+
+CastChannelMixerImpl::~CastChannelMixerImpl() = default;
+
+const float* CastChannelMixerImpl::Transform(const float* input,
+                                             int num_frames) {
+  SetChannelData(input_bus_.get(), const_cast<float*>(input), num_frames);
+
+  if (output_buffer_.size() <
+      static_cast<size_t>(num_frames * output_bus_->channels())) {
+    output_buffer_.resize(num_frames * output_bus_->channels());
+  }
+  SetChannelData(output_bus_.get(), output_buffer_.data(), num_frames);
+
+  channel_mixer_->Transform(input_bus_.get(), output_bus_.get());
+  return output_buffer_.data();
+}
+
+}  // namespace
+
+std::unique_ptr<CastChannelMixer> CastChannelMixer::Create(
+    ChannelLayout input,
+    ChannelLayout output) {
+  return std::make_unique<CastChannelMixerImpl>(input, output);
+}
+
+}  // namespace media
+}  // namespace chromecast
\ No newline at end of file
diff --git a/chromecast/media/cma/decoder/cast_audio_decoder.cc b/chromecast/media/cma/decoder/cast_audio_decoder.cc
index ed9e7b7..8ff9e8e 100644
--- a/chromecast/media/cma/decoder/cast_audio_decoder.cc
+++ b/chromecast/media/cma/decoder/cast_audio_decoder.cc
@@ -228,10 +228,18 @@
       output_config_.samples_per_second = decoded->sample_rate();
     }
 
-    if (decoded->channel_count() != output_config_.channel_number) {
+    ChannelLayout decoded_channel_layout =
+        DecoderConfigAdapter::ToChannelLayout(decoded->channel_layout());
+    if (decoded->channel_count() != output_config_.channel_number ||
+        decoded_channel_layout != output_config_.channel_layout) {
       LOG(WARNING) << "channel_count changed to " << decoded->channel_count()
-                   << " from " << output_config_.channel_number;
+                   << " from " << output_config_.channel_number
+                   << ", channel_layout changed to "
+                   << static_cast<int>(decoded_channel_layout) << " from "
+                   << static_cast<int>(output_config_.channel_layout);
       output_config_.channel_number = decoded->channel_count();
+      output_config_.channel_layout =
+          DecoderConfigAdapter::ToChannelLayout(decoded->channel_layout());
       decoded_bus_.reset();
     }
 
diff --git a/chromeos/CHROMEOS_LKGM b/chromeos/CHROMEOS_LKGM
index 7451bbd..0178278 100644
--- a/chromeos/CHROMEOS_LKGM
+++ b/chromeos/CHROMEOS_LKGM
@@ -1 +1 @@
-14542.0.0
\ No newline at end of file
+14568.0.0
\ No newline at end of file
diff --git a/chromeos/chromeos_strings.grd b/chromeos/chromeos_strings.grd
index b5440e8..36457b9f 100644
--- a/chromeos/chromeos_strings.grd
+++ b/chromeos/chromeos_strings.grd
@@ -2231,6 +2231,21 @@
       <message name="IDS_PERONSONALIZATION_APP_AMBIENT_MODE_ART_ALBUM_DIALOG_CLOSE_BUTTON_LABEL" desc="Label on the close button in the art album dialog.">
         Got it
       </message>
+      <message name="IDS_PERSONALIZATION_APP_AMBIENT_MODE_ANIMATION_TITLE" desc="Label for the ambient mode animation section.">
+        Screensaver animation
+      </message>
+      <message name="IDS_PERSONALIZATION_APP_AMBIENT_MODE_ANIMATION_SLIDESHOW_LABEL" desc="Label for the ambient mode slideshow animation.">
+        Slide show
+      </message>
+      <message name="IDS_PERSONALIZATION_APP_AMBIENT_MODE_ANIMATION_FEEL_THE_BREEZE_LABEL" desc="Label for the ambient mode feel the breeze animation.">
+        Feel the breeze
+      </message>
+      <message name="IDS_PERSONALIZATION_APP_AMBIENT_MODE_ANIMATION_FLOAT_ON_BY_LABEL" desc="Label for the ambient mode float on by animation.">
+        Float on by
+      </message>
+      <message name="IDS_PERSONALIZATION_APP_AMBIENT_MODE_ZERO_STATE_MESSAGE" desc="Message shown in the Ambient subpage in the personalization hub when ambient mode is disabled.">
+        Turn on the toggle to select the screensaver options
+      </message>
 
       <!-- Traffic Counters UI -->
       <message name="IDS_TRAFFIC_COUNTERS_UNKNOWN" desc="Traffic counters related to an unknown source">
diff --git a/chromeos/chromeos_strings_grd/IDS_PERSONALIZATION_APP_AMBIENT_MODE_ANIMATION_FEEL_THE_BREEZE_LABEL.png.sha1 b/chromeos/chromeos_strings_grd/IDS_PERSONALIZATION_APP_AMBIENT_MODE_ANIMATION_FEEL_THE_BREEZE_LABEL.png.sha1
new file mode 100644
index 0000000..f104090
--- /dev/null
+++ b/chromeos/chromeos_strings_grd/IDS_PERSONALIZATION_APP_AMBIENT_MODE_ANIMATION_FEEL_THE_BREEZE_LABEL.png.sha1
@@ -0,0 +1 @@
+c56cfa2cd733197191119c3934f2ae170eaa98cc
\ No newline at end of file
diff --git a/chromeos/chromeos_strings_grd/IDS_PERSONALIZATION_APP_AMBIENT_MODE_ANIMATION_FLOAT_ON_BY_LABEL.png.sha1 b/chromeos/chromeos_strings_grd/IDS_PERSONALIZATION_APP_AMBIENT_MODE_ANIMATION_FLOAT_ON_BY_LABEL.png.sha1
new file mode 100644
index 0000000..215edc6
--- /dev/null
+++ b/chromeos/chromeos_strings_grd/IDS_PERSONALIZATION_APP_AMBIENT_MODE_ANIMATION_FLOAT_ON_BY_LABEL.png.sha1
@@ -0,0 +1 @@
+34834e8f8590914b063ad8f9d87b07677a24b0b2
\ No newline at end of file
diff --git a/chromeos/chromeos_strings_grd/IDS_PERSONALIZATION_APP_AMBIENT_MODE_ANIMATION_SLIDESHOW_LABEL.png.sha1 b/chromeos/chromeos_strings_grd/IDS_PERSONALIZATION_APP_AMBIENT_MODE_ANIMATION_SLIDESHOW_LABEL.png.sha1
new file mode 100644
index 0000000..f104090
--- /dev/null
+++ b/chromeos/chromeos_strings_grd/IDS_PERSONALIZATION_APP_AMBIENT_MODE_ANIMATION_SLIDESHOW_LABEL.png.sha1
@@ -0,0 +1 @@
+c56cfa2cd733197191119c3934f2ae170eaa98cc
\ No newline at end of file
diff --git a/chromeos/chromeos_strings_grd/IDS_PERSONALIZATION_APP_AMBIENT_MODE_ANIMATION_TITLE.png.sha1 b/chromeos/chromeos_strings_grd/IDS_PERSONALIZATION_APP_AMBIENT_MODE_ANIMATION_TITLE.png.sha1
new file mode 100644
index 0000000..f104090
--- /dev/null
+++ b/chromeos/chromeos_strings_grd/IDS_PERSONALIZATION_APP_AMBIENT_MODE_ANIMATION_TITLE.png.sha1
@@ -0,0 +1 @@
+c56cfa2cd733197191119c3934f2ae170eaa98cc
\ No newline at end of file
diff --git a/chromeos/chromeos_strings_grd/IDS_PERSONALIZATION_APP_AMBIENT_MODE_ZERO_STATE_MESSAGE.png.sha1 b/chromeos/chromeos_strings_grd/IDS_PERSONALIZATION_APP_AMBIENT_MODE_ZERO_STATE_MESSAGE.png.sha1
new file mode 100644
index 0000000..56b6676
--- /dev/null
+++ b/chromeos/chromeos_strings_grd/IDS_PERSONALIZATION_APP_AMBIENT_MODE_ZERO_STATE_MESSAGE.png.sha1
@@ -0,0 +1 @@
+9a01c0a9890fda62eef059f172c78336b50be7cc
\ No newline at end of file
diff --git a/components/exo/shell_surface_base.cc b/components/exo/shell_surface_base.cc
index b8700a2..5565bcb 100644
--- a/components/exo/shell_surface_base.cc
+++ b/components/exo/shell_surface_base.cc
@@ -656,9 +656,6 @@
     DLOG(WARNING) << "Surface geometry must be non-empty";
     return;
   }
-  if (!widget_) {
-    initial_size_ = gfx::Size(geometry.width(), geometry.height());
-  }
   pending_geometry_ = geometry;
 }
 
@@ -666,7 +663,7 @@
   TRACE_EVENT1("exo", "ShellSurfaceBase::SetWindowBounds", "bounds",
                bounds.ToString());
   if (!widget_) {
-    // TODO(crbug.com/1261321): Handle initial bounds.
+    initial_bounds_.emplace(bounds);
     return;
   }
   // TODO(crbug.com/1261321): This will not work if the full server side
@@ -1202,6 +1199,10 @@
 bool ShellSurfaceBase::IsDragged() const {
   if (in_extended_drag_)
     return true;
+
+  if (!widget_)
+    return false;
+
   ash::WindowState* window_state =
       ash::WindowState::Get(widget_->GetNativeWindow());
   return window_state->is_dragged();
@@ -1277,7 +1278,10 @@
   } else {
     params.parent = ash::Shell::GetContainer(root_window, container_);
   }
-  params.bounds = gfx::Rect(origin_, gfx::Size());
+  if (initial_bounds_)
+    params.bounds = *initial_bounds_;
+  else
+    params.bounds = gfx::Rect(origin_, gfx::Size());
 
   WMHelper::AppPropertyResolver::Params property_resolver_params;
   if (application_id_)
@@ -1693,6 +1697,10 @@
   bool should_show =
       !host_window()->bounds().IsEmpty() && !widget_->IsMinimized();
 
+  // Do not center if the initial bounds is set.
+  if (initial_bounds_)
+    needs_layout_on_show_ = false;
+
   // Show widget if needed.
   if (pending_show_widget_ && should_show) {
     DCHECK(!widget_->IsClosed());
diff --git a/components/exo/shell_surface_base.h b/components/exo/shell_surface_base.h
index a8c2bdde..6532bb95 100644
--- a/components/exo/shell_surface_base.h
+++ b/components/exo/shell_surface_base.h
@@ -370,7 +370,7 @@
   int container_;
   gfx::Rect geometry_;
   gfx::Rect pending_geometry_;
-  gfx::Size initial_size_;
+  absl::optional<gfx::Rect> initial_bounds_;
 
   int64_t display_id_ = display::kInvalidDisplayId;
   int64_t pending_display_id_ = display::kInvalidDisplayId;
diff --git a/components/exo/shell_surface_unittest.cc b/components/exo/shell_surface_unittest.cc
index f3a0b20..e2adbb3 100644
--- a/components/exo/shell_surface_unittest.cc
+++ b/components/exo/shell_surface_unittest.cc
@@ -2190,4 +2190,35 @@
             display::Screen::GetScreen()->GetPrimaryDisplay().work_area());
 }
 
+TEST_F(ShellSurfaceTest, InitialBounds) {
+  {
+    gfx::Size size(20, 30);
+    auto shell_surface =
+        test::ShellSurfaceBuilder(size).SetNoCommit().BuildShellSurface();
+
+    EXPECT_FALSE(shell_surface->GetWidget());
+    gfx::Rect bounds(gfx::Point(35, 135), size);
+    shell_surface->SetWindowBounds(bounds);
+    shell_surface->root_surface()->Commit();
+
+    ASSERT_TRUE(shell_surface->GetWidget());
+    EXPECT_EQ(bounds, shell_surface->GetWidget()->GetWindowBoundsInScreen());
+  }
+  {
+    // Requesting larger than display work area bounds should not be allowed.
+    gfx::Size size(2000, 3000);
+    auto shell_surface =
+        test::ShellSurfaceBuilder(size).SetNoCommit().BuildShellSurface();
+
+    EXPECT_FALSE(shell_surface->GetWidget());
+    gfx::Rect bounds(gfx::Point(35, 135), size);
+    shell_surface->SetWindowBounds(bounds);
+    shell_surface->root_surface()->Commit();
+
+    ASSERT_TRUE(shell_surface->GetWidget());
+    EXPECT_EQ(display::Screen::GetScreen()->GetPrimaryDisplay().work_area(),
+              shell_surface->GetWidget()->GetWindowBoundsInScreen());
+  }
+}
+
 }  // namespace exo
diff --git a/components/optimization_guide/content/browser/page_content_annotations_service.cc b/components/optimization_guide/content/browser/page_content_annotations_service.cc
index ab47b74..818f5f0 100644
--- a/components/optimization_guide/content/browser/page_content_annotations_service.cc
+++ b/components/optimization_guide/content/browser/page_content_annotations_service.cc
@@ -476,10 +476,17 @@
       "OptimizationGuide.PageContentAnnotationsService.ValidationRun",
       dummy_inputs.size());
 
-  BatchAnnotate(base::DoNothing(), dummy_inputs,
-                features::BatchAnnotationsValidationUsePageTopics()
-                    ? AnnotationType::kPageTopics
-                    : AnnotationType::kContentVisibility);
+  if (!features::BatchAnnotationsValidationUsePageTopics()) {
+    BatchAnnotate(base::DoNothing(), dummy_inputs,
+                  AnnotationType::kContentVisibility);
+    return;
+  }
+
+  std::vector<GURL> urls;
+  for (const std::string& domain : dummy_inputs) {
+    urls.emplace_back(GURL("https://" + domain));
+  }
+  BatchAnnotatePageTopics(base::DoNothing(), urls);
 }
 
 // static
diff --git a/components/optimization_guide/core/BUILD.gn b/components/optimization_guide/core/BUILD.gn
index 1e9b53d..7d044f5a 100644
--- a/components/optimization_guide/core/BUILD.gn
+++ b/components/optimization_guide/core/BUILD.gn
@@ -49,6 +49,7 @@
       "base_model_executor_helpers.h",
       "bert_model_executor.cc",
       "bert_model_executor.h",
+      "model_execution_timeout_watchdog.h",
       "tflite_model_executor.h",
     ]
   }
@@ -68,6 +69,7 @@
     ]
   }
   deps = [
+    ":features",
     "//base",
     "//components/optimization_guide/proto:optimization_guide_proto",
     "//net",
@@ -88,6 +90,30 @@
   }
 }
 
+static_library("features") {
+  sources = [
+    "insertion_ordered_set.h",
+    "optimization_guide_constants.cc",
+    "optimization_guide_constants.h",
+    "optimization_guide_features.cc",
+    "optimization_guide_features.h",
+    "optimization_guide_prefs.cc",
+    "optimization_guide_prefs.h",
+    "optimization_guide_switches.cc",
+    "optimization_guide_switches.h",
+  ]
+  deps = [
+    "//base",
+    "//components/optimization_guide:machine_learning_tflite_buildflags",
+    "//components/optimization_guide/proto:optimization_guide_proto",
+    "//components/prefs",
+    "//components/variations",
+    "//google_apis",
+    "//net",
+    "//ui/base",
+  ]
+}
+
 static_library("core") {
   sources = [
     "command_line_top_host_provider.cc",
@@ -105,7 +131,6 @@
     "hints_manager.h",
     "hints_processing_util.cc",
     "hints_processing_util.h",
-    "insertion_ordered_set.h",
     "local_page_entities_metadata_provider.cc",
     "local_page_entities_metadata_provider.h",
     "memory_hint.cc",
@@ -115,12 +140,8 @@
     "noisy_metrics_recorder.h",
     "optimization_filter.cc",
     "optimization_filter.h",
-    "optimization_guide_constants.cc",
-    "optimization_guide_constants.h",
     "optimization_guide_decision.h",
     "optimization_guide_enums.h",
-    "optimization_guide_features.cc",
-    "optimization_guide_features.h",
     "optimization_guide_logger.cc",
     "optimization_guide_logger.h",
     "optimization_guide_model_provider.h",
@@ -128,12 +149,8 @@
     "optimization_guide_navigation_data.h",
     "optimization_guide_permissions_util.cc",
     "optimization_guide_permissions_util.h",
-    "optimization_guide_prefs.cc",
-    "optimization_guide_prefs.h",
     "optimization_guide_store.cc",
     "optimization_guide_store.h",
-    "optimization_guide_switches.cc",
-    "optimization_guide_switches.h",
     "optimization_guide_util.cc",
     "optimization_guide_util.h",
     "optimization_hints_component_observer.h",
@@ -183,6 +200,7 @@
 
   public_deps = [
     ":entities",
+    ":features",
     ":model_executor",
     "//components/leveldb_proto",
     "//components/optimization_guide:machine_learning_tflite_buildflags",
@@ -208,12 +226,9 @@
     "//components/unified_consent",
     "//components/variations",
     "//components/variations/net",
-    "//google_apis",
-    "//net:net",
     "//services/metrics/public/cpp:metrics_cpp",
     "//services/metrics/public/cpp:ukm_builders",
     "//services/network/public/cpp",
-    "//ui/base:base",
     "//url:url",
   ]
   if (build_with_tflite_lib && build_with_internal_optimization_guide) {
diff --git a/components/optimization_guide/core/model_execution_timeout_watchdog.h b/components/optimization_guide/core/model_execution_timeout_watchdog.h
new file mode 100644
index 0000000..3238f53
--- /dev/null
+++ b/components/optimization_guide/core/model_execution_timeout_watchdog.h
@@ -0,0 +1,120 @@
+// 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 COMPONENTS_OPTIMIZATION_GUIDE_CORE_MODEL_EXECUTION_TIMEOUT_WATCHDOG_H_
+#define COMPONENTS_OPTIMIZATION_GUIDE_CORE_MODEL_EXECUTION_TIMEOUT_WATCHDOG_H_
+
+#include "base/metrics/histogram_functions.h"
+#include "base/synchronization/lock.h"
+#include "base/threading/thread_restrictions.h"
+#include "base/threading/watchdog.h"
+#include "base/time/time.h"
+#include "components/optimization_guide/core/model_util.h"
+#include "components/optimization_guide/proto/models.pb.h"
+#include "third_party/tflite_support/src/tensorflow_lite_support/cc/task/core/base_task_api.h"
+
+namespace optimization_guide {
+
+namespace {
+void RecordDidTimeoutHistogram(proto::OptimizationTarget optimization_target,
+                               bool did_timeout) {
+  base::UmaHistogramBoolean(
+      "OptimizationGuide.ModelExecutor.DidTimeout." +
+          GetStringNameForOptimizationTarget(optimization_target),
+      did_timeout);
+}
+}  // namespace
+
+// This is a helper class to |TFLiteModelExecutor| that watches for a model
+// execution that runs for too long. This is done using a |base::Watchdog| that
+// uses PlatformThread under the hood.
+//
+// Background/Motivation: TFLite Model Execution occurs on a background task
+// runner, but we've seen from metrics that some models are extremely long
+// running (example: 8s at 95 %ile) which is simply not tolerable, and should
+// instead be cancelled outright after a reasonable duration.
+//
+// Notes:
+// * The TFLite Library guarantees that their Cancel() method can be called from
+// any thread.
+// * This class is owned by TFLiteModelExecutor and lives on a background
+// SequencedTaskRunner.
+// * The base class' methods are privately inheirited because we must (re)set
+// the |task_| pointer correctly with logic in this class, and not have the
+// caller worry about it.
+//
+// Synchronization: This class takes a raw pointer to the TFLite Model Runner,
+// which is created and destroyed frequently on the background task runner.
+// Therefore the |task_| pointer is protected by a lock since it is set on one
+// thread (the background sequence that executes the model), but maybe used on
+// the watchdog's thread to cancel a long running execution.
+// Care must be taken to ensure |ArmWithTask| and |DisarmOnExecutionComplete|
+// are always called so that the internal |task_| pointer can be (re)set with
+// the same timing as the model execution.
+template <class OutputType, class... InputTypes>
+class ModelExecutionTimeoutWatchdog : private base::Watchdog {
+ public:
+  explicit ModelExecutionTimeoutWatchdog(
+      proto::OptimizationTarget optimization_target,
+      base::TimeDelta duration)
+      : base::Watchdog(
+            duration,
+            /*thread_watched_name=*/"OptGuideModelExecution_" +
+                GetStringNameForOptimizationTarget(optimization_target),
+            /*enabled=*/true),
+        optimization_target_(optimization_target) {}
+
+  void ArmWithTask(
+      tflite::task::core::BaseTaskApi<OutputType, InputTypes...>* task) {
+    {
+      base::AutoLock lock(task_lock_);
+      task_ = task;
+    }
+    Arm();
+  }
+
+  void DisarmOnExecutionComplete() {
+    {
+      base::AutoLock lock(task_lock_);
+      if (!task_) {
+        // This could happen in a race where Disarm was called at the same time
+        // as the alarm going off.
+        return;
+      }
+      task_ = nullptr;
+    }
+    Disarm();
+
+    RecordDidTimeoutHistogram(optimization_target_, false);
+  }
+
+ private:
+  // base::Watchdog:
+  void Alarm() override {
+    base::Watchdog::Alarm();
+
+    {
+      base::AutoLock lock(task_lock_);
+      if (!task_) {
+        // This could happen in a race where Disarm was called at the same time
+        // as the alarm going off.
+        return;
+      }
+      task_->Cancel();
+      task_ = nullptr;
+    }
+
+    RecordDidTimeoutHistogram(optimization_target_, true);
+  }
+
+  const proto::OptimizationTarget optimization_target_;
+
+  base::Lock task_lock_;
+  raw_ptr<tflite::task::core::BaseTaskApi<OutputType, InputTypes...>> task_
+      GUARDED_BY(task_lock_);
+};
+
+}  // namespace optimization_guide
+
+#endif  // COMPONENTS_OPTIMIZATION_GUIDE_CORE_MODEL_EXECUTION_TIMEOUT_WATCHDOG_H_
\ No newline at end of file
diff --git a/components/optimization_guide/core/optimization_guide_features.cc b/components/optimization_guide/core/optimization_guide_features.cc
index 3f339b8..c798be2e 100644
--- a/components/optimization_guide/core/optimization_guide_features.cc
+++ b/components/optimization_guide/core/optimization_guide_features.cc
@@ -144,6 +144,9 @@
 const base::Feature kBatchAnnotationsValidation{
     "BatchAnnotationsValidation", base::FEATURE_DISABLED_BY_DEFAULT};
 
+const base::Feature kPreventLongRunningPredictionModels{
+    "PreventLongRunningPredictionModels", base::FEATURE_DISABLED_BY_DEFAULT};
+
 // The default value here is a bit of a guess.
 // TODO(crbug/1163244): This should be tuned once metrics are available.
 base::TimeDelta PageTextExtractionOutstandingRequestsGracePeriod() {
@@ -395,6 +398,14 @@
       kOptimizationTargetPrediction, "fetch_interval_hours", 24));
 }
 
+absl::optional<base::TimeDelta> ModelExecutionTimeout() {
+  if (!base::FeatureList::IsEnabled(kPreventLongRunningPredictionModels)) {
+    return absl::nullopt;
+  }
+  return base::Milliseconds(GetFieldTrialParamByFeatureAsInt(
+      kPreventLongRunningPredictionModels, "model_execution_timeout_ms", 2000));
+}
+
 base::flat_set<uint32_t> FieldTrialNameHashesAllowedForFetch() {
   std::string value = base::GetFieldTrialParamValueByFeature(
       kOptimizationHintsFieldTrials, "allowed_field_trial_names");
diff --git a/components/optimization_guide/core/optimization_guide_features.h b/components/optimization_guide/core/optimization_guide_features.h
index c1fbe414..bc760ec 100644
--- a/components/optimization_guide/core/optimization_guide_features.h
+++ b/components/optimization_guide/core/optimization_guide_features.h
@@ -41,6 +41,7 @@
 extern const base::Feature kPageVisibilityBatchAnnotations;
 extern const base::Feature kUseLocalPageEntitiesMetadataProvider;
 extern const base::Feature kBatchAnnotationsValidation;
+extern const base::Feature kPreventLongRunningPredictionModels;
 
 // The grace period duration for how long to give outstanding page text dump
 // requests to respond after DidFinishLoad.
@@ -192,6 +193,9 @@
 // refresh models.
 base::TimeDelta PredictionModelFetchInterval();
 
+// The timeout for executing models, if enabled.
+absl::optional<base::TimeDelta> ModelExecutionTimeout();
+
 // Returns a set of field trial name hashes that can be sent in the request to
 // the remote Optimization Guide Service if the client is in one of the
 // specified field trials.
diff --git a/components/optimization_guide/core/prediction_manager.cc b/components/optimization_guide/core/prediction_manager.cc
index 171a1f92..41fec1f 100644
--- a/components/optimization_guide/core/prediction_manager.cc
+++ b/components/optimization_guide/core/prediction_manager.cc
@@ -487,7 +487,8 @@
       if (prediction_model_download_manager_) {
         GURL download_url(model.model().download_url());
         if (download_url.is_valid()) {
-          prediction_model_download_manager_->StartDownload(download_url);
+          prediction_model_download_manager_->StartDownload(
+              download_url, model.model_info().optimization_target());
         }
         base::UmaHistogramBoolean(
             "OptimizationGuide.PredictionManager.IsDownloadUrlValid." +
diff --git a/components/optimization_guide/core/prediction_manager_unittest.cc b/components/optimization_guide/core/prediction_manager_unittest.cc
index 87abad2..deb70202 100644
--- a/components/optimization_guide/core/prediction_manager_unittest.cc
+++ b/components/optimization_guide/core/prediction_manager_unittest.cc
@@ -116,12 +116,18 @@
                                        task_runner) {}
   ~FakePredictionModelDownloadManager() override = default;
 
-  void StartDownload(const GURL& url) override {
+  void StartDownload(const GURL& url,
+                     proto::OptimizationTarget optimization_target) override {
     last_requested_download_ = url;
+    last_requested_optimization_target_ = optimization_target;
   }
 
   GURL last_requested_download() const { return last_requested_download_; }
 
+  proto::OptimizationTarget last_requested_optimization_target() const {
+    return last_requested_optimization_target_;
+  }
+
   void CancelAllPendingDownloads() override { cancel_downloads_called_ = true; }
   bool cancel_downloads_called() const { return cancel_downloads_called_; }
 
@@ -132,6 +138,7 @@
 
  private:
   GURL last_requested_download_;
+  proto::OptimizationTarget last_requested_optimization_target_;
   bool cancel_downloads_called_ = false;
   bool is_available_ = true;
 };
@@ -960,6 +967,9 @@
 
   EXPECT_EQ(prediction_model_download_manager()->last_requested_download(),
             GURL("https://example.com/model"));
+  EXPECT_EQ(
+      prediction_model_download_manager()->last_requested_optimization_target(),
+      proto::OPTIMIZATION_TARGET_PAINFUL_PAGE_LOAD);
 }
 
 TEST_F(PredictionManagerTest, UpdateModelForUnregisteredTargetOnModelReady) {
diff --git a/components/optimization_guide/core/prediction_model_download_manager.cc b/components/optimization_guide/core/prediction_model_download_manager.cc
index 5c0e11d..37773dc 100644
--- a/components/optimization_guide/core/prediction_model_download_manager.cc
+++ b/components/optimization_guide/core/prediction_model_download_manager.cc
@@ -109,14 +109,17 @@
 
 PredictionModelDownloadManager::~PredictionModelDownloadManager() = default;
 
-void PredictionModelDownloadManager::StartDownload(const GURL& download_url) {
+void PredictionModelDownloadManager::StartDownload(
+    const GURL& download_url,
+    proto::OptimizationTarget optimization_target) {
   download::DownloadParams download_params;
   download_params.client =
       download::DownloadClient::OPTIMIZATION_GUIDE_PREDICTION_MODELS;
   download_params.guid = base::GenerateGUID();
   download_params.callback =
       base::BindRepeating(&PredictionModelDownloadManager::OnDownloadStarted,
-                          ui_weak_ptr_factory_.GetWeakPtr());
+                          ui_weak_ptr_factory_.GetWeakPtr(),
+                          optimization_target, base::TimeTicks::Now());
   download_params.traffic_annotation = net::MutableNetworkTrafficAnnotationTag(
       kOptimizationGuidePredictionModelsTrafficAnnotation);
   // The downloaded models are all Google-generated, so bypass the safety
@@ -139,6 +142,11 @@
       download::SchedulingParams::BatteryRequirements::BATTERY_INSENSITIVE;
   download_params.scheduling_params.network_requirements =
       download::SchedulingParams::NetworkRequirements::NONE;
+  base::UmaHistogramEnumeration(
+      "OptimizationGuide.PredictionModelDownloadManager.State." +
+          optimization_guide::GetStringNameForOptimizationTarget(
+              optimization_target),
+      PredictionModelDownloadManager::PredictionModelDownloadState::kRequested);
 
   download_service_->StartDownload(std::move(download_params));
 }
@@ -181,10 +189,24 @@
 }
 
 void PredictionModelDownloadManager::OnDownloadStarted(
+    proto::OptimizationTarget optimization_target,
+    base::TimeTicks download_requested_time,
     const std::string& guid,
     download::DownloadParams::StartResult start_result) {
-  if (start_result == download::DownloadParams::StartResult::ACCEPTED)
+  if (start_result == download::DownloadParams::StartResult::ACCEPTED) {
     pending_download_guids_.insert(guid);
+    base::UmaHistogramEnumeration(
+        "OptimizationGuide.PredictionModelDownloadManager.State." +
+            optimization_guide::GetStringNameForOptimizationTarget(
+                optimization_target),
+        PredictionModelDownloadManager::PredictionModelDownloadState::kStarted);
+    base::UmaHistogramLongTimes(
+        "OptimizationGuide.PredictionModelDownloadManager."
+        "DownloadStartLatency." +
+            optimization_guide::GetStringNameForOptimizationTarget(
+                optimization_target),
+        base::TimeTicks::Now() - download_requested_time);
+  }
 }
 
 void PredictionModelDownloadManager::OnDownloadSucceeded(
diff --git a/components/optimization_guide/core/prediction_model_download_manager.h b/components/optimization_guide/core/prediction_model_download_manager.h
index 5a29f2a3..30c27d2 100644
--- a/components/optimization_guide/core/prediction_model_download_manager.h
+++ b/components/optimization_guide/core/prediction_model_download_manager.h
@@ -14,6 +14,7 @@
 #include "base/memory/weak_ptr.h"
 #include "base/observer_list.h"
 #include "components/download/public/background_service/download_params.h"
+#include "components/optimization_guide/proto/models.pb.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace download {
@@ -30,8 +31,21 @@
 }  // namespace proto
 
 // Manages the downloads of prediction models.
+// Keep in sync with OptimizationGuidePredictionModelDownloadState in enums.xml.
 class PredictionModelDownloadManager {
  public:
+  // The different states a predition model download goes through.
+  enum class PredictionModelDownloadState {
+    kUnknown = 0,
+    // Model was requested to be downloaded.
+    kRequested = 1,
+    // Download service started the model download.
+    kStarted = 2,
+
+    // Add new values above this line.
+    kMaxValue = kStarted,
+  };
+
   PredictionModelDownloadManager(
       download::BackgroundDownloadService* download_service,
       const base::FilePath& models_dir_path,
@@ -43,7 +57,8 @@
       const PredictionModelDownloadManager&) = delete;
 
   // Starts a download for |download_url|.
-  virtual void StartDownload(const GURL& download_url);
+  virtual void StartDownload(const GURL& download_url,
+                             proto::OptimizationTarget optimization_target);
 
   // Cancels all pending downloads.
   virtual void CancelAllPendingDownloads();
@@ -76,8 +91,11 @@
   void OnDownloadServiceUnavailable();
 
   // Invoked when the download has been accepted and persisted by the
-  // BackgroundDownloadService.
-  void OnDownloadStarted(const std::string& guid,
+  // BackgroundDownloadService. The download was requested at
+  // |download_requested_time| for |optimization_target|.
+  void OnDownloadStarted(proto::OptimizationTarget optimization_target,
+                         base::TimeTicks download_requested_time,
+                         const std::string& guid,
                          download::DownloadParams::StartResult start_result);
 
   // Invoked when the download as specified by |downloaded_guid| succeeded.
diff --git a/components/optimization_guide/core/prediction_model_download_manager_unittest.cc b/components/optimization_guide/core/prediction_model_download_manager_unittest.cc
index 6d1b2b3..fbc71983 100644
--- a/components/optimization_guide/core/prediction_model_download_manager_unittest.cc
+++ b/components/optimization_guide/core/prediction_model_download_manager_unittest.cc
@@ -267,6 +267,7 @@
 }
 
 TEST_F(PredictionModelDownloadManagerTest, StartDownloadRestrictedDownloading) {
+  base::HistogramTester histogram_tester;
   base::test::ScopedFeatureList features;
   features.InitWithFeaturesAndParameters(
       {
@@ -278,7 +279,8 @@
   download::DownloadParams download_params;
   EXPECT_CALL(*download_service(), StartDownload_(_))
       .WillOnce(MoveArg<0>(&download_params));
-  download_manager()->StartDownload(GURL("someurl"));
+  download_manager()->StartDownload(
+      GURL("someurl"), proto::OPTIMIZATION_TARGET_PAINFUL_PAGE_LOAD);
 
   // Validate parameters - basically that we attach the correct client, just do
   // a passthrough of the URL, and attach the API key.
@@ -297,10 +299,31 @@
   EXPECT_EQ(download_params.scheduling_params.network_requirements,
             download::SchedulingParams::NetworkRequirements::NONE);
 
+  histogram_tester.ExpectUniqueSample(
+      "OptimizationGuide.PredictionModelDownloadManager.State.PainfulPageLoad",
+      PredictionModelDownloadManager::PredictionModelDownloadState::kRequested,
+      1);
+  histogram_tester.ExpectTotalCount(
+      "OptimizationGuide.PredictionModelDownloadManager.DownloadStartLatency."
+      "PainfulPageLoad",
+      0);
+
   // Now invoke start callback.
   std::move(download_params.callback)
       .Run("someguid", download::DownloadParams::StartResult::ACCEPTED);
 
+  histogram_tester.ExpectTotalCount(
+      "OptimizationGuide.PredictionModelDownloadManager.State.PainfulPageLoad",
+      2);
+  histogram_tester.ExpectBucketCount(
+      "OptimizationGuide.PredictionModelDownloadManager.State.PainfulPageLoad",
+      PredictionModelDownloadManager::PredictionModelDownloadState::kStarted,
+      1);
+  histogram_tester.ExpectTotalCount(
+      "OptimizationGuide.PredictionModelDownloadManager.DownloadStartLatency."
+      "PainfulPageLoad",
+      1);
+
   // Now cancel all downloads to ensure that callback persisted pending GUID.
   EXPECT_CALL(*download_service(), CancelDownload(Eq("someguid")));
   download_manager()->CancelAllPendingDownloads();
@@ -308,6 +331,7 @@
 
 TEST_F(PredictionModelDownloadManagerTest,
        StartDownloadUnrestrictedDownloading) {
+  base::HistogramTester histogram_tester;
   base::test::ScopedFeatureList features;
   features.InitWithFeaturesAndParameters(
       {
@@ -319,7 +343,8 @@
   download::DownloadParams download_params;
   EXPECT_CALL(*download_service(), StartDownload_(_))
       .WillOnce(MoveArg<0>(&download_params));
-  download_manager()->StartDownload(GURL("someurl"));
+  download_manager()->StartDownload(
+      GURL("someurl"), proto::OPTIMIZATION_TARGET_PAINFUL_PAGE_LOAD);
 
   // Validate parameters - basically that we attach the correct client, just do
   // a passthrough of the URL, and attach the API key.
@@ -337,10 +362,29 @@
       download::SchedulingParams::BatteryRequirements::BATTERY_INSENSITIVE);
   EXPECT_EQ(download_params.scheduling_params.network_requirements,
             download::SchedulingParams::NetworkRequirements::NONE);
+  histogram_tester.ExpectUniqueSample(
+      "OptimizationGuide.PredictionModelDownloadManager.State.PainfulPageLoad",
+      PredictionModelDownloadManager::PredictionModelDownloadState::kRequested,
+      1);
+  histogram_tester.ExpectTotalCount(
+      "OptimizationGuide.PredictionModelDownloadManager.DownloadStartLatency."
+      "PainfulPageLoad",
+      0);
 
   // Now invoke start callback.
   std::move(download_params.callback)
       .Run("someguid", download::DownloadParams::StartResult::ACCEPTED);
+  histogram_tester.ExpectTotalCount(
+      "OptimizationGuide.PredictionModelDownloadManager.State.PainfulPageLoad",
+      2);
+  histogram_tester.ExpectBucketCount(
+      "OptimizationGuide.PredictionModelDownloadManager.State.PainfulPageLoad",
+      PredictionModelDownloadManager::PredictionModelDownloadState::kStarted,
+      1);
+  histogram_tester.ExpectTotalCount(
+      "OptimizationGuide.PredictionModelDownloadManager.DownloadStartLatency."
+      "PainfulPageLoad",
+      1);
 
   // Now cancel all downloads to ensure that callback persisted pending GUID.
   EXPECT_CALL(*download_service(), CancelDownload(Eq("someguid")));
@@ -348,18 +392,28 @@
 }
 
 TEST_F(PredictionModelDownloadManagerTest, StartDownloadFailedToSchedule) {
+  base::HistogramTester histogram_tester;
   download::DownloadParams download_params;
   EXPECT_CALL(*download_service(), StartDownload_(_))
       .WillOnce(MoveArg<0>(&download_params));
-  download_manager()->StartDownload(GURL("someurl"));
+  download_manager()->StartDownload(
+      GURL("someurl"), proto::OPTIMIZATION_TARGET_PAINFUL_PAGE_LOAD);
 
   // Now invoke start callback.
   std::move(download_params.callback)
       .Run("someguid", download::DownloadParams::StartResult::INTERNAL_ERROR);
+  histogram_tester.ExpectUniqueSample(
+      "OptimizationGuide.PredictionModelDownloadManager.State.PainfulPageLoad",
+      PredictionModelDownloadManager::PredictionModelDownloadState::kRequested,
+      1);
 
   // Now cancel all downloads to ensure that bad GUID was not accepted.
   EXPECT_CALL(*download_service(), CancelDownload(_)).Times(0);
   download_manager()->CancelAllPendingDownloads();
+  histogram_tester.ExpectTotalCount(
+      "OptimizationGuide.PredictionModelDownloadManager.DownloadStartLatency."
+      "PainfulPageLoad",
+      0);
 }
 
 TEST_F(PredictionModelDownloadManagerTest, IsAvailableForDownloads) {
diff --git a/components/optimization_guide/core/tflite_model_executor.h b/components/optimization_guide/core/tflite_model_executor.h
index fa498630..f8f8a80 100644
--- a/components/optimization_guide/core/tflite_model_executor.h
+++ b/components/optimization_guide/core/tflite_model_executor.h
@@ -19,8 +19,10 @@
 #include "base/trace_event/trace_event.h"
 #include "components/optimization_guide/core/execution_status.h"
 #include "components/optimization_guide/core/model_enums.h"
+#include "components/optimization_guide/core/model_execution_timeout_watchdog.h"
 #include "components/optimization_guide/core/model_executor.h"
 #include "components/optimization_guide/core/model_util.h"
+#include "components/optimization_guide/core/optimization_guide_features.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/tflite/src/tensorflow/lite/c/common.h"
 #include "third_party/tflite_support/src/tensorflow_lite_support/cc/task/core/base_task_api.h"
@@ -76,6 +78,16 @@
   TFLiteModelExecutor() = default;
   ~TFLiteModelExecutor() override {
     DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+    // |watchdog_| uses a thread internally so we need to allow sync primitives
+    // when destroying (joining) it.
+    //
+    // Note that this dtor is already being called on a background task runner
+    // via DeleteSoon.
+    if (watchdog_) {
+      base::ScopedAllowBaseSyncPrimitives allow_sync_primitives;
+      watchdog_.reset();
+    }
   }
 
   // Should be called on the same sequence as the ctor, but once called |this|
@@ -93,6 +105,11 @@
     optimization_target_ = optimization_target;
     execution_task_runner_ = execution_task_runner;
     reply_task_runner_ = reply_task_runner;
+    if (features::ModelExecutionTimeout()) {
+      watchdog_ = std::make_unique<
+          ModelExecutionTimeoutWatchdog<OutputType, InputTypes...>>(
+          optimization_target_, *features::ModelExecutionTimeout());
+    }
   }
 
   // Called when a model file is available to load. Depending on feature flags,
@@ -186,6 +203,12 @@
 
     DCHECK(loaded_model_);
     absl::optional<OutputType> output;
+
+    // IMPORTANT: Once the arm method is called, disarm must be called when the
+    // model execution finishes. Do NOT early-return in this next block.
+    if (watchdog_) {
+      watchdog_->ArmWithTask(loaded_model_.get());
+    }
     {
       TRACE_EVENT1("browser", "OptGuideModelExecutor::Execute",
                    "OptimizationTarget",
@@ -208,6 +231,9 @@
               GetStringNameForOptimizationTarget(optimization_target_),
           execution_timer.Elapsed());
     }
+    if (watchdog_) {
+      watchdog_->DisarmOnExecutionComplete();
+    }
 
     DCHECK(callback_on_complete);
     reply_task_runner_->PostTask(
@@ -302,6 +328,9 @@
 
   bool should_unload_model_on_complete_ = true;
 
+  std::unique_ptr<ModelExecutionTimeoutWatchdog<OutputType, InputTypes...>>
+      watchdog_;
+
   scoped_refptr<base::SequencedTaskRunner> execution_task_runner_;
 
   scoped_refptr<base::SequencedTaskRunner> reply_task_runner_;
diff --git a/components/optimization_guide/core/tflite_model_executor_unittest.cc b/components/optimization_guide/core/tflite_model_executor_unittest.cc
index c0f5f76f..9fb084eb 100644
--- a/components/optimization_guide/core/tflite_model_executor_unittest.cc
+++ b/components/optimization_guide/core/tflite_model_executor_unittest.cc
@@ -4,6 +4,7 @@
 
 #include "base/path_service.h"
 #include "base/test/metrics/histogram_tester.h"
+#include "base/test/scoped_feature_list.h"
 #include "base/test/task_environment.h"
 #include "components/optimization_guide/core/test_model_info_builder.h"
 #include "components/optimization_guide/core/test_optimization_guide_model_provider.h"
@@ -28,6 +29,35 @@
   }
 };
 
+class EnsureCancelledTestTFLiteModelExecutor : public TestTFLiteModelExecutor {
+ protected:
+  absl::optional<std::vector<float>> Execute(
+      ModelExecutionTask* execution_task,
+      ExecutionStatus* out_status,
+      const std::vector<float>& input) override {
+    while (true) {
+      // Timing is tricky, so give a few invocations for the cancel flag in
+      // TFLite Support to be noticed.
+      absl::optional<std::vector<float>> out =
+          TestTFLiteModelExecutor::Execute(execution_task, out_status, input);
+      if (!out) {
+        return out;
+      }
+    }
+  }
+};
+
+class EnsureCancelledTestTFLiteModelHandler : public TestTFLiteModelHandler {
+ public:
+  EnsureCancelledTestTFLiteModelHandler(
+      OptimizationGuideModelProvider* model_provider,
+      scoped_refptr<base::SequencedTaskRunner> background_task_runner)
+      : TestTFLiteModelHandler(
+            model_provider,
+            background_task_runner,
+            std::make_unique<EnsureCancelledTestTFLiteModelExecutor>()) {}
+};
+
 class TFLiteModelExecutorTest : public testing::Test {
  public:
   TFLiteModelExecutorTest() = default;
@@ -48,7 +78,7 @@
 
   void TearDown() override { ResetModelHandler(); }
 
-  void CreateModelHandler() {
+  virtual void CreateModelHandler() {
     if (model_handler_)
       model_handler_.reset();
 
@@ -86,13 +116,13 @@
 
   void RunUntilIdle() { task_environment_.RunUntilIdle(); }
 
+ protected:
+  std::unique_ptr<TestTFLiteModelHandler> model_handler_;
+
  private:
   base::test::TaskEnvironment task_environment_;
-
   base::FilePath model_file_path_;
   std::unique_ptr<TestOptimizationGuideModelProvider> test_model_provider_;
-
-  std::unique_ptr<TestTFLiteModelHandler> model_handler_;
 };
 
 TEST_F(TFLiteModelExecutorTest, ExecuteReturnsImmediatelyIfNoModelLoaded) {
@@ -381,5 +411,61 @@
       true, 1);
 }
 
+class CancelledTFLiteModelExecutorTest : public TFLiteModelExecutorTest {
+ public:
+  CancelledTFLiteModelExecutorTest() {
+    scoped_feature_list_.InitAndEnableFeatureWithParameters(
+        features::kPreventLongRunningPredictionModels,
+        {{"model_execution_timeout_ms", "10"}});
+  }
+  ~CancelledTFLiteModelExecutorTest() override = default;
+
+  void SetUp() override { TFLiteModelExecutorTest::SetUp(); }
+
+  void CreateModelHandler() override {
+    model_handler_ = std::make_unique<EnsureCancelledTestTFLiteModelHandler>(
+        test_model_provider(), task_environment()->GetMainThreadTaskRunner());
+  }
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+TEST_F(CancelledTFLiteModelExecutorTest, RunsTooLong) {
+  base::HistogramTester histogram_tester;
+  CreateModelHandler();
+
+  PushModelFileToModelExecutor(
+      proto::OptimizationTarget::OPTIMIZATION_TARGET_PAINFUL_PAGE_LOAD,
+      /*model_metadata=*/absl::nullopt);
+  EXPECT_TRUE(model_handler()->ModelAvailable());
+
+  std::vector<float> input;
+  size_t expected_dims = 1 * 32 * 32 * 3;
+  input.reserve(expected_dims);
+  for (size_t i = 0; i < expected_dims; i++) {
+    input.emplace_back(1);
+  }
+
+  base::RunLoop run_loop;
+  model_handler()->ExecuteModelWithInput(
+      base::BindOnce(
+          [](base::RunLoop* run_loop,
+             const absl::optional<std::vector<float>>& output) {
+            EXPECT_FALSE(output.has_value());
+            run_loop->Quit();
+          },
+          &run_loop),
+      input);
+  run_loop.Run();
+
+  histogram_tester.ExpectUniqueSample(
+      "OptimizationGuide.ModelExecutor.ExecutionStatus.PainfulPageLoad",
+      ExecutionStatus::kErrorCancelled, 1);
+
+  histogram_tester.ExpectUniqueSample(
+      "OptimizationGuide.ModelExecutor.DidTimeout.PainfulPageLoad", true, 1);
+}
+
 }  // namespace
 }  // namespace optimization_guide
diff --git a/components/payments/content/installable_payment_app_crawler.cc b/components/payments/content/installable_payment_app_crawler.cc
index c1799fa..010dd8d 100644
--- a/components/payments/content/installable_payment_app_crawler.cc
+++ b/components/payments/content/installable_payment_app_crawler.cc
@@ -185,10 +185,9 @@
       continue;
     }
 
-    if (permission_controller->GetPermissionStatus(
+    if (permission_controller->GetPermissionStatusForOriginWithoutContext(
             content::PermissionType::PAYMENT_HANDLER,
-            web_app_manifest_url.DeprecatedGetOriginAsURL(),
-            web_app_manifest_url.DeprecatedGetOriginAsURL()) !=
+            url::Origin::Create(web_app_manifest_url)) !=
         blink::mojom::PermissionStatus::GRANTED) {
       // Do not download the web app manifest if it is blocked.
       continue;
@@ -212,7 +211,6 @@
             weak_ptr_factory_.GetWeakPtr(), method_manifest_url,
             web_app_manifest_url));
   }
-
   FinishCrawlingPaymentAppsIfReady();
 }
 
diff --git a/components/pdf/renderer/BUILD.gn b/components/pdf/renderer/BUILD.gn
index fb73ef42..20b186ca8 100644
--- a/components/pdf/renderer/BUILD.gn
+++ b/components/pdf/renderer/BUILD.gn
@@ -39,6 +39,7 @@
     "//pdf:features",
     "//pdf:pdf_view_web_plugin",
     "//printing/buildflags",
+    "//skia",
     "//third_party/blink/public:blink",
     "//third_party/blink/public/strings:accessibility_strings",
     "//third_party/icu",
diff --git a/components/pdf/renderer/DEPS b/components/pdf/renderer/DEPS
index 8a20e16..80d962d 100644
--- a/components/pdf/renderer/DEPS
+++ b/components/pdf/renderer/DEPS
@@ -13,6 +13,7 @@
   "+pdf/pdf_view_web_plugin.h",
   "+printing/buildflags/buildflags.h",
   "+third_party/blink/public",
+  "+third_party/skia/include/core",
   "+ui/accessibility",
   "+ui/base",
 ]
diff --git a/components/remote_cocoa/app_shim/BUILD.gn b/components/remote_cocoa/app_shim/BUILD.gn
index 7f00934..06246a1 100644
--- a/components/remote_cocoa/app_shim/BUILD.gn
+++ b/components/remote_cocoa/app_shim/BUILD.gn
@@ -40,6 +40,8 @@
     "native_widget_mac_nswindow.mm",
     "native_widget_ns_window_bridge.h",
     "native_widget_ns_window_bridge.mm",
+    "native_widget_ns_window_fullscreen_controller.h",
+    "native_widget_ns_window_fullscreen_controller.mm",
     "native_widget_ns_window_host_helper.h",
     "ns_view_ids.h",
     "ns_view_ids.mm",
diff --git a/components/remote_cocoa/app_shim/native_widget_ns_window_bridge.h b/components/remote_cocoa/app_shim/native_widget_ns_window_bridge.h
index a101b19..89da5d3 100644
--- a/components/remote_cocoa/app_shim/native_widget_ns_window_bridge.h
+++ b/components/remote_cocoa/app_shim/native_widget_ns_window_bridge.h
@@ -12,6 +12,7 @@
 
 #import "base/mac/scoped_nsobject.h"
 #import "components/remote_cocoa/app_shim/mouse_capture_delegate.h"
+#include "components/remote_cocoa/app_shim/native_widget_ns_window_fullscreen_controller.h"
 #include "components/remote_cocoa/app_shim/ns_view_ids.h"
 #include "components/remote_cocoa/app_shim/remote_cocoa_app_shim_export.h"
 #include "components/remote_cocoa/common/native_widget_ns_window.mojom.h"
@@ -60,6 +61,7 @@
 class REMOTE_COCOA_APP_SHIM_EXPORT NativeWidgetNSWindowBridge
     : public remote_cocoa::mojom::NativeWidgetNSWindow,
       public display::DisplayObserver,
+      public NativeWidgetNSWindowFullscreenController::Client,
       public ui::CATransactionCoordinator::PreCommitObserver,
       public CocoaMouseCaptureDelegate {
  public:
@@ -179,10 +181,21 @@
     return child_windows_;
   }
 
-  bool target_fullscreen_state() const { return target_fullscreen_state_; }
+  NativeWidgetNSWindowFullscreenController* fullscreen_controller() {
+    return fullscreen_controller_.get();
+  }
+  bool target_fullscreen_state() const {
+    if (fullscreen_controller_)
+      return fullscreen_controller_->GetTargetFullscreenState();
+    return target_fullscreen_state_;
+  }
   bool window_visible() const { return window_visible_; }
   bool wants_to_be_visible() const { return wants_to_be_visible_; }
-  bool in_fullscreen_transition() const { return in_fullscreen_transition_; }
+  bool in_fullscreen_transition() const {
+    if (fullscreen_controller_)
+      return fullscreen_controller_->IsInFullscreenTransition();
+    return in_fullscreen_transition_;
+  }
 
   // Whether to run a custom animation for the provided |transition|.
   bool ShouldRunCustomAnimationFor(
@@ -198,6 +211,18 @@
   void OnDisplayMetricsChanged(const display::Display& display,
                                uint32_t metrics) override;
 
+  // NativeWidgetNSWindowFullscreenController::Client:
+  void FullscreenControllerTransitionStart(bool is_target_fullscreen) override;
+  void FullscreenControllerTransitionComplete(bool is_fullscreen) override;
+  void FullscreenControllerSetFrame(const gfx::Rect& frame,
+                                    base::TimeDelta& transition_time) override;
+  void FullscreenControllerToggleFullscreen() override;
+  void FullscreenControllerCloseWindow() override;
+  int64_t FullscreenControllerGetDisplayId() const override;
+  gfx::Rect FullscreenControllerGetFrameForDisplay(
+      int64_t display_id) const override;
+  gfx::Rect FullscreenControllerGetFrame() const override;
+
   // ui::CATransactionCoordinator::PreCommitObserver:
   bool ShouldWaitInPreCommit() override;
   base::TimeDelta PreCommitTimeout() override;
@@ -232,6 +257,8 @@
       remote_cocoa::mojom::VisibilityTransition transitions) override;
   void SetVisibleOnAllSpaces(bool always_visible) override;
   void SetFullscreen(bool fullscreen) override;
+  void EnterFullscreen(int64_t target_display_id);
+  void ExitFullscreen();
   void SetCanAppearInExistingFullscreenSpaces(
       bool can_appear_in_existing_fullscreen_spaces) override;
   void SetMiniaturized(bool miniaturized) override;
@@ -379,6 +406,12 @@
   // https://crbug.com/945237
   bool has_deferred_window_close_ = false;
 
+  // Manager of fullscreen state transitions. If this is non-nullptr, then it
+  // replaces `target_fullscreen_state_`, `in_fullscreen_transition_`, and
+  // `has_deferred_window_close_`.
+  std::unique_ptr<NativeWidgetNSWindowFullscreenController>
+      fullscreen_controller_;
+
   // Stores the value last read from -[NSWindow isVisible], to detect visibility
   // changes.
   bool window_visible_ = false;
diff --git a/components/remote_cocoa/app_shim/native_widget_ns_window_bridge.mm b/components/remote_cocoa/app_shim/native_widget_ns_window_bridge.mm
index cb82b91e..7b6f6a7d 100644
--- a/components/remote_cocoa/app_shim/native_widget_ns_window_bridge.mm
+++ b/components/remote_cocoa/app_shim/native_widget_ns_window_bridge.mm
@@ -586,8 +586,13 @@
 }
 
 void NativeWidgetNSWindowBridge::CloseWindow() {
-  if (has_deferred_window_close_)
-    return;
+  if (fullscreen_controller_) {
+    if (fullscreen_controller_->HasDeferredWindowClose())
+      return;
+  } else {
+    if (has_deferred_window_close_)
+      return;
+  }
 
   // Keep |window| on the stack so that the ObjectiveC block below can capture
   // it and properly increment the reference count bound to the posted task.
@@ -630,9 +635,15 @@
   [window orderOut:nil];
 
   // Defer closing windows until after fullscreen transitions complete.
-  if (in_fullscreen_transition_) {
-    has_deferred_window_close_ = true;
-    return;
+  if (fullscreen_controller_) {
+    fullscreen_controller_->OnWindowWantsToClose();
+    if (fullscreen_controller_->HasDeferredWindowClose())
+      return;
+  } else {
+    if (in_fullscreen_transition_) {
+      has_deferred_window_close_ = true;
+      return;
+    }
   }
 
   // Many tests assume that base::RunLoop().RunUntilIdle() is always sufficient
@@ -880,12 +891,16 @@
 }
 
 void NativeWidgetNSWindowBridge::OnWindowWillClose() {
-  // If a window closes while in a fullscreen transition, then the window will
-  // hang in a zombie-like state.
-  // https://crbug.com/945237
-  if (in_fullscreen_transition_) {
-    DLOG(ERROR) << "-[NSWindow close] while in fullscreen transition will "
-                   "trigger zombie windows.";
+  if (fullscreen_controller_) {
+    fullscreen_controller_->OnWindowWillClose();
+  } else {
+    // If a window closes while in a fullscreen transition, then the window will
+    // hang in a zombie-like state.
+    // https://crbug.com/945237
+    if (in_fullscreen_transition_) {
+      DLOG(ERROR) << "-[NSWindow close] while in fullscreen transition will "
+                     "trigger zombie windows.";
+    }
   }
 
   [window_ setCommandHandler:nil];
@@ -927,6 +942,7 @@
 
 void NativeWidgetNSWindowBridge::OnFullscreenTransitionStart(
     bool target_fullscreen_state) {
+  DCHECK(!fullscreen_controller_);
   DCHECK_NE(target_fullscreen_state, target_fullscreen_state_);
   target_fullscreen_state_ = target_fullscreen_state;
   in_fullscreen_transition_ = true;
@@ -936,6 +952,7 @@
 
 void NativeWidgetNSWindowBridge::OnFullscreenTransitionComplete(
     bool actual_fullscreen_state) {
+  DCHECK(!fullscreen_controller_);
   in_fullscreen_transition_ = false;
 
   // Add any children that were skipped during the fullscreen transition.
@@ -960,6 +977,7 @@
 }
 
 void NativeWidgetNSWindowBridge::ToggleDesiredFullscreenState(bool async) {
+  DCHECK(!fullscreen_controller_);
   // If there is currently an animation into or out of fullscreen, then AppKit
   // emits the string "not in fullscreen state" to stdio and does nothing. For
   // this case, schedule a transition back into the desired state when the
@@ -1078,11 +1096,16 @@
                                                     const gfx::Size& max_size,
                                                     bool is_resizable,
                                                     bool is_maximizable) {
-  // Don't modify the size constraints or fullscreen collection behavior while
-  // in fullscreen or during a transition. OnFullscreenTransitionComplete will
-  // reset these after leaving fullscreen.
-  if (target_fullscreen_state_ || in_fullscreen_transition_)
-    return;
+  if (fullscreen_controller_) {
+    if (!fullscreen_controller_->CanResize())
+      return;
+  } else {
+    // Don't modify the size constraints or fullscreen collection behavior while
+    // in fullscreen or during a transition. OnFullscreenTransitionComplete will
+    // reset these after leaving fullscreen.
+    if (target_fullscreen_state_ || in_fullscreen_transition_)
+      return;
+  }
 
   bool shows_resize_controls =
       is_resizable && (min_size.IsEmpty() || min_size != max_size);
@@ -1253,6 +1276,57 @@
 }
 
 ////////////////////////////////////////////////////////////////////////////////
+// NativeWidgetNSWindowBridge, NativeWidgetNSWindowFullscreenController::Client:
+
+void NativeWidgetNSWindowBridge::FullscreenControllerTransitionStart(
+    bool is_target_fullscreen) {
+  host_->OnWindowFullscreenTransitionStart(is_target_fullscreen);
+}
+
+void NativeWidgetNSWindowBridge::FullscreenControllerTransitionComplete(
+    bool is_fullscreen) {
+  // Add any children that were skipped during the fullscreen transition.
+  OrderChildren();
+  host_->OnWindowFullscreenTransitionComplete(is_fullscreen);
+}
+
+void NativeWidgetNSWindowBridge::FullscreenControllerSetFrame(
+    const gfx::Rect& frame,
+    base::TimeDelta& transition_time) {
+  NSRect ns_frame = gfx::ScreenRectToNSRect(frame);
+  transition_time = base::Seconds([window_ animationResizeTime:ns_frame]);
+  [window_ setFrame:ns_frame display:NO animate:YES];
+}
+
+void NativeWidgetNSWindowBridge::FullscreenControllerToggleFullscreen() {
+  [window_ toggleFullScreen:nil];
+}
+
+void NativeWidgetNSWindowBridge::FullscreenControllerCloseWindow() {
+  [window_ close];
+}
+
+int64_t NativeWidgetNSWindowBridge::FullscreenControllerGetDisplayId() const {
+  return display::Screen::GetScreen()
+      ->GetDisplayNearestWindow(window_.get())
+      .id();
+}
+
+gfx::Rect NativeWidgetNSWindowBridge::FullscreenControllerGetFrameForDisplay(
+    int64_t display_id) const {
+  display::Display display;
+  if (display::Screen::GetScreen()->GetDisplayWithDisplayId(display_id,
+                                                            &display)) {
+    return display.work_area();
+  }
+  return gfx::Rect();
+}
+
+gfx::Rect NativeWidgetNSWindowBridge::FullscreenControllerGetFrame() const {
+  return gfx::ScreenRectFromNSRect([window_ frame]);
+}
+
+////////////////////////////////////////////////////////////////////////////////
 // NativeWidgetNSWindowBridge, ui::CATransactionObserver
 
 bool NativeWidgetNSWindowBridge::ShouldWaitInPreCommit() {
@@ -1262,6 +1336,14 @@
     return false;
   if (!bridged_view_)
     return false;
+  if (fullscreen_controller_) {
+    // Suppress synchronous CA transactions during AppKit fullscreen transition
+    // since there is no need for updates during such transition.
+    // Re-layout and re-paint will be done after the transition. See
+    // https://crbug.com/875707 for potiential problems if we don't suppress.
+    if (fullscreen_controller_->IsInFullscreenTransition())
+      return false;
+  }
   return content_dip_size_ != compositor_frame_dip_size_;
 }
 
@@ -1294,11 +1376,42 @@
 }
 
 void NativeWidgetNSWindowBridge::SetFullscreen(bool fullscreen) {
+  DCHECK(!fullscreen_controller_);
   if (fullscreen == target_fullscreen_state_)
     return;
   ToggleDesiredFullscreenState();
 }
 
+void NativeWidgetNSWindowBridge::EnterFullscreen(int64_t target_display_id) {
+  DCHECK(fullscreen_controller_);
+  // Going fullscreen implicitly makes the window visible. AppKit does this.
+  // That is, -[NSWindow isVisible] is always true after a call to -[NSWindow
+  // toggleFullScreen:]. Unfortunately, this change happens after AppKit calls
+  // -[NSWindowDelegate windowWillEnterFullScreen:], and AppKit doesn't send
+  // an orderWindow message. So intercepting the implicit change is hard.
+  // Luckily, to trigger externally, the window typically needs to be visible
+  // in the first place. So we can just ensure the window is visible here
+  // instead of relying on AppKit to do it, and not worry that
+  // OnVisibilityChanged() won't be called for externally triggered fullscreen
+  // requests.
+  if (!window_visible_)
+    SetVisibilityState(WindowVisibilityState::kShowInactive);
+
+  // Enable fullscreen collection behavior because:
+  // 1: -[NSWindow toggleFullscreen:] would otherwise be ignored,
+  // 2: the fullscreen button must be enabled so the user can leave
+  // fullscreen. This will be reset when a transition out of fullscreen
+  // completes.
+  gfx::SetNSWindowCanFullscreen(window_, true);
+
+  fullscreen_controller_->EnterFullscreen(target_display_id);
+}
+
+void NativeWidgetNSWindowBridge::ExitFullscreen() {
+  DCHECK(fullscreen_controller_);
+  fullscreen_controller_->ExitFullscreen();
+}
+
 void NativeWidgetNSWindowBridge::SetCanAppearInExistingFullscreenSpaces(
     bool can_appear_in_existing_fullscreen_spaces) {
   NSWindow* window = window_.get();
diff --git a/components/remote_cocoa/app_shim/native_widget_ns_window_fullscreen_controller.h b/components/remote_cocoa/app_shim/native_widget_ns_window_fullscreen_controller.h
new file mode 100644
index 0000000..0508189
--- /dev/null
+++ b/components/remote_cocoa/app_shim/native_widget_ns_window_fullscreen_controller.h
@@ -0,0 +1,190 @@
+// 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 COMPONENTS_REMOTE_COCOA_APP_SHIM_NATIVE_WIDGET_NS_WINDOW_FULLSCREEN_CONTROLLER_H_
+#define COMPONENTS_REMOTE_COCOA_APP_SHIM_NATIVE_WIDGET_NS_WINDOW_FULLSCREEN_CONTROLLER_H_
+
+#include "base/memory/raw_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/time/time.h"
+#include "components/remote_cocoa/app_shim/remote_cocoa_app_shim_export.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+#include "ui/display/types/display_constants.h"
+#include "ui/gfx/geometry/rect.h"
+
+namespace remote_cocoa {
+
+class NativeWidgetNSWindowBridge;
+
+class REMOTE_COCOA_APP_SHIM_EXPORT NativeWidgetNSWindowFullscreenController {
+ public:
+  class Client {
+   public:
+    // Called when a transition between fullscreen and windowed (or vice-versa).
+    // If `is_target_fullscreen` is true, then the target of the transition is
+    // fullscreen.
+    virtual void FullscreenControllerTransitionStart(
+        bool is_target_fullscreen) = 0;
+
+    // Called when a transition between fullscreen and windowed is complete.
+    // If `is_fullscreen` is true, then the window is now fullscreen.
+    virtual void FullscreenControllerTransitionComplete(bool is_fullscreen) = 0;
+
+    // Set the window's frame to the specified rectangle. Populate
+    // `transition_time` with the time that it will take for this transition
+    // to complete.
+    virtual void FullscreenControllerSetFrame(
+        const gfx::Rect& frame,
+        base::TimeDelta& transition_time) = 0;
+
+    // Call -[NSWindow toggleFullscreen:].
+    virtual void FullscreenControllerToggleFullscreen() = 0;
+
+    // Call -[NSWindow close]. Note that this call may result in the caller
+    // being destroyed.
+    virtual void FullscreenControllerCloseWindow() = 0;
+
+    // Return the display id for the display that the window is currently on.
+    virtual int64_t FullscreenControllerGetDisplayId() const = 0;
+
+    // Return the frame that should be set prior to transitioning to fullscreen
+    // on the display specified by `display_id`. If `display_id` is invalid,
+    // then return an empty rectangle.
+    virtual gfx::Rect FullscreenControllerGetFrameForDisplay(
+        int64_t display_id) const = 0;
+
+    // Get the window's current frame.
+    virtual gfx::Rect FullscreenControllerGetFrame() const = 0;
+  };
+
+  explicit NativeWidgetNSWindowFullscreenController(Client* client);
+  NativeWidgetNSWindowFullscreenController(
+      const NativeWidgetNSWindowFullscreenController&) = delete;
+  NativeWidgetNSWindowFullscreenController& operator=(
+      const NativeWidgetNSWindowFullscreenController&) = delete;
+  ~NativeWidgetNSWindowFullscreenController();
+
+  // Called by NativeWidget::SetFullscreen.
+  void EnterFullscreen(int64_t target_display_id);
+  void ExitFullscreen();
+
+  // Called from NativeWidgetNSWindowBridge:CloseWindow, indicating that the
+  // window has been requested to be closed. If a transition is in progress,
+  // then the close will be deferred until after the transition completes.
+  void OnWindowWantsToClose();
+
+  // Return true if an active transition has caused closing of the window to be
+  // deferred.
+  bool HasDeferredWindowClose() const { return has_deferred_window_close_; }
+
+  // Called by NativeWidgetNSWindowBridge::OnWindowWillClose.
+  void OnWindowWillClose();
+
+  // Called by -[NSWindowDelegate windowWill/DidEnter/ExitFullScreen:].
+  void OnWindowWillEnterFullscreen();
+  void OnWindowDidEnterFullscreen();
+  void OnWindowWillExitFullscreen();
+  void OnWindowDidExitFullscreen();
+
+  // Return false unless the state is kWindowed or kFullscreen.
+  bool IsInFullscreenTransition() const;
+
+  // Return true if the window can be resized. The window cannot be resized
+  // while fullscreen or during a transition.
+  bool CanResize() const;
+
+  // Return the fullscreen state that will be arrived at when all transition
+  // is done.
+  bool GetTargetFullscreenState() const;
+
+ private:
+  enum class State {
+    // In windowed mode.
+    kWindowed,
+    // Moving the window to the target display on which it will go fullscreen.
+    kWindowedMovingToFullscreenTarget,
+    // In transition to enter fullscreen mode. This encompases the following
+    // states:
+    // - From the kWindowed state, a task for ToggleFullscreen has been
+    //   posted.
+    // - OnWindowWillEnterFullscreen has been called (either as a result of
+    //   ToggleFullscreen, or as a result of user interaction), but neither
+    //   OnWindowDidEnterFullscreen nor OnWindowDidExitFullscreen have been
+    //   called yet.
+    kEnterFullscreenTransition,
+    // In fullscreen mode.
+    kFullscreen,
+    // In transition to exit fullscreen mode. This encompases the following
+    // states:
+    // - From the kFullscreen state, a task for ToggleFullscreen has been
+    //   posted.
+    // - OnWindowWillExitFullscreen has been called (either as a result of
+    //   ToggleFullscreen, or as a result of user interaction), but neither
+    //   OnWindowDidExitFullscreen nor OnWindowDidEnterFullscreen have been
+    //   called yet.
+    kExitFullscreenTransition,
+    // Moving the window back to its original position from before it entered
+    // fullscreen.
+    kWindowedRestoringOriginalFrame,
+    // The window has been closed.
+    kClosed,
+  };
+  struct PendingState {
+    bool is_fullscreen = false;
+    int64_t display_id = display::kInvalidDisplayId;
+  };
+
+  // Move the window to `target_display_id`, and then post a task to go
+  // fullscreen.
+  void MoveToTargetDisplayThenToggleFullscreen(int64_t target_display_id);
+
+  // Set the window's frame back to `windowed_frame_`, and then return to
+  // the kWindowed state.
+  void RestoreWindowedFrame();
+
+  // Helper function wrapping -[NSWindow toggleFullscreen:].
+  void ToggleFullscreen();
+
+  // If not currently in transition, consume `pending_state_` and start a
+  // transition to the state it specifies.
+  void HandlePendingState();
+
+  // If there exists a deferred close, then close the window, set the
+  // current state to kClosed, and return true.
+  bool HandleDeferredClose();
+
+  // Set `state` to `new_state`, and invalidate any posted tasks. Posted tasks
+  // exist to transition from the current state to a new state, and so if the
+  // current state changes, then those tasks are no longer applicable.
+  void SetStateAndCancelPostedTasks(State new_state);
+
+  State state_ = State::kWindowed;
+
+  // If a call to EnterFullscreen or ExitFullscreen happens during a
+  // transition, then that final requested state is stored in `pending_state_`.
+  absl::optional<PendingState> pending_state_;
+
+  // If we call setFrame while in fullscreen transitions, then we will need to
+  // restore the original window frame when we return to windowed mode. We save
+  // that original frame in `windowed_frame_`, and set set
+  // `restore_windowed_frame_` to true if we call setFrame.
+  bool restore_windowed_frame_ = false;
+  absl::optional<gfx::Rect> windowed_frame_;
+
+  // Trying to close an NSWindow during a fullscreen transition will cause the
+  // window to lock up. Use this to track if CloseWindow was called during a
+  // fullscreen transition, to defer the -[NSWindow close] call until the
+  // transition is complete.
+  // https://crbug.com/945237
+  bool has_deferred_window_close_ = false;
+
+  // Weak, owns `this`.
+  const raw_ptr<Client> client_;
+  base::WeakPtrFactory<NativeWidgetNSWindowFullscreenController> weak_factory_{
+      this};
+};
+
+}  // namespace remote_cocoa
+
+#endif  // COMPONENTS_REMOTE_COCOA_APP_SHIM_NATIVE_WIDGET_NS_WINDOW_FULLSCREEN_CONTROLLER_H_
diff --git a/components/remote_cocoa/app_shim/native_widget_ns_window_fullscreen_controller.mm b/components/remote_cocoa/app_shim/native_widget_ns_window_fullscreen_controller.mm
new file mode 100644
index 0000000..b7d972d8
--- /dev/null
+++ b/components/remote_cocoa/app_shim/native_widget_ns_window_fullscreen_controller.mm
@@ -0,0 +1,312 @@
+// 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 "components/remote_cocoa/app_shim/native_widget_ns_window_fullscreen_controller.h"
+
+#include "base/logging.h"
+#include "base/threading/thread_task_runner_handle.h"
+
+namespace remote_cocoa {
+
+NativeWidgetNSWindowFullscreenController::
+    NativeWidgetNSWindowFullscreenController(Client* client)
+    : client_(client) {}
+
+NativeWidgetNSWindowFullscreenController::
+    ~NativeWidgetNSWindowFullscreenController() {}
+
+void NativeWidgetNSWindowFullscreenController::EnterFullscreen(
+    int64_t target_display_id) {
+  // Early-out for no-ops.
+  if (state_ == State::kFullscreen) {
+    if (target_display_id == display::kInvalidDisplayId ||
+        target_display_id == client_->FullscreenControllerGetDisplayId()) {
+      return;
+    }
+  }
+
+  // If we are starting a new transition, then notify `client_`.
+  if (!IsInFullscreenTransition()) {
+    if (!windowed_frame_)
+      windowed_frame_ = client_->FullscreenControllerGetFrame();
+    client_->FullscreenControllerTransitionStart(true);
+  }
+
+  pending_state_ = PendingState();
+  pending_state_->is_fullscreen = true;
+  pending_state_->display_id = target_display_id;
+  HandlePendingState();
+  DCHECK(IsInFullscreenTransition());
+}
+
+void NativeWidgetNSWindowFullscreenController::ExitFullscreen() {
+  // Early-out for no-ops.
+  if (state_ == State::kWindowed)
+    return;
+
+  // If we are starting a new transition, then notify `client_`.
+  if (!IsInFullscreenTransition())
+    client_->FullscreenControllerTransitionStart(false);
+
+  pending_state_ = PendingState();
+  pending_state_->is_fullscreen = false;
+  HandlePendingState();
+  DCHECK(IsInFullscreenTransition());
+}
+
+void NativeWidgetNSWindowFullscreenController::
+    MoveToTargetDisplayThenToggleFullscreen(int64_t target_display_id) {
+  DCHECK_EQ(state_, State::kWindowedMovingToFullscreenTarget);
+
+  gfx::Rect display_frame =
+      client_->FullscreenControllerGetFrameForDisplay(target_display_id);
+  base::TimeDelta animation_time;
+  if (!display_frame.IsEmpty()) {
+    restore_windowed_frame_ = true;
+    client_->FullscreenControllerSetFrame(display_frame, animation_time);
+  }
+
+  // Calling toggleFullscreen immediately after calling setFrame causes the
+  // fullscreen toggle to happen on the target display, and does not cause
+  // unexpected focus losses. This is the desired behavior. Were this not
+  // the case, we could consider not posting ToggleFullscreen until after
+  // `animation_time` has completed and we have received the notification
+  // NSWorkspaceActiveDisplayDidChangeNotification.
+  // https://crbug.com/1210548#c27
+  SetStateAndCancelPostedTasks(State::kEnterFullscreenTransition);
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE,
+      base::BindOnce(
+          &NativeWidgetNSWindowFullscreenController::ToggleFullscreen,
+          weak_factory_.GetWeakPtr()));
+}
+
+void NativeWidgetNSWindowFullscreenController::RestoreWindowedFrame() {
+  DCHECK_EQ(state_, State::kWindowedRestoringOriginalFrame);
+  DCHECK(restore_windowed_frame_);
+  DCHECK(windowed_frame_);
+
+  base::TimeDelta animation_time;
+  client_->FullscreenControllerSetFrame(windowed_frame_.value(),
+                                        animation_time);
+  restore_windowed_frame_ = false;
+  windowed_frame_.reset();
+
+  // TODO(https://crbug.com/1302857): Consider not transitioning the state
+  // until `animation_time` has elapsed, and the window has been restored
+  // to its original position.
+  SetStateAndCancelPostedTasks(State::kWindowed);
+  HandlePendingState();
+  if (!IsInFullscreenTransition()) {
+    client_->FullscreenControllerTransitionComplete(
+        /*target_fullscreen_state=*/false);
+  }
+}
+
+void NativeWidgetNSWindowFullscreenController::ToggleFullscreen() {
+  // Note that OnWindowWillEnterFullscreen or OnWindowWillExitFullscreen will
+  // be called within the below call.
+  client_->FullscreenControllerToggleFullscreen();
+}
+
+bool NativeWidgetNSWindowFullscreenController::CanResize() const {
+  // Don't modify the size constraints or fullscreen collection behavior while
+  // in fullscreen or during a transition. OnFullscreenTransitionComplete will
+  // reset these after leaving fullscreen.
+  return state_ == State::kWindowed;
+}
+
+void NativeWidgetNSWindowFullscreenController::SetStateAndCancelPostedTasks(
+    State new_state) {
+  weak_factory_.InvalidateWeakPtrs();
+  state_ = new_state;
+}
+
+void NativeWidgetNSWindowFullscreenController::OnWindowWantsToClose() {
+  if (state_ == State::kEnterFullscreenTransition ||
+      state_ == State::kExitFullscreenTransition) {
+    has_deferred_window_close_ = true;
+  }
+}
+
+void NativeWidgetNSWindowFullscreenController::OnWindowWillClose() {
+  // If a window closes while in a fullscreen transition, then the window will
+  // hang in a zombie-like state.
+  // https://crbug.com/945237
+  if (state_ != State::kWindowed && state_ != State::kFullscreen) {
+    DLOG(ERROR) << "-[NSWindow close] while in fullscreen transition will "
+                   "trigger zombie windows.";
+  }
+}
+
+void NativeWidgetNSWindowFullscreenController::OnWindowWillEnterFullscreen() {
+  // If we are starting a new transition, then notify `client_`.
+  if (!IsInFullscreenTransition()) {
+    if (!windowed_frame_)
+      windowed_frame_ = client_->FullscreenControllerGetFrame();
+    client_->FullscreenControllerTransitionStart(true);
+  }
+
+  SetStateAndCancelPostedTasks(State::kEnterFullscreenTransition);
+  DCHECK(IsInFullscreenTransition());
+}
+
+void NativeWidgetNSWindowFullscreenController::OnWindowDidEnterFullscreen() {
+  if (HandleDeferredClose())
+    return;
+  if (state_ == State::kExitFullscreenTransition) {
+    // If transitioning out of fullscreen failed, then just remain in
+    // fullscreen. Note that `pending_state_` could have been left present for a
+    // fullscreen-to-fullscreen-on-another-display transition. If it looks like
+    // we are in that situation, reset `pending_state_`.
+    if (pending_state_ && pending_state_->is_fullscreen)
+      pending_state_.reset();
+  }
+  SetStateAndCancelPostedTasks(State::kFullscreen);
+  HandlePendingState();
+  if (!IsInFullscreenTransition()) {
+    client_->FullscreenControllerTransitionComplete(
+        /*target_fullscreen_state=*/true);
+  }
+}
+
+void NativeWidgetNSWindowFullscreenController::OnWindowWillExitFullscreen() {
+  // If we are starting a new transition, then notify `client_`.
+  if (!IsInFullscreenTransition())
+    client_->FullscreenControllerTransitionStart(false);
+
+  SetStateAndCancelPostedTasks(State::kExitFullscreenTransition);
+  DCHECK(IsInFullscreenTransition());
+}
+
+void NativeWidgetNSWindowFullscreenController::OnWindowDidExitFullscreen() {
+  if (HandleDeferredClose())
+    return;
+  SetStateAndCancelPostedTasks(State::kWindowed);
+  HandlePendingState();
+  if (!IsInFullscreenTransition()) {
+    client_->FullscreenControllerTransitionComplete(
+        /*actual_fullscreen_state=*/false);
+  }
+}
+
+void NativeWidgetNSWindowFullscreenController::HandlePendingState() {
+  // If in kWindowed or kFullscreen, then consume `pending_state_`.
+  switch (state_) {
+    case State::kClosed:
+      pending_state_.reset();
+      return;
+    case State::kWindowed:
+      if (pending_state_ && pending_state_->is_fullscreen) {
+        if (pending_state_->display_id != display::kInvalidDisplayId) {
+          // Handle entering fullscreen on a specified display (note that this
+          // applies to entering fullscreen on the default display, if specified
+          // explicitly).
+          SetStateAndCancelPostedTasks(
+              State::kWindowedMovingToFullscreenTarget);
+          base::ThreadTaskRunnerHandle::Get()->PostTask(
+              FROM_HERE,
+              base::BindOnce(&NativeWidgetNSWindowFullscreenController::
+                                 MoveToTargetDisplayThenToggleFullscreen,
+                             weak_factory_.GetWeakPtr(),
+                             pending_state_->display_id));
+        } else {
+          // Handle entering fullscreen on the default display.
+          SetStateAndCancelPostedTasks(State::kEnterFullscreenTransition);
+          base::ThreadTaskRunnerHandle::Get()->PostTask(
+              FROM_HERE,
+              base::BindOnce(
+                  &NativeWidgetNSWindowFullscreenController::ToggleFullscreen,
+                  weak_factory_.GetWeakPtr()));
+        }
+      } else if (restore_windowed_frame_) {
+        // Handle returning to the kWindowed state after having been fullscreen
+        // and having called setFrame during some transition. It is necessary
+        // to restore the original frame prior to having entered fullscreen.
+        SetStateAndCancelPostedTasks(State::kWindowedRestoringOriginalFrame);
+        base::ThreadTaskRunnerHandle::Get()->PostTask(
+            FROM_HERE,
+            base::BindOnce(
+                &NativeWidgetNSWindowFullscreenController::RestoreWindowedFrame,
+                weak_factory_.GetWeakPtr()));
+      } else {
+        // Handle returning to the kWindowed state without having called
+        // setFrame.
+        windowed_frame_.reset();
+      }
+      // Always reset `pending_state_` when handling kWindowed state.
+      pending_state_.reset();
+      return;
+    case State::kFullscreen:
+      if (pending_state_) {
+        if (pending_state_->is_fullscreen) {
+          // If `pending_state_` is a no-op, then reset it.
+          if (pending_state_->display_id == display::kInvalidDisplayId ||
+              pending_state_->display_id ==
+                  client_->FullscreenControllerGetDisplayId()) {
+            pending_state_.reset();
+            return;
+          }
+          // Leave `pending_state_` in place. It will be consumed when we
+          // come through here again via OnWindowDidExitFullscreen (or
+          // via OnWindowDidEnterFullscreen, if we fail to exit fullscreen).
+        } else {
+          pending_state_.reset();
+        }
+        SetStateAndCancelPostedTasks(State::kExitFullscreenTransition);
+        base::ThreadTaskRunnerHandle::Get()->PostTask(
+            FROM_HERE,
+            base::BindOnce(
+                &NativeWidgetNSWindowFullscreenController::ToggleFullscreen,
+                weak_factory_.GetWeakPtr()));
+      }
+      return;
+    default:
+      // Leave `pending_state_` unchanged. It will be re-examined when our
+      // transition completes.
+      break;
+  }
+}
+
+bool NativeWidgetNSWindowFullscreenController::HandleDeferredClose() {
+  CHECK_NE(state_, State::kClosed);
+  if (has_deferred_window_close_) {
+    SetStateAndCancelPostedTasks(State::kClosed);
+    // Note that `this` may be deleted by the below call.
+    client_->FullscreenControllerCloseWindow();
+    return true;
+  }
+  return false;
+}
+
+bool NativeWidgetNSWindowFullscreenController::GetTargetFullscreenState()
+    const {
+  if (pending_state_)
+    return pending_state_->is_fullscreen;
+  switch (state_) {
+    case State::kWindowed:
+    case State::kWindowedRestoringOriginalFrame:
+    case State::kExitFullscreenTransition:
+    case State::kClosed:
+      return false;
+    case State::kWindowedMovingToFullscreenTarget:
+    case State::kEnterFullscreenTransition:
+    case State::kFullscreen:
+      return true;
+  }
+}
+
+bool NativeWidgetNSWindowFullscreenController::IsInFullscreenTransition()
+    const {
+  switch (state_) {
+    case State::kWindowed:
+    case State::kFullscreen:
+    case State::kClosed:
+      return false;
+    default:
+      return true;
+  }
+}
+
+}  // namespace remote_cocoa
diff --git a/components/remote_cocoa/app_shim/views_nswindow_delegate.mm b/components/remote_cocoa/app_shim/views_nswindow_delegate.mm
index 4e419c6..ede47ec 100644
--- a/components/remote_cocoa/app_shim/views_nswindow_delegate.mm
+++ b/components/remote_cocoa/app_shim/views_nswindow_delegate.mm
@@ -10,6 +10,7 @@
 #include "base/threading/thread_task_runner_handle.h"
 #import "components/remote_cocoa/app_shim/bridged_content_view.h"
 #import "components/remote_cocoa/app_shim/native_widget_ns_window_bridge.h"
+#include "components/remote_cocoa/app_shim/native_widget_ns_window_fullscreen_controller.h"
 #include "components/remote_cocoa/app_shim/native_widget_ns_window_host_helper.h"
 #include "components/remote_cocoa/common/native_widget_ns_window_host.mojom.h"
 #include "ui/gfx/geometry/resize_utils.h"
@@ -204,15 +205,24 @@
 }
 
 - (void)windowWillEnterFullScreen:(NSNotification*)notification {
-  _parent->OnFullscreenTransitionStart(true);
+  if (_parent->fullscreen_controller())
+    _parent->fullscreen_controller()->OnWindowWillEnterFullscreen();
+  else
+    _parent->OnFullscreenTransitionStart(true);
 }
 
 - (void)windowDidEnterFullScreen:(NSNotification*)notification {
-  _parent->OnFullscreenTransitionComplete(true);
+  if (_parent->fullscreen_controller())
+    _parent->fullscreen_controller()->OnWindowDidEnterFullscreen();
+  else
+    _parent->OnFullscreenTransitionComplete(true);
 }
 
 - (void)windowWillExitFullScreen:(NSNotification*)notification {
-  _parent->OnFullscreenTransitionStart(false);
+  if (_parent->fullscreen_controller())
+    _parent->fullscreen_controller()->OnWindowWillExitFullscreen();
+  else
+    _parent->OnFullscreenTransitionStart(false);
 }
 
 - (void)windowDidExitFullScreen:(NSNotification*)notification {
@@ -227,7 +237,10 @@
                                     afterDelay:0];
   }
 
-  _parent->OnFullscreenTransitionComplete(false);
+  if (_parent->fullscreen_controller())
+    _parent->fullscreen_controller()->OnWindowDidExitFullscreen();
+  else
+    _parent->OnFullscreenTransitionComplete(false);
 }
 
 // Allow non-resizable windows (without NSResizableWindowMask) to fill the
diff --git a/components/services/app_service/public/cpp/app_update.cc b/components/services/app_service/public/cpp/app_update.cc
index 26ab0af..225c22e 100644
--- a/components/services/app_service/public/cpp/app_update.cc
+++ b/components/services/app_service/public/cpp/app_update.cc
@@ -109,8 +109,6 @@
     state->install_time = delta->install_time;
   }
   if (!delta->permissions.empty()) {
-    DCHECK(state->permissions.empty() ||
-           (delta->permissions.size() == state->permissions.size()));
     state->permissions.clear();
     ::CloneMojomPermissions(delta->permissions, &state->permissions);
   }
@@ -211,8 +209,6 @@
   SET_OPTIONAL_VALUE(install_time);
 
   if (!delta->permissions.empty()) {
-    DCHECK(state->permissions.empty() ||
-           (delta->permissions.size() == state->permissions.size()));
     state->permissions.clear();
     state->permissions = ClonePermissions(delta->permissions);
   }
diff --git a/components/services/app_service/public/cpp/intent_util.cc b/components/services/app_service/public/cpp/intent_util.cc
index e4679cb..f173d9e1 100644
--- a/components/services/app_service/public/cpp/intent_util.cc
+++ b/components/services/app_service/public/cpp/intent_util.cc
@@ -80,74 +80,6 @@
          intent_component == filter_component;
 }
 
-// TODO(crbug.com/1092784): Handle file path with extension with mime type.
-// Unlike Android mime type matching logic, if the intent mime type has *, it
-// can only match with *, not anything. The reason for this is the way we find
-// the common mime type for multiple files. It uses * to represent more than one
-// types in the list, which will cause an issue if we treat that as we want to
-// match with any filter. e.g. If we select a .zip, .jep and a .txt, the common
-// mime type will be */*, with Android matching logic, it will match with filter
-// that has mime type video, which is not what we expected.
-bool MimeTypeMatched(const std::string& intent_mime_type,
-                     const std::string& filter_mime_type) {
-  std::vector<std::string> intent_components =
-      base::SplitString(intent_mime_type, kMimeTypeSeparator,
-                        base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
-
-  std::vector<std::string> filter_components =
-      base::SplitString(filter_mime_type, kMimeTypeSeparator,
-                        base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
-
-  if (intent_components.size() > kMimeTypeComponentSize ||
-      filter_components.size() > kMimeTypeComponentSize ||
-      intent_components.size() == 0 || filter_components.size() == 0) {
-    return false;
-  }
-
-  // If the filter component only contain main mime type, check if main type
-  // matches.
-  if (filter_components.size() == 1) {
-    return ComponentMatched(intent_components[0], filter_components[0]);
-  }
-
-  // If the intent component only contain main mime type, complete the
-  // mime type.
-  if (intent_components.size() == 1) {
-    intent_components.push_back(kWildCardAny);
-  }
-
-  // Both intent and intent filter can use wildcard for mime type.
-  for (size_t i = 0; i < kMimeTypeComponentSize; i++) {
-    if (!ComponentMatched(intent_components[i], filter_components[i])) {
-      return false;
-    }
-  }
-  return true;
-}
-
-bool ExtensionMatched(const std::string& file_name,
-                      const std::string& filter_extension) {
-  if (filter_extension == kWildCardAny)
-    return true;
-
-  // Normalise to have a preceding ".".
-  std::string normalised_extension = filter_extension;
-  if (filter_extension.length() > 0 && filter_extension[0] != '.') {
-    normalised_extension = '.' + normalised_extension;
-  }
-  base::FilePath::StringType handler_extension =
-      base::FilePath::FromUTF8Unsafe(normalised_extension).Extension();
-
-  base::FilePath file_path = base::FilePath::FromUTF8Unsafe(file_name);
-
-  // Accept files whose extension or combined extension (e.g. ".tar.gz")
-  // match the filter extension.
-  return base::FilePath::CompareEqualIgnoreCase(handler_extension,
-                                                file_path.Extension()) ||
-         base::FilePath::CompareEqualIgnoreCase(handler_extension,
-                                                file_path.FinalExtension());
-}
-
 }  // namespace
 
 namespace apps_util {
@@ -253,6 +185,33 @@
   return intent;
 }
 
+bool ConditionValueMatches(const std::string& value,
+                           const apps::ConditionValuePtr& condition_value) {
+  switch (condition_value->match_type) {
+    // Fallthrough as kNone and kLiteral has same matching type.
+    case apps::PatternMatchType::kNone:
+    case apps::PatternMatchType::kLiteral:
+      return value == condition_value->value;
+    case apps::PatternMatchType::kPrefix:
+      return base::StartsWith(value, condition_value->value,
+                              base::CompareCase::INSENSITIVE_ASCII);
+    case apps::PatternMatchType::kSuffix:
+      return base::EndsWith(value, condition_value->value,
+                            base::CompareCase::INSENSITIVE_ASCII);
+    case apps::PatternMatchType::kGlob:
+      return MatchGlob(value, condition_value->value);
+    case apps::PatternMatchType::kMimeType:
+      // kMimeType as a match for kFile is handled in FileMatchesConditionValue.
+      return MimeTypeMatched(value, condition_value->value);
+    case apps::PatternMatchType::kFileExtension:
+    case apps::PatternMatchType::kIsDirectory: {
+      // Handled in FileMatchesConditionValue.
+      NOTREACHED();
+      return false;
+    }
+  }
+}
+
 bool ConditionValueMatches(
     const std::string& value,
     const apps::mojom::ConditionValuePtr& condition_value) {
@@ -527,6 +486,66 @@
 #undef GET_CHAR
 }
 
+bool MimeTypeMatched(const std::string& intent_mime_type,
+                     const std::string& filter_mime_type) {
+  std::vector<std::string> intent_components =
+      base::SplitString(intent_mime_type, kMimeTypeSeparator,
+                        base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
+
+  std::vector<std::string> filter_components =
+      base::SplitString(filter_mime_type, kMimeTypeSeparator,
+                        base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
+
+  if (intent_components.size() > kMimeTypeComponentSize ||
+      filter_components.size() > kMimeTypeComponentSize ||
+      intent_components.size() == 0 || filter_components.size() == 0) {
+    return false;
+  }
+
+  // If the filter component only contain main mime type, check if main type
+  // matches.
+  if (filter_components.size() == 1) {
+    return ComponentMatched(intent_components[0], filter_components[0]);
+  }
+
+  // If the intent component only contain main mime type, complete the
+  // mime type.
+  if (intent_components.size() == 1) {
+    intent_components.push_back(kWildCardAny);
+  }
+
+  // Both intent and intent filter can use wildcard for mime type.
+  for (size_t i = 0; i < kMimeTypeComponentSize; i++) {
+    if (!ComponentMatched(intent_components[i], filter_components[i])) {
+      return false;
+    }
+  }
+  return true;
+}
+
+bool ExtensionMatched(const std::string& file_name,
+                      const std::string& filter_extension) {
+  if (filter_extension == kWildCardAny)
+    return true;
+
+  // Normalise to have a preceding ".".
+  std::string normalised_extension = filter_extension;
+  if (filter_extension.length() > 0 && filter_extension[0] != '.') {
+    normalised_extension = '.' + normalised_extension;
+  }
+  base::FilePath::StringType handler_extension =
+      base::FilePath::FromUTF8Unsafe(normalised_extension).Extension();
+
+  base::FilePath file_path = base::FilePath::FromUTF8Unsafe(file_name);
+
+  // Accept files whose extension or combined extension (e.g. ".tar.gz")
+  // match the filter extension.
+  return base::FilePath::CompareEqualIgnoreCase(handler_extension,
+                                                file_path.Extension()) ||
+         base::FilePath::CompareEqualIgnoreCase(handler_extension,
+                                                file_path.FinalExtension());
+}
+
 bool OnlyShareToDrive(const apps::mojom::IntentPtr& intent) {
   return IsShareIntent(intent) && intent->drive_share_url &&
          !intent->share_text && !intent->files;
diff --git a/components/services/app_service/public/cpp/intent_util.h b/components/services/app_service/public/cpp/intent_util.h
index e7c7c7e..2b3cfa1 100644
--- a/components/services/app_service/public/cpp/intent_util.h
+++ b/components/services/app_service/public/cpp/intent_util.h
@@ -9,6 +9,7 @@
 
 #include <string>
 
+#include "components/services/app_service/public/cpp/intent_filter.h"
 #include "components/services/app_service/public/mojom/types.mojom.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "url/gurl.h"
@@ -75,6 +76,13 @@
 
 // Return true if |value| matches with the |condition_value|, based on the
 // pattern match type in the |condition_value|.
+bool ConditionValueMatches(const std::string& value,
+                           const apps::ConditionValuePtr& condition_value);
+
+// Return true if |value| matches with the |condition_value|, based on the
+// pattern match type in the |condition_value|.
+// TODO(crbug.com/1253250): Remove this function after migrating to non-mojo
+// AppService.
 bool ConditionValueMatches(
     const std::string& value,
     const apps::mojom::ConditionValuePtr& condition_value);
@@ -108,6 +116,20 @@
 // https://android.googlesource.com/platform/frameworks/base.git/+/e93165456c3c28278f275566bd90bfbcf1a0e5f7/core/java/android/os/PatternMatcher.java#186
 bool MatchGlob(const std::string& value, const std::string& pattern);
 
+// TODO(crbug.com/1092784): Handle file path with extension with mime type.
+// Unlike Android mime type matching logic, if the intent mime type has *, it
+// can only match with *, not anything. The reason for this is the way we find
+// the common mime type for multiple files. It uses * to represent more than one
+// types in the list, which will cause an issue if we treat that as we want to
+// match with any filter. e.g. If we select a .zip, .jep and a .txt, the common
+// mime type will be */*, with Android matching logic, it will match with filter
+// that has mime type video, which is not what we expected.
+bool MimeTypeMatched(const std::string& intent_mime_type,
+                     const std::string& filter_mime_type);
+
+bool ExtensionMatched(const std::string& file_name,
+                      const std::string& filter_extension);
+
 // Check if the intent only mean to share to Google Drive.
 bool OnlyShareToDrive(const apps::mojom::IntentPtr& intent);
 
diff --git a/components/services/app_service/public/cpp/intent_util_unittest.cc b/components/services/app_service/public/cpp/intent_util_unittest.cc
index 0b097de0..4d43c4db 100644
--- a/components/services/app_service/public/cpp/intent_util_unittest.cc
+++ b/components/services/app_service/public/cpp/intent_util_unittest.cc
@@ -111,19 +111,38 @@
 }
 
 // Test Condition Value match with different pattern match type.
-TEST_F(IntentUtilTest, NoneMatchType) {
+// TODO(crbug.com/1253250): Remove after migrating to non-mojo AppService.
+TEST_F(IntentUtilTest, NoneMatchTypeMojom) {
   auto condition_value = apps_util::MakeConditionValue(
       "https", apps::mojom::PatternMatchType::kNone);
   EXPECT_TRUE(apps_util::ConditionValueMatches("https", condition_value));
   EXPECT_FALSE(apps_util::ConditionValueMatches("http", condition_value));
 }
-TEST_F(IntentUtilTest, LiteralMatchType) {
+
+TEST_F(IntentUtilTest, NoneMatchType) {
+  auto condition_value = std::make_unique<apps::ConditionValue>(
+      "https", apps::PatternMatchType::kNone);
+  EXPECT_TRUE(apps_util::ConditionValueMatches("https", condition_value));
+  EXPECT_FALSE(apps_util::ConditionValueMatches("http", condition_value));
+}
+
+// TODO(crbug.com/1253250): Remove after migrating to non-mojo AppService.
+TEST_F(IntentUtilTest, LiteralMatchTypeMojom) {
   auto condition_value = apps_util::MakeConditionValue(
       "https", apps::mojom::PatternMatchType::kLiteral);
   EXPECT_TRUE(apps_util::ConditionValueMatches("https", condition_value));
   EXPECT_FALSE(apps_util::ConditionValueMatches("http", condition_value));
 }
-TEST_F(IntentUtilTest, PrefixMatchType) {
+
+TEST_F(IntentUtilTest, LiteralMatchType) {
+  auto condition_value = std::make_unique<apps::ConditionValue>(
+      "https", apps::PatternMatchType::kLiteral);
+  EXPECT_TRUE(apps_util::ConditionValueMatches("https", condition_value));
+  EXPECT_FALSE(apps_util::ConditionValueMatches("http", condition_value));
+}
+
+// TODO(crbug.com/1253250): Remove after migrating to non-mojo AppService.
+TEST_F(IntentUtilTest, PrefixMatchTypeMojom) {
   auto condition_value = apps_util::MakeConditionValue(
       "/ab", apps::mojom::PatternMatchType::kPrefix);
   EXPECT_TRUE(apps_util::ConditionValueMatches("/abc", condition_value));
@@ -131,7 +150,16 @@
   EXPECT_FALSE(apps_util::ConditionValueMatches("/d", condition_value));
 }
 
-TEST_F(IntentUtilTest, SuffixMatchType) {
+TEST_F(IntentUtilTest, PrefixMatchType) {
+  auto condition_value = std::make_unique<apps::ConditionValue>(
+      "/ab", apps::PatternMatchType::kPrefix);
+  EXPECT_TRUE(apps_util::ConditionValueMatches("/abc", condition_value));
+  EXPECT_TRUE(apps_util::ConditionValueMatches("/ABC", condition_value));
+  EXPECT_FALSE(apps_util::ConditionValueMatches("/d", condition_value));
+}
+
+// TODO(crbug.com/1253250): Remove after migrating to non-mojo AppService.
+TEST_F(IntentUtilTest, SuffixMatchTypeMojom) {
   auto condition_value = apps_util::MakeConditionValue(
       ".google.com", apps::mojom::PatternMatchType::kSuffix);
   EXPECT_TRUE(
@@ -145,7 +173,22 @@
   EXPECT_FALSE(apps_util::ConditionValueMatches("other", condition_value));
 }
 
-TEST_F(IntentUtilTest, GlobMatchType) {
+TEST_F(IntentUtilTest, SuffixMatchType) {
+  auto condition_value = std::make_unique<apps::ConditionValue>(
+      ".google.com", apps::PatternMatchType::kSuffix);
+  EXPECT_TRUE(
+      apps_util::ConditionValueMatches("en.google.com", condition_value));
+  EXPECT_TRUE(
+      apps_util::ConditionValueMatches("es.google.com", condition_value));
+  EXPECT_TRUE(apps_util::ConditionValueMatches(".google.com", condition_value));
+  EXPECT_FALSE(
+      apps_util::ConditionValueMatches("es.google.org", condition_value));
+  EXPECT_FALSE(apps_util::ConditionValueMatches("google.com", condition_value));
+  EXPECT_FALSE(apps_util::ConditionValueMatches("other", condition_value));
+}
+
+// TODO(crbug.com/1253250): Remove after migrating to non-mojo AppService.
+TEST_F(IntentUtilTest, GlobMatchTypeMojom) {
   auto condition_value_star = apps_util::MakeConditionValue(
       "/a*b", apps::mojom::PatternMatchType::kGlob);
   EXPECT_TRUE(apps_util::ConditionValueMatches("/b", condition_value_star));
@@ -205,6 +248,66 @@
       apps_util::ConditionValueMatches("/acb", condition_value_escape_star));
 }
 
+TEST_F(IntentUtilTest, GlobMatchType) {
+  auto condition_value_star = std::make_unique<apps::ConditionValue>(
+      "/a*b", apps::PatternMatchType::kGlob);
+  EXPECT_TRUE(apps_util::ConditionValueMatches("/b", condition_value_star));
+  EXPECT_TRUE(apps_util::ConditionValueMatches("/ab", condition_value_star));
+  EXPECT_TRUE(apps_util::ConditionValueMatches("/aab", condition_value_star));
+  EXPECT_TRUE(
+      apps_util::ConditionValueMatches("/aaaaaab", condition_value_star));
+  EXPECT_FALSE(apps_util::ConditionValueMatches("/aabb", condition_value_star));
+  EXPECT_FALSE(apps_util::ConditionValueMatches("/aabc", condition_value_star));
+  EXPECT_FALSE(apps_util::ConditionValueMatches("/bb", condition_value_star));
+
+  auto condition_value_dot = std::make_unique<apps::ConditionValue>(
+      "/a.b", apps::PatternMatchType::kGlob);
+  EXPECT_TRUE(apps_util::ConditionValueMatches("/aab", condition_value_dot));
+  EXPECT_TRUE(apps_util::ConditionValueMatches("/acb", condition_value_dot));
+  EXPECT_FALSE(apps_util::ConditionValueMatches("/ab", condition_value_dot));
+  EXPECT_FALSE(apps_util::ConditionValueMatches("/abd", condition_value_dot));
+  EXPECT_FALSE(apps_util::ConditionValueMatches("/abbd", condition_value_dot));
+
+  auto condition_value_dot_and_star = std::make_unique<apps::ConditionValue>(
+      "/a.*b", apps::PatternMatchType::kGlob);
+  EXPECT_TRUE(
+      apps_util::ConditionValueMatches("/aab", condition_value_dot_and_star));
+  EXPECT_TRUE(apps_util::ConditionValueMatches("/aadsfadslkjb",
+                                               condition_value_dot_and_star));
+  EXPECT_TRUE(
+      apps_util::ConditionValueMatches("/ab", condition_value_dot_and_star));
+
+  // This arguably should be true, however the algorithm is transcribed from the
+  // upstream Android codebase, which behaves like this.
+  EXPECT_FALSE(apps_util::ConditionValueMatches("/abasdfab",
+                                                condition_value_dot_and_star));
+  EXPECT_FALSE(apps_util::ConditionValueMatches("/abasdfad",
+                                                condition_value_dot_and_star));
+  EXPECT_FALSE(apps_util::ConditionValueMatches("/bbasdfab",
+                                                condition_value_dot_and_star));
+  EXPECT_FALSE(
+      apps_util::ConditionValueMatches("/a", condition_value_dot_and_star));
+  EXPECT_FALSE(
+      apps_util::ConditionValueMatches("/b", condition_value_dot_and_star));
+
+  auto condition_value_escape_dot = std::make_unique<apps::ConditionValue>(
+      "/a\\.b", apps::PatternMatchType::kGlob);
+  EXPECT_TRUE(
+      apps_util::ConditionValueMatches("/a.b", condition_value_escape_dot));
+
+  // This arguably should be false, however the transcribed is carried from the
+  // upstream Android codebase, which behaves like this.
+  EXPECT_TRUE(
+      apps_util::ConditionValueMatches("/acb", condition_value_escape_dot));
+
+  auto condition_value_escape_star = std::make_unique<apps::ConditionValue>(
+      "/a\\*b", apps::PatternMatchType::kGlob);
+  EXPECT_TRUE(
+      apps_util::ConditionValueMatches("/a*b", condition_value_escape_star));
+  EXPECT_FALSE(
+      apps_util::ConditionValueMatches("/acb", condition_value_escape_star));
+}
+
 TEST_F(IntentUtilTest, FilterMatchLevel) {
   auto filter_scheme_only = apps_util::CreateSchemeOnlyFilter("http");
   auto filter_scheme_and_host_only =
diff --git a/components/services/unzip/public/cpp/unzip_unittest.cc b/components/services/unzip/public/cpp/unzip_unittest.cc
index 8d5f6fc..c791516 100644
--- a/components/services/unzip/public/cpp/unzip_unittest.cc
+++ b/components/services/unzip/public/cpp/unzip_unittest.cc
@@ -32,28 +32,25 @@
       .AppendASCII(archive_name);
 }
 
-// Sets the number of files under |dir| in |file_count| and if
-// |some_files_empty| is not null, sets it to true if at least one of the files
-// is empty.
-void CountFiles(const base::FilePath& dir,
-                int64_t* file_count,
-                bool* some_files_empty) {
-  ASSERT_TRUE(file_count);
-  *file_count = 0;
+// Counts the number of files under |dir|. If |some_files_empty| is not null,
+// sets it to true if at least one of the files is empty.
+int CountFiles(const base::FilePath& dir, bool* some_files_empty = nullptr) {
+  int file_count = 0;
   base::FileEnumerator file_enumerator(dir, /*recursive=*/true,
                                        base::FileEnumerator::FILES);
   for (base::FilePath path = file_enumerator.Next(); !path.empty();
        path = file_enumerator.Next()) {
-    if (some_files_empty) {
-      int64_t file_size = 0;
-      ASSERT_TRUE(base::GetFileSize(path, &file_size));
-      if (file_size == 0) {
-        *some_files_empty = true;
-        some_files_empty = nullptr;  // So we don't check files again.
-      }
+    if (int64_t file_size; some_files_empty != nullptr &&
+                           base::GetFileSize(path, &file_size) &&
+                           file_size == 0) {
+      *some_files_empty = true;
+      some_files_empty = nullptr;  // So we don't check files again.
     }
-    (*file_count)++;
+
+    file_count++;
   }
+
+  return file_count;
 }
 
 class UnzipTest : public testing::Test {
@@ -123,18 +120,21 @@
   EXPECT_FALSE(DoUnzip(GetArchivePath("absent_archive.zip"), unzip_dir_));
 
   // No files should have been extracted.
-  int64_t file_count = -1;
-  CountFiles(unzip_dir_, &file_count, /*some_files_empty=*/nullptr);
-  EXPECT_EQ(0, file_count);
+  EXPECT_EQ(0, CountFiles(unzip_dir_));
 }
 
 TEST_F(UnzipTest, UnzipBadArchive) {
   EXPECT_FALSE(DoUnzip(GetArchivePath("bad_archive.zip"), unzip_dir_));
 
   // No files should have been extracted.
-  int64_t file_count = -1;
-  CountFiles(unzip_dir_, &file_count, /*some_files_empty=*/nullptr);
-  EXPECT_EQ(0, file_count);
+  EXPECT_EQ(0, CountFiles(unzip_dir_));
+}
+
+TEST_F(UnzipTest, UnzipWrongCrc) {
+  EXPECT_FALSE(DoUnzip(GetArchivePath("Wrong CRC.zip"), unzip_dir_));
+
+  // No files should have been extracted.
+  EXPECT_EQ(0, CountFiles(unzip_dir_));
 }
 
 TEST_F(UnzipTest, UnzipGoodArchive) {
@@ -142,10 +142,8 @@
 
   // Sanity check that the right number of files have been extracted and that
   // they are not empty.
-  int64_t file_count = -1;
   bool some_files_empty = false;
-  CountFiles(unzip_dir_, &file_count, /*some_files_empty=*/&some_files_empty);
-  EXPECT_EQ(8, file_count);
+  EXPECT_EQ(8, CountFiles(unzip_dir_, &some_files_empty));
   EXPECT_FALSE(some_files_empty);
 }
 
@@ -156,10 +154,8 @@
                       })));
 
   // It should only have kept the 2 text files from the archive.
-  int64_t file_count = -1;
   bool some_files_empty = false;
-  CountFiles(unzip_dir_, &file_count, /*some_files_empty=*/&some_files_empty);
-  EXPECT_EQ(2, file_count);
+  EXPECT_EQ(2, CountFiles(unzip_dir_, &some_files_empty));
   EXPECT_FALSE(some_files_empty);
 }
 
diff --git a/components/services/unzip/unzipper_impl.cc b/components/services/unzip/unzipper_impl.cc
index 04fb495c..5aa5be6f 100644
--- a/components/services/unzip/unzipper_impl.cc
+++ b/components/services/unzip/unzipper_impl.cc
@@ -16,60 +16,92 @@
 #include "components/services/filesystem/public/mojom/directory.mojom.h"
 #include "mojo/public/cpp/bindings/remote.h"
 #include "third_party/ced/src/compact_enc_det/compact_enc_det.h"
+#include "third_party/zlib/google/redact.h"
 #include "third_party/zlib/google/zip.h"
 #include "third_party/zlib/google/zip_reader.h"
 
 namespace unzip {
-
 namespace {
 
-std::string PathToMojoString(const base::FilePath& path) {
-#if BUILDFLAG(IS_WIN)
-  return base::WideToUTF8(path.value());
-#else
-  return path.value();
-#endif
-}
-
 // Modifies output_dir to point to the final directory.
 bool CreateDirectory(filesystem::mojom::Directory* output_dir,
                      const base::FilePath& path) {
   base::File::Error err = base::File::Error::FILE_OK;
-  return output_dir->OpenDirectory(PathToMojoString(path), mojo::NullReceiver(),
+  return output_dir->OpenDirectory(path.AsUTF8Unsafe(), mojo::NullReceiver(),
                                    filesystem::mojom::kFlagOpenAlways, &err) &&
          err == base::File::Error::FILE_OK;
 }
 
-std::unique_ptr<zip::WriterDelegate> MakeFileWriterDelegateNoParent(
-    filesystem::mojom::Directory* output_dir,
-    const base::FilePath& path) {
-  base::File file;
-  base::File::Error err;
-  if (!output_dir->OpenFileHandle(PathToMojoString(path),
-                                  filesystem::mojom::kFlagCreate |
-                                      filesystem::mojom::kFlagWrite |
-                                      filesystem::mojom::kFlagWriteAttributes,
-                                  &err, &file) ||
-      err != base::File::Error::FILE_OK) {
-    return nullptr;
+// A file writer that uses a mojom::Directory.
+class Writer : public zip::FileWriterDelegate {
+ public:
+  Writer(mojo::Remote<filesystem::mojom::Directory> output_dir,
+         base::FilePath path)
+      : FileWriterDelegate(base::File()),
+        owned_output_dir_(std::move(output_dir)),
+        output_dir_(owned_output_dir_.get()),
+        path_(std::move(path)) {
+    DCHECK(output_dir_);
   }
-  return std::make_unique<zip::FileWriterDelegate>(std::move(file));
-}
+
+  Writer(filesystem::mojom::Directory* output_dir, base::FilePath path)
+      : FileWriterDelegate(base::File()),
+        output_dir_(output_dir),
+        path_(std::move(path)) {
+    DCHECK(output_dir_);
+  }
+
+  // Creates the output file.
+  bool PrepareOutput() override {
+    if (base::File::Error err;
+        !output_dir_->OpenFileHandle(
+            path_.AsUTF8Unsafe(),
+            filesystem::mojom::kFlagCreate | filesystem::mojom::kFlagWrite |
+                filesystem::mojom::kFlagWriteAttributes,
+            &err, &owned_file_) ||
+        err != base::File::Error::FILE_OK) {
+      LOG(ERROR) << "Cannot create extracted file " << zip::Redact(path_);
+      return false;
+    }
+
+    return FileWriterDelegate::PrepareOutput();
+  }
+
+  // Deletes the output file.
+  void OnError() override {
+    FileWriterDelegate::OnError();
+    owned_file_.Close();
+
+    if (base::File::Error err;
+        !output_dir_->Delete(path_.AsUTF8Unsafe(), 0, &err) ||
+        err != base::File::Error::FILE_OK) {
+      LOG(ERROR) << "Cannot delete extracted file " << zip::Redact(path_);
+    }
+  }
+
+ private:
+  const mojo::Remote<filesystem::mojom::Directory> owned_output_dir_;
+  filesystem::mojom::Directory* const output_dir_;
+  const base::FilePath path_;
+};
 
 std::unique_ptr<zip::WriterDelegate> MakeFileWriterDelegate(
     filesystem::mojom::Directory* output_dir,
     const base::FilePath& path) {
   if (path == path.BaseName())
-    return MakeFileWriterDelegateNoParent(output_dir, path);
+    return std::make_unique<Writer>(output_dir, path);
+
   mojo::Remote<filesystem::mojom::Directory> parent;
-  base::File::Error err;
-  if (!output_dir->OpenDirectory(PathToMojoString(path.DirName()),
+
+  if (base::File::Error err;
+      !output_dir->OpenDirectory(path.DirName().AsUTF8Unsafe(),
                                  parent.BindNewPipeAndPassReceiver(),
                                  filesystem::mojom::kFlagOpenAlways, &err) ||
       err != base::File::Error::FILE_OK) {
     return nullptr;
   }
-  return MakeFileWriterDelegateNoParent(parent.get(), path.BaseName());
+
+  return std::make_unique<Writer>(std::move(parent), path.BaseName());
 }
 
 bool Filter(const mojo::Remote<mojom::UnzipFilter>& filter,
@@ -128,8 +160,10 @@
     mojo::PendingRemote<mojom::UnzipFilter> filter_remote,
     UnzipCallback callback) {
   DCHECK(zip_file.IsValid());
+
   mojo::Remote<filesystem::mojom::Directory> output_dir(
       std::move(output_dir_remote));
+
   zip::FilterCallback filter_cb;
   if (filter_remote) {
     filter_cb = base::BindRepeating(
diff --git a/components/test/data/unzip_service/Wrong CRC.zip b/components/test/data/unzip_service/Wrong CRC.zip
new file mode 100644
index 0000000..ee9a1ef
--- /dev/null
+++ b/components/test/data/unzip_service/Wrong CRC.zip
Binary files differ
diff --git a/content/browser/android/battery_metrics.cc b/content/browser/android/battery_metrics.cc
index fcb42bb..be532dd9 100644
--- a/content/browser/android/battery_metrics.cc
+++ b/content/browser/android/battery_metrics.cc
@@ -4,6 +4,7 @@
 
 #include "content/browser/android/battery_metrics.h"
 
+#include "base/android/application_status_listener.h"
 #include "base/android/radio_utils.h"
 #include "base/bind.h"
 #include "base/feature_list.h"
@@ -13,8 +14,10 @@
 #include "base/metrics/histogram_macros.h"
 #include "base/no_destructor.h"
 #include "base/power_monitor/power_monitor.h"
+#include "base/trace_event/application_state_proto_android.h"
 #include "base/trace_event/trace_event.h"
 #include "base/trace_event/typed_macros.h"
+#include "base/tracing/protos/chrome_track_event.pbzero.h"
 #include "content/browser/web_contents/web_contents_impl.h"
 #include "net/android/network_library.h"
 #include "net/android/traffic_stats.h"
@@ -27,6 +30,22 @@
 namespace content {
 namespace {
 
+perfetto::protos::pbzero::DeviceThermalState ToTraceEnum(
+    base::PowerThermalObserver::DeviceThermalState state) {
+  switch (state) {
+    case base::PowerThermalObserver::DeviceThermalState::kUnknown:
+      return perfetto::protos::pbzero::DEVICE_THERMAL_STATE_UNKNOWN;
+    case base::PowerThermalObserver::DeviceThermalState::kNominal:
+      return perfetto::protos::pbzero::DEVICE_THERMAL_STATE_NOMINAL;
+    case base::PowerThermalObserver::DeviceThermalState::kFair:
+      return perfetto::protos::pbzero::DEVICE_THERMAL_STATE_FAIR;
+    case base::PowerThermalObserver::DeviceThermalState::kSerious:
+      return perfetto::protos::pbzero::DEVICE_THERMAL_STATE_SERIOUS;
+    case base::PowerThermalObserver::DeviceThermalState::kCritical:
+      return perfetto::protos::pbzero::DEVICE_THERMAL_STATE_CRITICAL;
+  }
+}
+
 void Report30SecondRadioUsage(int64_t tx_bytes, int64_t rx_bytes, int wakeups) {
   if (!base::android::RadioUtils::IsSupported())
     return;
@@ -224,9 +243,16 @@
 }
 
 void AndroidBatteryMetrics::OnThermalStateChange(DeviceThermalState new_state) {
-  TRACE_EVENT_INSTANT("power", "OnThermalStateChange",
-                      perfetto::Track::Global(0), "new_state", new_state,
-                      "app_visible", app_visible_);
+  TRACE_EVENT_INSTANT(
+      "power", "OnThermalStateChange", perfetto::Track::Global(0),
+      [&](perfetto::EventContext ctx) {
+        auto* event = ctx.event<perfetto::protos::pbzero::ChromeTrackEvent>();
+        event->set_chrome_application_state_info()->set_application_state(
+            base::trace_event::ApplicationStateToTraceEnum(
+                base::android::ApplicationStatusListener::GetState()));
+        event->set_device_thermal_state(ToTraceEnum(new_state));
+      });
+
   if (!app_visible_)
     return;
 
diff --git a/content/browser/android/nfc_host.cc b/content/browser/android/nfc_host.cc
index a60ac45..8085e2d9 100644
--- a/content/browser/android/nfc_host.cc
+++ b/content/browser/android/nfc_host.cc
@@ -54,9 +54,8 @@
     return;
   }
 
-  GURL origin_url = render_frame_host->GetLastCommittedOrigin().GetURL();
-  if (permission_controller_->GetPermissionStatusForFrame(
-          PermissionType::NFC, render_frame_host, origin_url) !=
+  if (permission_controller_->GetPermissionStatusForCurrentDocument(
+          PermissionType::NFC, render_frame_host) !=
       blink::mojom::PermissionStatus::GRANTED) {
     return;
   }
@@ -64,8 +63,11 @@
   if (!subscription_id_) {
     // base::Unretained() is safe here because the subscription is canceled when
     // this object is destroyed.
+    // TODO(crbug.com/1271543) : Move `SubscribePermissionStatusChange` to
+    // `PermissionController`.
     subscription_id_ = permission_controller_->SubscribePermissionStatusChange(
-        PermissionType::NFC, render_frame_host, origin_url,
+        PermissionType::NFC, render_frame_host,
+        render_frame_host->GetMainFrame()->GetLastCommittedOrigin().GetURL(),
         base::BindRepeating(&NFCHost::OnPermissionStatusChange,
                             base::Unretained(this)));
   }
diff --git a/content/browser/back_forward_cache_basics_browsertest.cc b/content/browser/back_forward_cache_basics_browsertest.cc
index 6fc91a3c..21481ec 100644
--- a/content/browser/back_forward_cache_basics_browsertest.cc
+++ b/content/browser/back_forward_cache_basics_browsertest.cc
@@ -742,7 +742,8 @@
   web_contents()->GetController().GoBack();
   EXPECT_FALSE(WaitForLoadStop(shell()->web_contents()));
   ExpectNotRestored(
-      {NotRestoredReason::kHTTPStatusNotOK, NotRestoredReason::kNoResponseHead},
+      {NotRestoredReason::kHTTPStatusNotOK, NotRestoredReason::kNoResponseHead,
+       NotRestoredReason::kErrorDocument},
       {}, {}, {}, {}, FROM_HERE);
 }
 
diff --git a/content/browser/back_forward_cache_browsertest.cc b/content/browser/back_forward_cache_browsertest.cc
index 6d0743b4..cf3b0e7 100644
--- a/content/browser/back_forward_cache_browsertest.cc
+++ b/content/browser/back_forward_cache_browsertest.cc
@@ -52,6 +52,7 @@
 #include "content/public/test/test_navigation_throttle_inserter.h"
 #include "content/public/test/test_utils.h"
 #include "content/public/test/text_input_test_utils.h"
+#include "content/public/test/url_loader_interceptor.h"
 #include "content/shell/browser/shell.h"
 #include "content/shell/browser/shell_javascript_dialog_manager.h"
 #include "content/test/content_browser_test_utils_internal.h"
@@ -671,6 +672,23 @@
       << location.ToString();
 }
 
+void BackForwardCacheBrowserTest::NavigateAndBlock(GURL url,
+                                                   int history_offset) {
+  // Block the navigation with an error.
+  std::unique_ptr<URLLoaderInterceptor> url_interceptor =
+      URLLoaderInterceptor::SetupRequestFailForURL(url,
+                                                   net::ERR_BLOCKED_BY_CLIENT);
+  TestNavigationManager manager(web_contents(), url);
+  if (history_offset) {
+    shell()->GoBackOrForward(history_offset);
+  } else {
+    shell()->LoadURL(url);
+  }
+  manager.WaitForNavigationFinished();
+  ASSERT_EQ(current_frame_host()->GetLastCommittedURL(), url);
+  ASSERT_TRUE(current_frame_host()->IsErrorDocument());
+}
+
 std::initializer_list<RenderFrameHostImpl*> Elements(
     std::initializer_list<RenderFrameHostImpl*> t) {
   return t;
diff --git a/content/browser/back_forward_cache_browsertest.h b/content/browser/back_forward_cache_browsertest.h
index 31d9a88..17e3c7ae 100644
--- a/content/browser/back_forward_cache_browsertest.h
+++ b/content/browser/back_forward_cache_browsertest.h
@@ -167,6 +167,11 @@
 
   void ReleaseKeyboardLock(RenderFrameHostImpl* rfh);
 
+  // Start a navigation to |url| but block it on an error. If |history_offset|
+  // is not 0, then the navigation will be a history navigation and this will
+  // assert that the URL after navigation is |url|.
+  void NavigateAndBlock(GURL url, int history_offset);
+
   base::HistogramTester histogram_tester_;
 
   bool same_site_back_forward_cache_enabled_ = true;
diff --git a/content/browser/back_forward_cache_internal_browsertest.cc b/content/browser/back_forward_cache_internal_browsertest.cc
index 297d86ae..1228a5b 100644
--- a/content/browser/back_forward_cache_internal_browsertest.cc
+++ b/content/browser/back_forward_cache_internal_browsertest.cc
@@ -3746,4 +3746,69 @@
                     {}, {}, {}, FROM_HERE);
 }
 
+// Test that when we navigate away from an error page and back with no error
+// that we don't serve the error page from BFCache.
+IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
+                       ErrorDocumentNotCachedWithSecondError) {
+  ASSERT_TRUE(embedded_test_server()->Start());
+
+  GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
+  GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
+
+  // Navigate to a.com.
+  ASSERT_TRUE(NavigateToURL(web_contents(), url_a));
+
+  // Navigate to b.com and block due to an error.
+  NavigateAndBlock(url_b, /*history_offset=*/0);
+  RenderFrameHostImplWrapper rfh_b(current_frame_host());
+
+  // Navigate back to a.com.
+  ASSERT_TRUE(HistoryGoBack(web_contents()));
+  ExpectRestored(FROM_HERE);
+  ASSERT_TRUE(rfh_b.WaitUntilRenderFrameDeleted());
+
+  // Navigate forward to b.com again and block with an error again.
+  NavigateAndBlock(url_b, /*history_offset=*/1);
+  ExpectNotRestored(
+      {NotRestoredReason::kHTTPStatusNotOK, NotRestoredReason::kNoResponseHead,
+       NotRestoredReason::kErrorDocument},
+      {}, {}, {}, {}, FROM_HERE);
+}
+
+// Test that when we navigate away from an error page and back with no error
+// that we don't serve the error page from BFCache.
+IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
+                       ErrorDocumentNotCachedWithoutSecondError) {
+  ASSERT_TRUE(embedded_test_server()->Start());
+
+  GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
+  GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
+
+  // Navigate to a.com.
+  ASSERT_TRUE(NavigateToURL(web_contents(), url_a));
+
+  // Navigate to b.com and block due to an error.
+  NavigateAndBlock(url_b, /*history_offset=*/0);
+  RenderFrameHostImplWrapper rfh_b(current_frame_host());
+
+  int history_entry_id =
+      web_contents()->GetController().GetLastCommittedEntry()->GetUniqueID();
+
+  // Navigate back to a.com.
+  ASSERT_TRUE(HistoryGoBack(web_contents()));
+  ASSERT_TRUE(rfh_b.WaitUntilRenderFrameDeleted());
+
+  // Navigate forward to b.com again with no error.
+  ASSERT_TRUE(HistoryGoForward(web_contents()));
+
+  // We would normally confirm that the blocking reasons are correct, however,
+  // when performing a history navigations back to an error document, a new
+  // entry is created and the reasons in the old entry are not recorded.
+  //
+  // Check that we indeed got a new history entry.
+  ASSERT_NE(
+      history_entry_id,
+      web_contents()->GetController().GetLastCommittedEntry()->GetUniqueID());
+}
+
 }  // namespace content
diff --git a/content/browser/background_fetch/background_fetch_delegate_proxy.cc b/content/browser/background_fetch/background_fetch_delegate_proxy.cc
index a6d8b01f..e65aee2bb 100644
--- a/content/browser/background_fetch/background_fetch_delegate_proxy.cc
+++ b/content/browser/background_fetch/background_fetch_delegate_proxy.cc
@@ -84,9 +84,8 @@
 
   if (auto* controller = GetPermissionController()) {
     blink::mojom::PermissionStatus permission_status =
-        controller->GetPermissionStatus(PermissionType::BACKGROUND_FETCH,
-                                        /*requesting_origin=*/origin.GetURL(),
-                                        /*embedding_origin=*/origin.GetURL());
+        controller->GetPermissionStatusForServiceWorker(
+            PermissionType::BACKGROUND_FETCH, origin);
     switch (permission_status) {
       case blink::mojom::PermissionStatus::GRANTED:
         result = BackgroundFetchPermission::ALLOWED;
diff --git a/content/browser/background_sync/background_sync_manager.cc b/content/browser/background_sync/background_sync_manager.cc
index 23d64f1b..f468a6da 100644
--- a/content/browser/background_sync/background_sync_manager.cc
+++ b/content/browser/background_sync/background_sync_manager.cc
@@ -133,14 +133,15 @@
   DCHECK(permission_controller);
 
   // The requesting origin always matches the embedding origin.
-  GURL origin_url = origin.GetURL();
-  auto sync_permission = permission_controller->GetPermissionStatus(
-      sync_type == BackgroundSyncType::ONE_SHOT
-          ? PermissionType::BACKGROUND_SYNC
-          : PermissionType::PERIODIC_BACKGROUND_SYNC,
-      origin_url, origin_url);
-  auto notification_permission = permission_controller->GetPermissionStatus(
-      PermissionType::NOTIFICATIONS, origin_url, origin_url);
+  auto sync_permission =
+      permission_controller->GetPermissionStatusForServiceWorker(
+          sync_type == BackgroundSyncType::ONE_SHOT
+              ? PermissionType::BACKGROUND_SYNC
+              : PermissionType::PERIODIC_BACKGROUND_SYNC,
+          origin);
+  auto notification_permission =
+      permission_controller->GetPermissionStatusForServiceWorker(
+          PermissionType::NOTIFICATIONS, origin);
   return {sync_permission, notification_permission};
 }
 
diff --git a/content/browser/client_hints/critical_client_hints_throttle.cc b/content/browser/client_hints/critical_client_hints_throttle.cc
index 115ad8c..ced85a2d 100644
--- a/content/browser/client_hints/critical_client_hints_throttle.cc
+++ b/content/browser/client_hints/critical_client_hints_throttle.cc
@@ -66,6 +66,12 @@
   if (restarted_origins_.contains(response_origin))
     return;
 
+  if (!ShouldAddClientHints(
+          response_origin, FrameTreeNode::GloballyFindByID(frame_tree_node_id_),
+          client_hint_delegate_)) {
+    return;
+  }
+
   // Ensure that only hints in the accept-ch header are examined
   blink::EnabledClientHints hints;
   for (const WebClientHintsType hint :
diff --git a/content/browser/devtools/protocol/page_handler.cc b/content/browser/devtools/protocol/page_handler.cc
index aa1fba3..1781d43 100644
--- a/content/browser/devtools/protocol/page_handler.cc
+++ b/content/browser/devtools/protocol/page_handler.cc
@@ -1413,6 +1413,8 @@
           CacheControlNoStoreHTTPOnlyCookieModified;
     case Reason::kNoResponseHead:
       return Page::BackForwardCacheNotRestoredReasonEnum::NoResponseHead;
+    case Reason::kErrorDocument:
+      return Page::BackForwardCacheNotRestoredReasonEnum::ErrorDocument;
     case Reason::kBlocklistedFeatures:
       // Blocklisted features should be handled separately and be broken down
       // into sub reasons.
@@ -1691,6 +1693,7 @@
     case Reason::kCacheControlNoStoreCookieModified:
     case Reason::kCacheControlNoStoreHTTPOnlyCookieModified:
     case Reason::kNoResponseHead:
+    case Reason::kErrorDocument:
       return Page::BackForwardCacheNotRestoredReasonTypeEnum::Circumstantial;
     case Reason::kOptInUnloadHeaderNotPresent:
     case Reason::kUnloadHandlerExistsInMainFrame:
diff --git a/content/browser/font_access/font_access_manager_impl.cc b/content/browser/font_access/font_access_manager_impl.cc
index 3e2aa2a..95c9e974 100644
--- a/content/browser/font_access/font_access_manager_impl.cc
+++ b/content/browser/font_access/font_access_manager_impl.cc
@@ -17,12 +17,12 @@
 #include "base/types/pass_key.h"
 #include "content/browser/font_access/font_enumeration_cache.h"
 #include "content/browser/font_access/font_enumeration_data_source.h"
-#include "content/browser/permissions/permission_controller_impl.h"
 #include "content/browser/renderer_host/frame_tree_node.h"
 #include "content/browser/renderer_host/render_frame_host_impl.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/global_routing_id.h"
+#include "content/public/browser/permission_controller.h"
 #include "content/public/browser/render_process_host.h"
 #include "content/public/common/content_client.h"
 #include "third_party/blink/public/common/features.h"
@@ -57,14 +57,12 @@
 }
 
 void FontAccessManagerImpl::BindReceiver(
-    url::Origin origin,
     GlobalRenderFrameHostId frame_id,
     mojo::PendingReceiver<blink::mojom::FontAccessManager> receiver) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   receivers_.Add(this, std::move(receiver),
                  {
-                     .origin = std::move(origin),
                      .frame_id = frame_id,
                  });
 }
@@ -99,12 +97,12 @@
     return;
   }
 
-  PermissionControllerImpl* permission_controller =
-      PermissionControllerImpl::FromBrowserContext(rfh->GetBrowserContext());
+  content::PermissionController* permission_controller =
+      rfh->GetBrowserContext()->GetPermissionController();
   DCHECK(permission_controller);
 
-  auto status = permission_controller->GetPermissionStatusForFrame(
-      PermissionType::FONT_ACCESS, rfh, context.origin.GetURL());
+  auto status = permission_controller->GetPermissionStatusForCurrentDocument(
+      PermissionType::FONT_ACCESS, rfh);
 
   if (status != blink::mojom::PermissionStatus::ASK) {
     // Permission has been requested before.
@@ -124,8 +122,8 @@
       blink::mojom::UserActivationUpdateType::kConsumeTransientActivation,
       blink::mojom::UserActivationNotificationType::kNone);
 
-  permission_controller->RequestPermission(
-      PermissionType::FONT_ACCESS, rfh, context.origin.GetURL(),
+  permission_controller->RequestPermissionFromCurrentDocument(
+      PermissionType::FONT_ACCESS, rfh,
       /*user_gesture=*/true,
       base::BindOnce(&FontAccessManagerImpl::DidRequestPermission,
                      weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
diff --git a/content/browser/font_access/font_access_manager_impl.h b/content/browser/font_access/font_access_manager_impl.h
index 84bf3203..0064e27c 100644
--- a/content/browser/font_access/font_access_manager_impl.h
+++ b/content/browser/font_access/font_access_manager_impl.h
@@ -20,7 +20,6 @@
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/receiver_set.h"
 #include "third_party/blink/public/mojom/font_access/font_access.mojom.h"
-#include "url/origin.h"
 
 namespace content {
 
@@ -71,7 +70,6 @@
   ~FontAccessManagerImpl() override;
 
   void BindReceiver(
-      url::Origin origin,
       GlobalRenderFrameHostId frame_id,
       mojo::PendingReceiver<blink::mojom::FontAccessManager> receiver);
 
@@ -85,7 +83,6 @@
 
  private:
   struct BindingContext {
-    url::Origin origin;
     GlobalRenderFrameHostId frame_id;
   };
 
diff --git a/content/browser/font_access/font_access_manager_impl_unittest.cc b/content/browser/font_access/font_access_manager_impl_unittest.cc
index d60b06e1..f95ca3e 100644
--- a/content/browser/font_access/font_access_manager_impl_unittest.cc
+++ b/content/browser/font_access/font_access_manager_impl_unittest.cc
@@ -92,7 +92,7 @@
             /* locale_override= */ absl::nullopt);
     manager_impl_ = FontAccessManagerImpl::CreateForTesting(
         std::move(font_enumeration_cache));
-    manager_impl_->BindReceiver(kTestOrigin, main_frame_id,
+    manager_impl_->BindReceiver(main_frame_id,
                                 manager_.BindNewPipeAndPassReceiver());
     manager_sync_ = std::make_unique<FontAccessManagerSync>(manager_.get());
 
diff --git a/content/browser/generic_sensor/sensor_provider_proxy_impl.cc b/content/browser/generic_sensor/sensor_provider_proxy_impl.cc
index f4d34d2..cb6bf31 100644
--- a/content/browser/generic_sensor/sensor_provider_proxy_impl.cc
+++ b/content/browser/generic_sensor/sensor_provider_proxy_impl.cc
@@ -10,10 +10,10 @@
 
 #include "base/bind.h"
 #include "base/no_destructor.h"
-#include "content/browser/permissions/permission_controller_impl.h"
 #include "content/browser/renderer_host/render_frame_host_impl.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/device_service.h"
+#include "content/public/browser/permission_controller.h"
 #include "content/public/browser/permission_type.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/web_contents.h"
@@ -36,11 +36,8 @@
 }  // namespace
 
 SensorProviderProxyImpl::SensorProviderProxyImpl(
-    PermissionControllerImpl* permission_controller,
     RenderFrameHost* render_frame_host)
-    : permission_controller_(permission_controller),
-      render_frame_host_(render_frame_host) {
-  DCHECK(permission_controller);
+    : render_frame_host_(render_frame_host) {
   DCHECK(render_frame_host);
 }
 
@@ -76,12 +73,13 @@
       GetDeviceService().BindSensorProvider(std::move(receiver));
   }
 
-  permission_controller_->RequestPermission(
-      PermissionType::SENSORS, render_frame_host_,
-      render_frame_host_->GetLastCommittedURL().DeprecatedGetOriginAsURL(),
-      false,
-      base::BindOnce(&SensorProviderProxyImpl::OnPermissionRequestCompleted,
-                     weak_factory_.GetWeakPtr(), type, std::move(callback)));
+  render_frame_host_->GetBrowserContext()
+      ->GetPermissionController()
+      ->RequestPermissionFromCurrentDocument(
+          PermissionType::SENSORS, render_frame_host_, false,
+          base::BindOnce(&SensorProviderProxyImpl::OnPermissionRequestCompleted,
+                         weak_factory_.GetWeakPtr(), type,
+                         std::move(callback)));
 }
 
 void SensorProviderProxyImpl::OnPermissionRequestCompleted(
diff --git a/content/browser/generic_sensor/sensor_provider_proxy_impl.h b/content/browser/generic_sensor/sensor_provider_proxy_impl.h
index 8162569..754af043 100644
--- a/content/browser/generic_sensor/sensor_provider_proxy_impl.h
+++ b/content/browser/generic_sensor/sensor_provider_proxy_impl.h
@@ -17,7 +17,6 @@
 
 namespace content {
 
-class PermissionControllerImpl;
 class RenderFrameHost;
 
 // This proxy acts as a gatekeeper to the real sensor provider so that this
@@ -25,8 +24,7 @@
 // the permission statuses retrieved from a permission controller.
 class SensorProviderProxyImpl final : public device::mojom::SensorProvider {
  public:
-  SensorProviderProxyImpl(PermissionControllerImpl* permission_controller,
-                          RenderFrameHost* render_frame_host);
+  explicit SensorProviderProxyImpl(RenderFrameHost* render_frame_host);
 
   SensorProviderProxyImpl(const SensorProviderProxyImpl&) = delete;
   SensorProviderProxyImpl& operator=(const SensorProviderProxyImpl&) = delete;
@@ -58,8 +56,8 @@
   // invalidated before being discarded.
   mojo::Remote<device::mojom::SensorProvider> sensor_provider_;
   mojo::ReceiverSet<device::mojom::SensorProvider> receiver_set_;
-  raw_ptr<PermissionControllerImpl> permission_controller_;
-  raw_ptr<RenderFrameHost> render_frame_host_;
+  // Note: |render_frame_host_| owns |this| instance.
+  const raw_ptr<RenderFrameHost> render_frame_host_;
 
   base::WeakPtrFactory<SensorProviderProxyImpl> weak_factory_{this};
 };
diff --git a/content/browser/geolocation/geolocation_service_impl.cc b/content/browser/geolocation/geolocation_service_impl.cc
index a29598a..0e24b3f 100644
--- a/content/browser/geolocation/geolocation_service_impl.cc
+++ b/content/browser/geolocation/geolocation_service_impl.cc
@@ -7,7 +7,8 @@
 #include <utility>
 
 #include "base/bind.h"
-#include "content/browser/permissions/permission_controller_impl.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/permission_controller.h"
 #include "content/public/browser/permission_type.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/render_process_host.h"
@@ -16,12 +17,9 @@
 
 namespace content {
 
-GeolocationServiceImplContext::GeolocationServiceImplContext(
-    PermissionControllerImpl* permission_controller)
-    : permission_controller_(permission_controller) {}
+GeolocationServiceImplContext::GeolocationServiceImplContext() = default;
 
-GeolocationServiceImplContext::~GeolocationServiceImplContext() {
-}
+GeolocationServiceImplContext::~GeolocationServiceImplContext() = default;
 
 void GeolocationServiceImplContext::RequestPermission(
     RenderFrameHost* render_frame_host,
@@ -35,11 +33,13 @@
   }
 
   has_pending_permission_request_ = true;
-  permission_controller_->RequestPermission(
-      PermissionType::GEOLOCATION, render_frame_host,
-      render_frame_host->GetLastCommittedOrigin().GetURL(), user_gesture,
-      base::BindOnce(&GeolocationServiceImplContext::HandlePermissionStatus,
-                     weak_factory_.GetWeakPtr(), std::move(callback)));
+
+  render_frame_host->GetBrowserContext()
+      ->GetPermissionController()
+      ->RequestPermissionFromCurrentDocument(
+          PermissionType::GEOLOCATION, render_frame_host, user_gesture,
+          base::BindOnce(&GeolocationServiceImplContext::HandlePermissionStatus,
+                         weak_factory_.GetWeakPtr(), std::move(callback)));
 }
 
 void GeolocationServiceImplContext::HandlePermissionStatus(
@@ -56,18 +56,14 @@
       render_frame_host_(render_frame_host) {
   DCHECK(geolocation_context);
   DCHECK(render_frame_host);
-
-  permission_controller_ = PermissionControllerImpl::FromBrowserContext(
-      render_frame_host_->GetProcess()->GetBrowserContext());
 }
 
 GeolocationServiceImpl::~GeolocationServiceImpl() {}
 
 void GeolocationServiceImpl::Bind(
     mojo::PendingReceiver<blink::mojom::GeolocationService> receiver) {
-  receiver_set_.Add(
-      this, std::move(receiver),
-      std::make_unique<GeolocationServiceImplContext>(permission_controller_));
+  receiver_set_.Add(this, std::move(receiver),
+                    std::make_unique<GeolocationServiceImplContext>());
 }
 
 void GeolocationServiceImpl::CreateGeolocation(
diff --git a/content/browser/geolocation/geolocation_service_impl.h b/content/browser/geolocation/geolocation_service_impl.h
index 4b808bd7..2cdfa36 100644
--- a/content/browser/geolocation/geolocation_service_impl.h
+++ b/content/browser/geolocation/geolocation_service_impl.h
@@ -22,12 +22,10 @@
 
 namespace content {
 class RenderFrameHost;
-class PermissionControllerImpl;
 
 class GeolocationServiceImplContext {
  public:
-  explicit GeolocationServiceImplContext(
-      PermissionControllerImpl* permission_controller);
+  GeolocationServiceImplContext();
 
   GeolocationServiceImplContext(const GeolocationServiceImplContext&) = delete;
   GeolocationServiceImplContext& operator=(
@@ -41,7 +39,6 @@
                          PermissionCallback callback);
 
  private:
-  raw_ptr<PermissionControllerImpl> permission_controller_;
   bool has_pending_permission_request_ = false;
 
   void HandlePermissionStatus(PermissionCallback callback,
@@ -80,8 +77,8 @@
       blink::mojom::PermissionStatus permission_status);
 
   raw_ptr<device::mojom::GeolocationContext> geolocation_context_;
-  raw_ptr<PermissionControllerImpl> permission_controller_;
-  raw_ptr<RenderFrameHost> render_frame_host_;
+  // Note: |render_frame_host_| owns |this| instance.
+  const raw_ptr<RenderFrameHost> render_frame_host_;
 
   // Along with each GeolocationService, we store a
   // GeolocationServiceImplContext which primarily exists to manage a
diff --git a/content/browser/idle/idle_manager_impl.cc b/content/browser/idle/idle_manager_impl.cc
index 0a2e118..87461939 100644
--- a/content/browser/idle/idle_manager_impl.cc
+++ b/content/browser/idle/idle_manager_impl.cc
@@ -12,8 +12,6 @@
 #include "content/public/browser/permission_controller.h"
 #include "content/public/browser/permission_type.h"
 #include "content/public/browser/render_frame_host.h"
-#include "url/gurl.h"
-#include "url/origin.h"
 
 namespace content {
 
@@ -98,11 +96,9 @@
   PermissionController* permission_controller =
       render_frame_host_->GetBrowserContext()->GetPermissionController();
   DCHECK(permission_controller);
-  PermissionStatus status = permission_controller->GetPermissionStatusForFrame(
-      PermissionType::IDLE_DETECTION, render_frame_host_,
-      render_frame_host_->GetMainFrame()
-          ->GetLastCommittedURL()
-          .DeprecatedGetOriginAsURL());
+  PermissionStatus status =
+      permission_controller->GetPermissionStatusForCurrentDocument(
+          PermissionType::IDLE_DETECTION, render_frame_host_);
   return status == PermissionStatus::GRANTED;
 }
 
diff --git a/content/browser/loader/navigation_url_loader_impl.cc b/content/browser/loader/navigation_url_loader_impl.cc
index 5a7fb3e..e6c0340 100644
--- a/content/browser/loader/navigation_url_loader_impl.cc
+++ b/content/browser/loader/navigation_url_loader_impl.cc
@@ -1277,61 +1277,57 @@
 
   mojo::PendingRemote<network::mojom::TrustedURLLoaderHeaderClient>
       header_client;
-  // `frame_tree_node` may be null in some unit test environments.
-  if (frame_tree_node) {
-    DCHECK(frame_tree_node->navigation_request());
+  DCHECK(frame_tree_node);
+  DCHECK(frame_tree_node->navigation_request());
 
-    // Initialize proxied factory remote/receiver if necessary.
-    // This also populates `bypass_redirect_checks_`.
-    GetContentClient()
-        ->browser()
-        ->RegisterNonNetworkNavigationURLLoaderFactories(
-            frame_tree_node_id_,
-            ukm::SourceIdObj::FromInt64(frame_tree_node->navigation_request()
-                                            ->GetNextPageUkmSourceId()),
-            &non_network_url_loader_factories_);
+  // Initialize proxied factory remote/receiver if necessary.
+  // This also populates `bypass_redirect_checks_`.
+  GetContentClient()->browser()->RegisterNonNetworkNavigationURLLoaderFactories(
+      frame_tree_node_id_,
+      ukm::SourceIdObj::FromInt64(
+          frame_tree_node->navigation_request()->GetNextPageUkmSourceId()),
+      &non_network_url_loader_factories_);
 
-    // The embedder may want to proxy all network-bound URLLoaderFactory
-    // receivers that it can. If it elects to do so, those proxies will be
-    // connected when loader is created if the request type supports proxying.
-    mojo::PendingRemote<network::mojom::URLLoaderFactory> pending_factory;
-    auto factory_receiver = pending_factory.InitWithNewPipeAndPassReceiver();
-    // Here we give nullptr for `factory_override`, because CORS is no-op for
-    // navigations.
-    bool use_proxy = GetContentClient()->browser()->WillCreateURLLoaderFactory(
-        browser_context_, frame_tree_node->current_frame_host(),
-        frame_tree_node->current_frame_host()->GetProcess()->GetID(),
-        ContentBrowserClient::URLLoaderFactoryType::kNavigation, url::Origin(),
-        frame_tree_node->navigation_request()->GetNavigationId(),
-        ukm::SourceIdObj::FromInt64(
-            frame_tree_node->navigation_request()->GetNextPageUkmSourceId()),
-        &factory_receiver, &header_client, &bypass_redirect_checks_,
-        /*disable_secure_dns=*/nullptr, /*factory_override=*/nullptr);
-    if (devtools_instrumentation::WillCreateURLLoaderFactory(
-            frame_tree_node->current_frame_host(), /*is_navigation=*/true,
-            /*is_download=*/false, &factory_receiver,
-            /*factory_override=*/nullptr)) {
-      use_proxy = true;
-    }
-    if (use_proxy) {
-      proxied_factory_receiver_ = std::move(factory_receiver);
-      proxied_factory_remote_ = std::move(pending_factory);
-    }
-
-    const std::string storage_domain;
-    // TODO(https://crbug.com/1264405): Determine if we should deprecate
-    // navigation in filesystem: URLs entirely or in 3p contexts; alter the
-    // below as necessary. NOTE: while the logic below is appropriate for
-    // browser-initiated navigations, it is likely incorrect to always use
-    // first-party StorageKeys for renderer-initiated navigations.
-    non_network_url_loader_factories_.emplace(
-        url::kFileSystemScheme,
-        CreateFileSystemURLLoaderFactory(
-            ChildProcessHost::kInvalidUniqueID,
-            frame_tree_node->frame_tree_node_id(),
-            storage_partition_->GetFileSystemContext(), storage_domain,
-            blink::StorageKey(url::Origin::Create(url_))));
+  // The embedder may want to proxy all network-bound URLLoaderFactory
+  // receivers that it can. If it elects to do so, those proxies will be
+  // connected when loader is created if the request type supports proxying.
+  mojo::PendingRemote<network::mojom::URLLoaderFactory> pending_factory;
+  auto factory_receiver = pending_factory.InitWithNewPipeAndPassReceiver();
+  // Here we give nullptr for `factory_override`, because CORS is no-op for
+  // navigations.
+  bool use_proxy = GetContentClient()->browser()->WillCreateURLLoaderFactory(
+      browser_context_, frame_tree_node->current_frame_host(),
+      frame_tree_node->current_frame_host()->GetProcess()->GetID(),
+      ContentBrowserClient::URLLoaderFactoryType::kNavigation, url::Origin(),
+      frame_tree_node->navigation_request()->GetNavigationId(),
+      ukm::SourceIdObj::FromInt64(
+          frame_tree_node->navigation_request()->GetNextPageUkmSourceId()),
+      &factory_receiver, &header_client, &bypass_redirect_checks_,
+      /*disable_secure_dns=*/nullptr, /*factory_override=*/nullptr);
+  if (devtools_instrumentation::WillCreateURLLoaderFactory(
+          frame_tree_node->current_frame_host(), /*is_navigation=*/true,
+          /*is_download=*/false, &factory_receiver,
+          /*factory_override=*/nullptr)) {
+    use_proxy = true;
   }
+  if (use_proxy) {
+    proxied_factory_receiver_ = std::move(factory_receiver);
+    proxied_factory_remote_ = std::move(pending_factory);
+  }
+
+  const std::string storage_domain;
+  // TODO(https://crbug.com/1264405): Determine if we should deprecate
+  // navigation in filesystem: URLs entirely or in 3p contexts; alter the
+  // below as necessary. NOTE: while the logic below is appropriate for
+  // browser-initiated navigations, it is likely incorrect to always use
+  // first-party StorageKeys for renderer-initiated navigations.
+  non_network_url_loader_factories_.emplace(
+      url::kFileSystemScheme,
+      CreateFileSystemURLLoaderFactory(
+          ChildProcessHost::kInvalidUniqueID,
+          frame_tree_node->frame_tree_node_id(),
+          storage_partition_->GetFileSystemContext(), storage_domain,
+          blink::StorageKey(url::Origin::Create(url_))));
 
   non_network_url_loader_factories_.emplace(url::kAboutScheme,
                                             AboutURLLoaderFactory::Create());
diff --git a/content/browser/loader/url_loader_throttles.cc b/content/browser/loader/url_loader_throttles.cc
index 57da324..c0f9bd4f 100644
--- a/content/browser/loader/url_loader_throttles.cc
+++ b/content/browser/loader/url_loader_throttles.cc
@@ -46,13 +46,9 @@
 
   ClientHintsControllerDelegate* client_hint_delegate =
       browser_context->GetClientHintsControllerDelegate();
-  if ((base::FeatureList::IsEnabled(features::kCriticalClientHint) ||
-       base::FeatureList::IsEnabled(network::features::kAcceptCHFrame)) &&
-      request.is_main_frame && net::HttpUtil::IsMethodSafe(request.method) &&
-      client_hint_delegate &&
-      ShouldAddClientHints(url::Origin::Create(request.url),
-                           FrameTreeNode::GloballyFindByID(frame_tree_node_id),
-                           client_hint_delegate)) {
+  if (base::FeatureList::IsEnabled(features::kCriticalClientHint) &&
+      net::HttpUtil::IsMethodSafe(request.method) && request.is_main_frame &&
+      client_hint_delegate) {
     throttles.push_back(std::make_unique<CriticalClientHintsThrottle>(
         browser_context, client_hint_delegate, frame_tree_node_id));
   }
diff --git a/content/browser/media/media_devices_permission_checker.cc b/content/browser/media/media_devices_permission_checker.cc
index 03968249..7ef31e05 100644
--- a/content/browser/media/media_devices_permission_checker.cc
+++ b/content/browser/media/media_devices_permission_checker.cc
@@ -167,23 +167,13 @@
   if (!frame_host)
     return false;
 
-  auto* web_contents = WebContents::FromRenderFrameHost(frame_host);
-  if (!web_contents)
-    return false;
-
   auto* permission_controller =
-      web_contents->GetBrowserContext()->GetPermissionController();
+      frame_host->GetBrowserContext()->GetPermissionController();
   DCHECK(permission_controller);
 
-  // TODO(crbug.com/698985): The semantics of the passed-in origin is incorrect:
-  // It should be the requesting origin, not the embedding origin. With the
-  // current implementation of the //chrome embedder, this parameter will be
-  // ignored, so it has no impact.
-  const GURL& origin =
-      PermissionUtil::GetLastCommittedOriginAsURL(web_contents);
   blink::mojom::PermissionStatus status =
-      permission_controller->GetPermissionStatusForFrame(
-          PermissionType::CAMERA_PAN_TILT_ZOOM, frame_host, origin);
+      permission_controller->GetPermissionStatusForCurrentDocument(
+          PermissionType::CAMERA_PAN_TILT_ZOOM, frame_host);
 
   return status == blink::mojom::PermissionStatus::GRANTED;
 #endif
diff --git a/content/browser/net/http_cache_backend_file_operations_factory.cc b/content/browser/net/http_cache_backend_file_operations_factory.cc
index 73ed23e..9a4813c8 100644
--- a/content/browser/net/http_cache_backend_file_operations_factory.cc
+++ b/content/browser/net/http_cache_backend_file_operations_factory.cc
@@ -13,6 +13,14 @@
 
 namespace {
 
+using OpenFileFlags = network::mojom::HttpCacheBackendOpenFileFlags;
+static_assert(static_cast<uint32_t>(OpenFileFlags::kOpenAndRead) ==
+                  (base::File::FLAG_OPEN | base::File::FLAG_READ),
+              "kOpenAndRead");
+static_assert(static_cast<uint32_t>(OpenFileFlags::kCreateAndWrite) ==
+                  (base::File::FLAG_CREATE | base::File::FLAG_WRITE),
+              "kCreateAndWrite");
+
 class HttpCacheBackendFileOperations final
     : public network::mojom::HttpCacheBackendFileOperations {
  public:
@@ -48,17 +56,19 @@
   }
 
   void OpenFile(const base::FilePath& path,
-                uint32_t flags,
+                network::mojom::HttpCacheBackendOpenFileFlags flags,
                 OpenFileCallback callback) override {
+    // `flags` has already been checked in the deserializer.
     if (!IsValid(path, "OpenFile")) {
       std::move(callback).Run(base::File(),
                               base::File::FILE_ERROR_ACCESS_DENIED);
       return;
     }
 
-    base::File file(path, flags);
+    auto flags_to_pass = static_cast<uint32_t>(flags);
+    base::File file(path, flags_to_pass);
     base::File::Error error = file.error_details();
-    DVLOG(1) << "OpenFile: path = " << path << ", flags = " << flags
+    DVLOG(1) << "OpenFile: path = " << path << ", flags = " << flags_to_pass
              << " => file.IsValid() = " << file.IsValid();
     std::move(callback).Run(std::move(file), error);
   }
diff --git a/content/browser/notifications/blink_notification_service_impl.cc b/content/browser/notifications/blink_notification_service_impl.cc
index 442b856..fd2c2d7 100644
--- a/content/browser/notifications/blink_notification_service_impl.cc
+++ b/content/browser/notifications/blink_notification_service_impl.cc
@@ -179,8 +179,9 @@
   // TOOD(crbug.com/987654): It is odd that a service instance can be created
   // for cross-origin subframes, yet the instance is completely oblivious of
   // whether it is serving a top-level browsing context or an embedded one.
-  return browser_context_->GetPermissionController()->GetPermissionStatus(
-      PermissionType::NOTIFICATIONS, origin_.GetURL(), origin_.GetURL());
+  return browser_context_->GetPermissionController()
+      ->GetPermissionStatusForServiceWorker(PermissionType::NOTIFICATIONS,
+                                            origin_);
 }
 
 bool BlinkNotificationServiceImpl::ValidateNotificationDataAndResources(
diff --git a/content/browser/notifications/platform_notification_context_impl.cc b/content/browser/notifications/platform_notification_context_impl.cc
index 8e23ab2..5580c415a 100644
--- a/content/browser/notifications/platform_notification_context_impl.cc
+++ b/content/browser/notifications/platform_notification_context_impl.cc
@@ -363,8 +363,8 @@
 
   // Erase all valid origins so we're left with invalid ones.
   base::EraseIf(origins, [controller](const GURL& origin) {
-    auto permission = controller->GetPermissionStatus(
-        PermissionType::NOTIFICATIONS, origin, origin);
+    auto permission = controller->GetPermissionStatusForServiceWorker(
+        PermissionType::NOTIFICATIONS, url::Origin::Create(origin));
     return permission == blink::mojom::PermissionStatus::GRANTED;
   });
 
diff --git a/content/browser/payments/installed_payment_apps_finder_impl.cc b/content/browser/payments/installed_payment_apps_finder_impl.cc
index 2790de71..a0d5b5b8 100644
--- a/content/browser/payments/installed_payment_apps_finder_impl.cc
+++ b/content/browser/payments/installed_payment_apps_finder_impl.cc
@@ -77,8 +77,8 @@
   PaymentApps permitted_apps;
   for (auto& app : apps) {
     GURL origin = app.second->scope.DeprecatedGetOriginAsURL();
-    if (permission_controller->GetPermissionStatus(
-            PermissionType::PAYMENT_HANDLER, origin, origin) ==
+    if (permission_controller->GetPermissionStatusForServiceWorker(
+            PermissionType::PAYMENT_HANDLER, url::Origin::Create(origin)) ==
         blink::mojom::PermissionStatus::GRANTED) {
       permitted_apps[app.first] = std::move(app.second);
     }
diff --git a/content/browser/permissions/permission_controller_impl.cc b/content/browser/permissions/permission_controller_impl.cc
index f369ca98..c150a7f5 100644
--- a/content/browser/permissions/permission_controller_impl.cc
+++ b/content/browser/permissions/permission_controller_impl.cc
@@ -117,7 +117,8 @@
     BrowserContext* browser_context)
     : browser_context_(browser_context) {}
 
-// static
+// TODO(https://crbug.com/1271543): Remove this method and use
+// `PermissionController` instead. static
 PermissionControllerImpl* PermissionControllerImpl::FromBrowserContext(
     BrowserContext* browser_context) {
   return static_cast<PermissionControllerImpl*>(
@@ -151,9 +152,9 @@
     return GetPermissionStatusForFrame(subscription.permission, rfh,
                                        subscription.requesting_origin);
   }
-  return GetPermissionStatus(subscription.permission,
-                             subscription.requesting_origin,
-                             subscription.embedding_origin);
+  return DeprecatedGetPermissionStatus(subscription.permission,
+                                       subscription.requesting_origin,
+                                       subscription.embedding_origin);
 }
 
 PermissionControllerImpl::SubscriptionsStatusMap
@@ -329,7 +330,20 @@
                                std::move(wrapper));
 }
 
-blink::mojom::PermissionStatus PermissionControllerImpl::GetPermissionStatus(
+void PermissionControllerImpl::RequestPermissionFromCurrentDocument(
+    PermissionType permission,
+    RenderFrameHost* render_frame_host,
+    bool user_gesture,
+    base::OnceCallback<void(blink::mojom::PermissionStatus)> callback) {
+  // TODO(https://crbug.com/1271543): `RequestPermissionFromCurrentDocument`
+  // into `PermissionControllerDelegate` and use it here.
+  RequestPermission(permission, render_frame_host,
+                    render_frame_host->GetLastCommittedOrigin().GetURL(),
+                    user_gesture, std::move(callback));
+}
+
+blink::mojom::PermissionStatus
+PermissionControllerImpl::DeprecatedGetPermissionStatus(
     PermissionType permission,
     const GURL& requesting_origin,
     const GURL& embedding_origin) {
@@ -348,6 +362,32 @@
 }
 
 blink::mojom::PermissionStatus
+PermissionControllerImpl::GetPermissionStatusForServiceWorker(
+    PermissionType permission,
+    const url::Origin& service_worker_origin) {
+  return DeprecatedGetPermissionStatus(permission,
+                                       service_worker_origin.GetURL(),
+                                       service_worker_origin.GetURL());
+}
+
+blink::mojom::PermissionStatus
+PermissionControllerImpl::GetPermissionStatusForCurrentDocument(
+    PermissionType permission,
+    RenderFrameHost* render_frame_host) {
+  return GetPermissionStatusForFrame(
+      permission, render_frame_host,
+      render_frame_host->GetLastCommittedOrigin().GetURL());
+}
+
+blink::mojom::PermissionStatus
+PermissionControllerImpl::GetPermissionStatusForOriginWithoutContext(
+    PermissionType permission,
+    const url::Origin& origin) {
+  return DeprecatedGetPermissionStatus(permission, origin.GetURL(),
+                                       origin.GetURL());
+}
+
+blink::mojom::PermissionStatus
 PermissionControllerImpl::GetPermissionStatusForFrame(
     PermissionType permission,
     RenderFrameHost* render_frame_host,
diff --git a/content/browser/permissions/permission_controller_impl.h b/content/browser/permissions/permission_controller_impl.h
index 298493c8..bca8d76 100644
--- a/content/browser/permissions/permission_controller_impl.h
+++ b/content/browser/permissions/permission_controller_impl.h
@@ -48,22 +48,29 @@
   void ResetOverridesForDevTools();
 
   // PermissionController implementation.
-  blink::mojom::PermissionStatus GetPermissionStatus(
+  blink::mojom::PermissionStatus DeprecatedGetPermissionStatus(
       PermissionType permission,
       const GURL& requesting_origin,
       const GURL& embedding_origin) override;
 
-  blink::mojom::PermissionStatus GetPermissionStatusForFrame(
+  blink::mojom::PermissionStatus GetPermissionStatusForServiceWorker(
       PermissionType permission,
-      RenderFrameHost* render_frame_host,
-      const GURL& requesting_origin) override;
+      const url::Origin& service_worker_origin) override;
 
-  void RequestPermission(
+  blink::mojom::PermissionStatus GetPermissionStatusForCurrentDocument(
+      PermissionType permission,
+      RenderFrameHost* render_frame_host) override;
+
+  blink::mojom::PermissionStatus GetPermissionStatusForOriginWithoutContext(
+      PermissionType permission,
+      const url::Origin& origin) override;
+
+  void RequestPermissionFromCurrentDocument(
       PermissionType permission,
       RenderFrameHost* render_frame_host,
-      const GURL& requesting_origin,
       bool user_gesture,
-      base::OnceCallback<void(blink::mojom::PermissionStatus)> callback);
+      base::OnceCallback<void(blink::mojom::PermissionStatus)> callback)
+      override;
 
   void RequestPermissions(
       const std::vector<PermissionType>& permission,
@@ -87,6 +94,19 @@
   void UnsubscribePermissionStatusChange(SubscriptionId subscription_id);
 
  private:
+  blink::mojom::PermissionStatus GetPermissionStatusForFrame(
+      PermissionType permission,
+      RenderFrameHost* render_frame_host,
+      const GURL& requesting_origin);
+
+  void RequestPermission(
+      PermissionType permission,
+      RenderFrameHost* render_frame_host,
+      const GURL& requesting_origin,
+      bool user_gesture,
+      base::OnceCallback<void(blink::mojom::PermissionStatus)> callback)
+      override;
+
   struct Subscription;
   using SubscriptionsMap =
       base::IDMap<std::unique_ptr<Subscription>, SubscriptionId>;
diff --git a/content/browser/permissions/permission_controller_impl_unittest.cc b/content/browser/permissions/permission_controller_impl_unittest.cc
index 6411482..127fa8aa 100644
--- a/content/browser/permissions/permission_controller_impl_unittest.cc
+++ b/content/browser/permissions/permission_controller_impl_unittest.cc
@@ -269,8 +269,8 @@
 
   // Setup.
   blink::mojom::PermissionStatus sync_status =
-      permission_controller()->GetPermissionStatus(
-          PermissionType::BACKGROUND_SYNC, kUrl, kUrl);
+      permission_controller()->GetPermissionStatusForServiceWorker(
+          PermissionType::BACKGROUND_SYNC, kTestOrigin);
   permission_controller()->SetOverrideForDevTools(
       kTestOrigin, PermissionType::GEOLOCATION,
       blink::mojom::PermissionStatus::DENIED);
@@ -317,9 +317,8 @@
                 blink::mojom::PermissionStatus::ASK));
 
   blink::mojom::PermissionStatus status =
-      permission_controller()->GetPermissionStatus(PermissionType::GEOLOCATION,
-                                                   kTestOrigin.GetURL(),
-                                                   kTestOrigin.GetURL());
+      permission_controller()->GetPermissionStatusForServiceWorker(
+          PermissionType::GEOLOCATION, kTestOrigin);
   EXPECT_EQ(blink::mojom::PermissionStatus::DENIED, status);
 }
 
@@ -351,14 +350,14 @@
 
   // Keep original settings as before.
   EXPECT_EQ(blink::mojom::PermissionStatus::DENIED,
-            permission_controller()->GetPermissionStatus(
-                PermissionType::GEOLOCATION, kUrl, kUrl));
+            permission_controller()->GetPermissionStatusForServiceWorker(
+                PermissionType::GEOLOCATION, kTestOrigin));
   EXPECT_EQ(blink::mojom::PermissionStatus::ASK,
-            permission_controller()->GetPermissionStatus(PermissionType::MIDI,
-                                                         kUrl, kUrl));
+            permission_controller()->GetPermissionStatusForServiceWorker(
+                PermissionType::MIDI, kTestOrigin));
   EXPECT_EQ(blink::mojom::PermissionStatus::ASK,
-            permission_controller()->GetPermissionStatus(
-                PermissionType::BACKGROUND_SYNC, kUrl, kUrl));
+            permission_controller()->GetPermissionStatusForServiceWorker(
+                PermissionType::BACKGROUND_SYNC, kTestOrigin));
 
   EXPECT_CALL(*mock_manager(), IsPermissionOverridableByDevTools(
                                    PermissionType::GEOLOCATION, testing::_))
@@ -375,14 +374,14 @@
                     PermissionType::BACKGROUND_SYNC});
   EXPECT_EQ(OverrideStatus::kOverrideSet, result);
   EXPECT_EQ(blink::mojom::PermissionStatus::GRANTED,
-            permission_controller()->GetPermissionStatus(
-                PermissionType::GEOLOCATION, kUrl, kUrl));
+            permission_controller()->GetPermissionStatusForServiceWorker(
+                PermissionType::GEOLOCATION, kTestOrigin));
   EXPECT_EQ(blink::mojom::PermissionStatus::GRANTED,
-            permission_controller()->GetPermissionStatus(PermissionType::MIDI,
-                                                         kUrl, kUrl));
+            permission_controller()->GetPermissionStatusForServiceWorker(
+                PermissionType::MIDI, kTestOrigin));
   EXPECT_EQ(blink::mojom::PermissionStatus::GRANTED,
-            permission_controller()->GetPermissionStatus(
-                PermissionType::BACKGROUND_SYNC, kUrl, kUrl));
+            permission_controller()->GetPermissionStatusForServiceWorker(
+                PermissionType::BACKGROUND_SYNC, kTestOrigin));
 }
 
 }  // namespace
diff --git a/content/browser/permissions/permission_service_impl.cc b/content/browser/permissions/permission_service_impl.cc
index 1438d92..76d65d1e 100644
--- a/content/browser/permissions/permission_service_impl.cc
+++ b/content/browser/permissions/permission_service_impl.cc
@@ -203,16 +203,17 @@
   if (!browser_context)
     return PermissionStatus::DENIED;
 
-  GURL requesting_origin(origin_.GetURL());
   if (context_->render_frame_host()) {
     return browser_context->GetPermissionController()
-        ->GetPermissionStatusForFrame(type, context_->render_frame_host(),
-                                      requesting_origin);
+        ->GetPermissionStatusForCurrentDocument(type,
+                                                context_->render_frame_host());
+  } else {
+    // If `context_->render_frame_host()` is empty, it means `PermissionService`
+    // is created for a ServiceWorker.
+    DCHECK(context_->GetEmbeddingOrigin().is_empty());
+    return browser_context->GetPermissionController()
+        ->GetPermissionStatusForServiceWorker(type, origin_);
   }
-
-  DCHECK(context_->GetEmbeddingOrigin().is_empty());
-  return browser_context->GetPermissionController()->GetPermissionStatus(
-      type, requesting_origin, requesting_origin);
 }
 
 void PermissionServiceImpl::ResetPermissionStatus(PermissionType type) {
diff --git a/content/browser/prerender/prerender_navigation_throttle.cc b/content/browser/prerender/prerender_navigation_throttle.cc
index b8847d7..d131ffc 100644
--- a/content/browser/prerender/prerender_navigation_throttle.cc
+++ b/content/browser/prerender/prerender_navigation_throttle.cc
@@ -22,9 +22,14 @@
 
 // Returns true if a the response code is disallowed for pre-rendering (e.g 404,
 // etc), and false otherwise.
-// TODO(crbug.com/1167592): This should be eventually synced with the outcome
-// of https://github.com/jeremyroman/alternate-loading-modes/issues/30.
+// TODO(crbug.com/1299316): Sync with
+// https://github.com/WICG/nav-speculation/issues/138 once it's settled down.
 bool IsDisallowedHttpResponseCode(int response_code) {
+  // Disallow status code 204 and 205 because all error statuses should abandon
+  // prerendering as a default behavior.
+  if (response_code == 204 || response_code == 205) {
+    return true;
+  }
   return response_code < 100 || response_code > 399;
 }
 
diff --git a/content/browser/push_messaging/push_messaging_manager.cc b/content/browser/push_messaging/push_messaging_manager.cc
index 422c649..e4fda8f 100644
--- a/content/browser/push_messaging/push_messaging_manager.cc
+++ b/content/browser/push_messaging/push_messaging_manager.cc
@@ -19,11 +19,11 @@
 #include "base/time/time.h"
 #include "content/browser/bad_message.h"
 #include "content/browser/child_process_security_policy_impl.h"
-#include "content/browser/permissions/permission_controller_impl.h"
 #include "content/browser/service_worker/service_worker_context_core.h"
 #include "content/browser/service_worker/service_worker_context_wrapper.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/browser_task_traits.h"
+#include "content/public/browser/permission_controller.h"
 #include "content/public/browser/permission_type.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/render_process_host.h"
@@ -324,15 +324,14 @@
               blink::mojom::ConsoleMessageLevel::kError,
               kIncognitoPushUnsupportedMessage);
 
-          BrowserContext* browser_context =
-              render_frame_host->GetBrowserContext();
-
           // Request notifications permission (which will fail, since
           // notifications aren't supported in incognito), so the website can't
           // detect whether incognito is active.
           url::Origin requesting_origin = data.requesting_origin;
           bool user_gesture = data.user_gesture;
-          PermissionControllerImpl::FromBrowserContext(browser_context)
+
+          render_frame_host->GetBrowserContext()
+              ->GetPermissionController()
               ->RequestPermission(
                   PermissionType::NOTIFICATIONS, render_frame_host,
                   requesting_origin.GetURL(), user_gesture,
diff --git a/content/browser/renderer_host/back_forward_cache_can_store_document_result.cc b/content/browser/renderer_host/back_forward_cache_can_store_document_result.cc
index 14a454b..0ce2e1e0 100644
--- a/content/browser/renderer_host/back_forward_cache_can_store_document_result.cc
+++ b/content/browser/renderer_host/back_forward_cache_can_store_document_result.cc
@@ -181,6 +181,8 @@
       return ProtoEnum::CACHE_CONTROL_NO_STORE_HTTP_ONLY_COOKIE_MODIFIED;
     case Reason::kNoResponseHead:
       return ProtoEnum::NO_RESPONSE_HEAD;
+    case Reason::kErrorDocument:
+      return ProtoEnum::ERROR_DOCUMENT;
     case Reason::kBlocklistedFeatures:
       return ProtoEnum::BLOCKLISTED_FEATURES;
     case Reason::kUnknown:
@@ -389,6 +391,8 @@
     case Reason::kNoResponseHead:
       return "main RenderFrameHost doesn't have response headers set, probably "
              "due not having successfully committed a navigation.";
+    case Reason::kErrorDocument:
+      return "Error documents cannot be stored in bfcache";
   }
 }
 
diff --git a/content/browser/renderer_host/back_forward_cache_impl.cc b/content/browser/renderer_host/back_forward_cache_impl.cc
index 28360a3..75bcb5d6 100644
--- a/content/browser/renderer_host/back_forward_cache_impl.cc
+++ b/content/browser/renderer_host/back_forward_cache_impl.cc
@@ -797,6 +797,13 @@
   if (rfh->last_http_status_code() != net::HTTP_OK)
     result.No(BackForwardCacheMetrics::NotRestoredReason::kHTTPStatusNotOK);
 
+  // Interstitials and other internal error pages should set an error status
+  // code but there's no guarantee, e.g. https://crbug/1274308,
+  // https://crbug/1287996. This catches those cases. It might also make the
+  // kHTTPStatusNotOK check redundant.
+  if (rfh->IsErrorDocument())
+    result.No(BackForwardCacheMetrics::NotRestoredReason::kErrorDocument);
+
   // Only store documents that were fetched via HTTP GET method.
   if (rfh->last_http_method() != net::HttpRequestHeaders::kGetMethod)
     result.No(BackForwardCacheMetrics::NotRestoredReason::kHTTPMethodNotGET);
diff --git a/content/browser/renderer_host/back_forward_cache_metrics.h b/content/browser/renderer_host/back_forward_cache_metrics.h
index 33205be..dcd0c1f 100644
--- a/content/browser/renderer_host/back_forward_cache_metrics.h
+++ b/content/browser/renderer_host/back_forward_cache_metrics.h
@@ -111,7 +111,8 @@
     kCacheControlNoStoreHTTPOnlyCookieModified = 55,
     kNoResponseHead = 56,
     // 57: kActivationNavigationsDisallowedForBug1234857 was fixed.
-    kMaxValue = kNoResponseHead,
+    kErrorDocument = 58,
+    kMaxValue = kErrorDocument,
   };
 
   using NotRestoredReasons =
diff --git a/content/browser/renderer_host/render_frame_host_impl.cc b/content/browser/renderer_host/render_frame_host_impl.cc
index f507cc5..0ca1820 100644
--- a/content/browser/renderer_host/render_frame_host_impl.cc
+++ b/content/browser/renderer_host/render_frame_host_impl.cc
@@ -9374,12 +9374,14 @@
 bool RenderFrameHostImpl::WindowPlacementAllowsFullscreen() {
   if (!delegate_->IsTransientAllowFullscreenActive())
     return false;
-  auto* controller =
-      PermissionControllerImpl::FromBrowserContext(GetBrowserContext());
-  return controller &&
-         controller->GetPermissionStatusForFrame(
-             PermissionType::WINDOW_PLACEMENT, this, GetLastCommittedURL()) ==
-             blink::mojom::PermissionStatus::GRANTED;
+
+  content::PermissionController* permission_controller =
+      GetBrowserContext()->GetPermissionController();
+  DCHECK(permission_controller);
+
+  return permission_controller->GetPermissionStatusForCurrentDocument(
+             PermissionType::WINDOW_PLACEMENT, this) ==
+         blink::mojom::PermissionStatus::GRANTED;
 }
 
 mojo::AssociatedRemote<mojom::NavigationClient>
@@ -10052,10 +10054,7 @@
 void RenderFrameHostImpl::GetSensorProvider(
     mojo::PendingReceiver<device::mojom::SensorProvider> receiver) {
   if (!sensor_provider_proxy_) {
-    sensor_provider_proxy_ = std::make_unique<SensorProviderProxyImpl>(
-        PermissionControllerImpl::FromBrowserContext(
-            GetProcess()->GetBrowserContext()),
-        this);
+    sensor_provider_proxy_ = std::make_unique<SensorProviderProxyImpl>(this);
   }
   sensor_provider_proxy_->Bind(std::move(receiver));
 }
@@ -10186,7 +10185,7 @@
 void RenderFrameHostImpl::GetFontAccessManager(
     mojo::PendingReceiver<blink::mojom::FontAccessManager> receiver) {
   GetStoragePartition()->GetFontAccessManager()->BindReceiver(
-      GetLastCommittedOrigin(), GetGlobalId(), std::move(receiver));
+      GetGlobalId(), std::move(receiver));
 }
 
 void RenderFrameHostImpl::BindComputePressureHost(
diff --git a/content/browser/storage_partition_impl.cc b/content/browser/storage_partition_impl.cc
index 5e0ddb3..b78be9fb 100644
--- a/content/browser/storage_partition_impl.cc
+++ b/content/browser/storage_partition_impl.cc
@@ -2018,10 +2018,9 @@
 
   std::vector<url::Origin> origins_out;
   for (auto& origin : origins) {
-    GURL origin_url = origin.GetURL();
-    bool allowed = permission_controller->GetPermissionStatus(
-                       PermissionType::BACKGROUND_SYNC, origin_url,
-                       origin_url) == blink::mojom::PermissionStatus::GRANTED;
+    bool allowed = permission_controller->GetPermissionStatusForServiceWorker(
+                       PermissionType::BACKGROUND_SYNC, origin) ==
+                   blink::mojom::PermissionStatus::GRANTED;
     if (allowed)
       origins_out.push_back(origin);
   }
@@ -2036,8 +2035,9 @@
   PermissionController* permission_controller =
       browser_context_->GetPermissionController();
   std::move(callback).Run(
-      permission_controller->GetPermissionStatus(
-          content::PermissionType::BACKGROUND_SYNC, origin, origin) ==
+      permission_controller->GetPermissionStatusForServiceWorker(
+          content::PermissionType::BACKGROUND_SYNC,
+          url::Origin::Create(origin)) ==
       blink::mojom::PermissionStatus::GRANTED);
 }
 
diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc
index d3818fa..9082686 100644
--- a/content/browser/web_contents/web_contents_impl.cc
+++ b/content/browser/web_contents/web_contents_impl.cc
@@ -460,16 +460,13 @@
 
 // Returns true if |host| has the Window Placement permission granted.
 bool IsWindowPlacementGranted(RenderFrameHost* host) {
-  auto* controller =
-      PermissionControllerImpl::FromBrowserContext(host->GetBrowserContext());
+  content::PermissionController* permission_controller =
+      host->GetBrowserContext()->GetPermissionController();
+  DCHECK(permission_controller);
 
-  // TODO(crbug.com/698985): Resolve GetLastCommitted[URL|Origin]() usage.
-  return controller &&
-         controller->GetPermissionStatusForFrame(
-             PermissionType::WINDOW_PLACEMENT, host,
-             PermissionUtil::GetLastCommittedOriginAsURL(
-                 content::WebContents::FromRenderFrameHost(host))) ==
-             blink::mojom::PermissionStatus::GRANTED;
+  return permission_controller->GetPermissionStatusForCurrentDocument(
+             PermissionType::WINDOW_PLACEMENT, host) ==
+         blink::mojom::PermissionStatus::GRANTED;
 }
 
 // Adjust the requested |bounds| for opening or placing a window and return the
diff --git a/content/public/android/java/src/org/chromium/content_public/browser/NavigationHandle.java b/content/public/android/java/src/org/chromium/content_public/browser/NavigationHandle.java
index ba0d998..365579d5 100644
--- a/content/public/android/java/src/org/chromium/content_public/browser/NavigationHandle.java
+++ b/content/public/android/java/src/org/chromium/content_public/browser/NavigationHandle.java
@@ -41,7 +41,7 @@
     private final Impression mImpression;
     private final boolean mIsPost;
     private final boolean mHasUserGesture;
-    private boolean mIsRedirect;
+    private final boolean mIsRedirect;
     private final boolean mIsExternalProtocol;
     private final long mNavigationId;
     private final boolean mIsPageActivation;
@@ -78,7 +78,6 @@
     @CalledByNative
     private void didRedirect(GURL url) {
         mUrl = url;
-        mIsRedirect = true;
     }
 
     /**
diff --git a/content/public/browser/permission_controller.h b/content/public/browser/permission_controller.h
index ce7e195..2f476c2 100644
--- a/content/public/browser/permission_controller.h
+++ b/content/public/browser/permission_controller.h
@@ -13,6 +13,10 @@
 
 class GURL;
 
+namespace url {
+class Origin;
+}
+
 namespace content {
 class RenderFrameHost;
 
@@ -30,22 +34,54 @@
 
   // Returns the permission status of a given requesting_origin/embedding_origin
   // tuple. This is not taking a RenderFrameHost because the call might happen
-  // outside of a frame context. Prefer GetPermissionStatusForFrame (below)
-  // whenever possible.
-  virtual blink::mojom::PermissionStatus GetPermissionStatus(
+  // outside of a frame context. Prefer GetPermissionStatusForCurrentDocument
+  // (below) whenever possible.
+  virtual blink::mojom::PermissionStatus DeprecatedGetPermissionStatus(
       PermissionType permission,
       const GURL& requesting_origin,
       const GURL& embedding_origin) = 0;
 
-  // Returns the permission status for a given frame. Use this over
-  // GetPermissionStatus whenever possible.
-  // TODO(raymes): Currently we still pass the |requesting_origin| as a separate
-  // parameter because we can't yet guarantee that it matches the last committed
-  // origin of the RenderFrameHost. See https://crbug.com/698985.
-  virtual blink::mojom::PermissionStatus GetPermissionStatusForFrame(
+  // Returns the permission status for a given origin. ServiceWorker isn't
+  // associated with any WebContents, hence a document's lifecycle state isn't
+  // considered.
+  virtual blink::mojom::PermissionStatus GetPermissionStatusForServiceWorker(
+      PermissionType permission,
+      const url::Origin& service_worker_origin) = 0;
+
+  // Returns the permission status for the current document in the given
+  // RenderFrameHost. Use this over `DeprecatedGetPermissionStatus` whenever
+  // possible as this API takes into account the lifecycle state of a given
+  // document (i.e. whether it's in back-forward cache or being prerendered) in
+  // addition to its origin.
+  virtual blink::mojom::PermissionStatus GetPermissionStatusForCurrentDocument(
+      PermissionType permission,
+      RenderFrameHost* render_frame_host) = 0;
+
+  // Returns the permission status for a given origin. Use this API only if
+  // there is no document and it is not a ServiceWorker.
+  virtual blink::mojom::PermissionStatus
+  GetPermissionStatusForOriginWithoutContext(PermissionType permission,
+                                             const url::Origin& origin) = 0;
+
+  // Requests the permission for a given requesting_origin. Prefer
+  // `RequestPermissionFromCurrentDocument` whenever possible.
+  virtual void RequestPermission(
       PermissionType permission,
       RenderFrameHost* render_frame_host,
-      const GURL& requesting_origin) = 0;
+      const GURL& requesting_origin,
+      bool user_gesture,
+      base::OnceCallback<void(blink::mojom::PermissionStatus)> callback) = 0;
+
+  // Requests the permission for the current document in the given
+  // RenderFrameHost. Use this over `RequestPermission` whenever
+  // possible as this API takes into account the lifecycle state of a given
+  // document (i.e. whether it's in back-forward cache or being prerendered) in
+  // addition to its origin.
+  virtual void RequestPermissionFromCurrentDocument(
+      PermissionType permission,
+      RenderFrameHost* render_frame_host,
+      bool user_gesture,
+      base::OnceCallback<void(blink::mojom::PermissionStatus)> callback) = 0;
 };
 
 }  // namespace content
diff --git a/extensions/browser/api/alarms/alarm_manager.cc b/extensions/browser/api/alarms/alarm_manager.cc
index c6d3ba67..38d6d1e7 100644
--- a/extensions/browser/api/alarms/alarm_manager.cc
+++ b/extensions/browser/api/alarms/alarm_manager.cc
@@ -51,7 +51,8 @@
 
   void OnAlarm(const std::string& extension_id, const Alarm& alarm) override {
     std::unique_ptr<base::ListValue> args(new base::ListValue());
-    args->Append(alarm.js_alarm->ToValue());
+    args->GetList().Append(
+        base::Value::FromUniquePtrValue(alarm.js_alarm->ToValue()));
     std::unique_ptr<Event> event(
         new Event(events::ALARMS_ON_ALARM, alarms::OnAlarm::kEventName,
                   std::move(*args).TakeListDeprecated(), browser_context_));
@@ -104,7 +105,7 @@
         alarms[i]->js_alarm->ToValue();
     alarm->SetKey(kAlarmGranularity,
                   base::TimeDeltaToValue(alarms[i]->granularity));
-    list->Append(std::move(alarm));
+    list->GetList().Append(base::Value::FromUniquePtrValue(std::move(alarm)));
   }
   return list;
 }
diff --git a/extensions/browser/api/app_runtime/app_runtime_api.cc b/extensions/browser/api/app_runtime/app_runtime_api.cc
index 08c3b5f2..e95c186 100644
--- a/extensions/browser/api/app_runtime/app_runtime_api.cc
+++ b/extensions/browser/api/app_runtime/app_runtime_api.cc
@@ -36,12 +36,13 @@
     const std::string& extension_id,
     std::unique_ptr<base::DictionaryValue> app_embedding_request_data,
     content::BrowserContext* context) {
-  std::unique_ptr<base::ListValue> args(new base::ListValue());
-  args->Append(std::move(app_embedding_request_data));
-  auto event =
-      std::make_unique<Event>(events::APP_RUNTIME_ON_EMBED_REQUESTED,
-                              app_runtime::OnEmbedRequested::kEventName,
-                              std::move(*args).TakeListDeprecated(), context);
+  base::Value::List args;
+  args.Append(
+      base::Value::FromUniquePtrValue(std::move(app_embedding_request_data)));
+  auto event = std::make_unique<Event>(
+      events::APP_RUNTIME_ON_EMBED_REQUESTED,
+      app_runtime::OnEmbedRequested::kEventName,
+      base::Value(std::move(args)).TakeListDeprecated(), context);
   EventRouter::Get(context)
       ->DispatchEventWithLazyListener(extension_id, std::move(event));
 
diff --git a/extensions/browser/api/bluetooth_low_energy/bluetooth_low_energy_api.cc b/extensions/browser/api/bluetooth_low_energy/bluetooth_low_energy_api.cc
index 7d48f41..142d22e7 100644
--- a/extensions/browser/api/bluetooth_low_energy/bluetooth_low_energy_api.cc
+++ b/extensions/browser/api/bluetooth_low_energy/bluetooth_low_energy_api.cc
@@ -588,8 +588,8 @@
   // Manually construct the result instead of using
   // apibtle::GetCharacteristic::Result::Create as it doesn't convert lists of
   // enums correctly.
-  Respond(OneArgument(base::Value::FromUniquePtrValue(
-      apibtle::CharacteristicToValue(&characteristic))));
+  Respond(OneArgument(
+      base::Value(apibtle::CharacteristicToValue(&characteristic))));
 }
 
 BluetoothLowEnergyGetCharacteristicsFunction::
@@ -626,11 +626,11 @@
   // Manually construct the result instead of using
   // apibtle::GetCharacteristics::Result::Create as it doesn't convert lists of
   // enums correctly.
-  std::unique_ptr<base::ListValue> result(new base::ListValue());
+  base::Value::List result;
   for (apibtle::Characteristic& characteristic : characteristic_list)
-    result->Append(apibtle::CharacteristicToValue(&characteristic));
+    result.Append(apibtle::CharacteristicToValue(&characteristic));
 
-  Respond(OneArgument(base::Value::FromUniquePtrValue(std::move(result))));
+  Respond(OneArgument(base::Value(std::move(result))));
 }
 
 BluetoothLowEnergyGetIncludedServicesFunction::
@@ -792,8 +792,8 @@
   // Manually construct the result instead of using
   // apibtle::GetCharacteristic::Result::Create as it doesn't convert lists of
   // enums correctly.
-  Respond(OneArgument(base::Value::FromUniquePtrValue(
-      apibtle::CharacteristicToValue(&characteristic))));
+  Respond(OneArgument(
+      base::Value(apibtle::CharacteristicToValue(&characteristic))));
 }
 
 void BluetoothLowEnergyReadCharacteristicValueFunction::ErrorCallback(
diff --git a/extensions/browser/api/bluetooth_low_energy/bluetooth_low_energy_event_router.cc b/extensions/browser/api/bluetooth_low_energy/bluetooth_low_energy_event_router.cc
index 6655ce4..9a13b336 100644
--- a/extensions/browser/api/bluetooth_low_energy/bluetooth_low_energy_event_router.cc
+++ b/extensions/browser/api/bluetooth_low_energy/bluetooth_low_energy_event_router.cc
@@ -1061,8 +1061,8 @@
   apibtle::Characteristic api_characteristic;
   PopulateCharacteristic(characteristic, &api_characteristic);
   std::vector<base::Value> args;
-  args.push_back(base::Value::FromUniquePtrValue(
-      apibtle::CharacteristicToValue(&api_characteristic)));
+  args.push_back(
+      base::Value(apibtle::CharacteristicToValue(&api_characteristic)));
 
   DispatchEventToExtensionsWithPermission(
       events::BLUETOOTH_LOW_ENERGY_ON_CHARACTERISTIC_VALUE_CHANGED,
diff --git a/extensions/browser/api/bluetooth_low_energy/utils.cc b/extensions/browser/api/bluetooth_low_energy/utils.cc
index 6106e321..45e108b 100644
--- a/extensions/browser/api/bluetooth_low_energy/utils.cc
+++ b/extensions/browser/api/bluetooth_low_energy/utils.cc
@@ -27,15 +27,13 @@
 
 }  // namespace
 
-std::unique_ptr<base::DictionaryValue> CharacteristicToValue(
-    Characteristic* from) {
+base::Value::Dict CharacteristicToValue(Characteristic* from) {
   // Copy the properties. Use Characteristic::ToValue to generate the result
   // dictionary without the properties, to prevent json_schema_compiler from
   // failing.
-  std::vector<CharacteristicProperty> properties = from->properties;
-  from->properties.clear();
-  std::unique_ptr<base::DictionaryValue> to = from->ToValue();
-  to->SetKey("properties", CharacteristicPropertiesToValue(properties));
+  std::vector<CharacteristicProperty> properties = std::move(from->properties);
+  base::Value::Dict to = std::move(from->ToValue()->GetDict());
+  to.Set("properties", CharacteristicPropertiesToValue(properties));
   return to;
 }
 
diff --git a/extensions/browser/api/bluetooth_low_energy/utils.h b/extensions/browser/api/bluetooth_low_energy/utils.h
index ff1f770..1b28743 100644
--- a/extensions/browser/api/bluetooth_low_energy/utils.h
+++ b/extensions/browser/api/bluetooth_low_energy/utils.h
@@ -17,12 +17,11 @@
 // TODO(armansito): Remove these functions once the described bug is fixed.
 // (See crbug.com/368368)
 
-// Converts a Characteristic to a base::Value. This function is necessary as
-// json_schema_compiler::util::AddItemToList has no template specialization for
-// user defined enums, which get treated as integers. This is because
+// Converts a Characteristic to a base::Value::Dict. This function is necessary
+// as json_schema_compiler::util::AddItemToList has no template specialization
+// for user defined enums, which get treated as integers. This is because
 // Characteristic contains a list of enum CharacteristicProperty.
-std::unique_ptr<base::DictionaryValue> CharacteristicToValue(
-    Characteristic* from);
+base::Value::Dict CharacteristicToValue(Characteristic* from);
 
 // Converts a Descriptor to a base::Value. This function is necessary as a
 // Descriptor embeds a Characteristic and that needs special handling as
diff --git a/extensions/browser/api/declarative/declarative_api.cc b/extensions/browser/api/declarative/declarative_api.cc
index 644b909b..fee3b6d 100644
--- a/extensions/browser/api/declarative/declarative_api.cc
+++ b/extensions/browser/api/declarative/declarative_api.cc
@@ -218,10 +218,11 @@
   if (!error.empty())
     return Error(error);
 
-  auto rules_value = std::make_unique<base::ListValue>();
+  base::Value::List rules_value;
+  rules_value.reserve(rules_out.size());
   for (const auto* rule : rules_out)
-    rules_value->Append(rule->ToValue());
-  return OneArgument(base::Value::FromUniquePtrValue(std::move(rules_value)));
+    rules_value.Append(base::Value::FromUniquePtrValue(rule->ToValue()));
+  return OneArgument(base::Value(std::move(rules_value)));
 }
 
 void EventsEventAddRulesFunction::RecordUMA(
@@ -305,10 +306,11 @@
     rules_registry_->GetAllRules(extension_id(), &rules);
   }
 
-  auto rules_value = std::make_unique<base::ListValue>();
+  base::Value::List rules_value;
+  rules_value.reserve(rules.size());
   for (const auto* rule : rules)
-    rules_value->Append(rule->ToValue());
-  return OneArgument(base::Value::FromUniquePtrValue(std::move(rules_value)));
+    rules_value.Append(base::Value::FromUniquePtrValue(rule->ToValue()));
+  return OneArgument(base::Value(std::move(rules_value)));
 }
 
 void EventsEventGetRulesFunction::RecordUMA(
diff --git a/extensions/browser/event_listener_map_unittest.cc b/extensions/browser/event_listener_map_unittest.cc
index 7ec912c..09de5f8 100644
--- a/extensions/browser/event_listener_map_unittest.cc
+++ b/extensions/browser/event_listener_map_unittest.cc
@@ -78,14 +78,14 @@
 
   std::unique_ptr<DictionaryValue> CreateHostSuffixFilter(
       const std::string& suffix) {
-    auto filter_dict = std::make_unique<DictionaryValue>();
-    filter_dict->Set("hostSuffix", std::make_unique<Value>(suffix));
+    base::Value::Dict filter_dict;
+    filter_dict.Set("hostSuffix", suffix);
 
-    auto filter_list = std::make_unique<ListValue>();
-    filter_list->Append(std::move(filter_dict));
+    base::Value::List filter_list;
+    filter_list.Append(std::move(filter_dict));
 
     auto filter = std::make_unique<DictionaryValue>();
-    filter->Set("url", std::move(filter_list));
+    filter->GetDict().Set("url", base::Value(std::move(filter_list)));
     return filter;
   }
 
diff --git a/google_apis/drive/drive_api_requests.cc b/google_apis/drive/drive_api_requests.cc
index 5b9082e..a74bd20 100644
--- a/google_apis/drive/drive_api_requests.cc
+++ b/google_apis/drive/drive_api_requests.cc
@@ -94,9 +94,9 @@
   if (properties.empty())
     return;
 
-  base::ListValue properties_value;
+  base::Value::List properties_value;
   for (const auto& property : properties) {
-    auto property_value = std::make_unique<base::DictionaryValue>();
+    base::Value::Dict property_value;
     std::string visibility_as_string;
     switch (property.visibility()) {
       case Property::VISIBILITY_PRIVATE:
@@ -106,12 +106,12 @@
         visibility_as_string = "PUBLIC";
         break;
     }
-    property_value->SetString("visibility", visibility_as_string);
-    property_value->SetString("key", property.key());
-    property_value->SetString("value", property.value());
+    property_value.Set("visibility", visibility_as_string);
+    property_value.Set("key", property.key());
+    property_value.Set("value", property.value());
     properties_value.Append(std::move(property_value));
   }
-  request_body->SetKey("properties", std::move(properties_value));
+  request_body->SetKey("properties", base::Value(std::move(properties_value)));
 }
 
 // Creates metadata JSON string for multipart uploading.
@@ -129,9 +129,10 @@
 
   // Fill parent link.
   if (!parent_resource_id.empty()) {
-    base::ListValue parents;
-    parents.Append(google_apis::util::CreateParentValue(parent_resource_id));
-    root.SetKey("parents", std::move(parents));
+    base::Value::List parents;
+    parents.Append(base::Value::FromUniquePtrValue(
+        google_apis::util::CreateParentValue(parent_resource_id)));
+    root.SetKey("parents", base::Value(std::move(parents)));
   }
 
   if (!modified_date.is_null()) {
@@ -352,13 +353,13 @@
     root.SetString("modifiedDate", util::FormatTimeAsString(modified_date_));
 
   if (!parents_.empty()) {
-    base::ListValue parents_value;
+    base::Value::List parents_value;
     for (size_t i = 0; i < parents_.size(); ++i) {
-      auto parent = std::make_unique<base::DictionaryValue>();
-      parent->SetString("id", parents_[i]);
+      base::Value::Dict parent;
+      parent.Set("id", parents_[i]);
       parents_value.Append(std::move(parent));
     }
-    root.SetKey("parents", std::move(parents_value));
+    root.SetKey("parents", base::Value(std::move(parents_value)));
   }
 
   if (!title_.empty())
@@ -425,13 +426,13 @@
   }
 
   if (!parents_.empty()) {
-    base::ListValue parents_value;
+    base::Value::List parents_value;
     for (size_t i = 0; i < parents_.size(); ++i) {
-      auto parent = std::make_unique<base::DictionaryValue>();
-      parent->SetString("id", parents_[i]);
+      base::Value::Dict parent;
+      parent.Set("id", parents_[i]);
       parents_value.Append(std::move(parent));
     }
-    root.SetKey("parents", std::move(parents_value));
+    root.SetKey("parents", base::Value(std::move(parents_value)));
   }
 
   AttachProperties(properties_, &root);
@@ -475,13 +476,13 @@
     root.SetString("modifiedDate", util::FormatTimeAsString(modified_date_));
 
   if (!parents_.empty()) {
-    base::ListValue parents_value;
+    base::Value::List parents_value;
     for (size_t i = 0; i < parents_.size(); ++i) {
-      auto parent = std::make_unique<base::DictionaryValue>();
-      parent->SetString("id", parents_[i]);
+      base::Value::Dict parent;
+      parent.Set("id", parents_[i]);
       parents_value.Append(std::move(parent));
     }
-    root.SetKey("parents", std::move(parents_value));
+    root.SetKey("parents", base::Value(std::move(parents_value)));
   }
 
   if (!title_.empty())
@@ -732,9 +733,10 @@
   root.SetString("title", title_);
 
   // Fill parent link.
-  base::ListValue parents;
-  parents.Append(util::CreateParentValue(parent_resource_id_));
-  root.SetKey("parents", std::move(parents));
+  base::Value::List parents;
+  parents.Append(base::Value::FromUniquePtrValue(
+      util::CreateParentValue(parent_resource_id_)));
+  root.SetKey("parents", base::Value(std::move(parents)));
 
   if (!modified_date_.is_null())
     root.SetString("modifiedDate", util::FormatTimeAsString(modified_date_));
@@ -794,9 +796,10 @@
     std::string* upload_content) {
   base::DictionaryValue root;
   if (!parent_resource_id_.empty()) {
-    base::ListValue parents;
-    parents.Append(util::CreateParentValue(parent_resource_id_));
-    root.SetKey("parents", std::move(parents));
+    base::Value::List parents;
+    parents.Append(base::Value::FromUniquePtrValue(
+        util::CreateParentValue(parent_resource_id_)));
+    root.SetKey("parents", base::Value(std::move(parents)));
   }
 
   if (!title_.empty())
diff --git a/ios/chrome/browser/ui/follow/followed_web_channel.h b/ios/chrome/browser/ui/follow/followed_web_channel.h
index 131df9d1..5ed96b2 100644
--- a/ios/chrome/browser/ui/follow/followed_web_channel.h
+++ b/ios/chrome/browser/ui/follow/followed_web_channel.h
@@ -17,11 +17,8 @@
 // Title of the web channel.
 @property(nonatomic, copy) NSString* title;
 
-// The host name for the web channel.
-@property(nonatomic, copy) NSString* hostname;
-
-// CrURL from which to retrieve a favicon.
-@property(nonatomic, strong) CrURL* faviconURL;
+// URL of the web channel.
+@property(nonatomic, strong) CrURL* channelURL;
 
 // YES if the web channel is unavailable.
 @property(nonatomic, assign) BOOL unavailable;
diff --git a/ios/chrome/browser/ui/ntp/feed_management/BUILD.gn b/ios/chrome/browser/ui/ntp/feed_management/BUILD.gn
index 709ac2da..85f3119 100644
--- a/ios/chrome/browser/ui/ntp/feed_management/BUILD.gn
+++ b/ios/chrome/browser/ui/ntp/feed_management/BUILD.gn
@@ -12,6 +12,8 @@
     ":feed_management_ui",
     ":follow_management",
     ":follow_management_ui",
+    "//ios/chrome/browser/browser_state",
+    "//ios/chrome/browser/main:public",
     "//ios/chrome/browser/ui/coordinators:chrome_coordinators",
     "//ios/chrome/browser/ui/table_view",
   ]
@@ -46,9 +48,13 @@
   configs += [ "//build/config/compiler:enable_arc" ]
   deps = [
     ":follow_management_ui",
-    "//ios/chrome/browser/net",
+    "//ios/chrome/browser/favicon",
+    "//ios/chrome/browser/net:crurl",
+    "//ios/chrome/browser/ui/favicon:constants",
     "//ios/chrome/browser/ui/follow",
     "//ios/chrome/browser/ui/table_view",
+    "//ios/public/provider/chrome/browser",
+    "//ios/public/provider/chrome/browser/follow",
   ]
 }
 
@@ -64,10 +70,9 @@
   deps = [
     "//base",
     "//ios/chrome/app/strings:ios_strings_grit",
-    "//ios/chrome/browser/net",
+    "//ios/chrome/browser/net:crurl",
     "//ios/chrome/browser/ui/follow",
     "//ios/chrome/browser/ui/table_view",
-    "//ios/chrome/browser/ui/table_view/cells",
     "//ios/chrome/common/ui/favicon",
     "//ui/base",
   ]
diff --git a/ios/chrome/browser/ui/ntp/feed_management/feed_management_coordinator.mm b/ios/chrome/browser/ui/ntp/feed_management/feed_management_coordinator.mm
index bd166be..eadac20 100644
--- a/ios/chrome/browser/ui/ntp/feed_management/feed_management_coordinator.mm
+++ b/ios/chrome/browser/ui/ntp/feed_management/feed_management_coordinator.mm
@@ -4,6 +4,8 @@
 
 #import "ios/chrome/browser/ui/ntp/feed_management/feed_management_coordinator.h"
 
+#import "ios/chrome/browser/browser_state/chrome_browser_state.h"
+#import "ios/chrome/browser/main/browser.h"
 #import "ios/chrome/browser/ui/ntp/feed_management/feed_management_follow_delegate.h"
 #import "ios/chrome/browser/ui/ntp/feed_management/feed_management_view_controller.h"
 #import "ios/chrome/browser/ui/ntp/feed_management/follow_management_mediator.h"
@@ -65,7 +67,8 @@
   FollowManagementViewController* followManagementViewController =
       [[FollowManagementViewController alloc]
           initWithStyle:UITableViewStyleInsetGrouped];
-  FollowManagementMediator* mediator = [[FollowManagementMediator alloc] init];
+  FollowManagementMediator* mediator = [[FollowManagementMediator alloc]
+      initWithBrowserState:self.browser->GetBrowserState()];
   followManagementViewController.followedWebChannelsDataSource = mediator;
   followManagementViewController.faviconDataSource = mediator;
   self.followManagementMediator = mediator;
diff --git a/ios/chrome/browser/ui/ntp/feed_management/follow_management_mediator.h b/ios/chrome/browser/ui/ntp/feed_management/follow_management_mediator.h
index f88c047..cdf3c27 100644
--- a/ios/chrome/browser/ui/ntp/feed_management/follow_management_mediator.h
+++ b/ios/chrome/browser/ui/ntp/feed_management/follow_management_mediator.h
@@ -10,10 +10,21 @@
 #import "ios/chrome/browser/ui/ntp/feed_management/followed_web_channels_data_source.h"
 #import "ios/chrome/browser/ui/table_view/table_view_favicon_data_source.h"
 
+class ChromeBrowserState;
+
 // The intermediary between the model and view layers for the follow management
 // UI.
 @interface FollowManagementMediator
     : NSObject <FollowedWebChannelsDataSource, TableViewFaviconDataSource>
+
+// The current BrowserState.
+@property(nonatomic, assign) ChromeBrowserState* browserState;
+
+- (instancetype)init NS_UNAVAILABLE;
+// Init method. |browserState| can't be nil.
+- (instancetype)initWithBrowserState:(ChromeBrowserState*)browserState
+    NS_DESIGNATED_INITIALIZER;
+
 @end
 
 #endif  // IOS_CHROME_BROWSER_UI_NTP_FEED_MANAGEMENT_FOLLOW_MANAGEMENT_MEDIATOR_H_
diff --git a/ios/chrome/browser/ui/ntp/feed_management/follow_management_mediator.mm b/ios/chrome/browser/ui/ntp/feed_management/follow_management_mediator.mm
index 9ff80cb..c9f907c 100644
--- a/ios/chrome/browser/ui/ntp/feed_management/follow_management_mediator.mm
+++ b/ios/chrome/browser/ui/ntp/feed_management/follow_management_mediator.mm
@@ -4,27 +4,54 @@
 
 #import "ios/chrome/browser/ui/ntp/feed_management/follow_management_mediator.h"
 
+#import "ios/chrome/browser/favicon/favicon_loader.h"
+#import "ios/chrome/browser/favicon/ios_chrome_favicon_loader_factory.h"
 #import "ios/chrome/browser/net/crurl.h"
+#import "ios/chrome/browser/ui/favicon/favicon_constants.h"
 #import "ios/chrome/browser/ui/follow/followed_web_channel.h"
+#import "ios/public/provider/chrome/browser/chrome_browser_provider.h"
+#import "ios/public/provider/chrome/browser/follow/follow_provider.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
 #endif
 
+@interface FollowManagementMediator ()
+
+// FaviconLoader retrieves favicons for a given page URL.
+@property(nonatomic, assign) FaviconLoader* faviconLoader;
+
+@end
+
 @implementation FollowManagementMediator
 
+- (instancetype)initWithBrowserState:(ChromeBrowserState*)browserState {
+  self = [super init];
+  if (self) {
+    _browserState = browserState;
+    _faviconLoader =
+        IOSChromeFaviconLoaderFactory::GetForBrowserState(_browserState);
+  }
+  return self;
+}
+
 #pragma mark - FollowedWebChannelsDataSource
 
 - (NSArray<FollowedWebChannel*>*)followedWebChannels {
-  // TODO(crbug.com/1296745): Call provider API to get followed channels.
-  return @[];
+  return ios::GetChromeBrowserProvider()
+      .GetFollowProvider()
+      ->GetFollowedWebChannels();
 }
 
 #pragma mark - TableViewFaviconDataSource
 
 - (void)faviconForURL:(CrURL*)URL
            completion:(void (^)(FaviconAttributes*))completion {
-  // TODO(crbug.com/1296745): Call favicon loader.
+  self.faviconLoader->FaviconForPageUrl(
+      URL.gurl, kDesiredSmallFaviconSizePt, kMinFaviconSizePt,
+      /*fallback_to_google_server=*/false, ^(FaviconAttributes* attributes) {
+        completion(attributes);
+      });
 }
 
 @end
diff --git a/ios/chrome/browser/ui/ntp/feed_management/follow_management_view_controller.mm b/ios/chrome/browser/ui/ntp/feed_management/follow_management_view_controller.mm
index c90ecfa..602f301 100644
--- a/ios/chrome/browser/ui/ntp/feed_management/follow_management_view_controller.mm
+++ b/ios/chrome/browser/ui/ntp/feed_management/follow_management_view_controller.mm
@@ -55,9 +55,9 @@
       base::mac::ObjCCastStrict<FollowedWebChannelItem>(tableViewItem);
   FollowedWebChannelCell* followedWebChannelCell =
       base::mac::ObjCCastStrict<FollowedWebChannelCell>(cellToReturn);
-  CrURL* faviconURL = followedWebChannelItem.followedWebChannel.faviconURL;
+  CrURL* channelURL = followedWebChannelItem.followedWebChannel.channelURL;
 
-  [self.faviconDataSource faviconForURL:faviconURL
+  [self.faviconDataSource faviconForURL:channelURL
                              completion:^(FaviconAttributes* attributes) {
                                // Only set favicon if the cell hasn't been
                                // reused.
diff --git a/ios/chrome/browser/ui/ntp/feed_management/followed_web_channel_item.mm b/ios/chrome/browser/ui/ntp/feed_management/followed_web_channel_item.mm
index e7ccc56..69fbc0e 100644
--- a/ios/chrome/browser/ui/ntp/feed_management/followed_web_channel_item.mm
+++ b/ios/chrome/browser/ui/ntp/feed_management/followed_web_channel_item.mm
@@ -13,35 +13,32 @@
 #error "This file requires ARC support."
 #endif
 
-@interface FollowedWebChannelItem ()
-
-// This property contains a value if the WebChannel is unavailable.
-@property(nonatomic, copy) NSAttributedString* detailAttributedString;
-
-@end
-
 @implementation FollowedWebChannelItem
 
-- (void)setFollowedWebChannel:(FollowedWebChannel*)followedWebChannel {
-  _followedWebChannel = followedWebChannel;
-  self.title = followedWebChannel.title;
-  if (!_followedWebChannel.unavailable) {
-    self.detailText = _followedWebChannel.hostname;
-    return;
+- (instancetype)initWithType:(NSInteger)type {
+  self = [super initWithType:type];
+  if (self) {
+    self.cellClass = [FollowedWebChannelCell class];
   }
+  return self;
+}
 
-  // This approach repurposes an existing cell by simply adding a newline with
-  // additional text instead of creating a new cell with an additional label.
-  NSString* unavailableText =
-      l10n_util::GetNSString(IDS_IOS_FOLLOW_MANAGEMENT_CHANNEL_UNAVAILABLE);
-  NSAttributedString* unavailableString = [[NSAttributedString alloc]
-      initWithString:[NSString stringWithFormat:@"\n%@", unavailableText]
-          attributes:@{NSForegroundColorAttributeName : UIColor.redColor}];
-  NSMutableAttributedString* concatenatedString =
-      [[NSMutableAttributedString alloc]
-          initWithString:_followedWebChannel.hostname];
-  [concatenatedString appendAttributedString:unavailableString];
-  _detailAttributedString = concatenatedString;
+#pragma mark - Properties
+
+- (NSString*)title {
+  return _followedWebChannel.title;
+}
+
+- (CrURL*)URL {
+  return _followedWebChannel.channelURL;
+}
+
+- (NSString*)supplementalURLText {
+  if (_followedWebChannel.unavailable) {
+    return l10n_util::GetNSString(
+        IDS_IOS_FOLLOW_MANAGEMENT_CHANNEL_UNAVAILABLE);
+  }
+  return nil;
 }
 
 - (void)configureCell:(TableViewCell*)tableCell
@@ -50,9 +47,6 @@
   FollowedWebChannelCell* cell =
       base::mac::ObjCCastStrict<FollowedWebChannelCell>(tableCell);
   cell.followedWebChannel = self.followedWebChannel;
-
-  // TODO(crbug.com/1296745): Modify TableViewURLCell to have spinner and third
-  // row text.
 }
 
 @end
diff --git a/ios/chrome/browser/ui/settings/password/BUILD.gn b/ios/chrome/browser/ui/settings/password/BUILD.gn
index a1e63a7..dca1c33 100644
--- a/ios/chrome/browser/ui/settings/password/BUILD.gn
+++ b/ios/chrome/browser/ui/settings/password/BUILD.gn
@@ -155,6 +155,7 @@
     "//components/password_manager/core/common",
     "//components/password_manager/core/common:features",
     "//components/prefs:test_support",
+    "//components/signin/public/identity_manager/objc",
     "//components/strings",
     "//ios/chrome/app/strings",
     "//ios/chrome/app/strings",
diff --git a/ios/chrome/browser/ui/settings/password/passwords_mediator.mm b/ios/chrome/browser/ui/settings/password/passwords_mediator.mm
index cbd541f..e1df8930 100644
--- a/ios/chrome/browser/ui/settings/password/passwords_mediator.mm
+++ b/ios/chrome/browser/ui/settings/password/passwords_mediator.mm
@@ -369,14 +369,14 @@
       });
 }
 
-#pragma mark - IdentityManagerObserverBrdigeDelegate
+#pragma mark - IdentityManagerObserverBridgeDelegate
 
 - (void)onPrimaryAccountChanged:
     (const signin::PrimaryAccountChangeEvent&)event {
   [self.consumer updateOnDeviceEncryptionSessionAndUpdateTableView];
 }
 
-#pragma mark - SyncObserverBridge
+#pragma mark - SyncObserverModelBridge
 
 - (void)onSyncStateChanged {
   [self.consumer updateOnDeviceEncryptionSessionAndUpdateTableView];
diff --git a/ios/chrome/browser/ui/settings/password/passwords_mediator_unittest.mm b/ios/chrome/browser/ui/settings/password/passwords_mediator_unittest.mm
index 2e1b181..e5b3944 100644
--- a/ios/chrome/browser/ui/settings/password/passwords_mediator_unittest.mm
+++ b/ios/chrome/browser/ui/settings/password/passwords_mediator_unittest.mm
@@ -4,6 +4,7 @@
 
 #import "ios/chrome/browser/ui/settings/password/passwords_mediator.h"
 
+#include "base/mac/foundation_util.h"
 #include "base/strings/string_piece.h"
 #include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversions.h"
@@ -13,6 +14,7 @@
 #include "components/password_manager/core/common/password_manager_features.h"
 #include "components/password_manager/core/common/password_manager_pref_names.h"
 #include "components/prefs/testing_pref_service.h"
+#import "components/signin/public/identity_manager/objc/identity_manager_observer_bridge.h"
 #include "ios/chrome/browser/browser_state/chrome_browser_state.h"
 #include "ios/chrome/browser/browser_state/test_chrome_browser_state.h"
 #import "ios/chrome/browser/favicon/favicon_loader.h"
@@ -22,6 +24,7 @@
 #include "ios/chrome/browser/passwords/ios_chrome_password_check_manager_factory.h"
 #include "ios/chrome/browser/passwords/ios_chrome_password_store_factory.h"
 #include "ios/chrome/browser/passwords/password_check_observer_bridge.h"
+#include "ios/chrome/browser/sync/sync_observer_bridge.h"
 #include "ios/chrome/browser/sync/sync_setup_service_factory.h"
 #include "ios/chrome/browser/sync/sync_setup_service_mock.h"
 #import "ios/chrome/browser/ui/settings/password/passwords_consumer.h"
@@ -71,6 +74,11 @@
   std::vector<password_manager::PasswordForm> _blockedForms;
 }
 
+// Number of time the method updateOnDeviceEncryptionSessionAndUpdateTableView
+// was called. Used to test that primary account change and sync change
+// causes the update to occur.
+@property(nonatomic, assign) NSInteger numberOfCallToChangeOnDeviceEncryption;
+
 @property(nonatomic, assign) NSString* detailedText;
 
 @end
@@ -98,6 +106,7 @@
 }
 
 - (void)updateOnDeviceEncryptionSessionAndUpdateTableView {
+  self.numberOfCallToChangeOnDeviceEncryption += 1;
 }
 
 @end
@@ -213,3 +222,21 @@
   [mediator() passwordAutoFillStatusDidChange];
   EXPECT_NSEQ([consumer() detailedText], @"On");
 }
+
+TEST_F(PasswordsMediatorTest, SyncChangeTriggersChangeOnDeviceEncryption) {
+  DCHECK([mediator() conformsToProtocol:@protocol(SyncObserverModelBridge)]);
+  PasswordsMediator<SyncObserverModelBridge>* syncObserver =
+      static_cast<PasswordsMediator<SyncObserverModelBridge>*>(mediator());
+  [syncObserver onSyncStateChanged];
+  ASSERT_EQ(1, consumer().numberOfCallToChangeOnDeviceEncryption);
+}
+
+TEST_F(PasswordsMediatorTest, IdentityChangeTriggersChangeOnDeviceEncryption) {
+  DCHECK([mediator() conformsToProtocol:@protocol(SyncObserverModelBridge)]);
+  PasswordsMediator<IdentityManagerObserverBridgeDelegate>* syncObserver =
+      static_cast<PasswordsMediator<IdentityManagerObserverBridgeDelegate>*>(
+          mediator());
+  const signin::PrimaryAccountChangeEvent event;
+  [syncObserver onPrimaryAccountChanged:event];
+  ASSERT_EQ(1, consumer().numberOfCallToChangeOnDeviceEncryption);
+}
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_view_controller.mm b/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_view_controller.mm
index 9bc1c19..bb62589a3 100644
--- a/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_view_controller.mm
+++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_view_controller.mm
@@ -1032,6 +1032,9 @@
   CGFloat pageWidth = self.scrollView.frame.size.width;
   NSUInteger pageIndex = GetPageIndexFromPage(targetPage);
   CGPoint targetOffset = CGPointMake(pageIndex * pageWidth, 0);
+  BOOL changed = self.currentPage != targetPage;
+  BOOL scrolled =
+      !CGPointEqualToPoint(self.scrollView.contentOffset, targetOffset);
 
   // If the view is visible and |animated| is YES, animate the change.
   // Otherwise don't.
@@ -1047,12 +1050,11 @@
     // Only set |scrollViewAnimatingContentOffset| to YES if there's an actual
     // change in the contentOffset, as |-scrollViewDidEndScrollingAnimation:| is
     // never called if the animation does not occur.
-    if (!CGPointEqualToPoint(self.scrollView.contentOffset, targetOffset)) {
+    if (scrolled) {
       self.scrollViewAnimatingContentOffset = YES;
       [self.scrollView setContentOffset:targetOffset animated:YES];
       // |self.currentPage| is set in scrollViewDidEndScrollingAnimation:
     } else {
-      BOOL changed = self.currentPage != targetPage;
       self.currentPage = targetPage;
       if (changed) {
         // When there is no scrolling and the page changed, it can be due to
@@ -1069,7 +1071,7 @@
   // does not notify observers when entries are removed. When close all tabs
   // removes entries, the remote tabs page in the tab grid are not updated. This
   // ensures that the table is updated whenever scrolling to it.
-  if (targetPage == TabGridPageRemoteTabs) {
+  if (targetPage == TabGridPageRemoteTabs && (changed || scrolled)) {
     [self.remoteTabsViewController loadModel];
     [self.remoteTabsViewController.tableView reloadData];
   }
diff --git a/ios/public/provider/chrome/browser/follow/follow_provider.h b/ios/public/provider/chrome/browser/follow/follow_provider.h
index cecc9696..37cfbd45 100644
--- a/ios/public/provider/chrome/browser/follow/follow_provider.h
+++ b/ios/public/provider/chrome/browser/follow/follow_provider.h
@@ -8,6 +8,7 @@
 #import <Foundation/Foundation.h>
 
 @class FollowSiteInfo;
+@class FollowedWebChannel;
 
 // FollowProvider provides and updates the following status of websites and
 // provides information related to these.
@@ -22,9 +23,13 @@
   // Returns true if the website with |followSiteInfo| has been followed.
   virtual bool GetFollowStatus(FollowSiteInfo* followSiteInfo);
 
+  // TODO(crbug.com/1296745): Deprecated function.
   // Returns a list of followed websites.
   virtual NSArray<FollowSiteInfo*>* GetFollowedChannels();
 
+  // Returns a list of followed web channels.
+  virtual NSArray<FollowedWebChannel*>* GetFollowedWebChannels();
+
   // Updates the following status of |site| to |state|.
   virtual void UpdateFollowStatus(FollowSiteInfo* site, bool state);
 };
diff --git a/ios/public/provider/chrome/browser/follow/follow_provider.mm b/ios/public/provider/chrome/browser/follow/follow_provider.mm
index a158771..f7a81ac9 100644
--- a/ios/public/provider/chrome/browser/follow/follow_provider.mm
+++ b/ios/public/provider/chrome/browser/follow/follow_provider.mm
@@ -16,4 +16,8 @@
   return nil;
 }
 
+NSArray<FollowedWebChannel*>* FollowProvider::GetFollowedWebChannels() {
+  return nil;
+}
+
 void FollowProvider::UpdateFollowStatus(FollowSiteInfo* site, bool state) {}
diff --git a/net/socket/udp_socket_posix.cc b/net/socket/udp_socket_posix.cc
index b2c49071..bb5af55a 100644
--- a/net/socket/udp_socket_posix.cc
+++ b/net/socket/udp_socket_posix.cc
@@ -2,6 +2,14 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "build/build_config.h"
+
+#if BUILDFLAG(IS_APPLE)
+// This must be defined before including <netinet/in.h>
+// to use IPV6_DONTFRAG, one of the IPv6 Sockets option introduced by RFC 3542
+#define __APPLE_USE_RFC_3542
+#endif  // BUILDFLAG(IS_APPLE)
+
 #include "net/socket/udp_socket_posix.h"
 
 #include <errno.h>
@@ -30,7 +38,6 @@
 #include "base/task/task_runner_util.h"
 #include "base/task/thread_pool.h"
 #include "base/trace_event/typed_macros.h"
-#include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
 #include "net/base/features.h"
 #include "net/base/io_buffer.h"
@@ -64,6 +71,10 @@
 #include <pthread.h>
 #endif  // BUILDFLAG(IS_APPLE) && !BUILDFLAG(CRONET_BUILD)
 
+#if BUILDFLAG(IS_MAC)
+#include "base/mac/mac_util.h"
+#endif  // BUILDFLAG(IS_MAC)
+
 namespace net {
 
 namespace {
@@ -565,8 +576,24 @@
   DCHECK_NE(socket_, kInvalidSocket);
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
 
-#if !defined(IP_PMTUDISC_DO)
+#if !defined(IP_PMTUDISC_DO) && !BUILDFLAG(IS_MAC)
   return ERR_NOT_IMPLEMENTED;
+
+// setsockopt(IP_DONTFRAG) is supported on macOS from Big Sur
+#elif BUILDFLAG(IS_MAC)
+  if (!base::mac::IsAtLeastOS11()) {
+    return ERR_NOT_IMPLEMENTED;
+  }
+  int val = 1;
+  if (addr_family_ == AF_INET6) {
+    int rv =
+        setsockopt(socket_, IPPROTO_IPV6, IPV6_DONTFRAG, &val, sizeof(val));
+    // IP_DONTFRAG is not supported on v4mapped addresses.
+    return rv == 0 ? OK : MapSystemError(errno);
+  }
+  int rv = setsockopt(socket_, IPPROTO_IP, IP_DONTFRAG, &val, sizeof(val));
+  return rv == 0 ? OK : MapSystemError(errno);
+
 #else
   if (addr_family_ == AF_INET6) {
     int val = IPV6_PMTUDISC_DO;
diff --git a/net/socket/udp_socket_unittest.cc b/net/socket/udp_socket_unittest.cc
index 5776be9..5292cdc 100644
--- a/net/socket/udp_socket_unittest.cc
+++ b/net/socket/udp_socket_unittest.cc
@@ -55,6 +55,10 @@
 #include <TargetConditionals.h>
 #endif
 
+#if BUILDFLAG(IS_MAC)
+#include "base/mac/mac_util.h"
+#endif  // BUILDFLAG(IS_MAC)
+
 using net::test::IsError;
 using net::test::IsOk;
 using testing::DoAll;
@@ -583,9 +587,15 @@
     EXPECT_THAT(rv, IsOk());
 
     rv = client.SetDoNotFragment();
-#if BUILDFLAG(IS_APPLE) || BUILDFLAG(IS_FUCHSIA)
+#if BUILDFLAG(IS_IOS) || BUILDFLAG(IS_FUCHSIA)
     // TODO(crbug.com/945590): IP_MTU_DISCOVER is not implemented on Fuchsia.
     EXPECT_THAT(rv, IsError(ERR_NOT_IMPLEMENTED));
+#elif BUILDFLAG(IS_MAC)
+    if (base::mac::IsAtLeastOS11()) {
+      EXPECT_THAT(rv, IsOk());
+    } else {
+      EXPECT_THAT(rv, IsError(ERR_NOT_IMPLEMENTED));
+    }
 #else
     EXPECT_THAT(rv, IsOk());
 #endif
@@ -605,7 +615,7 @@
     EXPECT_THAT(rv, IsOk());
 
     rv = server.SetDoNotFragment();
-#if BUILDFLAG(IS_APPLE) || BUILDFLAG(IS_FUCHSIA)
+#if BUILDFLAG(IS_IOS) || BUILDFLAG(IS_FUCHSIA)
     // TODO(crbug.com/945590): IP_MTU_DISCOVER is not implemented on Fuchsia.
     EXPECT_THAT(rv, IsError(ERR_NOT_IMPLEMENTED));
 #else
diff --git a/pdf/BUILD.gn b/pdf/BUILD.gn
index b41c60fb..3fac00db 100644
--- a/pdf/BUILD.gn
+++ b/pdf/BUILD.gn
@@ -228,6 +228,7 @@
 
     deps = [
       "//base",
+      "//skia",
       "//ui/gfx/geometry",
     ]
   }
diff --git a/pdf/accessibility_structs.cc b/pdf/accessibility_structs.cc
index 6799b2d1..8accefc8 100644
--- a/pdf/accessibility_structs.cc
+++ b/pdf/accessibility_structs.cc
@@ -76,8 +76,12 @@
 
 AccessibilityImageInfo::AccessibilityImageInfo(const std::string& alt_text,
                                                uint32_t text_run_index,
-                                               const gfx::RectF& bounds)
-    : alt_text(alt_text), text_run_index(text_run_index), bounds(bounds) {}
+                                               const gfx::RectF& bounds,
+                                               const SkBitmap& image_data)
+    : alt_text(alt_text),
+      text_run_index(text_run_index),
+      bounds(bounds),
+      image_data(image_data) {}
 
 AccessibilityImageInfo::AccessibilityImageInfo(
     const AccessibilityImageInfo& other) = default;
diff --git a/pdf/accessibility_structs.h b/pdf/accessibility_structs.h
index 2d275119..00e17e7 100644
--- a/pdf/accessibility_structs.h
+++ b/pdf/accessibility_structs.h
@@ -10,6 +10,7 @@
 #include <string>
 #include <vector>
 
+#include "third_party/skia/include/core/SkBitmap.h"
 #include "ui/gfx/geometry/point.h"
 #include "ui/gfx/geometry/rect.h"
 #include "ui/gfx/geometry/rect_f.h"
@@ -130,18 +131,25 @@
   AccessibilityImageInfo();
   AccessibilityImageInfo(const std::string& alt_text,
                          uint32_t text_run_index,
-                         const gfx::RectF& bounds);
+                         const gfx::RectF& bounds,
+                         const SkBitmap& image_data);
   AccessibilityImageInfo(const AccessibilityImageInfo& other);
   ~AccessibilityImageInfo();
 
   // Alternate text for the image provided by PDF.
   std::string alt_text;
+
   // We anchor the image to a char index, this denotes the text run before
   // which the image should be inserted in the accessibility tree. The text run
   // at this index should contain the anchor char index.
   uint32_t text_run_index = 0;
+
   // Bounding box of the image.
   gfx::RectF bounds;
+
+  // Only populated if `alt_text` is empty or unavailable, and if the user has
+  // requested that the OCR service tag the PDF so that it is made accessible.
+  SkBitmap image_data;
 };
 
 struct AccessibilityHighlightInfo {
diff --git a/pdf/pdf_engine.h b/pdf/pdf_engine.h
index eba81b7..64ad1471 100644
--- a/pdf/pdf_engine.h
+++ b/pdf/pdf_engine.h
@@ -391,7 +391,10 @@
       int page_index,
       const std::vector<AccessibilityTextRunInfo>& text_runs) = 0;
   // For all the images in page `page_index`, get their alt texts and bounding
-  // boxes.
+  // boxes. If the alt text is empty or unavailable, and if the user has
+  // requested that the OCR service tag the PDF so that it is made accessible,
+  // transfer the raw image pixels in the `image_data` field. Otherwise do not
+  // populate the `image_data` field.
   virtual std::vector<AccessibilityImageInfo> GetImageInfo(
       int page_index,
       uint32_t text_run_count) = 0;
diff --git a/pdf/pdfium/DEPS b/pdf/pdfium/DEPS
index cdf6a231..cdf346f 100644
--- a/pdf/pdfium/DEPS
+++ b/pdf/pdfium/DEPS
@@ -1,5 +1,6 @@
 include_rules = [
   "+components/services/font/public/cpp",
   "+third_party/pdfium/public",
+  "+ui/accessibility",
   "+ui/gfx/codec",
 ]
diff --git a/pdf/pdfium/accessibility_unittest.cc b/pdf/pdfium/accessibility_unittest.cc
index 6ed916cb..c2d3477 100644
--- a/pdf/pdfium/accessibility_unittest.cc
+++ b/pdf/pdfium/accessibility_unittest.cc
@@ -114,9 +114,9 @@
 
 TEST_F(AccessibilityTest, GetAccessibilityImageInfo) {
   static const AccessibilityImageInfo kExpectedImageInfo[] = {
-      {"Image 1", 0, {380, 78, 67, 68}},
-      {"Image 2", 0, {380, 385, 27, 28}},
-      {"Image 3", 0, {380, 678, 1, 1}}};
+      {"Image 1", 0, {380, 78, 67, 68}, {}},
+      {"Image 2", 0, {380, 385, 27, 28}, {}},
+      {"Image 3", 0, {380, 678, 1, 1}, {}}};
 
   TestClient client;
   std::unique_ptr<PDFiumEngine> engine =
diff --git a/pdf/pdfium/pdfium_page.cc b/pdf/pdfium/pdfium_page.cc
index 4de936a..8e2524c52 100644
--- a/pdf/pdfium/pdfium_page.cc
+++ b/pdf/pdfium/pdfium_page.cc
@@ -31,6 +31,9 @@
 #include "third_party/pdfium/public/cpp/fpdf_scopers.h"
 #include "third_party/pdfium/public/fpdf_annot.h"
 #include "third_party/pdfium/public/fpdf_catalog.h"
+#include "third_party/skia/include/core/SkImageInfo.h"
+#include "third_party/skia/include/core/SkPixmap.h"
+#include "ui/accessibility/accessibility_features.h"
 #include "ui/gfx/geometry/point.h"
 #include "ui/gfx/geometry/point_f.h"
 #include "ui/gfx/geometry/rect.h"
@@ -683,6 +686,7 @@
     cur_info.bounds =
         gfx::RectF(image.bounding_rect.x(), image.bounding_rect.y(),
                    image.bounding_rect.width(), image.bounding_rect.height());
+    cur_info.image_data = image.image_data;
     image_info.push_back(std::move(cur_info));
   }
   return image_info;
@@ -1160,6 +1164,7 @@
       continue;
 
     Image image;
+    image.page_object_index = i;
     image.bounding_rect = PageToScreen(gfx::Point(), 1.0, left, top, right,
                                        bottom, PageOrientation::kOriginal);
 
@@ -1182,6 +1187,33 @@
 
   if (!marked_content_id_image_map.empty())
     PopulateImageAltText(marked_content_id_image_map);
+
+  if (!features::IsPdfOcrEnabled())
+    return;
+
+  // If requested by the user, we store the raw image data so that the OCR
+  // service can try and retrieve textual and layout information from the image.
+  // This is because alt text might be empty, or the PDF might simply be
+  // untagged for accessibility.
+  for (Image& image : images_) {
+    if (!image.alt_text.empty())
+      continue;
+
+    FPDF_PAGEOBJECT page_object =
+        FPDFPage_GetObject(page, image.page_object_index);
+    ScopedFPDFBitmap bitmap(
+        FPDFImageObj_GetRenderedBitmap(engine_->doc(), page, page_object));
+    if (!bitmap)
+      continue;
+
+    SkImageInfo info = SkImageInfo::Make(
+        FPDFBitmap_GetWidth(bitmap.get()), FPDFBitmap_GetHeight(bitmap.get()),
+        kBGRA_8888_SkColorType, kOpaque_SkAlphaType);
+    const size_t row_bytes = FPDFBitmap_GetStride(bitmap.get());
+    SkPixmap pixels(info, FPDFBitmap_GetBuffer(bitmap.get()), row_bytes);
+    if (image.image_data.tryAllocPixels(info, row_bytes))
+      image.image_data.writePixels(pixels);
+  }
 }
 
 void PDFiumPage::PopulateImageAltText(
diff --git a/pdf/pdfium/pdfium_page.h b/pdf/pdfium/pdfium_page.h
index 78e06960f..181ac3e 100644
--- a/pdf/pdfium/pdfium_page.h
+++ b/pdf/pdfium/pdfium_page.h
@@ -21,6 +21,7 @@
 #include "third_party/pdfium/public/fpdf_doc.h"
 #include "third_party/pdfium/public/fpdf_formfill.h"
 #include "third_party/pdfium/public/fpdf_text.h"
+#include "third_party/skia/include/core/SkBitmap.h"
 #include "ui/gfx/geometry/point_f.h"
 #include "ui/gfx/geometry/rect.h"
 
@@ -75,8 +76,11 @@
   // bounding boxes.
   std::vector<AccessibilityLinkInfo> GetLinkInfo(
       const std::vector<AccessibilityTextRunInfo>& text_runs);
-
-  // For all the images on the page, get their alt texts and bounding boxes.
+  // For all the images on the page, get their alt texts and bounding boxes. If
+  // the alt text is empty or unavailable, and if the user has requested that
+  // the OCR service tag the PDF so that it is made accessible, transfer the raw
+  // image pixels in the `image_data` field. Otherwise do not populate the
+  // `image_data` field.
   std::vector<AccessibilityImageInfo> GetImageInfo(uint32_t text_run_count);
 
   // For all the highlights on the page, get their underlying text ranges and
@@ -220,6 +224,7 @@
   FRIEND_TEST_ALL_PREFIXES(PDFiumPageHighlightTest, PopulateHighlights);
   FRIEND_TEST_ALL_PREFIXES(PDFiumPageImageTest, CalculateImages);
   FRIEND_TEST_ALL_PREFIXES(PDFiumPageImageTest, ImageAltText);
+  FRIEND_TEST_ALL_PREFIXES(PDFiumPageImageDataTest, ImageData);
   FRIEND_TEST_ALL_PREFIXES(PDFiumPageLinkTest, AnnotLinkGeneration);
   FRIEND_TEST_ALL_PREFIXES(PDFiumPageLinkTest, GetLinkTarget);
   FRIEND_TEST_ALL_PREFIXES(PDFiumPageLinkTest, LinkGeneration);
@@ -256,9 +261,13 @@
     Image(const Image& other);
     ~Image();
 
-    gfx::Rect bounding_rect;
-    // Alt text is available only for tagged PDFs.
+    int page_object_index;
+    // Alt text is available only for PDFs that are tagged for accessibility.
     std::string alt_text;
+    gfx::Rect bounding_rect;
+    // Image data is only stored if the user has requested that the OCR service
+    // try to retrieve textual and layout information from this image.
+    SkBitmap image_data;
   };
 
   // Represents a highlight within the page.
diff --git a/pdf/pdfium/pdfium_page_unittest.cc b/pdf/pdfium/pdfium_page_unittest.cc
index 0129c40..76fbda8 100644
--- a/pdf/pdfium/pdfium_page_unittest.cc
+++ b/pdf/pdfium/pdfium_page_unittest.cc
@@ -11,6 +11,7 @@
 #include "base/files/file_path.h"
 #include "base/strings/string_util.h"
 #include "base/strings/stringprintf.h"
+#include "base/test/scoped_feature_list.h"
 #include "cc/test/pixel_comparator.h"
 #include "cc/test/pixel_test_utils.h"
 #include "pdf/accessibility_structs.h"
@@ -23,6 +24,7 @@
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/pdfium/public/fpdf_formfill.h"
 #include "third_party/skia/include/core/SkBitmap.h"
+#include "ui/accessibility/accessibility_features.h"
 #include "ui/gfx/geometry/rect.h"
 #include "ui/gfx/geometry/rect_f.h"
 #include "ui/gfx/geometry/size_f.h"
@@ -283,6 +285,39 @@
   EXPECT_EQ("", page.images_[2].alt_text);
 }
 
+class PDFiumPageImageDataTest : public PDFiumPageImageTest {
+ public:
+  PDFiumPageImageDataTest() : enable_pdf_ocr_({features::kPdfOcr}) {}
+
+  PDFiumPageImageDataTest(const PDFiumPageImageDataTest&) = delete;
+  PDFiumPageImageDataTest& operator=(const PDFiumPageImageDataTest&) = delete;
+  ~PDFiumPageImageDataTest() override = default;
+
+ private:
+  base::test::ScopedFeatureList enable_pdf_ocr_;
+};
+
+TEST_F(PDFiumPageImageDataTest, ImageData) {
+  TestClient client;
+  std::unique_ptr<PDFiumEngine> engine =
+      InitializeEngine(&client, FILE_PATH_LITERAL("text_with_image.pdf"));
+  ASSERT_TRUE(engine);
+  ASSERT_EQ(1, engine->GetNumberOfPages());
+
+  PDFiumPage& page = GetPDFiumPageForTest(*engine, 0);
+  page.CalculateImages();
+  ASSERT_EQ(3u, page.images_.size());
+
+  ASSERT_FALSE(page.images_[0].alt_text.empty());
+  EXPECT_TRUE(page.images_[0].image_data.drawsNothing());
+  EXPECT_EQ(page.images_[0].image_data.width(), 0);
+  EXPECT_EQ(page.images_[0].image_data.height(), 0);
+
+  ASSERT_TRUE(page.images_[2].alt_text.empty());
+  EXPECT_EQ(page.images_[1].image_data.width(), 20);
+  EXPECT_EQ(page.images_[1].image_data.height(), 20);
+}
+
 using PDFiumPageTextTest = PDFiumTestBase;
 
 TEST_F(PDFiumPageTextTest, TextRunBounds) {
diff --git a/services/network/disk_cache/mojo_backend_file_operations.cc b/services/network/disk_cache/mojo_backend_file_operations.cc
index 50469f0..86c9a06 100644
--- a/services/network/disk_cache/mojo_backend_file_operations.cc
+++ b/services/network/disk_cache/mojo_backend_file_operations.cc
@@ -30,7 +30,9 @@
                                                uint32_t flags) {
   base::File file;
   base::File::Error error;
-  remote_->OpenFile(path, flags, &file, &error);
+  // The value will be checked in the message receiver side.
+  auto flags_to_pass = static_cast<mojom::HttpCacheBackendOpenFileFlags>(flags);
+  remote_->OpenFile(path, flags_to_pass, &file, &error);
   if (error != base::File::FILE_OK) {
     return base::File(error);
   }
diff --git a/services/network/public/cpp/features.cc b/services/network/public/cpp/features.cc
index c655f04e..982c868 100644
--- a/services/network/public/cpp/features.cc
+++ b/services/network/public/cpp/features.cc
@@ -280,5 +280,10 @@
 const base::Feature kBatchSimpleURLLoader{"BatchSimpleURLLoader",
                                           base::FEATURE_DISABLED_BY_DEFAULT};
 
+// Do not send TLS client certificates in CORS preflight. Omit all client certs
+// and continue the handshake without sending one if requested.
+const base::Feature kOmitCorsClientCert{"OmitCorsClientCert",
+                                        base::FEATURE_DISABLED_BY_DEFAULT};
+
 }  // namespace features
 }  // namespace network
diff --git a/services/network/public/cpp/features.h b/services/network/public/cpp/features.h
index 9c57c979..e1a4c94 100644
--- a/services/network/public/cpp/features.h
+++ b/services/network/public/cpp/features.h
@@ -113,6 +113,9 @@
 COMPONENT_EXPORT(NETWORK_CPP)
 extern const base::Feature kBatchSimpleURLLoader;
 
+COMPONENT_EXPORT(NETWORK_CPP)
+extern const base::Feature kOmitCorsClientCert;
+
 }  // namespace features
 }  // namespace network
 
diff --git a/services/network/public/mojom/http_cache_backend_file_operations.mojom b/services/network/public/mojom/http_cache_backend_file_operations.mojom
index b09ff10..d0a189ac 100644
--- a/services/network/public/mojom/http_cache_backend_file_operations.mojom
+++ b/services/network/public/mojom/http_cache_backend_file_operations.mojom
@@ -9,6 +9,14 @@
 import "mojo/public/mojom/base/file_info.mojom";
 import "mojo/public/mojom/base/file_path.mojom";
 
+
+// Flags for HttpCacheBackendFileOperations.OpenFile. Flag values must be
+// aligned with base::File::Flags.
+enum HttpCacheBackendOpenFileFlags {
+  kOpenAndRead = 0x21,
+  kCreateAndWrite = 0x42,
+};
+
 // An interface to provide file operations so that the HTTP cache works in
 // a sandboxed network service.
 // All the paths must be absolute paths.
@@ -21,7 +29,8 @@
 
   // Opens a file with the given path and flags. Returns the opened file and
   // the error information.
-  [Sync] OpenFile(mojo_base.mojom.FilePath path, uint32 flags) =>
+  [Sync] OpenFile(
+      mojo_base.mojom.FilePath path, HttpCacheBackendOpenFileFlags flags) =>
       (mojo_base.mojom.File? file, mojo_base.mojom.FileError error);
 
   // Deletes a file with the given path and returns whether that succeeded.
diff --git a/services/network/url_loader.cc b/services/network/url_loader.cc
index be78e41..5ad28ab9 100644
--- a/services/network/url_loader.cc
+++ b/services/network/url_loader.cc
@@ -377,10 +377,19 @@
 bool ShouldSendClientCertificates(mojom::CredentialsMode credentials_mode) {
   switch (credentials_mode) {
     case mojom::CredentialsMode::kInclude:
-    case mojom::CredentialsMode::kOmit:
     case mojom::CredentialsMode::kSameOrigin:
       return true;
 
+    // TODO(https://crbug.com/775438): Due to a bug, the default behavior does
+    // not properly correspond to Fetch's "credentials mode", in that client
+    // certificates will be sent if available, or the handshake will be aborted
+    // to allow selecting a client cert.
+    // With the feature kOmitCorsClientCert enabled, the correct
+    // behavior is done; omit all client certs and continue the handshake
+    // without sending one if requested.
+    case mojom::CredentialsMode::kOmit:
+      return !base::FeatureList::IsEnabled(features::kOmitCorsClientCert);
+
     case mojom::CredentialsMode::kOmitBug_775438_Workaround:
       return false;
   }
diff --git a/services/network/url_loader_unittest.cc b/services/network/url_loader_unittest.cc
index 0c41909..a6f8789 100644
--- a/services/network/url_loader_unittest.cc
+++ b/services/network/url_loader_unittest.cc
@@ -4727,9 +4727,46 @@
   EXPECT_TRUE(url_loader->AllowCookies(third_party_url, site_for_cookies));
 }
 
+namespace {
+
+enum class TestMode {
+  kCredentialsModeOmit,
+  kCredentialsModeOmitWorkaround,
+  kCredentialsModeOmitWithFeatureFix,
+};
+
+}  // namespace
+
+class URLLoaderParameterTest : public URLLoaderTest,
+                               public ::testing::WithParamInterface<TestMode> {
+ public:
+  ~URLLoaderParameterTest() override = default;
+
+ private:
+  void SetUp() override {
+    URLLoaderTest::SetUp();
+    if (GetParam() == TestMode::kCredentialsModeOmitWithFeatureFix) {
+      scoped_feature_list.InitAndEnableFeature(features::kOmitCorsClientCert);
+    } else {
+      scoped_feature_list.InitAndDisableFeature(features::kOmitCorsClientCert);
+    }
+  }
+  base::test::ScopedFeatureList scoped_feature_list;
+};
+
+INSTANTIATE_TEST_SUITE_P(
+    All,
+    URLLoaderParameterTest,
+    testing::Values(TestMode::kCredentialsModeOmit,
+                    TestMode::kCredentialsModeOmitWorkaround,
+                    TestMode::kCredentialsModeOmitWithFeatureFix));
+
 // Tests that a request with CredentialsMode::kOmit still sends client
-// certificates. This should be removed when crbug.com/775438 is fixed.
-TEST_F(URLLoaderTest, CredentialsModeOmit) {
+// certificates when features::kOmitCorsClientCert is disabled, and when the
+// feature is enabled client certificates are not sent. Also test that when
+// CredentialsMode::kOmitBug_775438_Workaround is used client certificates are
+// not sent as well. This should be removed when crbug.com/775438 is fixed.
+TEST_P(URLLoaderParameterTest, CredentialsModeOmitRequireClientCert) {
   // Set up a server that requires certificates.
   net::SSLServerConfig ssl_config;
   ssl_config.client_cert_type =
@@ -4756,7 +4793,12 @@
 
   ResourceRequest request =
       CreateResourceRequest("GET", test_server.GetURL("/simple_page.html"));
-  request.credentials_mode = mojom::CredentialsMode::kOmit;
+  if (GetParam() == TestMode::kCredentialsModeOmitWorkaround) {
+    request.credentials_mode =
+        mojom::CredentialsMode::kOmitBug_775438_Workaround;
+  } else {
+    request.credentials_mode = mojom::CredentialsMode::kOmit;
+  }
 
   base::RunLoop delete_run_loop;
   mojo::Remote<mojom::URLLoader> loader;
@@ -4776,68 +4818,20 @@
 
   client()->RunUntilComplete();
   delete_run_loop.Run();
-
-  EXPECT_EQ(net::OK, client()->completion_status().error_code);
+  if (GetParam() == TestMode::kCredentialsModeOmitWithFeatureFix ||
+      GetParam() == TestMode::kCredentialsModeOmitWorkaround) {
+    EXPECT_EQ(0, client_cert_observer.on_certificate_requested_counter());
+    EXPECT_NE(net::OK, client()->completion_status().error_code);
+  } else {
+    EXPECT_EQ(1, client_cert_observer.on_certificate_requested_counter());
+    EXPECT_EQ(net::OK, client()->completion_status().error_code);
+  }
 }
 
-// Tests that a request with CredentialsMode::kOmitBug_775438_Workaround
-// doesn't send client certificates.
-TEST_F(URLLoaderTest, CredentialsModeOmitWorkaround) {
-  // Set up a server that requires certificates.
-  net::SSLServerConfig ssl_config;
-  ssl_config.client_cert_type =
-      net::SSLServerConfig::ClientCertType::REQUIRE_CLIENT_CERT;
-  net::EmbeddedTestServer test_server(net::EmbeddedTestServer::TYPE_HTTPS);
-  test_server.SetSSLConfig(net::EmbeddedTestServer::CERT_OK, ssl_config);
-  test_server.AddDefaultHandlers(
-      base::FilePath(FILE_PATH_LITERAL("services/test/data")));
-  ASSERT_TRUE(test_server.Start());
-
-  // Make sure the client has valid certificates.
-  std::unique_ptr<net::FakeClientCertIdentity> identity =
-      net::FakeClientCertIdentity::CreateFromCertAndKeyFiles(
-          net::GetTestCertsDirectory(), "client_1.pem", "client_1.pk8");
-  ASSERT_TRUE(identity);
-  scoped_refptr<TestSSLPrivateKey> private_key =
-      base::MakeRefCounted<TestSSLPrivateKey>(identity->ssl_private_key());
-
-  ClientCertAuthObserver client_cert_observer;
-  client_cert_observer.set_certificate_response(
-      ClientCertAuthObserver::CertificateResponse::VALID_CERTIFICATE_SIGNATURE);
-  client_cert_observer.set_private_key(private_key);
-  client_cert_observer.set_certificate(identity->certificate());
-
-  ResourceRequest request =
-      CreateResourceRequest("GET", test_server.GetURL("/simple_page.html"));
-  request.credentials_mode = mojom::CredentialsMode::kOmitBug_775438_Workaround;
-
-  base::RunLoop delete_run_loop;
-  mojo::Remote<mojom::URLLoader> loader;
-  std::unique_ptr<URLLoader> url_loader;
-  context().mutable_factory_params().process_id = kProcessId;
-  context().mutable_factory_params().is_corb_enabled = false;
-  url_loader = std::make_unique<URLLoader>(
-      context(), DeleteLoaderCallback(&delete_run_loop, &url_loader),
-      loader.BindNewPipeAndPassReceiver(), mojom::kURLLoadOptionNone, request,
-      client()->CreateRemote(), nullptr /* sync_url_loader_client */,
-      TRAFFIC_ANNOTATION_FOR_TESTS, 0 /* request_id */,
-      0 /* keepalive_request_size */, nullptr, nullptr /* trust_token_helper */,
-      mojo::NullRemote() /* cookie_observer */,
-      client_cert_observer.Bind() /* url_loader_network_observer */,
-      /*devtools_observer=*/mojo::NullRemote(),
-      /*accept_ch_frame_observer=*/mojo::NullRemote());
-
-  client()->RunUntilComplete();
-  delete_run_loop.Run();
-
-  EXPECT_EQ(0, client_cert_observer.on_certificate_requested_counter());
-  EXPECT_NE(net::OK, client()->completion_status().error_code);
-}
-
-// Tests that a request with CredentialsMode::kOmitBug_775438_Workaround
-// doesn't send client certificates with a server that optionally requires
-// certificates.
-TEST_F(URLLoaderTest, CredentialsModeOmitWorkaroundWithOptionalCerts) {
+// Tests that a request with CredentialsMode::kOmitBug_775438_Workaround, and
+// CredentialsMode::kOmit when features::kOmitCorsClientCert is enabled doesn't
+// send client certificates with a server that optionally requires certificates.
+TEST_P(URLLoaderParameterTest, CredentialsModeOmitOptionalClientCert) {
   // Set up a server that requires certificates.
   net::SSLServerConfig ssl_config;
   ssl_config.client_cert_type =
@@ -4864,7 +4858,12 @@
 
   ResourceRequest request =
       CreateResourceRequest("GET", test_server.GetURL("/simple_page.html"));
-  request.credentials_mode = mojom::CredentialsMode::kOmitBug_775438_Workaround;
+  if (GetParam() == TestMode::kCredentialsModeOmitWorkaround) {
+    request.credentials_mode =
+        mojom::CredentialsMode::kOmitBug_775438_Workaround;
+  } else {
+    request.credentials_mode = mojom::CredentialsMode::kOmit;
+  }
 
   base::RunLoop delete_run_loop;
   mojo::Remote<mojom::URLLoader> loader;
@@ -4884,10 +4883,15 @@
 
   client()->RunUntilComplete();
   delete_run_loop.Run();
-
-  EXPECT_EQ(0, client_cert_observer.on_certificate_requested_counter());
+  if (GetParam() == TestMode::kCredentialsModeOmitWithFeatureFix ||
+      GetParam() == TestMode::kCredentialsModeOmitWorkaround) {
+    EXPECT_EQ(0, client_cert_observer.on_certificate_requested_counter());
+  } else {
+    EXPECT_EQ(1, client_cert_observer.on_certificate_requested_counter());
+  }
   EXPECT_EQ(net::OK, client()->completion_status().error_code);
 }
+
 #endif  // !BUILDFLAG(IS_IOS)
 
 TEST_F(URLLoaderTest, CookieReporting) {
diff --git a/sql/BUILD.gn b/sql/BUILD.gn
index 8a52057..480f9a3f 100644
--- a/sql/BUILD.gn
+++ b/sql/BUILD.gn
@@ -12,6 +12,8 @@
     "database_memory_dump_provider.h",
     "error_delegate_util.cc",
     "error_delegate_util.h",
+    "error_metrics.cc",
+    "error_metrics.h",
     "init_status.h",
     "initialization.cc",
     "initialization.h",
@@ -107,6 +109,7 @@
 test("sql_unittests") {
   sources = [
     "database_unittest.cc",
+    "error_metrics_unittest.cc",
     "meta_table_unittest.cc",
     "recover_module/module_unittest.cc",
     "recovery_unittest.cc",
diff --git a/sql/error_metrics.cc b/sql/error_metrics.cc
new file mode 100644
index 0000000..1724582
--- /dev/null
+++ b/sql/error_metrics.cc
@@ -0,0 +1,259 @@
+// 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 "sql/error_metrics.h"
+
+#include <ostream>  // Needed to compile NOTREACHED() with operator <<.
+#include <utility>
+
+#include "base/check_op.h"
+#include "base/metrics/histogram_functions.h"
+#include "base/notreached.h"
+#include "base/ranges/algorithm.h"
+#include "third_party/sqlite/sqlite3.h"
+
+namespace sql {
+
+namespace {
+
+constexpr std::pair<int, SqliteLoggedResultCode> kResultCodeMapping[] = {
+    // Entries are ordered by SQLite result code value. This should match the
+    // ordering in https://www.sqlite.org/rescode.html.
+
+    {SQLITE_OK, SqliteLoggedResultCode::kNoError},
+    {SQLITE_ERROR, SqliteLoggedResultCode::kGeneric},
+    {SQLITE_INTERNAL, SqliteLoggedResultCode::kUnusedSqlite},
+    {SQLITE_PERM, SqliteLoggedResultCode::kPermission},
+    {SQLITE_ABORT, SqliteLoggedResultCode::kAbort},
+
+    // Chrome features shouldn't execute conflicting statements concurrently.
+    {SQLITE_LOCKED, SqliteLoggedResultCode::kUnusedChrome},
+
+    // Chrome should crash on OOM.
+    {SQLITE_NOMEM, SqliteLoggedResultCode::kUnusedChrome},
+
+    {SQLITE_READONLY, SqliteLoggedResultCode::kReadOnly},
+
+    // Chrome doesn't use sqlite3_interrupt().
+    {SQLITE_INTERRUPT, SqliteLoggedResultCode::kUnusedChrome},
+
+    {SQLITE_IOERR, SqliteLoggedResultCode::kIo},
+    {SQLITE_CORRUPT, SqliteLoggedResultCode::kCorrupt},
+
+    // Chrome only passes a few known-good opcodes to sqlite3_file_control().
+    {SQLITE_NOTFOUND, SqliteLoggedResultCode::kUnusedChrome},
+
+    {SQLITE_FULL, SqliteLoggedResultCode::kFullDisk},
+    {SQLITE_CANTOPEN, SqliteLoggedResultCode::kCantOpen},
+    {SQLITE_PROTOCOL, SqliteLoggedResultCode::kLockingProtocol},
+    {SQLITE_EMPTY, SqliteLoggedResultCode::kUnusedSqlite},
+    {SQLITE_SCHEMA, SqliteLoggedResultCode::kSchemaChanged},
+    {SQLITE_TOOBIG, SqliteLoggedResultCode::kTooBig},
+    {SQLITE_CONSTRAINT, SqliteLoggedResultCode::kConstraint},
+    {SQLITE_MISMATCH, SqliteLoggedResultCode::kTypeMismatch},
+
+    // Chrome should not misuse SQLite's API.
+    {SQLITE_MISUSE, SqliteLoggedResultCode::kUnusedChrome},
+
+    {SQLITE_NOLFS, SqliteLoggedResultCode::kNoLargeFileSupport},
+
+    // Chrome does not set an authorizer callback.
+    {SQLITE_AUTH, SqliteLoggedResultCode::kUnusedChrome},
+
+    {SQLITE_FORMAT, SqliteLoggedResultCode::kUnusedSqlite},
+
+    // Chrome should not use invalid column indexes in sqlite3_{bind,column}*().
+    {SQLITE_RANGE, SqliteLoggedResultCode::kUnusedChrome},
+
+    {SQLITE_NOTADB, SqliteLoggedResultCode::kNotADatabase},
+    {SQLITE_NOTICE, SqliteLoggedResultCode::kUnusedSqlite},
+    {SQLITE_WARNING, SqliteLoggedResultCode::kUnusedSqlite},
+    {SQLITE_ROW, SqliteLoggedResultCode::kNoError},
+    {SQLITE_DONE, SqliteLoggedResultCode::kNoError},
+    {SQLITE_OK_LOAD_PERMANENTLY, SqliteLoggedResultCode::kUnusedSqlite},
+
+    // Chrome should not use collating sequence names in SQL statements.
+    {SQLITE_ERROR_MISSING_COLLSEQ, SqliteLoggedResultCode::kUnusedChrome},
+
+    {SQLITE_BUSY_RECOVERY, SqliteLoggedResultCode::kBusyRecovery},
+
+    // Chrome does not use a shared page cache.
+    {SQLITE_LOCKED_SHAREDCACHE, SqliteLoggedResultCode::kUnusedChrome},
+
+    {SQLITE_READONLY_RECOVERY, SqliteLoggedResultCode::kReadOnlyRecovery},
+    {SQLITE_IOERR_READ, SqliteLoggedResultCode::kIoRead},
+
+    // Chrome does not use a virtual table that signals corruption. We only use
+    // a
+    // virtual table code for recovery. That code does not use this error.
+    {SQLITE_CORRUPT_VTAB, SqliteLoggedResultCode::kUnusedChrome},
+
+    {SQLITE_CANTOPEN_NOTEMPDIR, SqliteLoggedResultCode::kUnusedSqlite},
+    {SQLITE_CONSTRAINT_CHECK, SqliteLoggedResultCode::kConstraintCheck},
+
+    // Chrome does not set an authorizer callback.
+    {SQLITE_AUTH_USER, SqliteLoggedResultCode::kUnusedChrome},
+
+    {SQLITE_NOTICE_RECOVER_WAL, SqliteLoggedResultCode::kUnusedSqlite},
+    {SQLITE_WARNING_AUTOINDEX, SqliteLoggedResultCode::kUnusedSqlite},
+    {SQLITE_ERROR_RETRY, SqliteLoggedResultCode::kUnusedSqlite},
+    {SQLITE_ABORT_ROLLBACK, SqliteLoggedResultCode::kAbortRollback},
+    {SQLITE_BUSY_SNAPSHOT, SqliteLoggedResultCode::kBusySnapshot},
+
+    // Chrome does not use a virtual table that signals conflicts. We only use a
+    // virtual table code for recovery. That code does not use this error.
+    {SQLITE_LOCKED_VTAB, SqliteLoggedResultCode::kUnusedChrome},
+
+    {SQLITE_READONLY_CANTLOCK, SqliteLoggedResultCode::kReadOnlyCantLock},
+    {SQLITE_IOERR_SHORT_READ, SqliteLoggedResultCode::kIoShortRead},
+    {SQLITE_CORRUPT_SEQUENCE, SqliteLoggedResultCode::kCorruptSequence},
+    {SQLITE_CANTOPEN_ISDIR, SqliteLoggedResultCode::kCantOpenIsDir},
+
+    // Chrome does not use commit hook callbacks.
+    {SQLITE_CONSTRAINT_COMMITHOOK, SqliteLoggedResultCode::kUnusedChrome},
+
+    {SQLITE_NOTICE_RECOVER_ROLLBACK, SqliteLoggedResultCode::kUnusedSqlite},
+
+    // Chrome does not use sqlite3_snapshot_open().
+    {SQLITE_ERROR_SNAPSHOT, SqliteLoggedResultCode::kUnusedChrome},
+#ifdef SQLITE_ENABLE_SNAPSHOT
+#error "This code assumes that Chrome does not use sqlite3_snapshot_open()"
+#endif
+
+    // Chrome does not use blocking Posix advisory file lock requests.
+    {SQLITE_ERROR_SNAPSHOT, SqliteLoggedResultCode::kUnusedChrome},
+#ifdef SQLITE_ENABLE_SETLK_TIMEOUT
+#error "This code assumes that Chrome does not use
+#endif
+
+    {SQLITE_READONLY_ROLLBACK, SqliteLoggedResultCode::kReadOnlyRollback},
+    {SQLITE_IOERR_WRITE, SqliteLoggedResultCode::kIoWrite},
+    {SQLITE_CORRUPT_INDEX, SqliteLoggedResultCode::kCorruptIndex},
+
+    // Chrome should always pass full paths to SQLite.
+    {SQLITE_CANTOPEN_FULLPATH, SqliteLoggedResultCode::kUnusedChrome},
+
+    {SQLITE_CONSTRAINT_FOREIGNKEY,
+     SqliteLoggedResultCode::kConstraintForeignKey},
+    {SQLITE_READONLY_DBMOVED, SqliteLoggedResultCode::kReadOnlyDbMoved},
+    {SQLITE_IOERR_FSYNC, SqliteLoggedResultCode::kIoFsync},
+
+    // Chrome does not support Cygwin and does not use its VFS.
+    {SQLITE_CANTOPEN_CONVPATH, SqliteLoggedResultCode::kUnusedChrome},
+
+    // Chrome does not use extension functions.
+    {SQLITE_CONSTRAINT_FUNCTION, SqliteLoggedResultCode::kUnusedChrome},
+
+    {SQLITE_READONLY_CANTINIT, SqliteLoggedResultCode::kUnusedSqlite},
+    {SQLITE_IOERR_DIR_FSYNC, SqliteLoggedResultCode::kIoDirFsync},
+    {SQLITE_CANTOPEN_DIRTYWAL, SqliteLoggedResultCode::kUnusedSqlite},
+    {SQLITE_CONSTRAINT_NOTNULL, SqliteLoggedResultCode::kConstraintNotNull},
+    {SQLITE_READONLY_DIRECTORY, SqliteLoggedResultCode::kReadOnlyDirectory},
+    {SQLITE_IOERR_TRUNCATE, SqliteLoggedResultCode::kIoTruncate},
+
+    // Chrome does not use the SQLITE_OPEN_NOFOLLOW flag.
+    {SQLITE_CANTOPEN_SYMLINK, SqliteLoggedResultCode::kUnusedChrome},
+
+    {SQLITE_CONSTRAINT_PRIMARYKEY,
+     SqliteLoggedResultCode::kConstraintPrimaryKey},
+    {SQLITE_IOERR_FSTAT, SqliteLoggedResultCode::kIoFstat},
+
+    // Chrome unconditionally disables database triggers via
+    // sqlite3_db_config(SQLITE_DBCONFIG_ENABLE_TRIGGER).
+    {SQLITE_CONSTRAINT_TRIGGER, SqliteLoggedResultCode::kUnusedChrome},
+
+    {SQLITE_IOERR_UNLOCK, SqliteLoggedResultCode::kIoUnlock},
+    {SQLITE_CONSTRAINT_UNIQUE, SqliteLoggedResultCode::kConstraintUnique},
+    {SQLITE_IOERR_RDLOCK, SqliteLoggedResultCode::kIoReadLock},
+
+    // Chrome does not use a virtual table that signals constraints. We only use
+    // a virtual table code for recovery. That code does not use this error.
+    {SQLITE_CONSTRAINT_VTAB, SqliteLoggedResultCode::kUnusedChrome},
+
+    {SQLITE_IOERR_DELETE, SqliteLoggedResultCode::kIoDelete},
+    {SQLITE_CONSTRAINT_ROWID, SqliteLoggedResultCode::kConstraintRowId},
+    {SQLITE_IOERR_BLOCKED, SqliteLoggedResultCode::kUnusedSqlite},
+
+    // Chrome unconditionally disables database triggers via
+    // sqlite3_db_config(SQLITE_DBCONFIG_ENABLE_TRIGGER).
+    {SQLITE_CONSTRAINT_PINNED, SqliteLoggedResultCode::kUnusedChrome},
+
+    // The SQLite docus claim that this error code is "normally" converted to
+    // SQLITE_NOMEM. This doesn't seem 100% categorical, so we're flagging this
+    // as "unused in Chrome" per the same rationale as SQLITE_NOMEM.
+    {SQLITE_IOERR_NOMEM, SqliteLoggedResultCode::kUnusedChrome},
+
+    {SQLITE_CONSTRAINT_DATATYPE, SqliteLoggedResultCode::kConstraintDataType},
+    {SQLITE_IOERR_ACCESS, SqliteLoggedResultCode::kIoAccess},
+    {SQLITE_IOERR_CHECKRESERVEDLOCK,
+     SqliteLoggedResultCode::kIoCheckReservedLock},
+    {SQLITE_IOERR_LOCK, SqliteLoggedResultCode::kIoLock},
+    {SQLITE_IOERR_CLOSE, SqliteLoggedResultCode::kIoClose},
+    {SQLITE_IOERR_DIR_CLOSE, SqliteLoggedResultCode::kUnusedSqlite},
+
+    // Chrome will only allow enabling WAL on databases with exclusive locking.
+    {SQLITE_IOERR_SHMOPEN, SqliteLoggedResultCode::kUnusedChrome},
+
+    // Chrome will only allow enabling WAL on databases with exclusive locking.
+    {SQLITE_IOERR_SHMSIZE, SqliteLoggedResultCode::kUnusedChrome},
+
+    {SQLITE_IOERR_SHMLOCK, SqliteLoggedResultCode::kUnusedSqlite},
+
+    // Chrome will only allow enabling WAL on databases with exclusive locking.
+    {SQLITE_IOERR_SHMMAP, SqliteLoggedResultCode::kUnusedChrome},
+
+    {SQLITE_IOERR_SEEK, SqliteLoggedResultCode::kIoSeek},
+    {SQLITE_IOERR_DELETE_NOENT, SqliteLoggedResultCode::kIoDeleteNoEntry},
+    {SQLITE_IOERR_MMAP, SqliteLoggedResultCode::kIoMemoryMapping},
+    {SQLITE_IOERR_GETTEMPPATH, SqliteLoggedResultCode::kIoGetTemporaryPath},
+
+    // Chrome does not support Cygwin and does not use its VFS.
+    {SQLITE_IOERR_CONVPATH, SqliteLoggedResultCode::kUnusedChrome},
+
+    // Chrome does not use SQLite extensions.
+    {SQLITE_IOERR_VNODE, SqliteLoggedResultCode::kUnusedChrome},
+
+    // Chrome does not use SQLite extensions.
+    {SQLITE_IOERR_AUTH, SqliteLoggedResultCode::kUnusedChrome},
+
+    {SQLITE_IOERR_BEGIN_ATOMIC, SqliteLoggedResultCode::kIoBeginAtomic},
+    {SQLITE_IOERR_COMMIT_ATOMIC, SqliteLoggedResultCode::kIoCommitAtomic},
+    {SQLITE_IOERR_ROLLBACK_ATOMIC, SqliteLoggedResultCode::kIoRollbackAtomic},
+
+    // Chrome does not use the checksum VFS shim.
+    {SQLITE_IOERR_DATA, SqliteLoggedResultCode::kUnusedChrome},
+
+    {SQLITE_IOERR_CORRUPTFS, SqliteLoggedResultCode::kIoCorruptFileSystem},
+};
+
+}  // namespace
+
+SqliteLoggedResultCode CreateSqliteLoggedResultCode(int sqlite_result_code) {
+  const auto* mapping_it = base::ranges::find_if(
+      kResultCodeMapping,
+      [&sqlite_result_code](const std::pair<int, SqliteLoggedResultCode>& rhs) {
+        return sqlite_result_code == rhs.first;
+      });
+
+  if (mapping_it == base::ranges::end(kResultCodeMapping)) {
+    NOTREACHED() << "Unsupported SQLite result code: " << sqlite_result_code;
+    return SqliteLoggedResultCode::kUnusedChrome;
+  }
+  SqliteLoggedResultCode logged_code = mapping_it->second;
+
+  DCHECK_NE(logged_code, SqliteLoggedResultCode::kUnusedSqlite)
+      << "SQLite reported code marked for internal use: " << sqlite_result_code;
+  DCHECK_NE(logged_code, SqliteLoggedResultCode::kUnusedChrome)
+      << "SQLite reported code that should never show up in Chrome: "
+      << sqlite_result_code;
+  return logged_code;
+}
+
+void UmaHistogramSqliteResult(const char* histogram_name,
+                              int sqlite_result_code) {
+  auto logged_code = CreateSqliteLoggedResultCode(sqlite_result_code);
+  base::UmaHistogramEnumeration(histogram_name, logged_code);
+}
+
+}  // namespace sql
diff --git a/sql/error_metrics.h b/sql/error_metrics.h
new file mode 100644
index 0000000..300c262e
--- /dev/null
+++ b/sql/error_metrics.h
@@ -0,0 +1,223 @@
+// 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 SQL_ERROR_METRICS_H_
+#define SQL_ERROR_METRICS_H_
+
+#include "base/component_export.h"
+
+namespace sql {
+
+// Helper for logging a SQLite result code to a UMA histogram.
+//
+// The histogram should be declared as enum="SqliteLoggedResultCode".
+//
+// Works for all result codes, including success codes and extended error codes.
+// DCHECKs if provided result code should not occur in Chrome's usage of SQLite.
+COMPONENT_EXPORT(SQL)
+void UmaHistogramSqliteResult(const char* histogram_name,
+                              int sqlite_result_code);
+
+// SQLite result codes, mapped into a more compact form for UMA logging.
+//
+// SQLite's (extended) result codes cover a wide range of integer values, and
+// are not suitable for direct use with our UMA logging infrastructure. This
+// enum compresses the range by removing gaps and by mapping multiple SQLite
+// result codes to the same value where appropriate.
+//
+// These values are persisted to logs. Entries should not be renumbered and
+// numeric values should never be reused.
+enum class SqliteLoggedResultCode {
+  // A success code: OK, DONE, ROW.
+  kNoError = 0,
+
+  // Codes that SQLite APIs should never return, such as SQLITE_INTERNAL.
+  kUnusedSqlite = 1,
+
+  // Codes that SQLite APIs should never return, given Chrome's usage pattern.
+  kUnusedChrome = 2,
+
+  // SQLITE_ERROR
+  kGeneric = 3,
+
+  // SQLITE_PERM
+  kPermission = 4,
+
+  // SQLITE_ABORT
+  kAbort = 5,
+
+  // SQLITE_BUSY
+  kBusy = 6,
+
+  // SQLITE_READONLY
+  kReadOnly = 7,
+
+  // SQLITE_IOERR
+  kIo = 8,
+
+  // SQLITE_CORRUPT
+  kCorrupt = 9,
+
+  // SQLITE_FULL
+  kFullDisk = 10,
+
+  // SQLITE_CANTOPEN
+  kCantOpen = 11,
+
+  // SQLITE_PROTOCOL
+  kLockingProtocol = 12,
+
+  // SQLITE_SCHEMA
+  kSchemaChanged = 13,
+
+  // SQLITE_TOOBIG
+  kTooBig = 14,
+
+  // SQLITE_CONSTRAINT
+  kConstraint = 15,
+
+  // SQLITE_MISMATCH
+  kTypeMismatch = 16,
+
+  // SQLITE_NOLFS
+  kNoLargeFileSupport = 17,
+
+  // SQLITE_NOTADB
+  kNotADatabase = 18,
+
+  // SQLITE_BUSY_RECOVERY
+  kBusyRecovery = 19,
+
+  // SQLITE_READONLY_RECOVERY
+  kReadOnlyRecovery = 20,
+
+  // SQLITE_IOERR_READ
+  kIoRead = 21,
+
+  // SQLITE_CONSTRAINT_CHECK
+  kConstraintCheck = 22,
+
+  // SQLITE_ABORT_ROLLBACK
+  kAbortRollback = 23,
+
+  // SQLITE_BUSY_SNAPSHOT
+  kBusySnapshot = 24,
+
+  // SQLITE_READONLY_CANTLOCK
+  kReadOnlyCantLock = 25,
+
+  // SQLITE_IOERR_SHORT_READ
+  kIoShortRead = 26,
+
+  // SQLITE_CORRUPT_SEQUENCE
+  kCorruptSequence = 27,
+
+  // SQLITE_CANTOPEN_ISDIR
+  kCantOpenIsDir = 28,
+
+  // SQLITE_READONLY_ROLLBACK
+  kReadOnlyRollback = 29,
+
+  // SQLITE_IOERR_WRITE
+  kIoWrite = 30,
+
+  // SQLITE_CORRUPT_INDEX
+  kCorruptIndex = 31,
+
+  // SQLITE_CONSTRAINT_FOREIGN_KEY
+  kConstraintForeignKey = 32,
+
+  // SQLITE_READONLY_DBMOVED
+  kReadOnlyDbMoved = 33,
+
+  // SQLITE_IOERR_FSYNC
+  kIoFsync = 34,
+
+  // SQLITE_IOERR_DIR_FSYNC
+  kIoDirFsync = 35,
+
+  // SQLITE_CONSTRAINT_NOTNULL
+  kConstraintNotNull = 36,
+
+  // SQLITE_READONLY_DIRECTORY
+  kReadOnlyDirectory = 37,
+
+  // SQLITE_IOERR_TRUNCATE
+  kIoTruncate = 38,
+
+  // SQLITE_CONSTRAINT_PRIMARYKEY
+  kConstraintPrimaryKey = 39,
+
+  // SQLITE_IOERR_FSTAT
+  kIoFstat = 40,
+
+  // SQLITE_IOERR_UNLOCK
+  kIoUnlock = 41,
+
+  // SQLITE_CONSTRAINT_UNIQUE
+  kConstraintUnique = 42,
+
+  // SQLITE_IOERR_RDLOCK
+  kIoReadLock = 43,
+
+  // SQLITE_IOERR_DELETE
+  kIoDelete = 44,
+
+  // SQLITE_CONSTRAINT_ROWID
+  kConstraintRowId = 45,
+
+  // SQLITE_CONSTRAINT_DATATYPE
+  kConstraintDataType = 46,
+
+  // SQLITE_IOERR_ACCESS
+  kIoAccess = 47,
+
+  // SQLITE_IOERR_CHECKRESERVEDLOCK
+  kIoCheckReservedLock = 48,
+
+  // SQLITE_IOERR_LOCK
+  kIoLock = 49,
+
+  // SQLITE_IOERR_CLOSE
+  kIoClose = 50,
+
+  // SQLITE_IOERR_SEEK
+  kIoSeek = 51,
+
+  // SQLITE_IOERR_DELETE_NOENT
+  kIoDeleteNoEntry = 52,
+
+  // SQLITE_IOERR_MMAP
+  kIoMemoryMapping = 53,
+
+  // SQLITE_IOERR_GETTEMPPATH
+  kIoGetTemporaryPath = 54,
+
+  // SQLITE_IOERR_BEGIN_ATOMIC
+  kIoBeginAtomic = 55,
+
+  // SQLITE_IOERR_COMMIT_ATOMIC
+  kIoCommitAtomic = 56,
+
+  // SQLITE_IOERR_ROLLBACK_ATOMIC
+  kIoRollbackAtomic = 57,
+
+  // SQLITE_IOERR_CORRUPTFS
+  kIoCorruptFileSystem = 58,
+
+  kMaxValue = kIoCorruptFileSystem,
+};
+
+// Converts a SQLite result code into a UMA logging-friendly form.
+//
+// Works for all result codes, including success codes and extended error codes.
+// DCHECKs if provided result code should not occur in Chrome's usage of SQLite.
+//
+// UmaHistogramSqliteResult() should be preferred for logging results to UMA.
+COMPONENT_EXPORT(SQL)
+SqliteLoggedResultCode CreateSqliteLoggedResultCode(int sqlite_result_code);
+
+}  // namespace sql
+
+#endif  // SQL_ERROR_METRICS_H_
diff --git a/sql/error_metrics_unittest.cc b/sql/error_metrics_unittest.cc
new file mode 100644
index 0000000..75f92bb
--- /dev/null
+++ b/sql/error_metrics_unittest.cc
@@ -0,0 +1,110 @@
+// 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 "sql/error_metrics.h"
+
+#include "base/test/gtest_util.h"
+#include "base/test/metrics/histogram_tester.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/sqlite/sqlite3.h"
+
+namespace sql {
+
+namespace {
+
+TEST(ErrorMetricsTest, CreateSqliteLoggedResultCode_Success) {
+  EXPECT_EQ(SqliteLoggedResultCode::kNoError,
+            CreateSqliteLoggedResultCode(SQLITE_OK));
+  EXPECT_EQ(SqliteLoggedResultCode::kNoError,
+            CreateSqliteLoggedResultCode(SQLITE_DONE));
+  EXPECT_EQ(SqliteLoggedResultCode::kNoError,
+            CreateSqliteLoggedResultCode(SQLITE_ROW));
+}
+
+TEST(ErrorMetricsTest, CreateSqliteLoggedResultCode_PrimaryErrorCodes) {
+  EXPECT_EQ(SqliteLoggedResultCode::kIo,
+            CreateSqliteLoggedResultCode(SQLITE_IOERR));
+  EXPECT_EQ(SqliteLoggedResultCode::kCorrupt,
+            CreateSqliteLoggedResultCode(SQLITE_CORRUPT));
+  EXPECT_EQ(SqliteLoggedResultCode::kConstraint,
+            CreateSqliteLoggedResultCode(SQLITE_CONSTRAINT));
+}
+
+TEST(ErrorMetricsTest, CreateSqliteLoggedResultCode_ExtendedErrorCodes) {
+  EXPECT_EQ(SqliteLoggedResultCode::kIoRead,
+            CreateSqliteLoggedResultCode(SQLITE_IOERR_READ));
+  EXPECT_EQ(SqliteLoggedResultCode::kIoWrite,
+            CreateSqliteLoggedResultCode(SQLITE_IOERR_WRITE));
+  EXPECT_EQ(SqliteLoggedResultCode::kCorruptIndex,
+            CreateSqliteLoggedResultCode(SQLITE_CORRUPT_INDEX));
+  EXPECT_EQ(SqliteLoggedResultCode::kConstraintUnique,
+            CreateSqliteLoggedResultCode(SQLITE_CONSTRAINT_UNIQUE));
+}
+
+TEST(ErrorMetricsTest, CreateSqliteLoggedResultCode_MissingLowValue) {
+  EXPECT_DCHECK_DEATH_WITH(CreateSqliteLoggedResultCode(-65536),
+                           "Unsupported SQLite result code: -65536");
+}
+
+TEST(ErrorMetricsTest, CreateSqliteLoggedResultCode_MissingHighValue) {
+  EXPECT_DCHECK_DEATH_WITH(CreateSqliteLoggedResultCode(65536),
+                           "Unsupported SQLite result code: 65536");
+}
+
+TEST(ErrorMetricsTest, CreateSqliteLoggedResultCode_SqliteInternalError) {
+#if DCHECK_IS_ON()
+  EXPECT_DCHECK_DEATH_WITH(CreateSqliteLoggedResultCode(SQLITE_INTERNAL),
+                           "SQLite reported code marked for internal use: 2");
+#else
+  EXPECT_EQ(SqliteLoggedResultCode::kUnusedSqlite,
+            CreateSqliteLoggedResultCode(SQLITE_INTERNAL));
+#endif
+}
+
+// TODO(crbug.com/1306382): Fails when dcheck_always_on = false.
+#if !DCHECK_IS_ON()
+#define MAYBE_CreateSqliteLoggedResultCode_ChromeBugError \
+  DISABLED_CreateSqliteLoggedResultCode_ChromeBugError
+#else
+#define MAYBE_CreateSqliteLoggedResultCode_ChromeBugError \
+  CreateSqliteLoggedResultCode_ChromeBugError
+#endif  // DCHECK_IS_ON()
+TEST(ErrorMetricsTest, MAYBE_CreateSqliteLoggedResultCode_ChromeBugError) {
+#if DCHECK_IS_ON()
+  EXPECT_DCHECK_DEATH_WITH(
+      CreateSqliteLoggedResultCode(SQLITE_NOTFOUND),
+      "SQLite reported code that should never show up in Chrome: 12");
+#else
+  EXPECT_EQ(SqliteLoggedResultCode::kUnusedSqlite,
+            CreateSqliteLoggedResultCode(SQLITE_NOTFOUND));
+#endif
+}
+
+TEST(ErrorMetricsTest, UmaHistogramSqliteResult_Success) {
+  base::HistogramTester histogram_tester;
+  UmaHistogramSqliteResult("Sql.ResultTest", SQLITE_OK);
+  histogram_tester.ExpectTotalCount("Sql.ResultTest", 1);
+  histogram_tester.ExpectBucketCount("Sql.ResultTest",
+                                     SqliteLoggedResultCode::kNoError, 1);
+}
+
+TEST(ErrorMetricsTest, UmaHistogramSqliteResult_PrimaryErrorCode) {
+  base::HistogramTester histogram_tester;
+  UmaHistogramSqliteResult("Sql.ResultTest", SQLITE_CORRUPT);
+  histogram_tester.ExpectTotalCount("Sql.ResultTest", 1);
+  histogram_tester.ExpectBucketCount("Sql.ResultTest",
+                                     SqliteLoggedResultCode::kCorrupt, 1);
+}
+
+TEST(ErrorMetricsTest, UmaHistogramSqliteResult_ExtendedErrorCode) {
+  base::HistogramTester histogram_tester;
+  UmaHistogramSqliteResult("Sql.ResultTest", SQLITE_CORRUPT_INDEX);
+  histogram_tester.ExpectTotalCount("Sql.ResultTest", 1);
+  histogram_tester.ExpectBucketCount("Sql.ResultTest",
+                                     SqliteLoggedResultCode::kCorruptIndex, 1);
+}
+
+}  // namespace
+
+}  // namespace sql
diff --git a/storage/browser/quota/quota_database.cc b/storage/browser/quota/quota_database.cc
index 0d85e07..eadff5d 100644
--- a/storage/browser/quota/quota_database.cc
+++ b/storage/browser/quota/quota_database.cc
@@ -20,6 +20,7 @@
 #include "components/services/storage/public/cpp/buckets/constants.h"
 #include "components/services/storage/public/cpp/quota_error_or.h"
 #include "sql/database.h"
+#include "sql/error_metrics.h"
 #include "sql/meta_table.h"
 #include "sql/statement.h"
 #include "sql/transaction.h"
@@ -718,6 +719,22 @@
              : QuotaError::kDatabaseError;
 }
 
+QuotaError QuotaDatabase::RazeAndReopen() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK(db_);
+
+  // Abort the long-running transaction.
+  db_->RollbackTransaction();
+
+  // Raze and close the database. Reset `db_` to nullptr so EnsureOpened will
+  // recreate the database.
+  if (!db_->Raze())
+    return QuotaError::kDatabaseError;
+  db_ = nullptr;
+
+  return EnsureOpened(EnsureOpenedMode::kCreateIfNotFound);
+}
+
 QuotaError QuotaDatabase::CorruptForTesting(
     base::OnceCallback<void(const base::FilePath&)> corrupter) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
@@ -789,9 +806,12 @@
 
   db_->set_histogram_tag("Quota");
 
-  // Don't crash on database errors in DCHECK builds.
-  db_->set_error_callback(base::BindRepeating(
-      [](int sqlite_error, sql::Statement* statement) { return; }));
+  // UMA logging and don't crash on database errors in DCHECK builds.
+  db_->set_error_callback(
+      base::BindRepeating([](int sqlite_error_code, sql::Statement* statement) {
+        sql::UmaHistogramSqliteResult("Quota.QuotaDatabaseError",
+                                      sqlite_error_code);
+      }));
 
   if (!OpenDatabase() || !EnsureDatabaseVersion()) {
     LOG(ERROR) << "Could not open the quota database, resetting.";
diff --git a/storage/browser/quota/quota_database.h b/storage/browser/quota/quota_database.h
index ff7a7ec..8e6757e45 100644
--- a/storage/browser/quota/quota_database.h
+++ b/storage/browser/quota/quota_database.h
@@ -215,6 +215,10 @@
   bool IsBootstrapped();
   QuotaError SetIsBootstrapped(bool bootstrap_flag);
 
+  // Razes and re-opens the database. Should only be called if the database is
+  // actually open.
+  QuotaError RazeAndReopen();
+
   // Testing support for database corruption handling.
   //
   // Runs `corrupter` on the same sequence used to do database I/O,
diff --git a/storage/browser/quota/quota_database_unittest.cc b/storage/browser/quota/quota_database_unittest.cc
index 6d760aa9..9a162b0f 100644
--- a/storage/browser/quota/quota_database_unittest.cc
+++ b/storage/browser/quota/quota_database_unittest.cc
@@ -21,6 +21,7 @@
 #include "components/services/storage/public/cpp/buckets/bucket_locator.h"
 #include "components/services/storage/public/cpp/buckets/constants.h"
 #include "sql/database.h"
+#include "sql/error_metrics.h"
 #include "sql/meta_table.h"
 #include "sql/statement.h"
 #include "sql/test/scoped_error_expecter.h"
@@ -923,6 +924,45 @@
   histograms.ExpectTotalCount("Quota.QuotaDatabaseReset", 1);
   histograms.ExpectBucketCount("Quota.QuotaDatabaseReset",
                                DatabaseResetReason::kCreateSchema, 1);
+
+  EXPECT_GE(histograms.GetTotalSum("Quota.QuotaDatabaseError"), 1);
+  EXPECT_GE(histograms.GetBucketCount("Quota.QuotaDatabaseError",
+                                      sql::SqliteLoggedResultCode::kCorrupt),
+            1);
+}
+
+TEST_F(QuotaDatabaseTest, GetOrCreateBucket_CorruptedDatabase) {
+  QuotaDatabase db(DbPath());
+  StorageKey storage_key =
+      StorageKey::CreateFromStringForTesting("http://google/");
+  std::string bucket_name = "google_bucket";
+
+  {
+    QuotaErrorOr<BucketInfo> result =
+        db.GetOrCreateBucket(storage_key, bucket_name);
+    ASSERT_TRUE(result.ok()) << "Failed to create bucket to be used in test";
+  }
+
+  // Bucket lookup uses the `buckets_by_storage_key` index.
+  QuotaError corruption_error =
+      db.CorruptForTesting(base::BindOnce([](const base::FilePath& db_path) {
+        ASSERT_TRUE(
+            sql::test::CorruptIndexRootPage(db_path, "buckets_by_storage_key"));
+      }));
+  ASSERT_EQ(QuotaError::kNone, corruption_error)
+      << "Failed to corrupt the database";
+
+  {
+    base::HistogramTester histograms;
+
+    QuotaErrorOr<BucketInfo> result =
+        db.GetOrCreateBucket(storage_key, bucket_name);
+    EXPECT_FALSE(result.ok());
+
+    histograms.ExpectTotalCount("Quota.QuotaDatabaseError", 1);
+    histograms.ExpectBucketCount("Quota.QuotaDatabaseError",
+                                 sql::SqliteLoggedResultCode::kCorrupt, 1);
+  }
 }
 
 INSTANTIATE_TEST_SUITE_P(All,
diff --git a/storage/browser/quota/quota_manager_impl.cc b/storage/browser/quota/quota_manager_impl.cc
index 3b22d45..7b09d41e 100644
--- a/storage/browser/quota/quota_manager_impl.cc
+++ b/storage/browser/quota/quota_manager_impl.cc
@@ -1837,7 +1837,8 @@
 
 void QuotaManagerImpl::DidBootstrapDatabase(QuotaError error) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  DidDatabaseWork(error != QuotaError::kDatabaseError);
+  DidDatabaseWork(error != QuotaError::kDatabaseError,
+                  /*is_bootstrap_work=*/true);
 
   PostTaskAndReplyWithResultForDBThread(
       base::BindOnce(&SetDatabaseBootstrappedOnDBThread),
@@ -1851,7 +1852,8 @@
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(is_bootstrapping_database_);
   is_bootstrapping_database_ = false;
-  DidDatabaseWork(error != QuotaError::kDatabaseError);
+  DidDatabaseWork(error != QuotaError::kDatabaseError,
+                  /*is_bootstrap_work=*/true);
 
   RunDatabaseCallbacks();
   StartEviction();
@@ -2637,14 +2639,58 @@
   DetermineStoragePressure(total_space, available_space);
 }
 
-void QuotaManagerImpl::DidDatabaseWork(bool success) {
+void QuotaManagerImpl::DidDatabaseWork(bool success, bool is_bootstrap_work) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   if (success)
     return;
 
+  // Ignore any errors that happen while a new bootstrap attempt is already in
+  // progress or queued.
+  if (is_bootstrapping_database_)
+    return;
+
   db_error_count_++;
-  if (db_error_count_ >= QuotaManagerImpl::kThresholdOfErrorsToDisableDatabase)
-    db_disabled_ = true;
+
+  if (db_error_count_ >=
+      QuotaManagerImpl::kThresholdOfErrorsToDisableDatabase) {
+    if (bootstrap_disabled_for_testing_ || is_bootstrap_work) {
+      // If we got an error during bootstrapping there is no point in
+      // immediately trying again. Disable the database instead.
+      db_disabled_ = true;
+      return;
+    }
+
+    // Start another bootstrapping process. Pause eviction while bootstrapping
+    // is in progress. When bootstrapping finishes a new Evictor will be
+    // created.
+    is_bootstrapping_database_ = true;
+    temporary_storage_evictor_ = nullptr;
+    db_error_count_ = 0;
+
+    // Wipe the database before triggering another bootstrap.
+    base::PostTaskAndReplyWithResult(
+        db_runner_.get(), FROM_HERE,
+        base::BindOnce(&QuotaDatabase::RazeAndReopen,
+                       base::Unretained(database_.get())),
+        base::BindOnce(&QuotaManagerImpl::DidRazeForReBootstrap,
+                       weak_factory_.GetWeakPtr()));
+  }
+}
+
+void QuotaManagerImpl::DidRazeForReBootstrap(
+    QuotaError raze_and_reopen_result) {
+  if (raze_and_reopen_result == QuotaError::kNone) {
+    BootstrapDatabase();
+    return;
+  }
+
+  // Deleting the database failed. Disable the database and hope we'll recover
+  // after Chrome restarts instead.
+  db_disabled_ = true;
+  is_bootstrapping_database_ = false;
+  RunDatabaseCallbacks();
+  // No reason to restart eviction here. Without a working database there is
+  // nothing to evict.
 }
 
 void QuotaManagerImpl::OnComplete(QuotaError result) {
diff --git a/storage/browser/quota/quota_manager_impl.h b/storage/browser/quota/quota_manager_impl.h
index 8213a471..5e8fff9 100644
--- a/storage/browser/quota/quota_manager_impl.h
+++ b/storage/browser/quota/quota_manager_impl.h
@@ -444,6 +444,12 @@
     bootstrap_disabled_for_testing_ = disable;
   }
 
+  bool is_bootstrapping_database_for_testing() {
+    return is_bootstrapping_database_;
+  }
+
+  bool is_db_disabled_for_testing() { return db_disabled_; }
+
  protected:
   ~QuotaManagerImpl() override;
   void SetQuotaChangeCallbackForTesting(
@@ -617,7 +623,8 @@
   void DidGetStorageCapacity(
       const std::tuple<int64_t, int64_t>& total_and_available);
 
-  void DidDatabaseWork(bool success);
+  void DidDatabaseWork(bool success, bool is_bootstrap_work = false);
+  void DidRazeForReBootstrap(QuotaError raze_and_reopen_result);
   void OnComplete(QuotaError result);
 
   void DidGetBucket(base::OnceCallback<void(QuotaErrorOr<BucketInfo>)> callback,
@@ -676,12 +683,6 @@
       const base::FilePath& path);
   static std::tuple<int64_t, int64_t> GetVolumeInfo(const base::FilePath& path);
 
-  bool is_bootstrapping_database_for_testing() {
-    return is_bootstrapping_database_;
-  }
-
-  bool is_db_disabled_for_testing() { return db_disabled_; }
-
   const bool is_incognito_;
   const base::FilePath profile_path_;
 
diff --git a/storage/browser/quota/quota_manager_unittest.cc b/storage/browser/quota/quota_manager_unittest.cc
index 651740a..33bad3a 100644
--- a/storage/browser/quota/quota_manager_unittest.cc
+++ b/storage/browser/quota/quota_manager_unittest.cc
@@ -650,6 +650,74 @@
   ASSERT_TRUE(bucket.ok());
 }
 
+TEST_F(QuotaManagerImplTest, CorruptionRecovery) {
+  // Setup clients with both unmigrated and migrated data. Before corruption the
+  // bucket data will be used, while after corruption recovery data should be
+  // migrated again.
+  static const ClientBucketData kData1[] = {
+      {"http://foo.com/", kDefaultBucketName, kTemp, 10},
+      {"http://foo.com:8080/", kDefaultBucketName, kTemp, 15},
+  };
+  static const UnmigratedStorageKeyData kUnmigratedData1[] = {
+      {"http://foo.com/", kTemp, 10},
+      {"http://foo.com:8080/", kTemp, 15},
+  };
+  static const ClientBucketData kData2[] = {
+      {"https://foo.com/", kDefaultBucketName, kTemp, 30},
+      {"https://foo.com:8081/", kDefaultBucketName, kTemp, 35},
+  };
+  static const UnmigratedStorageKeyData kUnmigratedData2[] = {
+      {"https://foo.com/", kTemp, 30},
+      {"https://foo.com:8081/", kTemp, 35},
+  };
+  MockQuotaClient* fs_client = CreateAndRegisterClient(
+      QuotaClientType::kFileSystem, {kTemp, kPerm}, kUnmigratedData1);
+  MockQuotaClient* database_client = CreateAndRegisterClient(
+      QuotaClientType::kDatabase, {kTemp, kPerm}, kUnmigratedData2);
+  RegisterClientBucketData(fs_client, kData1);
+  RegisterClientBucketData(database_client, kData2);
+
+  // Basic sanity checks, make sure setup worked correctly.
+  auto bucket =
+      GetBucket(ToStorageKey("http://foo.com/"), kDefaultBucketName, kTemp);
+  ASSERT_TRUE(bucket.ok());
+  bucket = GetBucket(ToStorageKey("http://foo.com:8080/"), kDefaultBucketName,
+                     kTemp);
+  ASSERT_TRUE(bucket.ok());
+  bucket = GetBucket(ToStorageKey("https://foo.com:8081/"), kDefaultBucketName,
+                     kTemp);
+  ASSERT_TRUE(bucket.ok());
+
+  // Corrupt the database to make bucket lookup fail.
+  QuotaError corruption_error = CorruptDatabaseForTesting(
+      base::BindOnce([](const base::FilePath& db_path) {
+        ASSERT_TRUE(
+            sql::test::CorruptIndexRootPage(db_path, "buckets_by_storage_key"));
+      }));
+  ASSERT_EQ(QuotaError::kNone, corruption_error);
+
+  // Try to lookup a bucket, this should fail until the error threshold is
+  // reached.
+  for (int i = 0; i < QuotaManagerImpl::kThresholdOfErrorsToDisableDatabase;
+       ++i) {
+    EXPECT_FALSE(quota_manager_impl_->is_db_disabled_for_testing());
+    EXPECT_FALSE(is_db_bootstrapping());
+
+    bucket =
+        GetBucket(ToStorageKey("http://foo.com/"), kDefaultBucketName, kTemp);
+    ASSERT_FALSE(bucket.ok());
+    EXPECT_EQ(QuotaError::kDatabaseError, bucket.error());
+  }
+
+  // The last lookup attempt should have started another bootstrap attempt.
+  EXPECT_TRUE(is_db_bootstrapping());
+
+  // And with that bucket lookup should be working again.
+  bucket =
+      GetBucket(ToStorageKey("http://foo.com/"), kDefaultBucketName, kTemp);
+  ASSERT_TRUE(bucket.ok());
+}
+
 TEST_F(QuotaManagerImplTest, GetUsageInfo) {
   static const ClientBucketData kData1[] = {
       {"http://foo.com/", kDefaultBucketName, kTemp, 10},
diff --git a/testing/buildbot/chromium.android.fyi.json b/testing/buildbot/chromium.android.fyi.json
index bd9c6f1b0..f25c15a 100644
--- a/testing/buildbot/chromium.android.fyi.json
+++ b/testing/buildbot/chromium.android.fyi.json
@@ -2304,7 +2304,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M100",
-              "revision": "version:100.0.4896.43"
+              "revision": "version:100.0.4896.44"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -2724,7 +2724,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M99",
-              "revision": "version:99.0.4844.73"
+              "revision": "version:99.0.4844.75"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -2808,7 +2808,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M100",
-              "revision": "version:100.0.4896.43"
+              "revision": "version:100.0.4896.44"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -3228,7 +3228,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M99",
-              "revision": "version:99.0.4844.73"
+              "revision": "version:99.0.4844.75"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
diff --git a/testing/buildbot/chromium.android.json b/testing/buildbot/chromium.android.json
index a1688c6..923f37f 100644
--- a/testing/buildbot/chromium.android.json
+++ b/testing/buildbot/chromium.android.json
@@ -11446,7 +11446,7 @@
       },
       {
         "args": [
-          "--gtest_filter=-org.chromium.chrome.browser.contextualsearch.ContextualSearchManagerTest.test*ExternalNavigationWithUserGesture*",
+          "--gtest_filter=-org.chromium.chrome.browser.contextualsearch.ContextualSearchManagerTest.test*ExternalNavigationWithUserGesture*:org.chromium.chrome.browser.externalnav.UrlOverridingTest.test*UserGesture*:org.chromium.chrome.browser.externalnav.UrlOverridingTest.testNavigationFromXHRCallback:org.chromium.chrome.browser.externalnav.UrlOverridingTest.testNavigationFromXHRCallbackAndShortTimeout:org.chromium.chrome.browser.externalnav.UrlOverridingTest.testNavigationFromXHRCallbackInSubFrame:org.chromium.chrome.browser.externalnav.UrlOverridingTest.testRedirectionFromIntent*",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
           "--avd-config=../../tools/android/avd/proto/generic_android30.textpb",
@@ -11524,7 +11524,7 @@
       },
       {
         "args": [
-          "--gtest_filter=org.chromium.chrome.browser.contextualsearch.ContextualSearchManagerTest.test*ExternalNavigationWithUserGesture*",
+          "--gtest_filter=org.chromium.chrome.browser.contextualsearch.ContextualSearchManagerTest.test*ExternalNavigationWithUserGesture*:org.chromium.chrome.browser.externalnav.UrlOverridingTest.test*UserGesture*:org.chromium.chrome.browser.externalnav.UrlOverridingTest.testNavigationFromXHRCallback:org.chromium.chrome.browser.externalnav.UrlOverridingTest.testNavigationFromXHRCallbackAndShortTimeout:org.chromium.chrome.browser.externalnav.UrlOverridingTest.testNavigationFromXHRCallbackInSubFrame:org.chromium.chrome.browser.externalnav.UrlOverridingTest.testRedirectionFromIntent*",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
           "--avd-config=../../tools/android/avd/proto/generic_android30.textpb",
@@ -15985,7 +15985,7 @@
       },
       {
         "args": [
-          "--gtest_filter=-org.chromium.chrome.browser.contextualsearch.ContextualSearchManagerTest.test*ExternalNavigationWithUserGesture*",
+          "--gtest_filter=-org.chromium.chrome.browser.contextualsearch.ContextualSearchManagerTest.test*ExternalNavigationWithUserGesture*:org.chromium.chrome.browser.externalnav.UrlOverridingTest.test*UserGesture*:org.chromium.chrome.browser.externalnav.UrlOverridingTest.testNavigationFromXHRCallback:org.chromium.chrome.browser.externalnav.UrlOverridingTest.testNavigationFromXHRCallbackAndShortTimeout:org.chromium.chrome.browser.externalnav.UrlOverridingTest.testNavigationFromXHRCallbackInSubFrame:org.chromium.chrome.browser.externalnav.UrlOverridingTest.testRedirectionFromIntent*",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
           "--avd-config=../../tools/android/avd/proto/generic_android31.textpb",
@@ -16063,7 +16063,7 @@
       },
       {
         "args": [
-          "--gtest_filter=org.chromium.chrome.browser.contextualsearch.ContextualSearchManagerTest.test*ExternalNavigationWithUserGesture*",
+          "--gtest_filter=org.chromium.chrome.browser.contextualsearch.ContextualSearchManagerTest.test*ExternalNavigationWithUserGesture*:org.chromium.chrome.browser.externalnav.UrlOverridingTest.test*UserGesture*:org.chromium.chrome.browser.externalnav.UrlOverridingTest.testNavigationFromXHRCallback:org.chromium.chrome.browser.externalnav.UrlOverridingTest.testNavigationFromXHRCallbackAndShortTimeout:org.chromium.chrome.browser.externalnav.UrlOverridingTest.testNavigationFromXHRCallbackInSubFrame:org.chromium.chrome.browser.externalnav.UrlOverridingTest.testRedirectionFromIntent*",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
           "--avd-config=../../tools/android/avd/proto/generic_android31.textpb",
@@ -31220,7 +31220,7 @@
       },
       {
         "args": [
-          "--gtest_filter=-org.chromium.chrome.browser.contextualsearch.ContextualSearchManagerTest.test*ExternalNavigationWithUserGesture*",
+          "--gtest_filter=-org.chromium.chrome.browser.contextualsearch.ContextualSearchManagerTest.test*ExternalNavigationWithUserGesture*:org.chromium.chrome.browser.externalnav.UrlOverridingTest.test*UserGesture*:org.chromium.chrome.browser.externalnav.UrlOverridingTest.testNavigationFromXHRCallback:org.chromium.chrome.browser.externalnav.UrlOverridingTest.testNavigationFromXHRCallbackAndShortTimeout:org.chromium.chrome.browser.externalnav.UrlOverridingTest.testNavigationFromXHRCallbackInSubFrame:org.chromium.chrome.browser.externalnav.UrlOverridingTest.testRedirectionFromIntent*",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
           "--avd-config=../../tools/android/avd/proto/generic_android23.textpb",
@@ -41085,7 +41085,7 @@
       },
       {
         "args": [
-          "--gtest_filter=-org.chromium.chrome.browser.contextualsearch.ContextualSearchManagerTest.test*ExternalNavigationWithUserGesture*",
+          "--gtest_filter=-org.chromium.chrome.browser.contextualsearch.ContextualSearchManagerTest.test*ExternalNavigationWithUserGesture*:org.chromium.chrome.browser.externalnav.UrlOverridingTest.test*UserGesture*:org.chromium.chrome.browser.externalnav.UrlOverridingTest.testNavigationFromXHRCallback:org.chromium.chrome.browser.externalnav.UrlOverridingTest.testNavigationFromXHRCallbackAndShortTimeout:org.chromium.chrome.browser.externalnav.UrlOverridingTest.testNavigationFromXHRCallbackInSubFrame:org.chromium.chrome.browser.externalnav.UrlOverridingTest.testRedirectionFromIntent*",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
           "--avd-config=../../tools/android/avd/proto/generic_android28.textpb",
@@ -41160,7 +41160,7 @@
       },
       {
         "args": [
-          "--gtest_filter=org.chromium.chrome.browser.contextualsearch.ContextualSearchManagerTest.test*ExternalNavigationWithUserGesture*",
+          "--gtest_filter=org.chromium.chrome.browser.contextualsearch.ContextualSearchManagerTest.test*ExternalNavigationWithUserGesture*:org.chromium.chrome.browser.externalnav.UrlOverridingTest.test*UserGesture*:org.chromium.chrome.browser.externalnav.UrlOverridingTest.testNavigationFromXHRCallback:org.chromium.chrome.browser.externalnav.UrlOverridingTest.testNavigationFromXHRCallbackAndShortTimeout:org.chromium.chrome.browser.externalnav.UrlOverridingTest.testNavigationFromXHRCallbackInSubFrame:org.chromium.chrome.browser.externalnav.UrlOverridingTest.testRedirectionFromIntent*",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
           "--avd-config=../../tools/android/avd/proto/generic_android28.textpb",
@@ -44881,7 +44881,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M100",
-              "revision": "version:100.0.4896.43"
+              "revision": "version:100.0.4896.44"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -45301,7 +45301,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M99",
-              "revision": "version:99.0.4844.73"
+              "revision": "version:99.0.4844.75"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -45385,7 +45385,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M100",
-              "revision": "version:100.0.4896.43"
+              "revision": "version:100.0.4896.44"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -45805,7 +45805,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M99",
-              "revision": "version:99.0.4844.73"
+              "revision": "version:99.0.4844.75"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -45893,7 +45893,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M100",
-              "revision": "version:100.0.4896.43"
+              "revision": "version:100.0.4896.44"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -46313,7 +46313,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M99",
-              "revision": "version:99.0.4844.73"
+              "revision": "version:99.0.4844.75"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -46397,7 +46397,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M100",
-              "revision": "version:100.0.4896.43"
+              "revision": "version:100.0.4896.44"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -46817,7 +46817,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M99",
-              "revision": "version:99.0.4844.73"
+              "revision": "version:99.0.4844.75"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -46972,7 +46972,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M100",
-              "revision": "version:100.0.4896.43"
+              "revision": "version:100.0.4896.44"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -47392,7 +47392,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M99",
-              "revision": "version:99.0.4844.73"
+              "revision": "version:99.0.4844.75"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -47476,7 +47476,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M100",
-              "revision": "version:100.0.4896.43"
+              "revision": "version:100.0.4896.44"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -47896,7 +47896,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M99",
-              "revision": "version:99.0.4844.73"
+              "revision": "version:99.0.4844.75"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -48051,7 +48051,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M100",
-              "revision": "version:100.0.4896.43"
+              "revision": "version:100.0.4896.44"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -48471,7 +48471,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M99",
-              "revision": "version:99.0.4844.73"
+              "revision": "version:99.0.4844.75"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -48555,7 +48555,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M100",
-              "revision": "version:100.0.4896.43"
+              "revision": "version:100.0.4896.44"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -48975,7 +48975,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M99",
-              "revision": "version:99.0.4844.73"
+              "revision": "version:99.0.4844.75"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
diff --git a/testing/buildbot/filters/linux-chromeos.browser_tests.require_lacros.filter b/testing/buildbot/filters/linux-chromeos.browser_tests.require_lacros.filter
index 1eb1ab3..fb34634 100644
--- a/testing/buildbot/filters/linux-chromeos.browser_tests.require_lacros.filter
+++ b/testing/buildbot/filters/linux-chromeos.browser_tests.require_lacros.filter
@@ -11,9 +11,10 @@
 SelectToSpeak*
 SwitchAccess*
 # TODO(crbug.com/1245416) Enable more accessibility tests.
--ChromeVoxBackgroundTest.*
--ChromeVoxEditingTest.*
--ChromeVoxOutputE2ETest.*
+-ChromeVoxBackgroundTest.SwipeLeftRight4ByContainers
+-ChromeVoxBackgroundTest.TabSwitchAndRefreshRecovery
+-ChromeVoxBackgroundTest.TextSelectionAndLiveRegion
+-ChromeVoxBackgroundTest.TouchEditingState
 -ChromeVoxDesktopAutomationHandlerTest.TaskManagerTableView
 -ChromeVoxISearchTest.Simple
 -ChromeVoxPanelTest.FormControlsMenu
@@ -28,6 +29,11 @@
 -SwitchAccessItemScanManagerTest.*
 -SwitchAccessPointScanManagerTest.PointScanLeftClick
 # Flaky (crbug.com/1268842).
--ChromeVoxLiveRegionsTest.LiveRegionRemoveElement
--ChromeVoxLiveRegionsTest.LiveRegionAddElement
+-ChromeVoxBackgroundTest.GestureOnPopUpButton
+-ChromeVoxBackgroundTest.HitTestOnExoSurface
+-ChromeVoxEditingTest.JumpCommandsSyncSelection
+-ChromeVoxBackgroundTest.ListBoxItemsNavigation
 -ChromeVoxLiveRegionsTest.FocusThenLiveRegion
+-ChromeVoxLiveRegionsTest.LiveRegionAddElement
+-ChromeVoxLiveRegionsTest.LiveRegionRemoveElement
+-ChromeVoxUserActionMonitorTest.Gestures
diff --git a/testing/buildbot/test_suites.pyl b/testing/buildbot/test_suites.pyl
index f28781d..c82e67e 100644
--- a/testing/buildbot/test_suites.pyl
+++ b/testing/buildbot/test_suites.pyl
@@ -75,7 +75,12 @@
             # on images that do not have playstore.
             # They will be ran in chrome_public_test_apk_with_playstore below.
             '--gtest_filter=-'
-            'org.chromium.chrome.browser.contextualsearch.ContextualSearchManagerTest.test*ExternalNavigationWithUserGesture*'
+            'org.chromium.chrome.browser.contextualsearch.ContextualSearchManagerTest.test*ExternalNavigationWithUserGesture*:'
+            'org.chromium.chrome.browser.externalnav.UrlOverridingTest.test*UserGesture*:'
+            'org.chromium.chrome.browser.externalnav.UrlOverridingTest.testNavigationFromXHRCallback:'
+            'org.chromium.chrome.browser.externalnav.UrlOverridingTest.testNavigationFromXHRCallbackAndShortTimeout:'
+            'org.chromium.chrome.browser.externalnav.UrlOverridingTest.testNavigationFromXHRCallbackInSubFrame:'
+            'org.chromium.chrome.browser.externalnav.UrlOverridingTest.testRedirectionFromIntent*'
         ],
         'swarming': {
           'shards': 20,
@@ -91,7 +96,12 @@
             # present. See crbug.com/1056330 for more details.
             # They should be ran in emulator on images that have playstore.
             '--gtest_filter='
-            'org.chromium.chrome.browser.contextualsearch.ContextualSearchManagerTest.test*ExternalNavigationWithUserGesture*'
+            'org.chromium.chrome.browser.contextualsearch.ContextualSearchManagerTest.test*ExternalNavigationWithUserGesture*:'
+            'org.chromium.chrome.browser.externalnav.UrlOverridingTest.test*UserGesture*:'
+            'org.chromium.chrome.browser.externalnav.UrlOverridingTest.testNavigationFromXHRCallback:'
+            'org.chromium.chrome.browser.externalnav.UrlOverridingTest.testNavigationFromXHRCallbackAndShortTimeout:'
+            'org.chromium.chrome.browser.externalnav.UrlOverridingTest.testNavigationFromXHRCallbackInSubFrame:'
+            'org.chromium.chrome.browser.externalnav.UrlOverridingTest.testRedirectionFromIntent*'
         ],
         'mixins': [
           'skia_gold_test',
diff --git a/testing/buildbot/variants.pyl b/testing/buildbot/variants.pyl
index 5cbfcb9..0383ff0 100644
--- a/testing/buildbot/variants.pyl
+++ b/testing/buildbot/variants.pyl
@@ -459,7 +459,7 @@
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M100',
-          'revision': 'version:100.0.4896.43',
+          'revision': 'version:100.0.4896.44',
         }
       ],
     },
@@ -483,7 +483,7 @@
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M99',
-          'revision': 'version:99.0.4844.73',
+          'revision': 'version:99.0.4844.75',
         }
       ],
     },
@@ -603,7 +603,7 @@
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M100',
-          'revision': 'version:100.0.4896.43',
+          'revision': 'version:100.0.4896.44',
         }
       ],
     },
@@ -627,7 +627,7 @@
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M99',
-          'revision': 'version:99.0.4844.73',
+          'revision': 'version:99.0.4844.75',
         }
       ],
     },
@@ -747,7 +747,7 @@
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M100',
-          'revision': 'version:100.0.4896.43',
+          'revision': 'version:100.0.4896.44',
         }
       ],
     },
@@ -771,7 +771,7 @@
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M99',
-          'revision': 'version:99.0.4844.73',
+          'revision': 'version:99.0.4844.75',
         }
       ],
     },
diff --git a/third_party/blink/public/devtools_protocol/browser_protocol.pdl b/third_party/blink/public/devtools_protocol/browser_protocol.pdl
index c681ef2..607d7429 100644
--- a/third_party/blink/public/devtools_protocol/browser_protocol.pdl
+++ b/third_party/blink/public/devtools_protocol/browser_protocol.pdl
@@ -8131,6 +8131,7 @@
       NoResponseHead
       Unknown
       ActivationNavigationsDisallowedForBug1234857
+      ErrorDocument
       #Blocklisted features
       WebSocket
       WebTransport
diff --git a/third_party/blink/renderer/core/layout/ng/ng_length_utils.cc b/third_party/blink/renderer/core/layout/ng/ng_length_utils.cc
index c04647d..f58f6f72 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_length_utils.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_length_utils.cc
@@ -997,7 +997,10 @@
       size = InlineSizeFromAspectRatio(border_padding, aspect_ratio, box_sizing,
                                        *replaced_block);
     } else if (natural_size) {
-      size = natural_size->inline_size;
+      DCHECK_NE(mode, ReplacedSizeMode::kIgnoreInlineLengths);
+      size = ComputeReplacedSize(node, space, border_padding,
+                                 ReplacedSizeMode::kIgnoreInlineLengths)
+                 .inline_size;
     } else {
       // We don't have a natural size - default to stretching.
       size = StretchFit();
diff --git a/third_party/blink/web_tests/FlagExpectations/disable-layout-ng b/third_party/blink/web_tests/FlagExpectations/disable-layout-ng
index 3019fe6e..74c9810 100644
--- a/third_party/blink/web_tests/FlagExpectations/disable-layout-ng
+++ b/third_party/blink/web_tests/FlagExpectations/disable-layout-ng
@@ -1490,6 +1490,7 @@
 crbug.com/591099 external/wpt/css/css-sizing/replaced-aspect-ratio-stretch-fit-001.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-sizing/replaced-aspect-ratio-stretch-fit-002.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-sizing/replaced-aspect-ratio-stretch-fit-003.html [ Failure ]
+crbug.com/1135287 external/wpt/css/css-sizing/replaced-aspect-ratio-intrinsic-size-001.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-sizing/aspect-ratio/abspos-020.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-sizing/aspect-ratio/block-aspect-ratio-035.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-typed-om/idlharness.html [ Failure ]
diff --git a/third_party/blink/web_tests/MSANExpectations b/third_party/blink/web_tests/MSANExpectations
index 10f615cf..a1fbbde 100644
--- a/third_party/blink/web_tests/MSANExpectations
+++ b/third_party/blink/web_tests/MSANExpectations
@@ -175,3 +175,9 @@
 crbug.com/1215390 [ Linux ] external/wpt/pointerevents/pointerevent_pointerId_scope.html [ Pass Failure Timeout ]
 crbug.com/1215632 [ Linux ] external/wpt/html/webappapis/scripting/events/event-handler-attributes-frameset-window.html [ Pass Timeout ]
 crbug.com/1215632 [ Linux ] external/wpt/html/webappapis/scripting/events/event-handler-attributes-windowless-body.html [ Pass Timeout ]
+
+# Flaking on WebKit Linux MSAN
+crbug.com/1126305 [ Linux ] virtual/prerender/external/wpt/speculation-rules/prerender/fetch-blob.html [ Skip ]
+crbug.com/1126305 [ Linux ] virtual/prerender/external/wpt/speculation-rules/prerender/cache-storage.https.html [ Skip ]
+crbug.com/1126305 [ Linux ] virtual/prerender/external/wpt/speculation-rules/prerender/indexeddb.html [ Skip ]
+crbug.com/1126305 [ Linux ] virtual/prerender/external/wpt/speculation-rules/prerender/restriction-focus.html [ Skip ]
\ No newline at end of file
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 672a0ef..55003cd 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -1317,6 +1317,7 @@
 crbug.com/1284251 external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-border-top-left-radius-001.html [ Failure ]
 crbug.com/1284270 [ Fuchsia ] external/wpt/css/css-ui/compute-kind-widget-no-fallback-props-001.html [ Failure ]
 crbug.com/1284270 [ Linux ] external/wpt/css/css-ui/compute-kind-widget-no-fallback-props-001.html [ Failure ]
+crbug.com/1284270 [ Mac10.12 ] external/wpt/css/css-ui/compute-kind-widget-no-fallback-props-001.html [ Failure Timeout ]
 crbug.com/1284270 [ Mac10.13 ] external/wpt/css/css-ui/compute-kind-widget-no-fallback-props-001.html [ Failure ]
 crbug.com/1284270 [ Mac10.14 ] external/wpt/css/css-ui/compute-kind-widget-no-fallback-props-001.html [ Failure ]
 crbug.com/1284270 [ Mac10.15 ] external/wpt/css/css-ui/compute-kind-widget-no-fallback-props-001.html [ Failure ]
@@ -3372,7 +3373,6 @@
 crbug.com/626703 [ Win ] virtual/partitioned-cookies/http/tests/inspector-protocol/network/disabled-cache-navigation.js [ Failure ]
 
 # ====== New tests from wpt-importer added here ======
-crbug.com/626703 [ Mac10.12 ] external/wpt/css/css-ui/compute-kind-widget-no-fallback-props-001.html [ Timeout ]
 crbug.com/626703 [ Mac10.12 ] virtual/fenced-frame-mparch/wpt_internal/fenced_frame/maxframes.https.html [ Timeout ]
 crbug.com/626703 [ Mac10.12 ] virtual/portals/external/wpt/portals/history/history-manipulation-inside-portal-with-subframes.html [ Timeout ]
 crbug.com/626703 [ Mac10.12 ] virtual/portals/wpt_internal/portals/create-many-portals.html [ Timeout ]
@@ -4138,29 +4138,10 @@
 crbug.com/626703 [ Mac10.12 ] virtual/portals/external/wpt/portals/idlharness.window.html [ Failure ]
 
 ### virtual/prerender/external/wpt/speculation-rules/prerender
-crbug.com/1126305 [ Mac10.14 ] virtual/prerender/external/wpt/speculation-rules/prerender/fetch-blob.html [ Skip Timeout ]
-crbug.com/1126305 [ Win10.20h2 ] virtual/prerender/external/wpt/speculation-rules/prerender/cache-storage.https.html [ Skip Timeout ]
-crbug.com/1126305 [ Mac11 ] virtual/prerender/external/wpt/speculation-rules/prerender/cache-storage.https.html [ Skip Timeout ]
 crbug.com/1126305 [ Mac10.15 ] virtual/prerender/external/wpt/speculation-rules/prerender/local-storage.html [ Skip Timeout ]
-crbug.com/1126305 [ Mac11 ] virtual/prerender/external/wpt/speculation-rules/prerender/restriction-focus.html [ Skip Timeout ]
-crbug.com/1126305 [ Mac10.14 ] virtual/prerender/external/wpt/speculation-rules/prerender/cache-storage.https.html [ Skip Timeout ]
-crbug.com/1126305 [ Mac10.15 ] virtual/prerender/external/wpt/speculation-rules/prerender/fetch-blob.html [ Skip Timeout ]
-crbug.com/1126305 [ Mac11 ] virtual/prerender/external/wpt/speculation-rules/prerender/fetch-blob.html [ Skip Timeout ]
-crbug.com/1126305 [ Mac10.14 ] virtual/prerender/external/wpt/speculation-rules/prerender/indexeddb.html [ Skip Timeout ]
-crbug.com/1126305 [ Mac11 ] virtual/prerender/external/wpt/speculation-rules/prerender/indexeddb.html [ Skip Timeout ]
-crbug.com/1126305 [ Mac10.15 ] virtual/prerender/external/wpt/speculation-rules/prerender/cache-storage.https.html [ Skip Timeout ]
-crbug.com/1126305 [ Mac10.15 ] virtual/prerender/external/wpt/speculation-rules/prerender/indexeddb.html [ Skip Timeout ]
-crbug.com/1126305 [ Win ] virtual/prerender/external/wpt/speculation-rules/prerender/indexeddb.html [ Skip Timeout ]
 crbug.com/1126305 [ Linux ] virtual/prerender/external/wpt/speculation-rules/prerender/local-storage.html [ Skip Timeout ]
 crbug.com/1126305 [ Linux ] virtual/prerender/external/wpt/speculation-rules/prerender/media-autoplay.html [ Failure Skip Timeout ]
-crbug.com/1126305 [ Mac10.14 ] virtual/prerender/external/wpt/speculation-rules/prerender/restriction-focus.html [ Skip Timeout ]
-crbug.com/1126305 [ Mac10.15 ] virtual/prerender/external/wpt/speculation-rules/prerender/restriction-focus.html [ Skip Timeout ]
-crbug.com/1126305 [ Win7 ] virtual/prerender/external/wpt/speculation-rules/prerender/restriction-focus.html [ Skip Timeout ]
 crbug.com/1126305 virtual/prerender/external/wpt/speculation-rules/prerender/restrictions.html [ Skip Timeout ]
-crbug.com/1126305 [ Linux ] virtual/prerender/external/wpt/speculation-rules/prerender/fetch-blob.html [ Failure Skip Timeout ]
-crbug.com/1126305 [ Linux ] virtual/prerender/external/wpt/speculation-rules/prerender/cache-storage.https.html [ Failure Skip Timeout ]
-crbug.com/1126305 [ Linux ] virtual/prerender/external/wpt/speculation-rules/prerender/indexeddb.html [ Failure Skip Timeout ]
-crbug.com/1126305 [ Linux ] virtual/prerender/external/wpt/speculation-rules/prerender/restriction-focus.html [ Failure Skip Timeout ]
 
 # selectmenu timeouts
 crbug.com/1253971 [ Linux ] external/wpt/html/semantics/forms/the-selectmenu-element/selectmenu-parts-structure.tentative.html [ Timeout ]
@@ -7671,6 +7652,9 @@
 crbug.com/1298836 external/wpt/fetch/api/basic/response-null-body.any.* [ Failure Pass ]
 crbug.com/1298836 virtual/plz-dedicated-worker/external/wpt/fetch/api/basic/response-null-body.any.* [ Failure Pass ]
 
+# TODO(https://crbug.com/1305896): Make the following test pass.
+crbug.com/1305896 external/wpt/loading/early-hints/redirect-same-origin.h2.window.html [ Failure ]
+
 # Sheriff 2022-03-10
 crbug.com/1304956 storage/indexeddb/dont-wedge.html [ Failure Pass ]
 
diff --git a/third_party/blink/web_tests/external/Version b/third_party/blink/web_tests/external/Version
index e82251c..809cf8e9 100644
--- a/third_party/blink/web_tests/external/Version
+++ b/third_party/blink/web_tests/external/Version
@@ -1 +1 @@
-Version: e0cf318a60e583451b0cc045099749b5e2d8802e
+Version: 6bce541b6701e2517f5ddcd447e29d875bd8c822
diff --git a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
index 3fe115b..2b6d060 100644
--- a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
+++ b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
@@ -546,6 +546,13 @@
        {}
       ]
      ],
+     "out-of-flow-in-new-column-crash.html": [
+      "0571af7a9793abdd5a2b317c51d91de30df0bebf",
+      [
+       null,
+       {}
+      ]
+     ],
      "resumed-float-and-inline-block-crash.html": [
       "c86034c3723c3dc8cb89a5383b4dcfdf9abb94d9",
       [
@@ -79750,6 +79757,71 @@
         {}
        ]
       ],
+      "multi-line-column-flex-fragmentation-012.html": [
+       "7552a741857a07e94878e495da1118438725110e",
+       [
+        null,
+        [
+         [
+          "/css/reference/ref-filled-green-100px-square.xht",
+          "=="
+         ]
+        ],
+        {}
+       ]
+      ],
+      "multi-line-column-flex-fragmentation-013.html": [
+       "ed739947e3d754b2f331d3a9a3dae6f327f559f4",
+       [
+        null,
+        [
+         [
+          "/css/reference/ref-filled-green-100px-square.xht",
+          "=="
+         ]
+        ],
+        {}
+       ]
+      ],
+      "multi-line-column-flex-fragmentation-014.html": [
+       "bcc32dd9ba9a10fb71ac2ea51a78c6d983993d09",
+       [
+        null,
+        [
+         [
+          "/css/reference/ref-filled-green-100px-square.xht",
+          "=="
+         ]
+        ],
+        {}
+       ]
+      ],
+      "multi-line-column-flex-fragmentation-015.html": [
+       "563f3663c38259aea6e245c53e24077f6e6d45ef",
+       [
+        null,
+        [
+         [
+          "/css/reference/ref-filled-green-100px-square.xht",
+          "=="
+         ]
+        ],
+        {}
+       ]
+      ],
+      "multi-line-column-flex-fragmentation-016.html": [
+       "e81ad10e037e4bc4208bfdf3eae72ea9b98b8adc",
+       [
+        null,
+        [
+         [
+          "/css/reference/ref-filled-green-100px-square.xht",
+          "=="
+         ]
+        ],
+        {}
+       ]
+      ],
       "multi-line-row-flex-fragmentation-001.html": [
        "ba6b0103e447994a2778cbd9ea356a490f62b8fb",
        [
@@ -79946,7 +80018,7 @@
        ]
       ],
       "multi-line-row-flex-fragmentation-016.html": [
-       "7b690f11237f271affc8486c72305e2cdb553568",
+       "fbd557cc3c3749b26847eefb34ce2d3e7e253452",
        [
         null,
         [
@@ -100567,6 +100639,19 @@
        {}
       ]
      ],
+     "fieldset-baseline-alignment.html": [
+      "d3fdba3d7f476cff486e765791f9c9d8f5de7b06",
+      [
+       null,
+       [
+        [
+         "/css/css-flexbox/fieldset-baseline-alignment-ref.html",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
      "fit-content-item-001.html": [
       "ac5c3406991a8b6d5db29d301be9680d7983c8d7",
       [
@@ -208671,6 +208756,19 @@
        {}
       ]
      ],
+     "filters-drop-shadow-003.html": [
+      "2dda6d0140b12f89fae372d2af01190d5346e78c",
+      [
+       null,
+       [
+        [
+         "/css/filter-effects/reference/filters-drop-shadow-003-ref.html",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
      "filters-grayscale-001-test.html": [
       "40e49b60bea249d54280ea9e74a5cd5cbf5e31a5",
       [
@@ -217066,6 +217164,19 @@
        ]
       },
       "the-fieldset-and-legend-elements": {
+       "fieldset-baseline.html": [
+        "23f5ad76f3eb302cd17f3a17d44ca37028cf98dc",
+        [
+         null,
+         [
+          [
+           "/html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/fieldset-baseline-ref.html",
+           "=="
+          ]
+         ],
+         {}
+        ]
+       ],
        "fieldset-border-gap-negative-margin.html": [
         "563a2aa68d833175767c2fe6507e460c25378d8e",
         [
@@ -247342,7 +247453,7 @@
       []
      ],
      "contain-size-fieldset-004-ref.html": [
-      "198e8e12acc247b3230460c39ce606d25e7bd119",
+      "59f7c7e2a78d5a859da92ddd1828e2af90bf50ba",
       []
      ],
      "contain-size-flex-001-ref.html": [
@@ -249643,6 +249754,10 @@
       "94e64dd1b829f68a935679ee6c26659437b730a7",
       []
      ],
+     "fieldset-baseline-alignment-ref.html": [
+      "d68e033342fa6bdcce44f70c4fa24eb9cb4ae88c",
+      []
+     ],
      "fixed-table-layout-with-percentage-width-in-flex-item-ref.html": [
       "8e3e6167176e4c3be16d4cdb84e4299052f9880f",
       []
@@ -280118,6 +280233,10 @@
        "4227e8cb140e13cbb88a6b1cc366b32b57c2f923",
        []
       ],
+      "filters-drop-shadow-003-ref.html": [
+       "94c265066b12ec9d5e7e9f11187b8da970ea1569",
+       []
+      ],
       "filters-opacity-001-ref.html": [
        "17d891ee707c36b7d404d9836060d7bb9275e841",
        []
@@ -298081,6 +298200,10 @@
         "f5b533c377160c77541187a301647e174069bd6d",
         []
        ],
+       "fieldset-baseline-ref.html": [
+        "ff583435a662a6c2c635619d8e4ecbbdf9f8a05f",
+        []
+       ],
        "fieldset-border-gap-position-relative-ref.html": [
         "95e2347121e26ec7cc7d080caf9db6ff253231c6",
         []
@@ -359519,7 +359642,7 @@
      ]
     ],
     "fedcm-revoke.https.html": [
-     "84a20b03267301d35ae49ef0c6ad0bfbb5bc811c",
+     "f0c3e5eb2d20e8f1992f56fe75d5291c0900e6b1",
      [
       null,
       {}
@@ -362722,6 +362845,13 @@
        {}
       ]
      ],
+     "all-prop-revert-noop.html": [
+      "696f498421e54d469f8304d46f34d9c39f81a4ef",
+      [
+       null,
+       {}
+      ]
+     ],
      "idlharness.html": [
       "9bde23b946ccf1537c92c6de2301005144bf12a6",
       [
diff --git a/third_party/blink/web_tests/external/wpt/css/css-cascade/all-prop-revert-noop.html b/third_party/blink/web_tests/external/wpt/css/css-cascade/all-prop-revert-noop.html
new file mode 100644
index 0000000..696f498
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-cascade/all-prop-revert-noop.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Cascade: "all: revert"</title>
+<link rel="author" title="Oriol Brufau" href="mailto:obrufau@igalia.com">
+<link rel="help" href="https://www.w3.org/TR/css-cascade-4/#default">
+<meta name="assert" content="Checks that adding 'all: revert' has no effect on elements with no other author rules.">
+
+<style>
+.revert-all {
+  all: revert;
+}
+</style>
+
+<div id="log"></div>
+<div id="wrapper"></div>
+
+<script src="/html/resources/common.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+function cloneStyle(style) {
+  const clone = Object.create(null);
+  for (let property of style) {
+    clone[property] = style.getPropertyValue(property);
+  }
+  return clone;
+}
+
+function assertSameClones(clone1, clone2, callback) {
+  for (let property in clone1) {
+    const value1 = clone1[property];
+    const value2 = clone2[property];
+    // assert_equals is slow, so only call it if it's going to fail.
+    if (value1 !== value2) {
+      assert_equals(value1, value2, property);
+    }
+  }
+}
+
+const wrapper = document.getElementById("wrapper");
+const elementNames = [...HTML5_ELEMENTS, "math", "svg", "z-custom"].sort();
+for (let elementName of elementNames) {
+  test(function() {
+    const element = document.createElement(elementName);
+    wrapper.appendChild(element);
+    const style = getComputedStyle(element);
+    const clonedBaseStyle = cloneStyle(style);
+    element.classList.add("revert-all");
+    const clonedRevertedStyle = cloneStyle(style);
+    assertSameClones(clonedRevertedStyle, clonedBaseStyle);
+  }, elementName);
+}
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-sizing/replaced-aspect-ratio-intrinsic-size-001.html b/third_party/blink/web_tests/external/wpt/css/css-sizing/replaced-aspect-ratio-intrinsic-size-001.html
new file mode 100644
index 0000000..8c1f213
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-sizing/replaced-aspect-ratio-intrinsic-size-001.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<meta name="assert"
+  content="Checks that a replaced element, with an aspect ratio, applies min/max block lengths for intrinsic sizing when width:max-content is specified.">
+<link rel="help" href="https://drafts.csswg.org/css-sizing-3/#intrinsic-sizes">
+<link rel="match" href="../reference/ref-filled-green-100px-square-only.html">
+<p>Test passes if there is a filled green square.</p>
+<img src="aspect-ratio/support/1x1-green.png" style="min-height: 100px; width:
+  max-content;"></img>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-sizing/replaced-aspect-ratio-intrinsic-size-002.html b/third_party/blink/web_tests/external/wpt/css/css-sizing/replaced-aspect-ratio-intrinsic-size-002.html
new file mode 100644
index 0000000..daf6f36
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-sizing/replaced-aspect-ratio-intrinsic-size-002.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<meta name="assert"
+  content="Checks that a replaced element, with an aspect ratio, applies min/max block lengths for intrinsic sizing when it's a float.">
+<link rel="help" href="https://drafts.csswg.org/css-sizing-3/#intrinsic-sizes">
+<link rel="match" href="../reference/ref-filled-green-100px-square-only.html">
+<p>Test passes if there is a filled green square.</p>
+<img src="aspect-ratio/support/1x1-green.png" style="min-height: 100px; float: left;"></img>
diff --git a/third_party/blink/web_tests/external/wpt/css/filter-effects/filters-drop-shadow-003.html b/third_party/blink/web_tests/external/wpt/css/filter-effects/filters-drop-shadow-003.html
new file mode 100644
index 0000000..2dda6d01
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/filter-effects/filters-drop-shadow-003.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<title>CSS Filters: drop-shadow filter on element in outside of the view</title>
+<link rel="author" title="Fujii Hironori" href="mailto:Hironori.Fujii@sony.com">
+<link rel="help" href="https://drafts.fxtf.org/filter-effects-1/#FilterProperty">
+<link rel="help" href="https://webkit.org/b/236800">
+<link rel="match" href="reference/filters-drop-shadow-003-ref.html">
+<meta name="assert" content="Check that a drop-shadow of an element with drop-shadow filter placed in the outside of the view appears"/>
+<style>
+div {
+    width: 300px;
+    height: 300px;
+    top: -1000px;
+    left: -1000px;
+    background-color: red;
+    position: relative;
+    filter: drop-shadow(1000px 1000px 0 green);
+}
+</style>
+You should see a green box.
+<div></div>
diff --git a/third_party/blink/web_tests/external/wpt/css/filter-effects/reference/filters-drop-shadow-003-ref.html b/third_party/blink/web_tests/external/wpt/css/filter-effects/reference/filters-drop-shadow-003-ref.html
new file mode 100644
index 0000000..94c26506
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/filter-effects/reference/filters-drop-shadow-003-ref.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<style>
+div {
+    width: 300px;
+    height: 300px;
+    background-color: green;
+}
+</style>
+You should see a green box.
+<div></div>
diff --git a/third_party/blink/web_tests/external/wpt/loading/early-hints/redirect-cross-origin.h2.window.js b/third_party/blink/web_tests/external/wpt/loading/early-hints/redirect-cross-origin.h2.window.js
new file mode 100644
index 0000000..548759b
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/loading/early-hints/redirect-cross-origin.h2.window.js
@@ -0,0 +1,10 @@
+// META: script=/common/utils.js
+// META: script=resources/early-hints-helpers.sub.js
+
+test(() => {
+    const params = new URLSearchParams();
+    params.set("preload-url", SAME_ORIGIN_RESOURCES_URL + "/empty.js?" + token());
+    params.set("redirect-url", CROSS_ORIGIN_RESOURCES_URL + "/redirect-cross-origin.html");
+    const test_url = "resources/redirect-with-early-hints.h2.py?" + params.toString();
+    window.location.replace(new URL(test_url, window.location));
+});
diff --git a/third_party/blink/web_tests/external/wpt/loading/early-hints/redirect-same-origin.h2.window.js b/third_party/blink/web_tests/external/wpt/loading/early-hints/redirect-same-origin.h2.window.js
new file mode 100644
index 0000000..88d64f39
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/loading/early-hints/redirect-same-origin.h2.window.js
@@ -0,0 +1,10 @@
+// META: script=/common/utils.js
+// META: script=resources/early-hints-helpers.sub.js
+
+test(() => {
+    const params = new URLSearchParams();
+    params.set("preload-url", SAME_ORIGIN_RESOURCES_URL + "/empty.js?" + token());
+    params.set("redirect-url", SAME_ORIGIN_RESOURCES_URL + "/redirect-same-origin.html");
+    const test_url = "resources/redirect-with-early-hints.h2.py?" + params.toString();
+    window.location.replace(new URL(test_url, window.location));
+});
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/loading/early-hints/resources/early-hints-helpers.sub.js b/third_party/blink/web_tests/external/wpt/loading/early-hints/resources/early-hints-helpers.sub.js
index 72cebf9..5c1b219 100644
--- a/third_party/blink/web_tests/external/wpt/loading/early-hints/resources/early-hints-helpers.sub.js
+++ b/third_party/blink/web_tests/external/wpt/loading/early-hints/resources/early-hints-helpers.sub.js
@@ -1,5 +1,12 @@
 "use strict";
 
+const SAME_ORIGIN = "https://{{host}}:{{ports[h2][0]}}";
+const CROSS_ORIGIN = "https://{{hosts[alt][www]}}:{{ports[h2][0]}}";
+
+const RESOURCES_PATH = "/loading/early-hints/resources";
+const SAME_ORIGIN_RESOURCES_URL = SAME_ORIGIN + RESOURCES_PATH;
+const CROSS_ORIGIN_RESOURCES_URL = CROSS_ORIGIN + RESOURCES_PATH;
+
 /**
  * Navigate to a test page with an Early Hints response.
  *
@@ -38,6 +45,32 @@
 }
 
 /**
+ * Fetches a script.
+ *
+ * @param {string} url
+ */
+ async function fetchScript(url) {
+    return new Promise((resolve) => {
+        const el = document.createElement("script");
+        el.src = url;
+        el.onload = resolve;
+        document.body.appendChild(el);
+    });
+}
+
+/**
+ * Returns true when the resource is preloaded via Early Hints.
+ *
+ * @param {string} url
+ * @returns {boolean}
+ */
+function isPreloadedByEarlyHints(url) {
+    const entries = performance.getEntriesByName(url);
+    assert_equals(entries.length, 1);
+    return entries[0].initiatorType === "early-hints";
+}
+
+/**
  * Navigate to the referrer policy test page.
  *
  * @param {string} referrer_policy - A value of Referrer-Policy to test.
@@ -45,9 +78,9 @@
 function testReferrerPolicy(referrer_policy) {
     const params = new URLSearchParams();
     params.set("referrer-policy", referrer_policy);
-    const same_origin_preload_url = "https://{{host}}:{{ports[h2][0]}}/loading/early-hints/resources/fetch-and-record-js.h2.py?id=" + token();
+    const same_origin_preload_url = SAME_ORIGIN_RESOURCES_URL + "/fetch-and-record-js.h2.py?id=" + token();
     params.set("same-origin-preload-url", same_origin_preload_url);
-    const cross_origin_preload_url = "https://{{hosts[alt][www]}}:{{ports[h2][0]}}/loading/early-hints/resources/fetch-and-record-js.h2.py?id=" + token();
+    const cross_origin_preload_url = CROSS_ORIGIN_RESOURCES_URL + "/fetch-and-record-js.h2.py?id=" + token();
     params.set("cross-origin-preload-url", cross_origin_preload_url);
 
     const path = "resources/referrer-policy-test-loader.h2.py?" + params.toString();
diff --git a/third_party/blink/web_tests/external/wpt/loading/early-hints/resources/empty.js.headers b/third_party/blink/web_tests/external/wpt/loading/early-hints/resources/empty.js.headers
index 175cdf8..74b688d 100644
--- a/third_party/blink/web_tests/external/wpt/loading/early-hints/resources/empty.js.headers
+++ b/third_party/blink/web_tests/external/wpt/loading/early-hints/resources/empty.js.headers
@@ -1 +1,2 @@
 cache-control: max-age=600
+access-control-allow-origin: *
diff --git a/third_party/blink/web_tests/external/wpt/loading/early-hints/resources/preload-initiator-type.html b/third_party/blink/web_tests/external/wpt/loading/early-hints/resources/preload-initiator-type.html
index 39b0db0f..0fdeb2b9 100644
--- a/third_party/blink/web_tests/external/wpt/loading/early-hints/resources/preload-initiator-type.html
+++ b/third_party/blink/web_tests/external/wpt/loading/early-hints/resources/preload-initiator-type.html
@@ -14,9 +14,7 @@
     el.src = preload.url;
     el.onload = t.step_func_done(() => {
         const name = new URL(preload.url, window.location);
-        const entries = performance.getEntriesByName(name);
-        assert_equals(entries.length, 1);
-        assert_equals(entries[0].initiatorType, "early-hints");
+        assert_true(isPreloadedByEarlyHints(name));
     });
     document.body.appendChild(el);
 }, "Ensure initiatorType is set to 'early-hints'");
diff --git a/third_party/blink/web_tests/external/wpt/loading/early-hints/resources/redirect-cross-origin.html b/third_party/blink/web_tests/external/wpt/loading/early-hints/resources/redirect-cross-origin.html
new file mode 100644
index 0000000..39b37f8
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/loading/early-hints/resources/redirect-cross-origin.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="early-hints-helpers.sub.js"></script>
+<body>
+<script>
+promise_test(async (t) => {
+    const params = new URLSearchParams(window.location.search);
+    const preload_url = params.get("preload-url");
+    await fetchScript(preload_url);
+    assert_false(isPreloadedByEarlyHints(preload_url));
+}, "Redirect to a cross origin doesn't bring early hints preload");
+</script>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/loading/early-hints/resources/redirect-same-origin.html b/third_party/blink/web_tests/external/wpt/loading/early-hints/resources/redirect-same-origin.html
new file mode 100644
index 0000000..6a2246a2
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/loading/early-hints/resources/redirect-same-origin.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="early-hints-helpers.sub.js"></script>
+<body>
+<script>
+promise_test(async (t) => {
+    const params = new URLSearchParams(window.location.search);
+    const preload_url = params.get("preload-url");
+    await fetchScript(preload_url);
+    assert_true(isPreloadedByEarlyHints(preload_url));
+}, "Redirect to the same origin keeps early hints preload");
+</script>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/loading/early-hints/resources/redirect-with-early-hints.h2.py b/third_party/blink/web_tests/external/wpt/loading/early-hints/resources/redirect-with-early-hints.h2.py
new file mode 100644
index 0000000..e501d85
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/loading/early-hints/resources/redirect-with-early-hints.h2.py
@@ -0,0 +1,20 @@
+def handle_headers(frame, request, response):
+    preload_url = request.GET.first(b"preload-url").decode()
+    link_header_value = "<{}>; rel=preload; as=script".format(preload_url)
+
+    early_hints = [
+        (b":status", b"103"),
+        (b"link", link_header_value),
+    ]
+    response.writer.write_raw_header_frame(headers=early_hints,
+                                           end_headers=True)
+
+    redirect_url = request.GET.first(b"redirect-url").decode()
+    location = "{}?preload-url={}".format(redirect_url, preload_url)
+    response.status = 302
+    response.headers["location"] = location
+    response.write_status_headers()
+
+
+def main(request, response):
+    pass
diff --git a/third_party/blink/web_tests/external/wpt/loading/early-hints/resources/referrer-policy-test.html b/third_party/blink/web_tests/external/wpt/loading/early-hints/resources/referrer-policy-test.html
index af4f393d..d0389c2e 100644
--- a/third_party/blink/web_tests/external/wpt/loading/early-hints/resources/referrer-policy-test.html
+++ b/third_party/blink/web_tests/external/wpt/loading/early-hints/resources/referrer-policy-test.html
@@ -8,15 +8,6 @@
 const SEARCH_PARAMS = new URLSearchParams(window.location.search);
 const REFERRER_POLICY = SEARCH_PARAMS.get("referrer-policy");
 
-async function fetch_script(url) {
-    return new Promise((resolve) => {
-        const el = document.createElement("script");
-        el.src = url;
-        el.onload = resolve;
-        document.body.appendChild(el);
-    });
-}
-
 async function get_fetch_timing_and_headers(url_string) {
     const url = new URL(url_string);
     const id = url.searchParams.get("id");
@@ -50,15 +41,13 @@
 }
 
 async function check_referrer(url, expected_referrer) {
-    await fetch_script(url);
+    await fetchScript(url);
 
     const { headers } = await get_fetch_timing_and_headers(url);
     assert_equals(headers["referer"], expected_referrer);
 
     const name = new URL(url, window.location);
-    const entries = performance.getEntriesByName(name);
-    assert_equals(entries.length, 1);
-    assert_equals(entries[0].initiatorType, "early-hints");
+    assert_true(isPreloadedByEarlyHints(name));
 }
 
 promise_test(async (t) => {
diff --git a/third_party/blink/web_tests/http/tests/origin_trials/webexposed/fedcm-origin-trial-interfaces.html b/third_party/blink/web_tests/http/tests/origin_trials/webexposed/fedcm-origin-trial-interfaces.html
new file mode 100644
index 0000000..c4c0387
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/origin_trials/webexposed/fedcm-origin-trial-interfaces.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<!-- Generate token with the command:
+generate_token.py http://127.0.0.1:8000 FedCM --expire-timestamp=2000000000
+-- -->
+<meta http-equiv="origin-trial"
+  content="A+HpfnpkW4XI4PiU2WZ8uegm47AUP0SjrLLcjGNbg7Vl6EexswWCM1vOSpoLdPEapiUkB9V+/UOm4yIRD59ybgoAAABNeyJvcmlnaW4iOiAiaHR0cDovLzEyNy4wLjAuMTo4MDAwIiwgImZlYXR1cmUiOiAiRmVkQ00iLCAiZXhwaXJ5IjogMjAwMDAwMDAwMH0=" />
+<title>FedCM API - interfaces and properties exposed by origin trial</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/origin-trials-helper.js"></script>
+<script>
+  test(t => {
+    OriginTrialsHelper.check_properties_exist(this,
+      {
+        'FederatedCredential': ['login'],
+      });
+  }, 'FedCM API interfaces and properties in Origin-Trial enabled document.');
+</script>
diff --git a/third_party/nearby/README.chromium b/third_party/nearby/README.chromium
index 3abebc59..f5b0237 100644
--- a/third_party/nearby/README.chromium
+++ b/third_party/nearby/README.chromium
@@ -1,7 +1,7 @@
 Name: Nearby Connections Library
 Short Name: Nearby
 URL: https://github.com/google/nearby-connections
-Version: 9239fb0767a790416cc5faf055f5169bce89e741
+Version: 0702409047f372c5ec7b7566ec8a86500caa8972
 License: Apache 2.0
 License File: LICENSE
 Security Critical: yes
diff --git a/tools/gritsettings/resource_ids.spec b/tools/gritsettings/resource_ids.spec
index 42ef144..08ed44be 100644
--- a/tools/gritsettings/resource_ids.spec
+++ b/tools/gritsettings/resource_ids.spec
@@ -172,7 +172,7 @@
     "includes": [1590],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/extensions/extensions_resources.grd": {
-    "META": {"sizes": {"includes": [50],}},
+    "META": {"sizes": {"includes": [80],}},
     "includes": [1600],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/history/history_resources.grd": {
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index cfb6db8..07e0d2c 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -54851,6 +54851,7 @@
   <int value="147342055" label="ChromeHomeClearUrlOnOpen:disabled"/>
   <int value="147373243" label="enable-deferred-image-decoding"/>
   <int value="147645817" label="DnsHttpssvc:enabled"/>
+  <int value="149272550" label="OmitCorsClientCert:disabled"/>
   <int value="149914698" label="SearchReadyOmnibox:disabled"/>
   <int value="150820177"
       label="SafeBrowsingEnhancedProtectionMessageInInterstitials:enabled"/>
@@ -54916,6 +54917,7 @@
   <int value="196103097" label="PaintPreviewShowOnStartup:disabled"/>
   <int value="196578182"
       label="AutofillEnableAugmentedPhoneCountryCode:disabled"/>
+  <int value="198386935" label="OmitCorsClientCert:enabled"/>
   <int value="198719062" label="WebAssemblyLazyCompilation:disabled"/>
   <int value="198762155" label="SharingPeerConnectionSender:enabled"/>
   <int value="200347243" label="WebVRExperimentalRendering:disabled"/>
@@ -67626,6 +67628,14 @@
   </int>
 </enum>
 
+<enum name="OptimizationGuidePredictionModelDownloadState">
+  <int value="0" label="Unknown"/>
+  <int value="1" label="Requested">Model was requested to be downloaded.</int>
+  <int value="2" label="Started">
+    Download service started the model download.
+  </int>
+</enum>
+
 <enum name="OptimizationGuidePredictionModelDownloadStatus">
   <int value="0" label="Unknown"/>
   <int value="1" label="Success">
@@ -84781,6 +84791,79 @@
   <int value="20" label="SQLITE_IOERR_SHMLOCK">Unused</int>
 </enum>
 
+<enum name="SqliteLoggedResultCode">
+  <summary>SQLite result codes mapped to logging-friendly values.</summary>
+  <int value="0" label="kNoError">Success result codes: OK, DONE, ROW</int>
+  <int value="1" label="kUnusedSqlite">
+    Codes that SQLite should never return, like SQLITE_INTERNAL
+  </int>
+  <int value="2" label="kUnusedChrome">
+    Codes that SQLite should never return, given Chrome's usage pattern
+  </int>
+  <int value="3" label="kGeneric">SQLITE_ERROR</int>
+  <int value="4" label="kPermission">SQLITE_PERM</int>
+  <int value="5" label="kAbort">SQLITE_ABORT</int>
+  <int value="6" label="kBusy">SQLITE_BUSY</int>
+  <int value="7" label="kReadOnly">SQLITE_READONLY</int>
+  <int value="8" label="kIo">SQLITE_IOERR</int>
+  <int value="9" label="kCorrupt">SQLITE_CORRUPT</int>
+  <int value="10" label="kFullDisk">SQLITE_FULL</int>
+  <int value="11" label="kCantOpen">SQLITE_CANTOPEN</int>
+  <int value="12" label="kLockingProtocol">SQLITE_PROTOCOL</int>
+  <int value="13" label="kSchemaChanged">SQLITE_SCHEMA</int>
+  <int value="14" label="kTooBig">SQLITE_TOOBIG</int>
+  <int value="15" label="kConstraint">SQLITE_CONSTRAINT</int>
+  <int value="16" label="kTypeMismatch">SQLITE_MISMATCH</int>
+  <int value="17" label="kNoLargeFileSupport">SQLITE_NOLFS</int>
+  <int value="18" label="kNotADatabase">SQLITE_NOTADB</int>
+  <int value="19" label="kBusyRecovery">SQLITE_BUSY_RECOVERY</int>
+  <int value="20" label="kReadOnlyRecovery">SQLITE_READONLY_RECOVERY</int>
+  <int value="21" label="kIoRead">SQLITE_IOERR_READ</int>
+  <int value="22" label="kConstraintCheck">SQLITE_CONSTRAINT_CHECK</int>
+  <int value="23" label="kAbortRollback">SQLITE_ABORT_ROLLBACK</int>
+  <int value="24" label="kBusySnapshot">SQLITE_BUSY_SNAPSHOT</int>
+  <int value="25" label="kReadOnlyCantLock">SQLITE_READONLY_CANTLOCK</int>
+  <int value="26" label="kIoShortRead">SQLITE_IOERR_SHORT_READ</int>
+  <int value="27" label="kCorruptSequence">SQLITE_CORRUPT_SEQUENCE</int>
+  <int value="28" label="kCantOpenIsDir">SQLITE_CANTOPEN_ISDIR</int>
+  <int value="29" label="kReadOnlyRollback">SQLITE_READONLY_ROLLBACK</int>
+  <int value="30" label="kIoWrite">SQLITE_IOERR_WRITE</int>
+  <int value="31" label="kCorruptIndex">SQLITE_CORRUPT_INDEX</int>
+  <int value="32" label="kConstraintForeignKey">
+    SQLITE_CONSTRAINT_FOREIGN_KEY
+  </int>
+  <int value="33" label="kReadOnlyDbMoved">SQLITE_READONLY_DBMOVED</int>
+  <int value="34" label="kIoFsync">SQLITE_IOERR_FSYNC</int>
+  <int value="35" label="kIoDirFsync">SQLITE_IOERR_DIR_FSYNC</int>
+  <int value="36" label="kConstraintNotNull">SQLITE_CONSTRAINT_NOTNULL</int>
+  <int value="37" label="kReadOnlyDirectory">SQLITE_READONLY_DIRECTORY</int>
+  <int value="38" label="kIoTruncate">SQLITE_IOERR_TRUNCATE</int>
+  <int value="39" label="kConstraintPrimaryKey">
+    SQLITE_CONSTRAINT_PRIMARYKEY
+  </int>
+  <int value="40" label="kIoFstat">SQLITE_IOERR_FSTAT</int>
+  <int value="41" label="kIoUnlock">SQLITE_IOERR_UNLOCK</int>
+  <int value="42" label="kConstraintUnique">SQLITE_CONSTRAINT_UNIQUE</int>
+  <int value="43" label="kIoReadLock">SQLITE_IOERR_RDLOCK</int>
+  <int value="44" label="kIoDelete">SQLITE_IOERR_DELETE</int>
+  <int value="45" label="kConstraintRowId">SQLITE_CONSTRAINT_ROWID</int>
+  <int value="46" label="kConstraintDataType">SQLITE_CONSTRAINT_DATATYPE</int>
+  <int value="47" label="kIoAccess">SQLITE_IOERR_ACCESS</int>
+  <int value="48" label="kIoCheckReservedLock">
+    SQLITE_IOERR_CHECKRESERVEDLOCK
+  </int>
+  <int value="49" label="kIoLock">SQLITE_IOERR_LOCK</int>
+  <int value="50" label="kIoClose">SQLITE_IOERR_CLOSE</int>
+  <int value="51" label="kIoSeek">SQLITE_IOERR_SEEK</int>
+  <int value="52" label="kIoDeleteNoEntry">SQLITE_IOERR_DELETE_NOENT</int>
+  <int value="53" label="kIoMemoryMapping">SQLITE_IOERR_MMAP</int>
+  <int value="54" label="kIoGetTemporaryPath">SQLITE_IOERR_GETTEMPPATH</int>
+  <int value="55" label="kIoBeginAtomic">SQLITE_IOERR_BEGIN_ATOMIC</int>
+  <int value="56" label="kIoCommitAtomic">SQLITE_IOERR_COMMIT_ATOMIC</int>
+  <int value="57" label="kIoRollbackAtomic">SQLITE_IOERR_ROLLBACK_ATOMIC</int>
+  <int value="58" label="kIoCorruptFileSystem">SQLITE_IOERR_CORRUPTFS</int>
+</enum>
+
 <enum name="SqliteRecoveryEventEnum">
   <summary>
     Track successful completion or failure of sql::Recovery implementation.
diff --git a/tools/metrics/histograms/metadata/accessibility/histograms.xml b/tools/metrics/histograms/metadata/accessibility/histograms.xml
index c948b7d..20c77dc 100644
--- a/tools/metrics/histograms/metadata/accessibility/histograms.xml
+++ b/tools/metrics/histograms/metadata/accessibility/histograms.xml
@@ -190,7 +190,7 @@
 </histogram>
 
 <histogram name="Accessibility.Android.OnDemand.OneHundredPercentEventsDropped"
-    units="count" expires_after="2022-08-28">
+    units="count" expires_after="2022-09-11">
   <owner>mschillaci@google.com</owner>
   <owner>abigailbklein@google.com</owner>
   <summary>
@@ -221,7 +221,7 @@
 </histogram>
 
 <histogram name="Accessibility.Android.OnDemand.PercentageDropped" units="%"
-    expires_after="2022-08-28">
+    expires_after="2022-09-11">
   <owner>mschillaci@google.com</owner>
   <owner>abigailbklein@google.com</owner>
   <summary>
@@ -1512,10 +1512,10 @@
 </histogram>
 
 <histogram name="Accessibility.WinAPIs.GetPropertyValue"
-    enum="AccessibilityWinAPIGetPropertyValueEnum" expires_after="2022-03-19">
+    enum="AccessibilityWinAPIGetPropertyValueEnum" expires_after="2022-09-19">
   <owner>aleventhal@chromium.org</owner>
   <owner>nektar@chromium.org</owner>
-  <owner>kbabbitt@microsoft.com</owner>
+  <owner>dlibby@microsoft.com</owner>
   <owner>chrome-a11y-core@google.com</owner>
   <summary>
     Tracks properties requested via UI Automation GetPropertyValue().
diff --git a/tools/metrics/histograms/metadata/android/histograms.xml b/tools/metrics/histograms/metadata/android/histograms.xml
index dc68a86..88217ce 100644
--- a/tools/metrics/histograms/metadata/android/histograms.xml
+++ b/tools/metrics/histograms/metadata/android/histograms.xml
@@ -415,7 +415,7 @@
 </histogram>
 
 <histogram name="Android.BackgroundTaskScheduler.TaskCanceled"
-    enum="BackgroundTaskId" expires_after="2022-07-03">
+    enum="BackgroundTaskId" expires_after="2022-09-11">
   <owner>fgorski@chromium.org</owner>
   <owner>nyquist@chromium.org</owner>
   <summary>Records that a specific background task has been canceled.</summary>
@@ -444,7 +444,7 @@
 </histogram>
 
 <histogram name="Android.BackgroundTaskScheduler.TaskExpired"
-    enum="BackgroundTaskId" expires_after="2022-07-03">
+    enum="BackgroundTaskId" expires_after="2022-09-11">
   <owner>fgorski@chromium.org</owner>
   <owner>nator@chromium.org</owner>
   <owner>knollr@chromium.org</owner>
@@ -455,7 +455,7 @@
 </histogram>
 
 <histogram name="Android.BackgroundTaskScheduler.TaskLoadedNative"
-    enum="BackgroundTaskId" expires_after="2022-07-03">
+    enum="BackgroundTaskId" expires_after="2022-09-11">
   <owner>fgorski@chromium.org</owner>
   <owner>nyquist@chromium.org</owner>
   <summary>
@@ -504,14 +504,14 @@
 </histogram>
 
 <histogram name="Android.BackgroundTaskScheduler.TaskStarted"
-    enum="BackgroundTaskId" expires_after="2022-07-03">
+    enum="BackgroundTaskId" expires_after="2022-09-11">
   <owner>fgorski@chromium.org</owner>
   <owner>nyquist@chromium.org</owner>
   <summary>Records that a specific background task has been started.</summary>
 </histogram>
 
 <histogram name="Android.BackgroundTaskScheduler.TaskStopped"
-    enum="BackgroundTaskId" expires_after="2022-07-03">
+    enum="BackgroundTaskId" expires_after="2022-09-11">
   <owner>fgorski@chromium.org</owner>
   <owner>nyquist@chromium.org</owner>
   <summary>
@@ -1002,7 +1002,7 @@
 </histogram>
 
 <histogram name="Android.DragDrop.FromWebContent.DropInWebContent.DistanceDip"
-    units="dp" expires_after="2022-07-01">
+    units="dp" expires_after="2022-09-11">
   <owner>wenyufu@chromium.org</owner>
   <owner>clank-large-form-factors@google.com</owner>
   <summary>
@@ -1016,7 +1016,7 @@
 </histogram>
 
 <histogram name="Android.DragDrop.FromWebContent.DropInWebContent.Duration"
-    units="ms" expires_after="2022-07-01">
+    units="ms" expires_after="2022-09-11">
   <owner>wenyufu@chromium.org</owner>
   <owner>clank-large-form-factors@google.com</owner>
   <summary>
@@ -1048,7 +1048,7 @@
 </histogram>
 
 <histogram name="Android.DragDrop.FromWebContent.TargetType"
-    enum="AndroidDragTargetType" expires_after="2022-07-01">
+    enum="AndroidDragTargetType" expires_after="2022-09-11">
   <owner>wenyufu@chromium.org</owner>
   <owner>clank-large-form-factors@google.com</owner>
   <summary>
@@ -1237,7 +1237,7 @@
 </histogram>
 
 <histogram name="Android.FontLookup.Blink.DLFontsLatencyFailure2"
-    units="microseconds" expires_after="2022-06-30">
+    units="microseconds" expires_after="2022-09-11">
   <owner>drott@chromium.org</owner>
   <owner>layout-dev@chromium.org</owner>
   <summary>
@@ -1251,7 +1251,7 @@
 </histogram>
 
 <histogram name="Android.FontLookup.Blink.DLFontsLatencySuccess2"
-    units="microseconds" expires_after="2022-06-30">
+    units="microseconds" expires_after="2022-09-11">
   <owner>drott@chromium.org</owner>
   <owner>layout-dev@chromium.org</owner>
   <summary>
@@ -1280,7 +1280,7 @@
 </histogram>
 
 <histogram name="Android.FontLookup.FetchAllFontFiles.Time" units="ms"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>twellington@chromium.org</owner>
   <owner>clank-app-team@google.com</owner>
   <summary>
@@ -1325,7 +1325,7 @@
 </histogram>
 
 <histogram name="Android.FontLookup.MatchLocalFontByUniqueName.Time" units="ms"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>twellington@chromium.org</owner>
   <owner>clank-app-team@google.com</owner>
   <summary>
@@ -1593,7 +1593,7 @@
 </histogram>
 
 <histogram name="Android.Jank.AsyncTaskGetOnUiThreadStatus"
-    enum="AsyncTaskStatus" expires_after="2022-07-11">
+    enum="AsyncTaskStatus" expires_after="2022-09-11">
   <owner>smaier@chromium.org</owner>
   <owner>agrieve@chromium.org</owner>
   <summary>
@@ -2459,7 +2459,7 @@
 </histogram>
 
 <histogram name="Android.PhotoPicker.EnumerationTime" units="ms"
-    expires_after="2022-06-01">
+    expires_after="2022-09-11">
   <owner>finnur@chromium.org</owner>
   <owner>twellington@chromium.org</owner>
   <summary>
@@ -3178,7 +3178,7 @@
 </histogram>
 
 <histogram name="Android.WebView.Callback.Counts" enum="WebViewCallbackType"
-    expires_after="2022-07-11">
+    expires_after="2022-09-11">
   <owner>ntfschr@chromium.org</owner>
   <owner>src/android_webview/OWNERS</owner>
   <summary>Records the invocation count of WebView callbacks.</summary>
@@ -3207,7 +3207,7 @@
 </histogram>
 
 <histogram name="Android.WebView.ComponentUpdater.GetFilesDuration" units="ms"
-    expires_after="2022-07-01">
+    expires_after="2022-09-11">
   <owner>nator@chromium.org</owner>
   <owner>hazems@chromium.org</owner>
   <owner>src/android_webview/OWNERS</owner>
@@ -3219,7 +3219,7 @@
 </histogram>
 
 <histogram name="Android.WebView.ComponentUpdater.GetFilesResult"
-    enum="WebViewComponentUpdaterGetFilesResult" expires_after="2022-07-01">
+    enum="WebViewComponentUpdaterGetFilesResult" expires_after="2022-09-11">
   <owner>nator@chromium.org</owner>
   <owner>hazems@chromium.org</owner>
   <owner>src/android_webview/OWNERS</owner>
@@ -3242,7 +3242,7 @@
 </histogram>
 
 <histogram name="Android.WebView.ComponentUpdater.UnexpectedExit"
-    enum="Boolean" expires_after="2022-07-01">
+    enum="Boolean" expires_after="2022-09-11">
   <owner>nator@chromium.org</owner>
   <owner>hazems@chromium.org</owner>
   <owner>src/android_webview/OWNERS</owner>
@@ -3254,7 +3254,7 @@
 </histogram>
 
 <histogram name="Android.WebView.ComponentUpdater.UpdateJobDuration" units="ms"
-    expires_after="2022-07-01">
+    expires_after="2022-09-11">
   <owner>nator@chromium.org</owner>
   <owner>hazems@chromium.org</owner>
   <owner>src/android_webview/OWNERS</owner>
@@ -3647,7 +3647,7 @@
 </histogram>
 
 <histogram name="Android.WebView.Metrics.PackagesAllowList.RecordStatus"
-    enum="AppPackageNameLoggingRuleStatus" expires_after="2022-07-01">
+    enum="AppPackageNameLoggingRuleStatus" expires_after="2022-09-11">
   <owner>hazems@chromium.org</owner>
   <owner>src/android_webview/OWNERS</owner>
   <summary>
@@ -3952,7 +3952,7 @@
 </histogram>
 
 <histogram name="Android.WebView.SecureCookieAction" enum="SecureCookieAction"
-    expires_after="2022-07-11">
+    expires_after="2022-09-11">
   <owner>ntfschr@chromium.org</owner>
   <owner>src/android_webview/OWNERS</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/apps/histograms.xml b/tools/metrics/histograms/metadata/apps/histograms.xml
index 9af0e1f..6cb5a868 100644
--- a/tools/metrics/histograms/metadata/apps/histograms.xml
+++ b/tools/metrics/histograms/metadata/apps/histograms.xml
@@ -191,7 +191,7 @@
 </histogram>
 
 <histogram name="Apps.AppInfoDialog.CreateWebAppShortcutSuccess" enum="Boolean"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>dmurph@chromium.org</owner>
   <owner>desktop-pwas-team@google.com</owner>
   <summary>
@@ -201,7 +201,7 @@
   </summary>
 </histogram>
 
-<histogram name="Apps.AppLaunch" enum="AppLaunch" expires_after="2022-06-30">
+<histogram name="Apps.AppLaunch" enum="AppLaunch" expires_after="2022-09-11">
   <owner>tapted@chromium.org</owner>
   <owner>benwells@chromium.org</owner>
   <summary>
@@ -1275,7 +1275,7 @@
 </histogram>
 
 <histogram name="Apps.AppListFolderOpened" enum="AppListFolderOpened"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>mmourgos@chromium.org</owner>
   <owner>newcomer@chromium.org</owner>
   <summary>
@@ -1355,7 +1355,7 @@
 </histogram>
 
 <histogram name="Apps.AppListPageSwitcherSource"
-    enum="AppListPageSwitcherSource" expires_after="2022-07-11">
+    enum="AppListPageSwitcherSource" expires_after="2022-09-11">
 <!-- Name completed by histogram_suffixes name="TabletOrClamshellMode" -->
 
   <owner>newcomer@chromium.org</owner>
@@ -1395,7 +1395,7 @@
 </histogram>
 
 <histogram name="Apps.AppListPlayStoreQueryState"
-    enum="AppListPlayStoreQueryState" expires_after="2022-07-11">
+    enum="AppListPlayStoreQueryState" expires_after="2022-09-11">
   <owner>hejq@chromium.org</owner>
   <summary>The state of a Play Store app search request.</summary>
 </histogram>
@@ -1486,7 +1486,7 @@
 </histogram>
 
 <histogram name="Apps.AppListSearchResultInternalApp.Show"
-    enum="AppListInternalAppName" expires_after="2022-07-03">
+    enum="AppListInternalAppName" expires_after="2022-09-11">
   <owner>wutao@chromium.org</owner>
   <summary>
     The app list search result of an internal app that was shown to the user.
@@ -1748,7 +1748,7 @@
 </histogram>
 
 <histogram name="Apps.CreateShortcutIcon.Linux.Result"
-    enum="WebAppCreateShortcutIconLinuxResult" expires_after="2022-07-03">
+    enum="WebAppCreateShortcutIconLinuxResult" expires_after="2022-09-11">
   <owner>estade@chromium.org</owner>
   <owner>desktop-pwas-team@google.com</owner>
   <summary>
@@ -1766,7 +1766,7 @@
 </histogram>
 
 <histogram name="Apps.CreateShortcuts.Mac.Result"
-    enum="WebAppCreateShortcutMacResult" expires_after="2022-07-03">
+    enum="WebAppCreateShortcutMacResult" expires_after="2022-09-11">
   <owner>phillis@chromium.org</owner>
   <owner>cmumford@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/arc/histograms.xml b/tools/metrics/histograms/metadata/arc/histograms.xml
index 44c42d1d..a83ecc83 100644
--- a/tools/metrics/histograms/metadata/arc/histograms.xml
+++ b/tools/metrics/histograms/metadata/arc/histograms.xml
@@ -1255,7 +1255,7 @@
 </histogram>
 
 <histogram name="Arc.PlayStoreSearch.QueryTime" units="ms"
-    expires_after="2022-07-11">
+    expires_after="2022-09-11">
   <owner>hejq@chromium.org</owner>
   <summary>
     Time between sending an Play Store app discovery request and the storing
diff --git a/tools/metrics/histograms/metadata/ash/histograms.xml b/tools/metrics/histograms/metadata/ash/histograms.xml
index a1dc7ea..2e831b23 100644
--- a/tools/metrics/histograms/metadata/ash/histograms.xml
+++ b/tools/metrics/histograms/metadata/ash/histograms.xml
@@ -2163,7 +2163,7 @@
 </histogram>
 
 <histogram name="Ash.MessageCenter.Scroll.PresentationTime" units="ms"
-    expires_after="2022-07-10">
+    expires_after="2022-09-11">
   <owner>leandre@chromium.org</owner>
   <owner>cros-status-area-eng@google.com</owner>
   <summary>
@@ -2283,7 +2283,7 @@
 </histogram>
 
 <histogram name="Ash.Notification.ClearAllVisible.AnimationSmoothness"
-    units="%" expires_after="2022-07-02">
+    units="%" expires_after="2022-09-11">
   <owner>leandre@chromium.org</owner>
   <owner>cros-status-area-eng@google.com</owner>
   <summary>
@@ -2326,7 +2326,7 @@
 </histogram>
 
 <histogram name="Ash.Notification.MoveDown.AnimationSmoothness" units="%"
-    expires_after="2022-07-02">
+    expires_after="2022-09-11">
   <owner>leandre@chromium.org</owner>
   <owner>cros-status-area-eng@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/autofill/histograms.xml b/tools/metrics/histograms/metadata/autofill/histograms.xml
index e86f8dc..2d4fdb4 100644
--- a/tools/metrics/histograms/metadata/autofill/histograms.xml
+++ b/tools/metrics/histograms/metadata/autofill/histograms.xml
@@ -410,7 +410,7 @@
 </histogram>
 
 <histogram name="Autofill.AutofilledFieldAtSubmission.ByStateSelectionField"
-    enum="AutofillSourceForStateSelectionField" expires_after="2022-07-11">
+    enum="AutofillSourceForStateSelectionField" expires_after="2022-09-11">
   <owner>vidhanj@google.com</owner>
   <owner>koerber@google.com</owner>
   <summary>
@@ -1547,7 +1547,7 @@
 </histogram>
 
 <histogram name="Autofill.ImageFetcher.Result" enum="BooleanSuccess"
-    expires_after="2022-07-01">
+    expires_after="2022-09-11">
   <owner>siyua@chromium.org</owner>
   <owner>payments-autofill-team@google.com</owner>
   <summary>
@@ -2285,7 +2285,7 @@
 </histogram>
 
 <histogram name="Autofill.ProfileImport.NewProfileEditedType"
-    enum="AutofillSettingsVisibleTypes" expires_after="2022-07-03">
+    enum="AutofillSettingsVisibleTypes" expires_after="2022-09-11">
   <owner>koerber@google.com</owner>
   <owner>src/components/autofill/OWNERS</owner>
   <summary>
@@ -2297,7 +2297,7 @@
 </histogram>
 
 <histogram name="Autofill.ProfileImport.NewProfileNumberOfEditedFields"
-    units="fields" expires_after="2022-07-03">
+    units="fields" expires_after="2022-09-11">
   <owner>koerber@google.com</owner>
   <owner>src/components/autofill/OWNERS</owner>
   <summary>
@@ -2454,7 +2454,7 @@
 </histogram>
 
 <histogram name="Autofill.ProfileImport.UpdateProfileEditedType"
-    enum="AutofillSettingsVisibleTypes" expires_after="2022-07-03">
+    enum="AutofillSettingsVisibleTypes" expires_after="2022-09-11">
   <owner>koerber@google.com</owner>
   <owner>src/components/autofill/OWNERS</owner>
   <summary>
@@ -2520,7 +2520,7 @@
 </histogram>
 
 <histogram name="Autofill.ProfileImport.UpdateProfileNumberOfEditedFields"
-    units="fields" expires_after="2022-07-03">
+    units="fields" expires_after="2022-09-11">
   <owner>koerber@google.com</owner>
   <owner>src/components/autofill/OWNERS</owner>
   <summary>
@@ -3129,7 +3129,7 @@
 </histogram>
 
 <histogram name="Autofill.SuggestionShown.OffTheRecord" units="units"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>rhalavati@chromium.org</owner>
   <owner>chrome-privacy-core@google.com</owner>
   <summary>
@@ -3191,7 +3191,7 @@
 </histogram>
 
 <histogram name="Autofill.UnmaskPrompt.Events" enum="AutofillUnmaskPromptEvent"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>jsaul@google.com</owner>
   <owner>siyua@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/background/histograms.xml b/tools/metrics/histograms/metadata/background/histograms.xml
index 79fdac65..ec1fe25 100644
--- a/tools/metrics/histograms/metadata/background/histograms.xml
+++ b/tools/metrics/histograms/metadata/background/histograms.xml
@@ -280,7 +280,7 @@
 </histogram>
 
 <histogram name="BackgroundSync.Event.OneShotResultPattern"
-    enum="BackgroundSyncResultPattern" expires_after="2022-07-11">
+    enum="BackgroundSyncResultPattern" expires_after="2022-09-11">
   <owner>nator@chromium.org</owner>
   <owner>rayankans@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/blink/histograms.xml b/tools/metrics/histograms/metadata/blink/histograms.xml
index fbc630a..1e4ce55 100644
--- a/tools/metrics/histograms/metadata/blink/histograms.xml
+++ b/tools/metrics/histograms/metadata/blink/histograms.xml
@@ -88,7 +88,7 @@
 </histogram>
 
 <histogram name="Blink.Animation.CompositedAnimationFailureReason"
-    enum="CompositorAnimationsFailureReason" expires_after="2022-07-03">
+    enum="CompositorAnimationsFailureReason" expires_after="2022-09-11">
   <owner>smcgruer@chromium.org</owner>
   <owner>animations-dev@chromium.org</owner>
   <summary>
@@ -182,7 +182,7 @@
 </histogram>
 
 <histogram name="Blink.Canvas.HibernationEvents" enum="CanvasHibernationEvent"
-    expires_after="2022-07-11">
+    expires_after="2022-09-11">
   <owner>fserb@chromium.org</owner>
   <summary>
     Records the occurrence of events related to 2D canvas GPU resource
@@ -464,7 +464,7 @@
 </histogram>
 
 <histogram name="Blink.Canvas.WebGPUStaleResourceCount"
-    units="recyclable resources" expires_after="2022-07-03">
+    units="recyclable resources" expires_after="2022-09-11">
   <owner>magchen@chromium.org</owner>
   <owner>enga@chromium.org</owner>
   <summary>
@@ -1231,7 +1231,7 @@
 </histogram>
 
 <histogram name="Blink.HTMLParsing.ParsingTimeMin2" units="microseconds"
-    expires_after="2022-06-30">
+    expires_after="2022-09-11">
   <owner>schenney@chromium.org</owner>
   <owner>dom-dev@chromium.org</owner>
   <summary>
@@ -1244,7 +1244,7 @@
 </histogram>
 
 <histogram name="Blink.HTMLParsing.ParsingTimeTotal2" units="microseconds"
-    expires_after="2022-06-30">
+    expires_after="2022-09-11">
   <owner>schenney@chromium.org</owner>
   <owner>dom-dev@chromium.org</owner>
   <summary>
@@ -1414,7 +1414,7 @@
 </histogram>
 
 <histogram name="Blink.ImageDecoders.Jpeg.ColorSpace" enum="JpegColorSpace"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>andrescj@chromium.org</owner>
   <owner>mcasas@chromium.org</owner>
   <summary>
@@ -1712,7 +1712,7 @@
 </histogram>
 
 <histogram base="true" name="Blink.MainFrame.UpdateTime" units="microseconds"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
 <!-- Name completed by histogram_suffixes name="BlinkUpdateTimePreFCPSuffixes" -->
 
 <!-- Name completed by histogram_suffixes name="BlinkUpdateTimePostFCPSuffixes" -->
@@ -1863,7 +1863,7 @@
 </histogram>
 
 <histogram base="true" name="Blink.Paint.UpdateTime" units="microseconds"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
 <!-- Name completed by histogram_suffixes name="BlinkUpdateTimePreFCPSuffixes" -->
 
 <!-- Name completed by histogram_suffixes name="BlinkUpdateTimePostFCPSuffixes" -->
@@ -1908,7 +1908,7 @@
 </histogram>
 
 <histogram base="true" name="Blink.PrePaint.UpdateTime" units="microseconds"
-    expires_after="2022-07-11">
+    expires_after="2022-09-11">
 <!-- Name completed by histogram_suffixes name="BlinkUpdateTimePreFCPSuffixes" -->
 
 <!-- Name completed by histogram_suffixes name="BlinkUpdateTimePostFCPSuffixes" -->
@@ -2019,7 +2019,7 @@
 </histogram>
 
 <histogram base="true" name="Blink.ScrollDocumentUpdate.UpdateTime"
-    units="microseconds" expires_after="2022-07-03">
+    units="microseconds" expires_after="2022-09-11">
 <!-- Name completed by histogram_suffixes name="BlinkUpdateTimePreFCPSuffixes" -->
 
 <!-- Name completed by histogram_suffixes name="BlinkUpdateTimePostFCPSuffixes" -->
@@ -2039,7 +2039,7 @@
 </histogram>
 
 <histogram base="true" name="Blink.ServiceDocumentUpdate.UpdateTime"
-    units="microseconds" expires_after="2022-07-03">
+    units="microseconds" expires_after="2022-09-11">
 <!-- Name completed by histogram_suffixes name="BlinkUpdateTimePreFCPSuffixes" -->
 
 <!-- Name completed by histogram_suffixes name="BlinkUpdateTimePostFCPSuffixes" -->
@@ -2188,7 +2188,7 @@
 </histogram>
 
 <histogram name="Blink.Sms.Receive.TimeUserCancel" units="ms"
-    expires_after="2022-07-11">
+    expires_after="2022-09-11">
   <owner>yigu@chromium.org</owner>
   <owner>goto@chromium.org</owner>
   <owner>web-identity@google.com</owner>
@@ -2519,7 +2519,7 @@
 </histogram>
 
 <histogram base="true" name="Blink.UserDrivenDocumentUpdate.UpdateTime"
-    units="microseconds" expires_after="2022-07-03">
+    units="microseconds" expires_after="2022-09-11">
 <!-- Name completed by histogram_suffixes name="BlinkUpdateTimePreFCPSuffixes" -->
 
 <!-- Name completed by histogram_suffixes name="BlinkUpdateTimePostFCPSuffixes" -->
@@ -2648,7 +2648,7 @@
 </histogram>
 
 <histogram name="Blink.WebCodecs.ImageDecoder.Success" enum="BooleanSuccess"
-    expires_after="M104">
+    expires_after="2022-09-11">
   <owner>dalecurtis@chromium.org</owner>
   <owner>media-dev@chromium.org</owner>
   <summary>
@@ -2658,7 +2658,7 @@
 </histogram>
 
 <histogram name="Blink.WebCodecs.ImageDecoder.Type" enum="DecodedImageType"
-    expires_after="M104">
+    expires_after="2022-09-11">
   <owner>dalecurtis@chromium.org</owner>
   <owner>media-dev@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/bluetooth/histograms.xml b/tools/metrics/histograms/metadata/bluetooth/histograms.xml
index e4774e9..17cd157 100644
--- a/tools/metrics/histograms/metadata/bluetooth/histograms.xml
+++ b/tools/metrics/histograms/metadata/bluetooth/histograms.xml
@@ -1379,7 +1379,7 @@
 </histogram>
 
 <histogram name="Bluetooth.Web.RequestDevice.UnionOfServices.Services"
-    enum="GATTServiceHash" expires_after="2022-07-03">
+    enum="GATTServiceHash" expires_after="2022-09-11">
   <owner>reillyg@chromium.org</owner>
   <owner>deviceapi-team@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/bookmarks/histograms.xml b/tools/metrics/histograms/metadata/bookmarks/histograms.xml
index 8fc2d31..7424ebbd 100644
--- a/tools/metrics/histograms/metadata/bookmarks/histograms.xml
+++ b/tools/metrics/histograms/metadata/bookmarks/histograms.xml
@@ -95,7 +95,7 @@
 </histogram>
 
 <histogram name="Bookmarks.BookmarkAllTabsWithTabsCount.Regular" units="tabs"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>roagarwal@chromium.org</owner>
   <owner>sideyilmaz@chromium.org</owner>
   <owner>chrome-incognito@google.com</owner>
@@ -141,7 +141,7 @@
 </histogram>
 
 <histogram name="Bookmarks.Count.OnProfileLoad" units="bookmarks"
-    expires_after="2022-08-07">
+    expires_after="2022-09-11">
   <owner>supertri@chromium.org</owner>
   <owner>isherman@chromium.org</owner>
   <owner>aidanday@google.com</owner>
@@ -601,7 +601,7 @@
 </histogram>
 
 <histogram name="Bookmarks.OpenBookmarkManager.PerProfileType"
-    enum="BrowserProfileType" expires_after="2022-07-03">
+    enum="BrowserProfileType" expires_after="2022-09-11">
   <owner>roagarwal@chromium.org</owner>
   <owner>chrome-incognito@google.com</owner>
   <component>UI&gt;Browser&gt;Bookmarks</component>
diff --git a/tools/metrics/histograms/metadata/browser/histograms.xml b/tools/metrics/histograms/metadata/browser/histograms.xml
index 4636909d..7ac41cc 100644
--- a/tools/metrics/histograms/metadata/browser/histograms.xml
+++ b/tools/metrics/histograms/metadata/browser/histograms.xml
@@ -721,7 +721,7 @@
 </histogram>
 
 <histogram name="BrowserRenderProcessHost.SpareProcessMaybeTakeAction"
-    enum="SpareProcessMaybeTakeAction" expires_after="2022-07-11">
+    enum="SpareProcessMaybeTakeAction" expires_after="2022-09-11">
   <owner>alexmos@chromium.org</owner>
   <owner>lukasza@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/chrome/histograms.xml b/tools/metrics/histograms/metadata/chrome/histograms.xml
index 3388cde..edbf35d7 100644
--- a/tools/metrics/histograms/metadata/chrome/histograms.xml
+++ b/tools/metrics/histograms/metadata/chrome/histograms.xml
@@ -42,14 +42,14 @@
 </histogram>
 
 <histogram name="Chrome.ProcessSingleton.RemoteHungProcessTerminateReason"
-    enum="RemoteHungProcessTerminateReason" expires_after="2022-07-03">
+    enum="RemoteHungProcessTerminateReason" expires_after="2022-09-11">
   <owner>gab@chromium.org</owner>
   <owner>etienneb@chromium.org</owner>
   <summary>The reason of remote hang processes termination.</summary>
 </histogram>
 
 <histogram name="Chrome.ProcessSingleton.RemoteProcessInteractionResult"
-    enum="RemoteProcessInteractionResult" expires_after="2022-07-03">
+    enum="RemoteProcessInteractionResult" expires_after="2022-09-11">
   <owner>gab@chromium.org</owner>
   <owner>etienneb@chromium.org</owner>
   <summary>
@@ -69,7 +69,7 @@
 </histogram>
 
 <histogram name="Chrome.ProcessSingleton.TerminateProcessErrorCode.Windows"
-    enum="WinGetLastError" expires_after="2022-07-03">
+    enum="WinGetLastError" expires_after="2022-09-11">
   <owner>gab@chromium.org</owner>
   <owner>etienneb@chromium.org</owner>
   <summary>
@@ -79,7 +79,7 @@
 </histogram>
 
 <histogram name="Chrome.ProcessSingleton.TerminateProcessTime" units="ms"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>gab@chromium.org</owner>
   <owner>etienneb@chromium.org</owner>
   <summary>
@@ -89,7 +89,7 @@
 </histogram>
 
 <histogram name="Chrome.ProcessSingleton.TerminationWaitErrorCode.Windows"
-    enum="WinGetLastError" expires_after="2022-07-03">
+    enum="WinGetLastError" expires_after="2022-09-11">
   <owner>gab@chromium.org</owner>
   <owner>etienneb@chromium.org</owner>
   <summary>
@@ -98,7 +98,7 @@
 </histogram>
 
 <histogram name="Chrome.ProcessSingleton.TimeToCreate" units="ms"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>etienneb@chromium.org</owner>
   <owner>gab@chromium.org</owner>
   <summary>
@@ -108,7 +108,7 @@
 </histogram>
 
 <histogram name="Chrome.ProcessSingleton.TimeToFailure" units="ms"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>etienneb@chromium.org</owner>
   <owner>gab@chromium.org</owner>
   <summary>
@@ -119,7 +119,7 @@
 </histogram>
 
 <histogram name="Chrome.ProcessSingleton.TimeToNotify" units="ms"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>etienneb@chromium.org</owner>
   <owner>gab@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/chromeos/histograms.xml b/tools/metrics/histograms/metadata/chromeos/histograms.xml
index 83e93a79..4fbaa6e 100644
--- a/tools/metrics/histograms/metadata/chromeos/histograms.xml
+++ b/tools/metrics/histograms/metadata/chromeos/histograms.xml
@@ -1298,7 +1298,7 @@
 </histogram>
 
 <histogram name="ChromeOS.PlatformVerification.Result2"
-    enum="ChromeOSPlatformVerificationResult2" expires_after="2022-07-03">
+    enum="ChromeOSPlatformVerificationResult2" expires_after="2022-09-11">
   <owner>erikchen@chromium.org</owner>
   <owner>cros-hwsec+uma@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/commerce/histograms.xml b/tools/metrics/histograms/metadata/commerce/histograms.xml
index 01afe2e0..93bb6b7 100644
--- a/tools/metrics/histograms/metadata/commerce/histograms.xml
+++ b/tools/metrics/histograms/metadata/commerce/histograms.xml
@@ -34,7 +34,7 @@
 </variants>
 
 <histogram name="Commerce.Carts.ExtractionElapsedTime" units="ms"
-    expires_after="2022-06-30">
+    expires_after="2022-09-11">
   <owner>wychen@chromium.org</owner>
   <owner>yuezhanggg@chromium.org</owner>
   <owner>chrome-shopping@google.com</owner>
@@ -47,7 +47,7 @@
 </histogram>
 
 <histogram name="Commerce.Carts.ExtractionExecutionTime" units="ms"
-    expires_after="2022-06-30">
+    expires_after="2022-09-11">
   <owner>wychen@chromium.org</owner>
   <owner>yuezhanggg@chromium.org</owner>
   <owner>chrome-shopping@google.com</owner>
@@ -60,7 +60,7 @@
 </histogram>
 
 <histogram name="Commerce.Carts.ExtractionLongestTaskTime" units="ms"
-    expires_after="2022-06-30">
+    expires_after="2022-09-11">
   <owner>wychen@chromium.org</owner>
   <owner>yuezhanggg@chromium.org</owner>
   <owner>chrome-shopping@google.com</owner>
@@ -72,7 +72,7 @@
 </histogram>
 
 <histogram name="Commerce.Carts.ExtractionTimedOut" enum="BooleanTimedOut"
-    expires_after="2022-06-30">
+    expires_after="2022-09-11">
   <owner>wychen@chromium.org</owner>
   <owner>yuezhanggg@chromium.org</owner>
   <owner>chrome-shopping@google.com</owner>
@@ -83,7 +83,7 @@
 </histogram>
 
 <histogram name="Commerce.Carts.ExtractionTotalTasksTime" units="ms"
-    expires_after="2022-06-30">
+    expires_after="2022-09-11">
   <owner>wychen@chromium.org</owner>
   <owner>yuezhanggg@chromium.org</owner>
   <owner>chrome-shopping@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/compositing/histograms.xml b/tools/metrics/histograms/metadata/compositing/histograms.xml
index 46d779f..792e33d269 100644
--- a/tools/metrics/histograms/metadata/compositing/histograms.xml
+++ b/tools/metrics/histograms/metadata/compositing/histograms.xml
@@ -40,7 +40,7 @@
 </histogram>
 
 <histogram name="Compositing.Browser.LayersUpdateTime" units="microseconds"
-    expires_after="2022-07-11">
+    expires_after="2022-09-11">
   <owner>schenney@chromium.org</owner>
   <owner>animations-dev@chromium.org</owner>
   <summary>
@@ -56,7 +56,7 @@
 </histogram>
 
 <histogram name="Compositing.Browser.LayerTreeImpl.CalculateDrawPropertiesUs"
-    units="microseconds" expires_after="2022-07-11">
+    units="microseconds" expires_after="2022-09-11">
   <owner>schenney@chromium.org</owner>
   <owner>paint-dev@chromium.org</owner>
   <summary>
@@ -151,7 +151,7 @@
 </histogram>
 
 <histogram name="Compositing.DirectRenderer.OverlayProcessingUs"
-    units="microseconds" expires_after="2022-07-03">
+    units="microseconds" expires_after="2022-09-11">
   <owner>khaslett@chromium.org</owner>
   <owner>kylechar@chromium.org</owner>
   <summary>
@@ -247,7 +247,7 @@
 </histogram>
 
 <histogram name="Compositing.Display.AdpfHintUs" units="microseconds"
-    expires_after="2022-06-30">
+    expires_after="2022-09-11">
   <owner>boliu@chromium.org</owner>
   <owner>vasilyt@chromium.org</owner>
   <summary>
@@ -479,7 +479,7 @@
 </histogram>
 
 <histogram name="Compositing.Renderer.CALayerResult" enum="CALayerResult"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>ccameron@chromium.org</owner>
   <owner>graphics-dev@chromium.org</owner>
   <summary>
@@ -704,7 +704,7 @@
 </histogram>
 
 <histogram name="Compositing.SurfaceAggregator.CopiedSurfaceCount"
-    units="surfaces" expires_after="2022-07-03">
+    units="surfaces" expires_after="2022-09-11">
   <owner>kylechar@chromium.org</owner>
   <owner>jonross@chromium.org</owner>
   <summary>
@@ -1068,7 +1068,7 @@
   </summary>
 </histogram>
 
-<histogram name="Graphics.Smoothness.Jank" units="%" expires_after="2022-08-28">
+<histogram name="Graphics.Smoothness.Jank" units="%" expires_after="2022-09-11">
   <owner>sadrul@chromium.org</owner>
   <owner>mjzhang@chromium.org</owner>
   <owner>graphics-dev@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/content/histograms.xml b/tools/metrics/histograms/metadata/content/histograms.xml
index 746fa34..f2578b72 100644
--- a/tools/metrics/histograms/metadata/content/histograms.xml
+++ b/tools/metrics/histograms/metadata/content/histograms.xml
@@ -508,7 +508,7 @@
 </histogram>
 
 <histogram name="ContentSettings.Popups" enum="ContentSettingPopupAction"
-    expires_after="2022-07-11">
+    expires_after="2022-09-11">
   <owner>charleszhao@chromium.org</owner>
   <owner>lazzzis@google.com</owner>
   <owner>src/components/blocked_content/OWNERS</owner>
diff --git a/tools/metrics/histograms/metadata/content_creation/histograms.xml b/tools/metrics/histograms/metadata/content_creation/histograms.xml
index 75a3c78..8ff65fc 100644
--- a/tools/metrics/histograms/metadata/content_creation/histograms.xml
+++ b/tools/metrics/histograms/metadata/content_creation/histograms.xml
@@ -350,7 +350,7 @@
 </histogram>
 
 <histogram name="SharedHighlights.LinkGenerated.Error.Iterations"
-    units="iterations" expires_after="2022-07-03">
+    units="iterations" expires_after="2022-09-11">
   <owner>gayane@chromium.org</owner>
   <owner>chrome-shared-highlighting@google.com</owner>
   <summary>
@@ -481,7 +481,7 @@
 </histogram>
 
 <histogram name="TextFragmentAnchor.ElementIdFragmentFound" enum="Boolean"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>bokan@chromium.org</owner>
   <owner>chrome-shared-highlighting@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/cookie/histograms.xml b/tools/metrics/histograms/metadata/cookie/histograms.xml
index 3eb26d2..4312d4fb 100644
--- a/tools/metrics/histograms/metadata/cookie/histograms.xml
+++ b/tools/metrics/histograms/metadata/cookie/histograms.xml
@@ -53,7 +53,7 @@
 </histogram>
 
 <histogram name="Cookie.CookiePrefix" enum="CookiePrefix"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>estark@chromium.org</owner>
   <summary>
     Number of times a cookie was set with a name prefixed by
@@ -63,7 +63,7 @@
 </histogram>
 
 <histogram name="Cookie.CookieSchemeRequestScheme" enum="CookieRequestScheme"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>bingler@chromium.org</owner>
   <owner>miketaylr@chromium.org</owner>
   <summary>
@@ -74,7 +74,7 @@
 </histogram>
 
 <histogram name="Cookie.CookieSourceScheme" enum="CookieSourceScheme"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>estark@chromium.org</owner>
   <summary>
     For each cookie added to the store, record whether its source URL has a
@@ -547,7 +547,7 @@
 </histogram>
 
 <histogram name="Cookie.Port.OmniboxURLNavigation.Localhost"
-    enum="InterestingCookiePorts" expires_after="2022-07-03">
+    enum="InterestingCookiePorts" expires_after="2022-09-11">
   <owner>bingler@chromium.org</owner>
   <owner>miketaylr@chromium.org</owner>
   <summary>
@@ -568,7 +568,7 @@
 </histogram>
 
 <histogram name="Cookie.Port.OmniboxURLNavigation.RemoteHost"
-    enum="InterestingCookiePorts" expires_after="2022-07-03">
+    enum="InterestingCookiePorts" expires_after="2022-09-11">
   <owner>bingler@chromium.org</owner>
   <owner>miketaylr@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/cros/histograms.xml b/tools/metrics/histograms/metadata/cros/histograms.xml
index b13fa04..39996c9 100644
--- a/tools/metrics/histograms/metadata/cros/histograms.xml
+++ b/tools/metrics/histograms/metadata/cros/histograms.xml
@@ -105,7 +105,7 @@
 </histogram>
 
 <histogram name="CrosDisksClient.FormatTime" units="ms"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>austinct@chromium.org</owner>
   <owner>src/ui/file_manager/OWNERS</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/crostini/histograms.xml b/tools/metrics/histograms/metadata/crostini/histograms.xml
index 8d8f955..45bceb3 100644
--- a/tools/metrics/histograms/metadata/crostini/histograms.xml
+++ b/tools/metrics/histograms/metadata/crostini/histograms.xml
@@ -46,7 +46,7 @@
 </histogram>
 
 <histogram name="Crostini.AppLaunchResult" enum="CrostiniResult"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>clumptini@google.com</owner>
   <owner>laurentt@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/custom_tabs/histograms.xml b/tools/metrics/histograms/metadata/custom_tabs/histograms.xml
index 3cca5ca6..32f6a1d 100644
--- a/tools/metrics/histograms/metadata/custom_tabs/histograms.xml
+++ b/tools/metrics/histograms/metadata/custom_tabs/histograms.xml
@@ -388,7 +388,7 @@
 </histogram>
 
 <histogram name="TrustedWebActivity.LocationDelegationEnrolled" enum="Boolean"
-    expires_after="M104">
+    expires_after="2022-09-11">
   <owner>eirage@chromium.org</owner>
   <owner>peconn@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/dev/histograms.xml b/tools/metrics/histograms/metadata/dev/histograms.xml
index 9f57b01..35b46635 100644
--- a/tools/metrics/histograms/metadata/dev/histograms.xml
+++ b/tools/metrics/histograms/metadata/dev/histograms.xml
@@ -276,7 +276,7 @@
 </histogram>
 
 <histogram name="DevTools.LinearMemoryInspector.RevealedFrom"
-    enum="DevToolsLinearMemoryInspectorRevealedFrom" expires_after="2022-07-01">
+    enum="DevToolsLinearMemoryInspectorRevealedFrom" expires_after="2022-09-11">
   <owner>yangguo@chromium.org</owner>
   <owner>kimanh@chromium.org</owner>
   <summary>
@@ -285,7 +285,7 @@
 </histogram>
 
 <histogram name="DevTools.LinearMemoryInspector.Target"
-    enum="DevToolsLinearMemoryInspectorTarget" expires_after="2022-07-01">
+    enum="DevToolsLinearMemoryInspectorTarget" expires_after="2022-09-11">
   <owner>yangguo@chromium.org</owner>
   <owner>kimanh@chromium.org</owner>
   <summary>
@@ -363,7 +363,7 @@
 </histogram>
 
 <histogram name="DevTools.SidebarPaneShown" enum="DevToolsSidebarPane"
-    expires_after="2022-07-01">
+    expires_after="2022-09-11">
   <owner>changhaohan@chromium.org</owner>
   <owner>yangguo@chromium.org</owner>
   <owner>bmeurer@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/disk/histograms.xml b/tools/metrics/histograms/metadata/disk/histograms.xml
index 4ba617d..d90da14 100644
--- a/tools/metrics/histograms/metadata/disk/histograms.xml
+++ b/tools/metrics/histograms/metadata/disk/histograms.xml
@@ -125,7 +125,7 @@
 </histogram>
 
 <histogram name="DiskCache.0.Error" enum="DiskCacheError"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>wfh@chromium.org</owner>
   <owner>src/net/disk_cache/OWNERS</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/download/histograms.xml b/tools/metrics/histograms/metadata/download/histograms.xml
index fb06b9f..1d754de 100644
--- a/tools/metrics/histograms/metadata/download/histograms.xml
+++ b/tools/metrics/histograms/metadata/download/histograms.xml
@@ -48,7 +48,7 @@
 </histogram>
 
 <histogram name="Download.BandwidthOverallBytesPerSecond2" units="bytes/second"
-    expires_after="2022-07-11">
+    expires_after="2022-09-11">
   <owner>qinmin@chromium.org</owner>
   <owner>xingliu@chromium.org</owner>
   <summary>
@@ -132,7 +132,7 @@
 </histogram>
 
 <histogram name="Download.DangerousFile.DownloadValidatedByType"
-    enum="SBClientDownloadExtensions" expires_after="2022-07-03">
+    enum="SBClientDownloadExtensions" expires_after="2022-09-11">
   <owner>drubery@chromium.org</owner>
   <owner>chrome-safebrowsing-alerts@chromium.org</owner>
   <summary>
@@ -535,7 +535,7 @@
 </histogram>
 
 <histogram base="true" name="Download.NetworkConnectionType.Complete"
-    enum="NetworkConnectionType" expires_after="2022-07-03">
+    enum="NetworkConnectionType" expires_after="2022-09-11">
 <!-- Name completed by histogram_suffixes name="DownloadSource" -->
 
   <owner>dtrainor@chromium.org</owner>
@@ -544,7 +544,7 @@
 </histogram>
 
 <histogram base="true" name="Download.NetworkConnectionType.StartNew"
-    enum="NetworkConnectionType" expires_after="2022-07-03">
+    enum="NetworkConnectionType" expires_after="2022-09-11">
 <!-- Name completed by histogram_suffixes name="DownloadSource" -->
 
   <owner>dtrainor@chromium.org</owner>
@@ -1121,7 +1121,7 @@
 </histogram>
 
 <histogram name="Download.ShowedDownloadWarning" enum="DownloadItem.DangerType"
-    expires_after="2022-07-11">
+    expires_after="2022-09-11">
   <owner>drubery@chromium.org</owner>
   <owner>chrome-safebrowsing-alerts@chromium.org</owner>
   <summary>
@@ -1172,7 +1172,7 @@
 </histogram>
 
 <histogram name="Download.UserValidatedDangerousDownload"
-    enum="DownloadItem.DangerType" expires_after="2022-07-11">
+    enum="DownloadItem.DangerType" expires_after="2022-09-11">
   <owner>drubery@chromium.org</owner>
   <owner>chrome-safebrowsing-team@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/enterprise/histograms.xml b/tools/metrics/histograms/metadata/enterprise/histograms.xml
index 6a61bdf..fa993c2 100644
--- a/tools/metrics/histograms/metadata/enterprise/histograms.xml
+++ b/tools/metrics/histograms/metadata/enterprise/histograms.xml
@@ -1870,7 +1870,7 @@
 </histogram>
 
 <histogram name="Enterprise.SystemLogUploadResult"
-    enum="EnterpriseSystemLogUploadResult" expires_after="2022-07-03">
+    enum="EnterpriseSystemLogUploadResult" expires_after="2022-09-11">
   <owner>bmalcolm@chromium.org</owner>
   <owner>cros-client-wa@google.com</owner>
   <summary>Result of a single attempt to upload system logs.</summary>
diff --git a/tools/metrics/histograms/metadata/event/histograms.xml b/tools/metrics/histograms/metadata/event/histograms.xml
index 481693c..0daa6d3 100644
--- a/tools/metrics/histograms/metadata/event/histograms.xml
+++ b/tools/metrics/histograms/metadata/event/histograms.xml
@@ -847,7 +847,7 @@
 </histogram>
 
 <histogram name="Event.Latency.ScrollBegin.Wheel.TimeToScrollUpdateSwapBegin4"
-    units="microseconds" expires_after="2022-07-11">
+    units="microseconds" expires_after="2022-09-11">
   <owner>flackr@chromium.org</owner>
   <summary>
     Time between initial creation of a wheel event and the start of the frame
@@ -1610,7 +1610,7 @@
 </histogram>
 
 <histogram name="Event.Latency.ScrollUpdate.Wheel.TimeToScrollUpdateSwapBegin4"
-    units="microseconds" expires_after="2022-07-11">
+    units="microseconds" expires_after="2022-09-11">
   <owner>flackr@chromium.org</owner>
   <summary>
     Time between initial creation of a wheel event and start of the frame swap
diff --git a/tools/metrics/histograms/metadata/extensions/histograms.xml b/tools/metrics/histograms/metadata/extensions/histograms.xml
index cf341f0..153745e 100644
--- a/tools/metrics/histograms/metadata/extensions/histograms.xml
+++ b/tools/metrics/histograms/metadata/extensions/histograms.xml
@@ -153,7 +153,7 @@
 </histogram>
 
 <histogram name="Extensions.AppLaunch" enum="AppLaunch"
-    expires_after="2022-06-30">
+    expires_after="2022-09-11">
   <owner>benwells@chromium.org</owner>
   <owner>dominickn@chromium.org</owner>
   <owner>tapted@chromium.org</owner>
@@ -203,7 +203,7 @@
 </histogram>
 
 <histogram name="Extensions.AppTabLaunchType" enum="ExtensionLaunchType"
-    expires_after="2022-06-30">
+    expires_after="2022-09-11">
   <owner>benwells@chromium.org</owner>
   <owner>tapted@chromium.org</owner>
   <summary>
@@ -579,7 +579,7 @@
 </histogram>
 
 <histogram name="Extensions.Debugger.UserIsInDeveloperMode"
-    enum="InDeveloperMode" expires_after="2022-07-11">
+    enum="InDeveloperMode" expires_after="2022-09-11">
   <owner>ghazale@chromium.org</owner>
   <owner>extensions-core@chromium.org</owner>
   <summary>
@@ -838,7 +838,7 @@
 </histogram>
 
 <histogram name="Extensions.DevTools.UserIsInDeveloperMode"
-    enum="InDeveloperMode" expires_after="2022-07-11">
+    enum="InDeveloperMode" expires_after="2022-09-11">
   <owner>ghazale@chromium.org</owner>
   <owner>extensions-core@chromium.org</owner>
   <summary>
@@ -1205,7 +1205,7 @@
 </histogram>
 
 <histogram name="Extensions.ExtensionDisabledRemotely2"
-    enum="ExtensionUpdateCheckDataKey" expires_after="2022-07-11">
+    enum="ExtensionUpdateCheckDataKey" expires_after="2022-09-11">
   <owner>drubery@chromium.org</owner>
   <owner>chrome-safebrowsing-alerts@google.com</owner>
   <summary>
@@ -2722,7 +2722,7 @@
 </histogram>
 
 <histogram name="Extensions.NewTabPageOverrides" units="units"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>rdevlin.cronin@chromium.org</owner>
   <owner>kelvinjiang@chromium.org</owner>
   <summary>
@@ -3112,7 +3112,7 @@
 </histogram>
 
 <histogram name="Extensions.SyncBlockedByDefaultWebAppMigration"
-    units="Boolean" expires_after="2022-06-30">
+    units="Boolean" expires_after="2022-09-11">
   <owner>alancutter@chromium.org</owner>
   <owner>extensions-core@chromium.org</owner>
   <summary>
@@ -3123,7 +3123,7 @@
 </histogram>
 
 <histogram name="Extensions.SyncGetMessageBundle" units="ms"
-    expires_after="2022-06-30">
+    expires_after="2022-09-11">
   <owner>rdevlin.cronin@chromium.org</owner>
   <owner>extensions-core@chromium.org</owner>
   <summary>
@@ -3144,7 +3144,7 @@
 </histogram>
 
 <histogram name="Extensions.Toolbar.InvocationSource"
-    enum="ExtensionActionInvocationSource" expires_after="2022-07-03">
+    enum="ExtensionActionInvocationSource" expires_after="2022-09-11">
   <owner>rdevlin.cronin@chromium.org</owner>
   <owner>extensions-core@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/feature_engagement/histograms.xml b/tools/metrics/histograms/metadata/feature_engagement/histograms.xml
index af80158..feb3339 100644
--- a/tools/metrics/histograms/metadata/feature_engagement/histograms.xml
+++ b/tools/metrics/histograms/metadata/feature_engagement/histograms.xml
@@ -231,7 +231,7 @@
 </variants>
 
 <histogram name="InProductHelp.Config.ParsingEvent" enum="ConfigParsingEvent"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>nyquist@chromium.org</owner>
   <owner>xingliu@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/geolocation/histograms.xml b/tools/metrics/histograms/metadata/geolocation/histograms.xml
index 9f2ccc74a..4883f41 100644
--- a/tools/metrics/histograms/metadata/geolocation/histograms.xml
+++ b/tools/metrics/histograms/metadata/geolocation/histograms.xml
@@ -87,7 +87,7 @@
 </histogram>
 
 <histogram name="Geolocation.LocationUpdate.ErrorCode"
-    enum="GeopositionErrorCode" expires_after="2022-07-11">
+    enum="GeopositionErrorCode" expires_after="2022-09-11">
   <owner>mattreynolds@chromium.org</owner>
   <owner>device-dev@chromium.org</owner>
   <summary>Error code for the geoposition sent to the renderers.</summary>
diff --git a/tools/metrics/histograms/metadata/gpu/histograms.xml b/tools/metrics/histograms/metadata/gpu/histograms.xml
index 1de2e7c..cf7472b 100644
--- a/tools/metrics/histograms/metadata/gpu/histograms.xml
+++ b/tools/metrics/histograms/metadata/gpu/histograms.xml
@@ -370,7 +370,7 @@
 </histogram>
 
 <histogram name="GPU.ANGLE.SupportsDXGI1_2" enum="BooleanSupported"
-    expires_after="2022-06-01">
+    expires_after="2022-09-11">
   <owner>jonahr@google.com</owner>
   <owner>angle-team@google.com</owner>
   <summary>
@@ -1728,7 +1728,7 @@
 </histogram>
 
 <histogram name="Viz.DisplayCompositor.OverlaySwitchInterval" units="ms"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>petermcneeley@chromium.org</owner>
   <owner>dcastagna@chromium.org</owner>
   <summary>
@@ -1855,7 +1855,7 @@
 </histogram>
 
 <histogram name="Viz.FrameSinkVideoCapturer.RGBA.CaptureDuration" units="ms"
-    expires_after="2022-05-01">
+    expires_after="2022-09-11">
   <owner>samans@chromium.org</owner>
   <owner>sadrul@chromium.org</owner>
   <owner>viz-team-wat@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/history/histograms.xml b/tools/metrics/histograms/metadata/history/histograms.xml
index d2f3312..fc4053b 100644
--- a/tools/metrics/histograms/metadata/history/histograms.xml
+++ b/tools/metrics/histograms/metadata/history/histograms.xml
@@ -540,7 +540,7 @@
 </histogram>
 
 <histogram name="History.Clusters.Backend.KeywordCache.AllKeywordPhraseCount"
-    units="count" expires_after="2022-07-11">
+    units="count" expires_after="2022-09-11">
   <owner>manukh@chromium.org</owner>
   <owner>chrome-journeys@google.com</owner>
   <summary>
@@ -556,7 +556,7 @@
 </histogram>
 
 <histogram name="History.Clusters.Backend.KeywordCache.AllKeywordsCount"
-    units="count" expires_after="2022-07-11">
+    units="count" expires_after="2022-09-11">
   <owner>manukh@chromium.org</owner>
   <owner>chrome-journeys@google.com</owner>
   <summary>
@@ -572,7 +572,7 @@
 </histogram>
 
 <histogram name="History.Clusters.Backend.KeywordCache.ShortKeywordPhraseCount"
-    units="count" expires_after="2022-07-11">
+    units="count" expires_after="2022-09-11">
   <owner>manukh@chromium.org</owner>
   <owner>chrome-journeys@google.com</owner>
   <summary>
@@ -587,7 +587,7 @@
 </histogram>
 
 <histogram name="History.Clusters.Backend.KeywordCache.ShortKeywordsCount"
-    units="count" expires_after="2022-07-11">
+    units="count" expires_after="2022-09-11">
   <owner>manukh@chromium.org</owner>
   <owner>chrome-journeys@google.com</owner>
   <summary>
@@ -1107,7 +1107,7 @@
 </histogram>
 
 <histogram name="History.HistoryPageView" enum="HistoryPageView"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>calamity@chromium.org</owner>
   <owner>src/chrome/browser/resources/history/OWNERS</owner>
   <summary>
@@ -1405,7 +1405,7 @@
 </histogram>
 
 <histogram name="History.WeeklyURLCount" units="URLs"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>mpearson@chromium.org</owner>
   <owner>sky@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/input/histograms.xml b/tools/metrics/histograms/metadata/input/histograms.xml
index 02047835..0827be86 100644
--- a/tools/metrics/histograms/metadata/input/histograms.xml
+++ b/tools/metrics/histograms/metadata/input/histograms.xml
@@ -983,7 +983,7 @@
 </histogram>
 
 <histogram name="InputMethod.VirtualKeyboard.InitLatency.KeyboardShownLongTail"
-    units="ms" expires_after="2022-07-03">
+    units="ms" expires_after="2022-09-11">
   <owner>jopalmer@chromium.org</owner>
   <owner>essential-inputs-team@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/language/histograms.xml b/tools/metrics/histograms/metadata/language/histograms.xml
index 1258373..f4b19a6 100644
--- a/tools/metrics/histograms/metadata/language/histograms.xml
+++ b/tools/metrics/histograms/metadata/language/histograms.xml
@@ -33,7 +33,7 @@
 </histogram>
 
 <histogram name="LanguageDetection.TFLiteModel.WasModelAvailableForDetection"
-    enum="BooleanAvailable" expires_after="2022-06-30">
+    enum="BooleanAvailable" expires_after="2022-09-11">
   <owner>mcrouse@chromium.org</owner>
   <owner>chrome-language@google.com</owner>
   <summary>
@@ -44,7 +44,7 @@
 </histogram>
 
 <histogram name="LanguageDetection.TFLiteModel.WasModelRequestDeferred"
-    enum="BooleanDeferred" expires_after="2022-06-30">
+    enum="BooleanDeferred" expires_after="2022-09-11">
   <owner>mcrouse@chromium.org</owner>
   <owner>chrome-language@google.com</owner>
   <summary>
@@ -79,7 +79,7 @@
 </histogram>
 
 <histogram name="LanguageSettings.AppLanguagePrompt.Action"
-    enum="LanguageSettingsAppLanguagePromptAction" expires_after="2022-06-30">
+    enum="LanguageSettingsAppLanguagePromptAction" expires_after="2022-09-11">
   <owner>perrier@chromium.org</owner>
   <owner>chrome-language@google.com</owner>
   <summary>
@@ -91,7 +91,7 @@
 </histogram>
 
 <histogram name="LanguageSettings.AppLanguagePrompt.IsOnline"
-    enum="BooleanYesNo" expires_after="2022-06-30">
+    enum="BooleanYesNo" expires_after="2022-09-11">
   <owner>perrier@chromium.org</owner>
   <owner>chrome-language@google.com</owner>
   <summary>
@@ -105,7 +105,7 @@
 </histogram>
 
 <histogram name="LanguageSettings.AppLanguagePrompt.IsTopLanguageSelected"
-    enum="BooleanYesNo" expires_after="2022-06-30">
+    enum="BooleanYesNo" expires_after="2022-09-11">
   <owner>perrier@chromium.org</owner>
   <owner>chrome-language@google.com</owner>
   <summary>
@@ -153,7 +153,7 @@
 </histogram>
 
 <histogram name="LanguageSettings.PageImpression"
-    enum="LanguageSettingsPageType" expires_after="2022-06-30">
+    enum="LanguageSettingsPageType" expires_after="2022-09-11">
   <owner>googleo@chromium.org</owner>
   <owner>chrome-language@google.com</owner>
   <summary>
@@ -164,7 +164,7 @@
 </histogram>
 
 <histogram name="LanguageSettings.SplitInstallFinalStatus"
-    enum="LanguageSettingsSplitInstallStatus" expires_after="2022-06-30">
+    enum="LanguageSettingsSplitInstallStatus" expires_after="2022-09-11">
   <owner>perrier@chromium.org</owner>
   <owner>chrome-language@google.com</owner>
   <summary>
@@ -176,7 +176,7 @@
 </histogram>
 
 <histogram name="LanguageUsage.AcceptLanguage" enum="LanguageName"
-    expires_after="2022-06-30">
+    expires_after="2022-09-11">
   <owner>perrier@chromium.org</owner>
   <owner>chrome-language@google.com</owner>
   <summary>
@@ -186,7 +186,7 @@
 </histogram>
 
 <histogram name="LanguageUsage.AcceptLanguage.Count" units="units"
-    expires_after="2022-06-30">
+    expires_after="2022-09-11">
   <owner>dvallet@chromium.org</owner>
   <owner>chrome-language@google.com</owner>
   <summary>
@@ -196,7 +196,7 @@
 </histogram>
 
 <histogram name="LanguageUsage.ApplicationLanguage" enum="LanguageName"
-    expires_after="2022-06-30">
+    expires_after="2022-09-11">
   <owner>perrier@chromium.org</owner>
   <owner>chrome-language@google.com</owner>
   <summary>
@@ -206,7 +206,7 @@
 </histogram>
 
 <histogram name="LanguageUsage.MostFrequentPageLanguages" enum="LanguageName"
-    expires_after="2022-06-30">
+    expires_after="2022-09-11">
   <owner>perrier@chromium.org</owner>
   <owner>chrome-language@google.com</owner>
   <summary>
@@ -224,7 +224,7 @@
 </histogram>
 
 <histogram name="LanguageUsage.UI.Android.Availability"
-    enum="LanguageUsage.UI.Android.Availability" expires_after="2022-06-30">
+    enum="LanguageUsage.UI.Android.Availability" expires_after="2022-09-11">
   <owner>perrier@chromium.org</owner>
   <owner>chrome-language@google.com</owner>
   <summary>
@@ -252,7 +252,7 @@
 </histogram>
 
 <histogram name="LanguageUsage.UI.Android.Correctness.NoOverride"
-    enum="LanguageUsage.UI.Android.Correctness" expires_after="2022-06-30">
+    enum="LanguageUsage.UI.Android.Correctness" expires_after="2022-09-11">
   <owner>perrier@chromium.org</owner>
   <owner>chrome-language@google.com</owner>
   <summary>
@@ -266,7 +266,7 @@
 </histogram>
 
 <histogram name="LanguageUsage.UI.Android.Correctness.Override"
-    enum="LanguageUsage.UI.Android.Correctness" expires_after="2022-06-30">
+    enum="LanguageUsage.UI.Android.Correctness" expires_after="2022-09-11">
   <owner>perrier@chromium.org</owner>
   <owner>chrome-language@google.com</owner>
   <summary>
@@ -296,7 +296,7 @@
 </histogram>
 
 <histogram name="LanguageUsage.UI.Android.OverrideLanguage"
-    enum="LocaleCodeISO639" expires_after="2022-06-30">
+    enum="LocaleCodeISO639" expires_after="2022-09-11">
   <owner>perrier@chromium.org</owner>
   <owner>chrome-language@google.com</owner>
   <summary>
@@ -310,7 +310,7 @@
 
 <histogram name="LanguageUsage.UI.Android.OverrideLanguage.IsSystemLanguage"
     enum="LanguageUsage.UI.Android.OverrideLanguage.IsSystemLanguage"
-    expires_after="2022-06-30">
+    expires_after="2022-09-11">
   <owner>perrier@chromium.org</owner>
   <owner>chrome-language@google.com</owner>
   <summary>
@@ -325,7 +325,7 @@
 </histogram>
 
 <histogram name="LanguageUsage.ULP.Initiation.AcceptLanguagesULPOverlap"
-    units="%" expires_after="2022-06-30">
+    units="%" expires_after="2022-09-11">
   <owner>jds@google.com</owner>
   <owner>chrome-language@google.com</owner>
   <summary>
@@ -339,7 +339,7 @@
 </histogram>
 
 <histogram name="LanguageUsage.ULP.Initiation.ChromeUILanguageInULP"
-    enum="ULPLanguageStatus" expires_after="2022-06-30">
+    enum="ULPLanguageStatus" expires_after="2022-09-11">
   <owner>jds@google.com</owner>
   <owner>chrome-language@google.com</owner>
   <summary>
@@ -352,7 +352,7 @@
 </histogram>
 
 <histogram name="LanguageUsage.ULP.Initiation.Count" units="count"
-    expires_after="2022-06-30">
+    expires_after="2022-09-11">
   <owner>jds@google.com</owner>
   <owner>chrome-language@google.com</owner>
   <summary>
@@ -379,7 +379,7 @@
 </histogram>
 
 <histogram name="LanguageUsage.ULP.Initiation.TopAcceptLanguageInULP"
-    enum="ULPLanguageStatus" expires_after="2022-06-30">
+    enum="ULPLanguageStatus" expires_after="2022-09-11">
   <owner>jds@google.com</owner>
   <owner>chrome-language@google.com</owner>
   <summary>
@@ -392,7 +392,7 @@
 </histogram>
 
 <histogram name="LanguageUsage.ULP.Initiation.TranslateTargetInULP"
-    enum="ULPLanguageStatus" expires_after="2022-06-30">
+    enum="ULPLanguageStatus" expires_after="2022-09-11">
   <owner>jds@google.com</owner>
   <owner>chrome-language@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/login/histograms.xml b/tools/metrics/histograms/metadata/login/histograms.xml
index f210439e..6d396c12 100644
--- a/tools/metrics/histograms/metadata/login/histograms.xml
+++ b/tools/metrics/histograms/metadata/login/histograms.xml
@@ -273,7 +273,7 @@
 </histogram>
 
 <histogram name="Login.SessionExitType" enum="LoginSessionExitType"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>iby@chromium.org</owner>
   <summary>
     Tracks whether a ChromeOS user was logged out because Chrome repeatedly
diff --git a/tools/metrics/histograms/metadata/media/histograms.xml b/tools/metrics/histograms/metadata/media/histograms.xml
index 06a7763..e3b17b9 100644
--- a/tools/metrics/histograms/metadata/media/histograms.xml
+++ b/tools/metrics/histograms/metadata/media/histograms.xml
@@ -83,7 +83,7 @@
 </histogram>
 
 <histogram name="Media.Android.BecomingNoisy" enum="Boolean"
-    expires_after="2022-07-01">
+    expires_after="2022-09-11">
   <owner>liberato@chromium.org</owner>
   <owner>media-dev@chromium.org</owner>
   <summary>
@@ -113,7 +113,7 @@
 </histogram>
 
 <histogram name="Media.Android.MediaPlayerWatchTime"
-    enum="MediaPlayerWatchTimeType" expires_after="2022-07-03">
+    enum="MediaPlayerWatchTimeType" expires_after="2022-09-11">
   <owner>sandersd@chromium.org</owner>
   <owner>media-dev@chromium.org</owner>
   <summary>
@@ -827,7 +827,7 @@
 </histogram>
 
 <histogram name="Media.Audio.Render.FramesRequested" units="frames"
-    expires_after="2022-07-11">
+    expires_after="2022-09-11">
   <owner>guidou@chromium.org</owner>
   <owner>olka@chromium.org</owner>
   <summary>
@@ -842,7 +842,7 @@
 </histogram>
 
 <histogram name="Media.Audio.Render.GetSourceDataTime.WebRTC"
-    units="microseconds" expires_after="2022-07-03">
+    units="microseconds" expires_after="2022-09-11">
   <owner>guidou@chromium.org</owner>
   <owner>olka@chromium.org</owner>
   <summary>
@@ -969,7 +969,7 @@
 
 <histogram
     name="Media.Audio.Render.SinkCache.GetOutputDeviceInfoCacheUtilization"
-    enum="GetOutputDeviceInfoCacheHit" expires_after="2022-07-03">
+    enum="GetOutputDeviceInfoCacheHit" expires_after="2022-09-11">
   <owner>guidou@chromium.org</owner>
   <owner>olka@chromium.org</owner>
   <summary>
@@ -1774,7 +1774,7 @@
 </histogram>
 
 <histogram name="Media.D3D11.DecoderLifetimeProgression"
-    enum="D3D11LifetimeProgression" expires_after="2022-07-03">
+    enum="D3D11LifetimeProgression" expires_after="2022-09-11">
   <owner>liberato@chromium.org</owner>
   <owner>tmathmeyer@chromium.org</owner>
   <summary>
@@ -1936,7 +1936,7 @@
 </histogram>
 
 <histogram name="Media.EME.CdmFileIO.FileSizeKBOnFirstRead" units="KB"
-    expires_after="2022-07-11">
+    expires_after="2022-09-11">
   <owner>xhwang@chromium.org</owner>
   <owner>media-dev@chromium.org</owner>
   <summary>
@@ -1985,13 +1985,13 @@
 </histogram>
 
 <histogram name="Media.EME.CdmLoadResult" enum="CdmLoadResult"
-    expires_after="2022-07-11">
+    expires_after="2022-09-11">
   <owner>xhwang@chromium.org</owner>
   <owner>media-dev@chromium.org</owner>
   <summary>The result from an attempt to load a library CDM.</summary>
 </histogram>
 
-<histogram name="Media.EME.CdmLoadTime" units="ms" expires_after="2022-07-11">
+<histogram name="Media.EME.CdmLoadTime" units="ms" expires_after="2022-09-11">
   <owner>xhwang@chromium.org</owner>
   <owner>media-dev@chromium.org</owner>
   <summary>The time spent to load a library CDM.</summary>
@@ -2050,7 +2050,7 @@
 </histogram>
 
 <histogram name="Media.EME.EncryptionScheme.Initial.Video"
-    enum="MediaEncryptionScheme" expires_after="2022-07-11">
+    enum="MediaEncryptionScheme" expires_after="2022-09-11">
   <owner>jrummell@chromium.org</owner>
   <owner>media-dev@chromium.org</owner>
   <summary>
@@ -2146,7 +2146,7 @@
 </histogram>
 
 <histogram name="Media.EME.MediaFoundationService.IsKeySystemSupported"
-    units="ms" expires_after="2022-05-07">
+    units="ms" expires_after="2022-09-11">
   <owner>xhwang@chromium.org</owner>
   <owner>media-dev@chromium.org</owner>
   <summary>
@@ -2182,7 +2182,7 @@
 </histogram>
 
 <histogram name="Media.EME.OutputProtection" enum="MediaOutputProtectionStatus"
-    expires_after="2022-07-11">
+    expires_after="2022-09-11">
   <owner>xhwang@chromium.org</owner>
   <owner>media-dev@chromium.org</owner>
   <summary>
@@ -2224,7 +2224,7 @@
 </histogram>
 
 <histogram name="Media.EME.Widevine.VideoCapability.HasEmptyRobustness"
-    enum="BooleanEmpty" expires_after="2022-07-11">
+    enum="BooleanEmpty" expires_after="2022-09-11">
   <owner>xhwang@chromium.org</owner>
   <owner>media-dev@chromium.org</owner>
   <summary>
@@ -2520,7 +2520,7 @@
 </histogram>
 
 <histogram name="Media.GlobalMediaControls.UserActionFocus"
-    enum="BooleanFocused" expires_after="2022-07-03">
+    enum="BooleanFocused" expires_after="2022-09-11">
   <owner>steimel@chromium.org</owner>
   <owner>media-dev@chromium.org</owner>
   <summary>
@@ -2899,7 +2899,7 @@
 </histogram>
 
 <histogram name="Media.MeanTimeBetweenRebuffers" units="ms"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>dalecurtis@chromium.org</owner>
   <summary>
     The total watch time (see Media.WatchTime) of a given playback divided by
@@ -4230,7 +4230,7 @@
 </histogram>
 
 <histogram name="Media.VaapiVideoDecoder.VaapiWrapperCreationSuccess"
-    enum="BooleanSuccess" expires_after="2022-07-03">
+    enum="BooleanSuccess" expires_after="2022-09-11">
   <owner>mcasas@chromium.org</owner>
   <owner>chromeos-gfx-video@google.com</owner>
   <summary>
@@ -4279,7 +4279,7 @@
 </histogram>
 
 <histogram name="Media.Video.Autoplay" enum="AutoplaySource"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>dalecurtis@chromium.org</owner>
   <owner>media-dev@chromium.org</owner>
   <summary>
@@ -4436,7 +4436,7 @@
 </histogram>
 
 <histogram name="Media.VideoCapture.Device.SupportedPixelFormat"
-    enum="VideoPixelFormatUnion" expires_after="2022-07-03">
+    enum="VideoPixelFormatUnion" expires_after="2022-09-11">
   <owner>eshr@google.com</owner>
   <owner>handellm@google.com</owner>
   <summary>
@@ -4514,7 +4514,7 @@
 </histogram>
 
 <histogram name="Media.VideoCapture.Mac.Device.CapturedIOSurface"
-    enum="Boolean" expires_after="2022-07-11">
+    enum="Boolean" expires_after="2022-09-11">
   <owner>eshr@google.com</owner>
   <owner>handellm@google.com</owner>
   <summary>
@@ -4526,7 +4526,7 @@
 
 <histogram
     name="Media.VideoCapture.Mac.Device.CapturedWithRequestedPixelFormat"
-    enum="Boolean" expires_after="2022-07-03">
+    enum="Boolean" expires_after="2022-09-11">
   <owner>eshr@google.com</owner>
   <owner>handellm@google.com</owner>
   <summary>
@@ -4663,7 +4663,7 @@
 </histogram>
 
 <histogram name="Media.VideoCapture.Win.Device.CapturePixelFormat"
-    enum="VideoPixelFormatUnion" expires_after="2022-07-03">
+    enum="VideoPixelFormatUnion" expires_after="2022-09-11">
   <owner>ilnik@google.com</owner>
   <owner>video-cmi-mpp@google.com</owner>
   <summary>
@@ -4673,7 +4673,7 @@
 </histogram>
 
 <histogram name="Media.VideoCapture.Win.Device.InternalPixelFormat"
-    enum="VideoPixelFormatUnion" expires_after="2022-07-03">
+    enum="VideoPixelFormatUnion" expires_after="2022-09-11">
   <owner>ilnik@google.com</owner>
   <owner>video-cmi-mpp@google.com</owner>
   <summary>
@@ -4683,7 +4683,7 @@
 </histogram>
 
 <histogram name="Media.VideoCapture.Win.Device.RequestedPixelFormat"
-    enum="VideoPixelFormatUnion" expires_after="2022-07-03">
+    enum="VideoPixelFormatUnion" expires_after="2022-09-11">
   <owner>ilnik@google.com</owner>
   <owner>video-cmi-mpp@google.com</owner>
   <summary>
@@ -4994,7 +4994,7 @@
 </histogram>
 
 <histogram name="Media.VideoRenderer.LowDelay" enum="Boolean"
-    expires_after="2022-07-11">
+    expires_after="2022-09-11">
   <owner>xhwang@chromium.org</owner>
   <owner>media-dev@chromium.org</owner>
   <summary>
@@ -5035,7 +5035,7 @@
   </summary>
 </histogram>
 
-<histogram name="Media.WatchTime" units="ms" expires_after="2022-07-03">
+<histogram name="Media.WatchTime" units="ms" expires_after="2022-09-11">
   <owner>dalecurtis@chromium.org</owner>
   <summary>
     Watch time is defined as the amount of elapsed media time for audio+video
@@ -5321,7 +5321,7 @@
 </histogram>
 
 <histogram name="MediaRouter.Cast.Discovery.CachedSinksAvailableCount"
-    units="devices" expires_after="2022-07-03">
+    units="devices" expires_after="2022-09-11">
   <owner>btolsch@chromium.org</owner>
   <owner>openscreen-eng@google.com</owner>
   <summary>
@@ -5341,7 +5341,7 @@
 </histogram>
 
 <histogram name="MediaRouter.Cast.Discovery.KnownDevicesCount" units="devices"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>mfoltz@chromium.org</owner>
   <owner>openscreen-eng@google.com</owner>
   <summary>
@@ -5351,7 +5351,7 @@
 </histogram>
 
 <histogram name="MediaRouter.Cast.Discovery.SinkSource"
-    enum="MediaRouterCastSinkSource" expires_after="2022-07-03">
+    enum="MediaRouterCastSinkSource" expires_after="2022-09-11">
   <owner>btolsch@chromium.org</owner>
   <owner>openscreen-eng@google.com</owner>
   <summary>The source of discovery for a newly-created Cast sink.</summary>
@@ -5424,7 +5424,7 @@
 </histogram>
 
 <histogram name="MediaRouter.CastStreaming.Session.Length" units="ms"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>takumif@chromium.org</owner>
   <owner>openscreen-eng@google.com</owner>
   <summary>
@@ -5585,7 +5585,7 @@
 </histogram>
 
 <histogram name="MediaRouter.PresentationRequest.AvailabilityUrlType"
-    enum="PresentationUrlType" expires_after="2022-06-26">
+    enum="PresentationUrlType" expires_after="2022-09-11">
   <owner>takumif@chromium.org</owner>
   <owner>mfoltz@chromium.org</owner>
   <owner>openscreen-eng@google.com</owner>
@@ -5619,7 +5619,7 @@
 </histogram>
 
 <histogram name="MediaRouter.Provider.JoinRoute.Result"
-    enum="MediaRouteProviderResult" expires_after="2022-07-03">
+    enum="MediaRouteProviderResult" expires_after="2022-09-11">
 <!-- Name completed by histogram_suffixes name="MediaRouteProvider" -->
 
   <owner>takumif@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/memory/histograms.xml b/tools/metrics/histograms/metadata/memory/histograms.xml
index 6353f3df..5219ec8d 100644
--- a/tools/metrics/histograms/metadata/memory/histograms.xml
+++ b/tools/metrics/histograms/metadata/memory/histograms.xml
@@ -499,7 +499,7 @@
 </histogram>
 
 <histogram name="Memory.Discardable.LargeAllocationFromFreelist"
-    enum="BooleanLargeAllocationFromFreelist" expires_after="2022-07-11">
+    enum="BooleanLargeAllocationFromFreelist" expires_after="2022-09-11">
   <owner>thiabaud@google.com</owner>
   <owner>lizeb@chromium.org</owner>
   <summary>
@@ -529,7 +529,7 @@
 </histogram>
 
 <histogram name="Memory.Discardable.VirtualSize.Foreground" units="KiB"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>thiabaud@google.com</owner>
   <owner>lizeb@chromium.org</owner>
   <summary>
@@ -539,7 +539,7 @@
 </histogram>
 
 <histogram name="Memory.DiscardableAllocationSize" units="KB"
-    expires_after="2022-07-11">
+    expires_after="2022-09-11">
   <owner>reveman@chromium.org</owner>
   <owner>thiabaud@google.com</owner>
   <summary>
@@ -1559,7 +1559,7 @@
 
 <histogram
     name="Memory.NativeLibrary.MappedAndResidentMemoryFootprintCollectionStatus"
-    enum="MappedAndResidentPagesDumpState" expires_after="2022-06-26">
+    enum="MappedAndResidentPagesDumpState" expires_after="2022-09-11">
   <owner>lizeb@chromium.org</owner>
   <owner>pasko@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/mobile/histograms.xml b/tools/metrics/histograms/metadata/mobile/histograms.xml
index 87c84caf..85674b935 100644
--- a/tools/metrics/histograms/metadata/mobile/histograms.xml
+++ b/tools/metrics/histograms/metadata/mobile/histograms.xml
@@ -77,7 +77,7 @@
 </histogram>
 
 <histogram name="Mobile.CanonicalURLResult" enum="CanonicalURLResult"
-    expires_after="2022-07-11">
+    expires_after="2022-09-11">
   <owner>pkl@chromium.org</owner>
   <owner>sebsg@chromium.org</owner>
   <owner>tedchoc@chromium.org</owner>
@@ -855,7 +855,7 @@
 </histogram>
 
 <histogram name="MobileFre.Progress" enum="MobileFreProgress"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>bsazonov@chromium.org</owner>
   <owner>msarda@chromium.org</owner>
   <owner>droger@chromium.org</owner>
@@ -1135,7 +1135,7 @@
 </histogram>
 
 <histogram name="MobileStartup.IntentToCreationTime" units="ms"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>tedchoc@chromium.org</owner>
   <owner>twellington@chromium.org</owner>
   <owner>yfriedman@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/navigation/histograms.xml b/tools/metrics/histograms/metadata/navigation/histograms.xml
index 999112f..8b2984b 100644
--- a/tools/metrics/histograms/metadata/navigation/histograms.xml
+++ b/tools/metrics/histograms/metadata/navigation/histograms.xml
@@ -654,7 +654,7 @@
 </histogram>
 
 <histogram name="Navigation.EngagementTime.Ratio" units="%"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>estark@chromium.org</owner>
   <owner>security-enamel@chromium.org</owner>
   <summary>
@@ -959,7 +959,7 @@
 </histogram>
 
 <histogram name="Navigation.Prerender.ActivationCommitDeferTime" units="ms"
-    expires_after="2022-07-01">
+    expires_after="2022-09-11">
   <owner>sreejakshetty@chromium.org</owner>
   <owner>altimin@chromium.org</owner>
   <summary>
@@ -1072,7 +1072,7 @@
 </histogram>
 
 <histogram name="Navigation.RequiresDedicatedProcess.HTTPOrHTTPS"
-    enum="NavigationRequiresDedicatedProcess" expires_after="2022-05-01">
+    enum="NavigationRequiresDedicatedProcess" expires_after="2022-09-11">
   <owner>alexmos@chromium.org</owner>
   <owner>lukasza@chromium.org</owner>
   <summary>
@@ -1240,7 +1240,7 @@
 </histogram>
 
 <histogram name="NavigationSuggestion.Event2" enum="NavigationSuggestionEvent"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>meacer@chromium.org</owner>
   <owner>security-enamel@chromium.org</owner>
   <summary>
@@ -1258,7 +1258,7 @@
 
 <histogram
     name="NavigationSuggestion.GetDomainInfoDelayBeforeAllowingNavigation"
-    units="ms" expires_after="2022-07-03">
+    units="ms" expires_after="2022-09-11">
   <owner>meacer@chromium.org</owner>
   <owner>blundell@chromium.org</owner>
   <owner>security-enamel@chromium.org</owner>
@@ -1274,7 +1274,7 @@
 
 <histogram
     name="NavigationSuggestion.IsLookalikeUrlDelayBeforeAllowingNavigation"
-    units="ms" expires_after="2022-07-03">
+    units="ms" expires_after="2022-09-11">
   <owner>meacer@chromium.org</owner>
   <owner>blundell@chromium.org</owner>
   <owner>security-enamel@chromium.org</owner>
@@ -1290,7 +1290,7 @@
 
 <histogram
     name="NavigationSuggestion.PerformChecksDelayBeforeAllowingNavigation"
-    units="ms" expires_after="2022-07-03">
+    units="ms" expires_after="2022-09-11">
   <owner>meacer@chromium.org</owner>
   <owner>blundell@chromium.org</owner>
   <owner>security-enamel@chromium.org</owner>
@@ -1305,7 +1305,7 @@
 </histogram>
 
 <histogram name="NavigationSuggestion.UpdateEngagedSitesDeferTime" units="ms"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>meacer@chromium.org</owner>
   <owner>cduvall@chromium.org</owner>
   <owner>security-enamel@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/net/histograms.xml b/tools/metrics/histograms/metadata/net/histograms.xml
index fa776f3..18bb82b9 100644
--- a/tools/metrics/histograms/metadata/net/histograms.xml
+++ b/tools/metrics/histograms/metadata/net/histograms.xml
@@ -42,7 +42,7 @@
   <variant name="SubresourceRedirectThrottle"/>
 </variants>
 
-<histogram name="HttpCache.AccessToDone" units="ms" expires_after="M104">
+<histogram name="HttpCache.AccessToDone" units="ms" expires_after="2022-09-11">
   <owner>morlovich@chromium.org</owner>
   <owner>yhirano@chromium.org</owner>
   <summary>
@@ -51,7 +51,7 @@
   </summary>
 </histogram>
 
-<histogram name="HttpCache.BeforeSend" units="ms" expires_after="M104">
+<histogram name="HttpCache.BeforeSend" units="ms" expires_after="2022-09-11">
   <owner>morlovich@chromium.org</owner>
   <owner>yhirano@chromium.org</owner>
   <summary>
@@ -88,7 +88,7 @@
 </histogram>
 
 <histogram name="Net.AlternateProtocolUsage" enum="AlternateProtocolUsage"
-    expires_after="2022-07-11">
+    expires_after="2022-09-11">
   <owner>dschinazi@chromium.org</owner>
   <owner>src/net/OWNERS</owner>
   <summary>
@@ -98,7 +98,7 @@
 </histogram>
 
 <histogram name="Net.AlternateProtocolUsageGoogle"
-    enum="AlternateProtocolUsage" expires_after="2022-07-11">
+    enum="AlternateProtocolUsage" expires_after="2022-09-11">
   <owner>dschinazi@chromium.org</owner>
   <owner>src/net/OWNERS</owner>
   <summary>
@@ -228,7 +228,7 @@
 </histogram>
 
 <histogram name="Net.CertificateTransparency.SCT.VerificationTime"
-    units="microseconds" expires_after="2022-07-03">
+    units="microseconds" expires_after="2022-09-11">
   <owner>estark@chromium.org</owner>
   <owner>rsleevi@chromium.org</owner>
   <summary>
@@ -240,7 +240,7 @@
 </histogram>
 
 <histogram name="Net.CertificateTransparency.SCTOrigin" enum="SCTOrigin"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>estark@chromium.org</owner>
   <owner>trusty-transport@chromium.org</owner>
   <summary>
@@ -276,7 +276,7 @@
 </histogram>
 
 <histogram base="true" name="Net.CertVerifier.NameNormalizationPrivateRoots"
-    enum="NetCertificateNameNormalization" expires_after="2022-07-11">
+    enum="NetCertificateNameNormalization" expires_after="2022-09-11">
   <owner>mattm@chromium.org</owner>
   <owner>rsleevi@chromium.org</owner>
   <summary>
@@ -338,7 +338,7 @@
 </histogram>
 
 <histogram name="Net.CertVerifier_TrialComparisonResult"
-    enum="CertVerifierTrialComparisonResult" expires_after="2022-07-11">
+    enum="CertVerifierTrialComparisonResult" expires_after="2022-09-11">
   <owner>mattm@chromium.org</owner>
   <owner>rsleevi@chromium.org</owner>
   <summary>
@@ -350,7 +350,7 @@
 </histogram>
 
 <histogram name="Net.ConnectionInfo.MainFrame" enum="ConnectionInfo"
-    expires_after="2022-07-11">
+    expires_after="2022-09-11">
   <owner>dschinazi@chromium.org</owner>
   <owner>src/net/OWNERS</owner>
   <summary>
@@ -434,7 +434,7 @@
 </histogram>
 
 <histogram name="Net.Cors.PreflightCheckError2" enum="CorsAccessCheckError"
-    expires_after="2022-07-11">
+    expires_after="2022-09-11">
   <owner>toyoshim@chromium.org</owner>
   <owner>yhirano@chromium.org</owner>
   <summary>
@@ -458,7 +458,7 @@
 </histogram>
 
 <histogram name="Net.CountOfBrokenAlternativeServices" units="services"
-    expires_after="2022-07-11">
+    expires_after="2022-09-11">
   <owner>dschinazi@chromium.org</owner>
   <owner>src/net/OWNERS</owner>
   <summary>
@@ -644,7 +644,7 @@
 </histogram>
 
 <histogram name="Net.DNS.DnsConfig.SecureDnsMode" enum="SecureDnsModeDetails"
-    expires_after="2022-07-11">
+    expires_after="2022-09-11">
   <owner>ericorth@chromium.org</owner>
   <owner>doh-core@google.com</owner>
   <summary>
@@ -1076,7 +1076,7 @@
 </histogram>
 
 <histogram name="Net.DNS.JobQueueTime.Failure" units="ms"
-    expires_after="2022-06-26">
+    expires_after="2022-09-11">
   <owner>dmcardle@chromium.org</owner>
   <owner>ericorth@chromium.org</owner>
   <summary>
@@ -1087,7 +1087,7 @@
 </histogram>
 
 <histogram name="Net.DNS.JobQueueTime.PerTransaction" units="ms"
-    expires_after="2022-06-26">
+    expires_after="2022-09-11">
   <owner>dmcardle@chromium.org</owner>
   <owner>ericorth@chromium.org</owner>
   <summary>
@@ -1100,7 +1100,7 @@
 </histogram>
 
 <histogram name="Net.DNS.JobQueueTime.Success" units="ms"
-    expires_after="2022-06-19">
+    expires_after="2022-09-11">
   <owner>dmcardle@chromium.org</owner>
   <owner>ericorth@chromium.org</owner>
   <summary>
@@ -1520,7 +1520,7 @@
 </histogram>
 
 <histogram name="Net.ExpectCT.HeaderPresentOnResponse" units="BooleanPresent"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>estark@chromium.org</owner>
   <owner>trusty-transport@chromium.org</owner>
   <summary>
@@ -1648,7 +1648,7 @@
 </histogram>
 
 <histogram name="Net.HttpJob.PrefilterBytesRead" units="bytes"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>jkarlin@chromium.org</owner>
   <owner>shivanisha@chromium.org</owner>
   <summary>
@@ -1658,7 +1658,7 @@
 </histogram>
 
 <histogram name="Net.HttpJob.ProxyTypeFailed" enum="ProxyScheme"
-    expires_after="2022-07-11">
+    expires_after="2022-09-11">
   <owner>rch@chromium.org</owner>
   <summary>
     Scheme of the proxy server used by an HTTP job that failed, or SCHEME_DIRECT
@@ -1667,7 +1667,7 @@
 </histogram>
 
 <histogram name="Net.HttpJob.ProxyTypeSuccess" enum="ProxyScheme"
-    expires_after="2022-07-11">
+    expires_after="2022-09-11">
   <owner>rch@chromium.org</owner>
   <summary>
     Scheme of the proxy server used by an HTTP job that succeeded, or
@@ -1969,7 +1969,7 @@
 </histogram>
 
 <histogram name="Net.NumQuicSessionsAtShutdown" units="units"
-    expires_after="2022-07-11">
+    expires_after="2022-09-11">
   <owner>dschinazi@chromium.org</owner>
   <owner>src/net/quic/OWNERS</owner>
   <summary>
@@ -2064,7 +2064,7 @@
 </histogram>
 
 <histogram name="Net.QuicActiveSessions" units="units"
-    expires_after="2022-07-11">
+    expires_after="2022-09-11">
   <owner>dschinazi@chromium.org</owner>
   <owner>src/net/quic/OWNERS</owner>
   <summary>
@@ -2302,7 +2302,7 @@
 </histogram>
 
 <histogram name="Net.QuicHandshakeNotConfirmedNumPacketsReceived" units="units"
-    expires_after="2022-07-11">
+    expires_after="2022-09-11">
   <owner>dschinazi@chromium.org</owner>
   <owner>src/net/quic/OWNERS</owner>
   <summary>
@@ -2382,7 +2382,7 @@
 </histogram>
 
 <histogram name="Net.QuicNumSentClientHellos" units="units"
-    expires_after="2022-07-11">
+    expires_after="2022-09-11">
   <owner>dschinazi@chromium.org</owner>
   <owner>src/net/quic/OWNERS</owner>
   <summary>The number of client hello messages sent.</summary>
@@ -2417,7 +2417,7 @@
 </histogram>
 
 <histogram name="Net.QuicSession.AcceptChForOrigin" enum="Boolean"
-    expires_after="2022-05-11">
+    expires_after="2022-09-11">
   <owner>bnc@chromium.org</owner>
   <owner>src/net/OWNERS</owner>
   <summary>
@@ -2429,7 +2429,7 @@
 </histogram>
 
 <histogram name="Net.QuicSession.AcceptChFrameReceivedViaAlps"
-    enum="AcceptChEntries" expires_after="2022-07-11">
+    enum="AcceptChEntries" expires_after="2022-09-11">
   <owner>bnc@chromium.org</owner>
   <owner>src/net/OWNERS</owner>
   <summary>
@@ -2451,7 +2451,7 @@
 </histogram>
 
 <histogram name="Net.QuicSession.AsyncRead" enum="Boolean"
-    expires_after="2022-07-11">
+    expires_after="2022-09-11">
   <owner>dschinazi@chromium.org</owner>
   <owner>src/net/quic/OWNERS</owner>
   <summary>
@@ -2638,7 +2638,7 @@
 
 <histogram
     name="Net.QuicSession.ConnectionClose.NumOpenStreams.HandshakeTimedOut"
-    units="units" expires_after="2022-07-11">
+    units="units" expires_after="2022-09-11">
   <owner>dschinazi@chromium.org</owner>
   <owner>src/net/quic/OWNERS</owner>
   <summary>
@@ -2647,7 +2647,7 @@
 </histogram>
 
 <histogram name="Net.QuicSession.ConnectionClose.NumOpenStreams.TimedOut"
-    units="units" expires_after="2022-07-11">
+    units="units" expires_after="2022-09-11">
   <owner>dschinazi@chromium.org</owner>
   <owner>src/net/quic/OWNERS</owner>
   <summary>The number of streams open when a QUIC session timed out.</summary>
@@ -2665,7 +2665,7 @@
 </histogram>
 
 <histogram name="Net.QuicSession.ConnectionCloseErrorCodeClient"
-    enum="QuicErrorCodes" expires_after="2022-07-11">
+    enum="QuicErrorCodes" expires_after="2022-09-11">
   <owner>dschinazi@chromium.org</owner>
   <owner>src/net/quic/OWNERS</owner>
   <summary>
@@ -2675,7 +2675,7 @@
 </histogram>
 
 <histogram name="Net.QuicSession.ConnectionCloseErrorCodeClientGoogle"
-    enum="QuicErrorCodes" expires_after="2022-07-11">
+    enum="QuicErrorCodes" expires_after="2022-09-11">
   <owner>dschinazi@chromium.org</owner>
   <owner>src/net/quic/OWNERS</owner>
   <summary>
@@ -2685,7 +2685,7 @@
 </histogram>
 
 <histogram name="Net.QuicSession.ConnectionCloseErrorCodeServer"
-    enum="QuicErrorCodes" expires_after="2022-07-11">
+    enum="QuicErrorCodes" expires_after="2022-09-11">
   <owner>dschinazi@chromium.org</owner>
   <owner>src/net/quic/OWNERS</owner>
   <summary>
@@ -2695,7 +2695,7 @@
 </histogram>
 
 <histogram name="Net.QuicSession.ConnectionCloseErrorCodeServerGoogle"
-    enum="QuicErrorCodes" expires_after="2022-07-11">
+    enum="QuicErrorCodes" expires_after="2022-09-11">
   <owner>dschinazi@chromium.org</owner>
   <owner>src/net/quic/OWNERS</owner>
   <summary>
@@ -2705,7 +2705,7 @@
 </histogram>
 
 <histogram name="Net.QuicSession.ConnectionCloseErrorCodeServerIetfApplication"
-    enum="QuicHttp3ErrorCodes" expires_after="2022-07-11">
+    enum="QuicHttp3ErrorCodes" expires_after="2022-09-11">
   <owner>dschinazi@chromium.org</owner>
   <owner>src/net/quic/OWNERS</owner>
   <summary>
@@ -2748,7 +2748,7 @@
 </histogram>
 
 <histogram name="Net.QuicSession.ConnectionCloseErrorCodeServerIetfTransport"
-    enum="QuicTransportErrorCodes" expires_after="2022-07-11">
+    enum="QuicTransportErrorCodes" expires_after="2022-09-11">
   <owner>dschinazi@chromium.org</owner>
   <owner>src/net/quic/OWNERS</owner>
   <summary>
@@ -2759,7 +2759,7 @@
 
 <histogram
     name="Net.QuicSession.ConnectionCloseErrorCodeServerIetfTransportGoogle"
-    enum="QuicTransportErrorCodes" expires_after="2022-07-11">
+    enum="QuicTransportErrorCodes" expires_after="2022-09-11">
   <owner>dschinazi@chromium.org</owner>
   <owner>src/net/quic/OWNERS</owner>
   <summary>
@@ -2827,7 +2827,7 @@
 </histogram>
 
 <histogram name="Net.QuicSession.ConnectionMigration"
-    enum="QuicConnectionMigrationStatus" expires_after="2022-07-11">
+    enum="QuicConnectionMigrationStatus" expires_after="2022-09-11">
   <owner>dschinazi@chromium.org</owner>
   <owner>src/net/quic/OWNERS</owner>
   <summary>
@@ -2925,7 +2925,7 @@
 </histogram>
 
 <histogram name="Net.QuicSession.EncryptionEstablishedTime"
-    units="Milliseconds" expires_after="2022-05-11">
+    units="Milliseconds" expires_after="2022-09-11">
   <owner>fayang@chromium.org</owner>
   <owner>src/net/quic/OWNERS</owner>
   <summary>
@@ -2945,7 +2945,7 @@
 </histogram>
 
 <histogram name="Net.QuicSession.FinchConfigIsValid" enum="Boolean"
-    expires_after="2022-07-11">
+    expires_after="2022-09-11">
   <owner>dschinazi@chromium.org</owner>
   <owner>src/net/quic/OWNERS</owner>
   <summary>
@@ -2998,7 +2998,7 @@
 </histogram>
 
 <histogram name="Net.QuicSession.HandshakeConfirmedTime" units="Milliseconds"
-    expires_after="2022-05-11">
+    expires_after="2022-09-11">
   <owner>dschinazi@chromium.org</owner>
   <owner>src/net/quic/OWNERS</owner>
   <summary>
@@ -3038,7 +3038,7 @@
 </histogram>
 
 <histogram name="Net.QuicSession.HeaderCompressionRatioHpackReceived" units="%"
-    expires_after="2022-07-11">
+    expires_after="2022-09-11">
   <owner>dschinazi@chromium.org</owner>
   <owner>src/net/quic/OWNERS</owner>
   <summary>
@@ -3047,7 +3047,7 @@
 </histogram>
 
 <histogram name="Net.QuicSession.HeaderCompressionRatioHpackSent" units="%"
-    expires_after="2022-07-11">
+    expires_after="2022-09-11">
   <owner>dschinazi@chromium.org</owner>
   <owner>src/net/quic/OWNERS</owner>
   <summary>
@@ -3056,7 +3056,7 @@
 </histogram>
 
 <histogram name="Net.QuicSession.HeaderCompressionRatioQpackReceived" units="%"
-    expires_after="2022-07-11">
+    expires_after="2022-09-11">
   <owner>dschinazi@chromium.org</owner>
   <owner>src/net/quic/OWNERS</owner>
   <summary>
@@ -3399,7 +3399,7 @@
 </histogram>
 
 <histogram name="Net.QuicSession.PacketRetransmitsPerMille" units="permille"
-    expires_after="2022-07-11">
+    expires_after="2022-09-11">
   <owner>dschinazi@chromium.org</owner>
   <owner>src/net/quic/OWNERS</owner>
   <summary>
@@ -3420,7 +3420,7 @@
 </histogram>
 
 <histogram name="Net.QuicSession.PathValidationSuccess" enum="BooleanSuccess"
-    expires_after="2022-07-11">
+    expires_after="2022-09-11">
   <owner>renjietang@chromium.org</owner>
   <owner>src/net/quic/OWNERS</owner>
   <summary>
@@ -3449,7 +3449,7 @@
 </histogram>
 
 <histogram name="Net.QuicSession.PortMigration"
-    enum="QuicConnectionMigrationStatus" expires_after="2022-07-11">
+    enum="QuicConnectionMigrationStatus" expires_after="2022-09-11">
   <owner>dschinazi@chromium.org</owner>
   <owner>src/net/quic/OWNERS</owner>
   <summary>The result of a QUIC port migration attempt.</summary>
@@ -3576,7 +3576,7 @@
 </histogram>
 
 <histogram name="Net.QuicSession.QuicVersion" units="units"
-    expires_after="2022-07-11">
+    expires_after="2022-09-11">
   <owner>dschinazi@chromium.org</owner>
   <owner>src/net/quic/OWNERS</owner>
   <summary>Version of the QUIC protocol used for this connection.</summary>
@@ -3633,7 +3633,7 @@
 </histogram>
 
 <histogram name="Net.QuicSession.ReceivedSettings.MaxTableCapacity2"
-    units="bytes" expires_after="2022-07-11">
+    units="bytes" expires_after="2022-09-11">
   <owner>dschinazi@chromium.org</owner>
   <owner>src/net/quic/OWNERS</owner>
   <summary>
@@ -3697,7 +3697,7 @@
 </histogram>
 
 <histogram name="Net.QuicSession.RstStreamErrorCodeServer"
-    enum="QuicRstStreamErrorCodes" expires_after="2022-07-11">
+    enum="QuicRstStreamErrorCodes" expires_after="2022-09-11">
   <owner>dschinazi@chromium.org</owner>
   <owner>src/net/quic/OWNERS</owner>
   <summary>
@@ -3748,7 +3748,7 @@
 </histogram>
 
 <histogram name="Net.QuicSession.SmoothedRTT" units="ms"
-    expires_after="2022-07-11">
+    expires_after="2022-09-11">
   <owner>dschinazi@chromium.org</owner>
   <owner>src/net/quic/OWNERS</owner>
   <summary>
@@ -3828,7 +3828,7 @@
 </histogram>
 
 <histogram name="Net.QuicSession.StreamCloseErrorCodeServer.HandshakeConfirmed"
-    enum="QuicErrorCodes" expires_after="2022-07-11">
+    enum="QuicErrorCodes" expires_after="2022-09-11">
   <owner>dschinazi@chromium.org</owner>
   <owner>src/net/quic/OWNERS</owner>
   <summary>
@@ -3992,7 +3992,7 @@
 </histogram>
 
 <histogram name="Net.QuicSession.VerifyProofTime" units="ms"
-    expires_after="2022-07-11">
+    expires_after="2022-09-11">
   <owner>dschinazi@chromium.org</owner>
   <owner>src/net/quic/OWNERS</owner>
   <summary>
@@ -4043,7 +4043,7 @@
 </histogram>
 
 <histogram name="Net.QuicSession.ZeroRttReason"
-    enum="SSLHandshakeEarlyDataReason" expires_after="2022-07-11">
+    enum="SSLHandshakeEarlyDataReason" expires_after="2022-09-11">
   <owner>dschinazi@chromium.org</owner>
   <owner>src/net/quic/OWNERS</owner>
   <summary>
@@ -4053,7 +4053,7 @@
 </histogram>
 
 <histogram name="Net.QuicSession.ZeroRttReasonGoogle"
-    enum="SSLHandshakeEarlyDataReason" expires_after="2022-07-11">
+    enum="SSLHandshakeEarlyDataReason" expires_after="2022-09-11">
   <owner>renjietang@chromium.org</owner>
   <owner>src/net/quic/OWNERS</owner>
   <summary>
@@ -4064,7 +4064,7 @@
 </histogram>
 
 <histogram name="Net.QuicSession.ZeroRttReasonNonGoogle"
-    enum="SSLHandshakeEarlyDataReason" expires_after="2022-07-11">
+    enum="SSLHandshakeEarlyDataReason" expires_after="2022-09-11">
   <owner>renjietang@chromium.org</owner>
   <owner>src/net/quic/OWNERS</owner>
   <summary>
@@ -4075,7 +4075,7 @@
 </histogram>
 
 <histogram name="Net.QuicSession.ZeroRttState" enum="ZeroRttState"
-    expires_after="2022-07-11">
+    expires_after="2022-09-11">
   <owner>renjietang@chromium.org</owner>
   <owner>src/net/quic/OWNERS</owner>
   <summary>Whether 0-RTT was successfully used in the connection.</summary>
@@ -4398,7 +4398,7 @@
 </histogram>
 
 <histogram name="Net.RestrictedCookieManager.TopFrameOriginOK" enum="Boolean"
-    expires_after="2022-07-11">
+    expires_after="2022-09-11">
   <owner>morlovich@chromium.org</owner>
   <owner>src/net/cookies/OWNERS</owner>
   <summary>
@@ -4469,7 +4469,7 @@
 </histogram>
 
 <histogram name="Net.SpdyResponseCode" enum="HttpResponseCode"
-    expires_after="2022-07-11">
+    expires_after="2022-09-11">
   <owner>dschinazi@chromium.org</owner>
   <owner>src/net/OWNERS</owner>
   <summary>
@@ -4492,7 +4492,7 @@
 </histogram>
 
 <histogram name="Net.SpdySession.AlpsAcceptChEntries" enum="AcceptChEntries"
-    expires_after="2022-07-11">
+    expires_after="2022-09-11">
   <owner>bnc@chromium.org</owner>
   <owner>src/net/OWNERS</owner>
   <summary>
@@ -4504,7 +4504,7 @@
 </histogram>
 
 <histogram name="Net.SpdySession.AlpsDecoderStatus" enum="AlpsDecoderError"
-    expires_after="2022-07-11">
+    expires_after="2022-09-11">
   <owner>bnc@chromium.org</owner>
   <owner>src/net/OWNERS</owner>
   <summary>
@@ -4526,7 +4526,7 @@
 </histogram>
 
 <histogram name="Net.SpdySession.ClosedOnError" enum="NetErrorCodes"
-    expires_after="2022-07-11">
+    expires_after="2022-09-11">
   <owner>dschinazi@chromium.org</owner>
   <owner>src/net/OWNERS</owner>
   <summary>Net error codes when SpdySession was closed.</summary>
@@ -4576,7 +4576,7 @@
 </histogram>
 
 <histogram name="Net.SpdySessionErrorDetails2" enum="SpdyProtocolErrorDetails2"
-    expires_after="2022-07-11">
+    expires_after="2022-09-11">
   <owner>dschinazi@chromium.org</owner>
   <owner>src/net/OWNERS</owner>
   <summary>The type of SPDY Protocol error encountered.</summary>
@@ -4622,7 +4622,7 @@
 </histogram>
 
 <histogram name="Net.SpdyStreamsPerSession" units="units"
-    expires_after="2022-07-11">
+    expires_after="2022-09-11">
   <owner>dschinazi@chromium.org</owner>
   <owner>src/net/OWNERS</owner>
   <summary>The number of streams issued over a single session.</summary>
@@ -4771,7 +4771,7 @@
 </histogram>
 
 <histogram name="Net.SSLNegotiatedAlpnProtocol"
-    enum="SSLNegotiatedAlpnProtocol" expires_after="2022-07-11">
+    enum="SSLNegotiatedAlpnProtocol" expires_after="2022-09-11">
   <owner>dschinazi@chromium.org</owner>
   <owner>src/net/OWNERS</owner>
   <summary>
@@ -4794,7 +4794,7 @@
 </histogram>
 
 <histogram name="Net.SSLRSAKeyUsage.UnknownRoot" enum="RSAKeyUsage"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>davidben@chromium.org</owner>
   <summary>
     For each TLS connection which uses a unknown root, an RSA key, and TLS 1.2
@@ -4903,7 +4903,7 @@
 </histogram>
 
 <histogram name="Net.TcpConnectAttempt.Latency.Error" units="ms"
-    expires_after="2022-07-12">
+    expires_after="2022-09-11">
   <owner>bashi@chromium.org</owner>
   <owner>src/net/OWNERS</owner>
   <summary>
@@ -4914,7 +4914,7 @@
 </histogram>
 
 <histogram name="Net.TcpConnectAttempt.Latency.Success" units="ms"
-    expires_after="2022-07-12">
+    expires_after="2022-09-11">
   <owner>bashi@chromium.org</owner>
   <owner>src/net/OWNERS</owner>
   <summary>
@@ -4924,7 +4924,7 @@
 </histogram>
 
 <histogram name="Net.TcpConnectAttempt.LatencyPercentRTT.Error" units="%"
-    expires_after="2022-07-11">
+    expires_after="2022-09-11">
   <owner>dschinazi@chromium.org</owner>
   <owner>src/net/OWNERS</owner>
   <summary>
@@ -4937,7 +4937,7 @@
 </histogram>
 
 <histogram name="Net.TcpConnectAttempt.LatencyPercentRTT.Success" units="%"
-    expires_after="2022-07-11">
+    expires_after="2022-09-11">
   <owner>dschinazi@chromium.org</owner>
   <owner>src/net/OWNERS</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/network/histograms.xml b/tools/metrics/histograms/metadata/network/histograms.xml
index 816159a..ca523c0 100644
--- a/tools/metrics/histograms/metadata/network/histograms.xml
+++ b/tools/metrics/histograms/metadata/network/histograms.xml
@@ -3130,7 +3130,7 @@
 </histogram>
 
 <histogram name="NetworkService.TimeToGrantCacheAccess" units="ms"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>wfh@chromium.org</owner>
   <owner>mmenke@chromium.org</owner>
   <summary>
@@ -3144,7 +3144,7 @@
 </histogram>
 
 <histogram name="NetworkService.TimeToGrantDataAccess" units="ms"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>wfh@chromium.org</owner>
   <owner>mmenke@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/new_tab_page/histograms.xml b/tools/metrics/histograms/metadata/new_tab_page/histograms.xml
index 2594ab2..51485cb8 100644
--- a/tools/metrics/histograms/metadata/new_tab_page/histograms.xml
+++ b/tools/metrics/histograms/metadata/new_tab_page/histograms.xml
@@ -94,7 +94,7 @@
 </histogram>
 
 <histogram name="NewTabPage.Carts.CartCount" units="count"
-    expires_after="2022-07-11">
+    expires_after="2022-09-11">
   <owner>wychen@chromium.org</owner>
   <owner>yuezhanggg@chromium.org</owner>
   <owner>chrome-shopping@google.com</owner>
@@ -129,7 +129,7 @@
 </histogram>
 
 <histogram name="NewTabPage.Carts.DataRequest" enum="CartDiscountDataType"
-    expires_after="2022-06-30">
+    expires_after="2022-09-11">
   <owner>meiliang@chromium.org</owner>
   <owner>yuezhanggg@chromium.org</owner>
   <owner>chrome-shopping@google.com</owner>
@@ -856,7 +856,7 @@
 </histogram>
 
 <histogram name="NewTabPage.HasCredentials" enum="BooleanYesNo"
-    expires_after="2022-06-30">
+    expires_after="2022-09-11">
   <owner>danpeng@google.com</owner>
   <owner>tiborg@chromium.org</owner>
   <owner>chrome-desktop-ntp@google.com</owner>
@@ -891,7 +891,7 @@
   </summary>
 </histogram>
 
-<histogram name="NewTabPage.LoadTime" units="ms" expires_after="2022-07-11">
+<histogram name="NewTabPage.LoadTime" units="ms" expires_after="2022-09-11">
   <owner>tiborg@chromium.org</owner>
   <owner>yyushkina@chromium.org</owner>
   <owner>chrome-desktop-ntp@google.com</owner>
@@ -956,7 +956,7 @@
 </histogram>
 
 <histogram name="NewTabPage.LogoShown" enum="NewTabPageLogoShown"
-    expires_after="2022-07-01">
+    expires_after="2022-09-11">
   <owner>danpeng@google.com</owner>
   <owner>tiborg@chromium.org</owner>
   <owner>yyushkina@chromium.org</owner>
@@ -1002,7 +1002,7 @@
 </histogram>
 
 <histogram name="NewTabPage.Modules.DataRequest" enum="NtpModules"
-    expires_after="2022-07-01">
+    expires_after="2022-09-11">
   <owner>danpeng@google.com</owner>
   <owner>tiborg@chromium.org</owner>
   <owner>yyushkina@chromium.org</owner>
@@ -1035,7 +1035,7 @@
 </histogram>
 
 <histogram name="NewTabPage.Modules.Dismissed" units="count"
-    expires_after="2022-07-01">
+    expires_after="2022-09-11">
   <owner>danpeng@google.com</owner>
   <owner>mahmadi@chromium.org</owner>
   <owner>tiborg@chromium.org</owner>
@@ -1049,7 +1049,7 @@
 </histogram>
 
 <histogram base="true" name="NewTabPage.Modules.EnabledOnNTPLoad"
-    enum="BooleanEnabled" expires_after="2022-07-01">
+    enum="BooleanEnabled" expires_after="2022-09-11">
   <owner>danpeng@google.com</owner>
   <owner>tiborg@chromium.org</owner>
   <owner>chrome-desktop-ntp@google.com</owner>
@@ -1093,7 +1093,7 @@
 </histogram>
 
 <histogram name="NewTabPage.Modules.Impression" units="ms"
-    expires_after="2022-07-01">
+    expires_after="2022-09-11">
   <owner>danpeng@google.com</owner>
   <owner>tiborg@chromium.org</owner>
   <owner>yyushkina@chromium.org</owner>
@@ -1108,7 +1108,7 @@
 </histogram>
 
 <histogram name="NewTabPage.Modules.ImpressionRatio" units="perdecage"
-    expires_after="2022-07-01">
+    expires_after="2022-09-11">
   <owner>danpeng@google.com</owner>
   <owner>tiborg@chromium.org</owner>
   <owner>chrome-desktop-ntp@google.com</owner>
@@ -1141,7 +1141,7 @@
 </histogram>
 
 <histogram name="NewTabPage.Modules.LoadDuration" units="ms"
-    expires_after="2022-07-01">
+    expires_after="2022-09-11">
   <owner>danpeng@google.com</owner>
   <owner>tiborg@chromium.org</owner>
   <owner>chrome-desktop-ntp@google.com</owner>
@@ -1151,7 +1151,7 @@
 </histogram>
 
 <histogram name="NewTabPage.Modules.Loaded" units="ms"
-    expires_after="2022-07-01">
+    expires_after="2022-09-11">
   <owner>danpeng@google.com</owner>
   <owner>tiborg@chromium.org</owner>
   <owner>yyushkina@chromium.org</owner>
@@ -1177,7 +1177,7 @@
 </histogram>
 
 <histogram name="NewTabPage.Modules.ShownTime" units="ms"
-    expires_after="2022-07-01">
+    expires_after="2022-09-11">
   <owner>danpeng@google.com</owner>
   <owner>tiborg@chromium.org</owner>
   <owner>yyushkina@chromium.org</owner>
@@ -1189,7 +1189,7 @@
 </histogram>
 
 <histogram name="NewTabPage.Modules.Usage" units="count"
-    expires_after="2022-07-01">
+    expires_after="2022-09-11">
   <owner>danpeng@google.com</owner>
   <owner>tiborg@chromium.org</owner>
   <owner>yyushkina@chromium.org</owner>
@@ -1202,7 +1202,7 @@
 </histogram>
 
 <histogram name="NewTabPage.Modules.VisibleOnNTPLoad" enum="BooleanVisible"
-    expires_after="2022-07-01">
+    expires_after="2022-09-11">
   <owner>danpeng@google.com</owner>
   <owner>tiborg@chromium.org</owner>
   <owner>yyushkina@chromium.org</owner>
@@ -1276,7 +1276,7 @@
 </histogram>
 
 <histogram name="NewTabPage.OneGoogleBar.RequestLatency" units="ms"
-    expires_after="2022-06-19">
+    expires_after="2022-09-11">
   <owner>tiborg@chromium.org</owner>
   <owner>yyushkina@chromium.org</owner>
   <owner>chrome-desktop-ntp@google.com</owner>
@@ -1299,7 +1299,7 @@
 </histogram>
 
 <histogram name="NewTabPage.Photos.DataRequest"
-    enum="PhotosModuleRequestResult" expires_after="2022-06-30">
+    enum="PhotosModuleRequestResult" expires_after="2022-09-11">
   <owner>jerem@google.com</owner>
   <owner>tiborg@chromium.org</owner>
   <owner>chrome-desktop-ntp@google.com</owner>
@@ -1312,7 +1312,7 @@
 </histogram>
 
 <histogram name="NewTabPage.Photos.DataResponseCount" units="count"
-    expires_after="2022-06-30">
+    expires_after="2022-09-11">
   <owner>jerem@google.com</owner>
   <owner>tiborg@chromium.org</owner>
   <owner>chrome-desktop-ntp@google.com</owner>
@@ -1325,7 +1325,7 @@
 </histogram>
 
 <histogram name="NewTabPage.Photos.ImageLoad" enum="BooleanSuccess"
-    expires_after="2022-06-30">
+    expires_after="2022-09-11">
   <owner>tiborg@chromium.org</owner>
   <owner>jerem@google.com</owner>
   <owner>chrome-desktop-ntp@google.com</owner>
@@ -1339,7 +1339,7 @@
 </histogram>
 
 <histogram name="NewTabPage.Photos.ModuleShown" enum="BooleanPhotosOptedIn"
-    expires_after="2022-06-30">
+    expires_after="2022-09-11">
   <owner>tiborg@chromium.org</owner>
   <owner>jerem@google.com</owner>
   <owner>chrome-desktop-ntp@google.com</owner>
@@ -1353,7 +1353,7 @@
 </histogram>
 
 <histogram name="NewTabPage.Photos.UserOptIn" enum="NtpPhotosModuleOptInStatus"
-    expires_after="2022-06-30">
+    expires_after="2022-09-11">
   <owner>tiborg@chromium.org</owner>
   <owner>jerem@google.com</owner>
   <owner>mplg@google.com</owner>
@@ -1393,7 +1393,7 @@
 </histogram>
 
 <histogram name="NewTabPage.Promos.RequestLatency2" units="ms"
-    expires_after="2022-07-01">
+    expires_after="2022-09-11">
   <owner>danpeng@google.com</owner>
   <owner>tiborg@chromium.org</owner>
   <owner>yyushkina@chromium.org</owner>
@@ -1418,7 +1418,7 @@
 </histogram>
 
 <histogram name="NewTabPage.Realbox.CharTypedToRepaintLatency.ToPaint"
-    units="ms" expires_after="2022-07-03">
+    units="ms" expires_after="2022-09-11">
   <owner>mahmadi@chromium.org</owner>
   <owner>tommycli@chromium.org</owner>
   <owner>chrome-desktop-ntp@google.com</owner>
@@ -1471,7 +1471,7 @@
 </histogram>
 
 <histogram name="NewTabPage.RecipeTasks.RelatedSearchDownloadCount"
-    units="count" expires_after="2022-07-01">
+    units="count" expires_after="2022-09-11">
   <owner>danpeng@google.com</owner>
   <owner>tiborg@chromium.org</owner>
   <owner>yyushkina@chromium.org</owner>
@@ -1692,7 +1692,7 @@
 </histogram>
 
 <histogram name="NewTabPage.TileType" enum="NTPTileVisualType"
-    expires_after="2022-07-01">
+    expires_after="2022-09-11">
   <owner>danpeng@google.com</owner>
   <owner>tiborg@chromium.org</owner>
   <owner>yyushkina@chromium.org</owner>
@@ -1705,7 +1705,7 @@
 </histogram>
 
 <histogram name="NewTabPage.TileTypeClicked" enum="NTPTileVisualType"
-    expires_after="2022-07-01">
+    expires_after="2022-09-11">
   <owner>danpeng@google.com</owner>
   <owner>tiborg@chromium.org</owner>
   <owner>yyushkina@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/notifications/histograms.xml b/tools/metrics/histograms/metadata/notifications/histograms.xml
index 9010024..0aed5e85 100644
--- a/tools/metrics/histograms/metadata/notifications/histograms.xml
+++ b/tools/metrics/histograms/metadata/notifications/histograms.xml
@@ -224,7 +224,7 @@
 </histogram>
 
 <histogram name="Notifications.Cros.Actions.{Action}"
-    enum="NotificationTypeDetailed" expires_after="2022-04-03">
+    enum="NotificationTypeDetailed" expires_after="2023-03-10">
   <owner>leandre@chromium.org</owner>
   <owner>cros-status-area-eng@google.com</owner>
   <summary>
@@ -716,7 +716,7 @@
 </histogram>
 
 <histogram name="Notifications.PersistentWebNotificationCloseResult"
-    enum="PlatformNotificationStatus" expires_after="2022-07-03">
+    enum="PlatformNotificationStatus" expires_after="2022-09-11">
   <owner>peter@chromium.org</owner>
   <owner>knollr@chromium.org</owner>
   <summary>
@@ -775,7 +775,7 @@
 </histogram>
 
 <histogram name="Notifications.Scheduler.IconDb.RecordCount" units="records"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>xingliu@chromium.org</owner>
   <owner>hesen@chromium.org</owner>
   <summary>
@@ -1035,7 +1035,7 @@
 </histogram>
 
 <histogram name="Notifications.Windows.DisplayFailure" enum="Hresult"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>finnur@chromium.org</owner>
   <owner>peter@chromium.org</owner>
   <summary>
@@ -1123,7 +1123,7 @@
 </histogram>
 
 <histogram name="Notifications.Windows.GetSettingStatusStartup"
-    enum="WindowsNotificationGetSettingStatus" expires_after="2022-07-03">
+    enum="WindowsNotificationGetSettingStatus" expires_after="2022-09-11">
   <owner>finnur@chromium.org</owner>
   <owner>knollr@chromium.org</owner>
   <owner>peter@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/offline/histograms.xml b/tools/metrics/histograms/metadata/offline/histograms.xml
index 72769df..d8edc5a 100644
--- a/tools/metrics/histograms/metadata/offline/histograms.xml
+++ b/tools/metrics/histograms/metadata/offline/histograms.xml
@@ -702,7 +702,7 @@
 </histogram>
 
 <histogram name="OfflinePages.OfflineUsage" enum="OfflinePagesOfflineUsage"
-    expires_after="2022-07-11">
+    expires_after="2022-09-11">
   <owner>dimich@chromium.org</owner>
   <owner>offline-dev@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/omnibox/histograms.xml b/tools/metrics/histograms/metadata/omnibox/histograms.xml
index 063814e1..7c6b56b 100644
--- a/tools/metrics/histograms/metadata/omnibox/histograms.xml
+++ b/tools/metrics/histograms/metadata/omnibox/histograms.xml
@@ -37,7 +37,7 @@
 </histogram>
 
 <histogram name="Omnibox.AnswerParseSuccess" enum="BooleanSuccess"
-    expires_after="2022-07-11">
+    expires_after="2022-09-11">
   <owner>jdonnelly@chromium.org</owner>
   <owner>chrome-omnibox-team@google.com</owner>
   <summary>
@@ -62,7 +62,7 @@
 </histogram>
 
 <histogram name="Omnibox.BitmapFetchLatency" units="ms"
-    expires_after="2022-07-11">
+    expires_after="2022-09-11">
 <!-- Name completed by histogram_suffixes name="Omnibox.BitmapFetchLatencyCacheSplit" -->
 
   <owner>manukh@chromium.org</owner>
@@ -79,7 +79,7 @@
 </histogram>
 
 <histogram name="Omnibox.CharTypedToRepaintLatency" units="ms"
-    expires_after="2022-07-11">
+    expires_after="2022-09-11">
   <owner>jdonnelly@chromium.org</owner>
   <owner>mpearson@chromium.org</owner>
   <owner>asvitkine@chromium.org</owner>
@@ -152,7 +152,7 @@
 </histogram>
 
 <histogram name="Omnibox.ClipboardSuggestionRemovedAge" units="ms"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>gangwu@chromium.org</owner>
   <owner>fgorski@chromium.org</owner>
   <summary>
@@ -355,7 +355,7 @@
 </histogram>
 
 <histogram name="Omnibox.FocusToOpenTimeAnyPopupState3" units="ms"
-    expires_after="2022-07-11">
+    expires_after="2022-09-11">
   <owner>jdonnelly@chromium.org</owner>
   <owner>mpearson@chromium.org</owner>
   <owner>chrome-omnibox-team@google.com</owner>
@@ -603,7 +603,7 @@
   </summary>
 </histogram>
 
-<histogram name="Omnibox.PaintTime" units="ms" expires_after="2022-07-03">
+<histogram name="Omnibox.PaintTime" units="ms" expires_after="2022-09-11">
   <owner>asvitkine@chromium.org</owner>
   <owner>mpearson@chromium.org</owner>
   <owner>jdonnelly@chromium.org</owner>
@@ -653,7 +653,7 @@
   </summary>
 </histogram>
 
-<histogram name="Omnibox.QueryTime2" units="ms" expires_after="2022-07-11">
+<histogram name="Omnibox.QueryTime2" units="ms" expires_after="2022-09-11">
   <owner>jdonnelly@chromium.org</owner>
   <owner>mpearson@chromium.org</owner>
   <owner>chrome-omnibox-team@google.com</owner>
@@ -745,7 +745,7 @@
 </histogram>
 
 <histogram name="Omnibox.SearchProviderMatches" units="units"
-    expires_after="2022-07-11">
+    expires_after="2022-09-11">
   <owner>jdonnelly@chromium.org</owner>
   <owner>mpearson@chromium.org</owner>
   <owner>chrome-omnibox-team@google.com</owner>
@@ -882,7 +882,7 @@
 
 <histogram
     name="Omnibox.SuggestionUsed.Search.NavigationToFirstContentfulPaint"
-    units="ms" expires_after="2022-07-11">
+    units="ms" expires_after="2022-09-11">
   <owner>jdonnelly@chromium.org</owner>
   <owner>mpearson@chromium.org</owner>
   <owner>chrome-omnibox-team@google.com</owner>
@@ -894,7 +894,7 @@
 
 <histogram
     name="Omnibox.SuggestionUsed.Search.NavigationToLargestContentfulPaint2"
-    units="ms" expires_after="2022-07-11">
+    units="ms" expires_after="2022-09-11">
   <owner>asamidoi@chromium.org</owner>
   <owner>chrome-prerendering@google.com</owner>
   <owner>chrome-omnibox-team@google.com</owner>
@@ -969,7 +969,7 @@
 </histogram>
 
 <histogram name="Omnibox.SuggestRequest.Failure.GoogleResponseTime" units="ms"
-    expires_after="2022-07-11">
+    expires_after="2022-09-11">
   <owner>mpearson@chromium.org</owner>
   <owner>jdonnelly@chromium.org</owner>
   <owner>cch@chromium.org</owner>
@@ -981,7 +981,7 @@
 </histogram>
 
 <histogram name="Omnibox.SuggestRequest.Success.GoogleResponseTime" units="ms"
-    expires_after="2022-07-11">
+    expires_after="2022-09-11">
   <owner>mpearson@chromium.org</owner>
   <owner>jdonnelly@chromium.org</owner>
   <owner>cch@chromium.org</owner>
@@ -1004,7 +1004,7 @@
 </histogram>
 
 <histogram name="Omnibox.SuggestRequests" enum="OmniboxSuggestRequests"
-    expires_after="2022-07-11">
+    expires_after="2022-09-11">
   <owner>mpearson@chromium.org</owner>
   <owner>jdonnelly@chromium.org</owner>
   <owner>cch@chromium.org</owner>
@@ -1072,7 +1072,7 @@
 </histogram>
 
 <histogram name="Omnibox.ToggleSuggestionGroupId.On" enum="SuggestionGroupId"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>mahmadi@google.com</owner>
   <owner>chrome-omnibox-team@google.com</owner>
   <summary>
@@ -1084,7 +1084,7 @@
 </histogram>
 
 <histogram name="Omnibox.URLNavigationScheme" enum="NavigationScheme"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>mpearson@chromium.org</owner>
   <owner>jdonnelly@chromium.org</owner>
   <owner>chrome-omnibox-team@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/optimization/histograms.xml b/tools/metrics/histograms/metadata/optimization/histograms.xml
index 53ca2d3..f0aa9e2 100644
--- a/tools/metrics/histograms/metadata/optimization/histograms.xml
+++ b/tools/metrics/histograms/metadata/optimization/histograms.xml
@@ -255,7 +255,7 @@
 </histogram>
 
 <histogram name="OptimizationGuide.HintsFetcher.GetHintsRequest.NetErrorCode"
-    enum="NetErrorCodes" expires_after="2022-07-11">
+    enum="NetErrorCodes" expires_after="2022-09-11">
   <owner>mcrouse@chromium.org</owner>
   <owner>sophiechang@chromium.org</owner>
   <summary>
@@ -265,7 +265,7 @@
 </histogram>
 
 <histogram name="OptimizationGuide.HintsFetcher.GetHintsRequest.Status"
-    enum="HttpResponseCode" expires_after="2022-07-11">
+    enum="HttpResponseCode" expires_after="2022-09-11">
   <owner>mcrouse@chromium.org</owner>
   <owner>sophiechang@chromium.org</owner>
   <summary>
@@ -379,6 +379,18 @@
 </histogram>
 
 <histogram
+    name="OptimizationGuide.ModelExecutor.DidTimeout.{OptimizationTarget}"
+    enum="Boolean" expires_after="M106">
+  <owner>robertogden@chromium.org</owner>
+  <owner>chrome-intelligence-core@google.com</owner>
+  <summary>
+    Records whether the model execution for {OptimizationTarget} was cancelled
+    due to running past the configured timeout.
+  </summary>
+  <token key="OptimizationTarget" variants="OptimizationTarget"/>
+</histogram>
+
+<histogram
     name="OptimizationGuide.ModelExecutor.ExecutionLatency.{OptimizationTarget}"
     units="ms" expires_after="M106">
   <owner>mcrouse@chromium.org</owner>
@@ -854,6 +866,20 @@
 </histogram>
 
 <histogram
+    name="OptimizationGuide.PredictionModelDownloadManager.DownloadStartLatency.{OptimizationTarget}"
+    units="ms" expires_after="M106">
+  <owner>rajendrant@chromium.org</owner>
+  <owner>chrome-intelligence-core@google.com</owner>
+  <summary>
+    Records the time duration it took for the model download to be started by
+    the download service for {OptimizationTarget} from the time it was requested
+    by the optimization guide. Recorded once per model download when model
+    download starts successfully.
+  </summary>
+  <token key="OptimizationTarget" variants="OptimizationTarget"/>
+</histogram>
+
+<histogram
     name="OptimizationGuide.PredictionModelDownloadManager.DownloadStatus"
     enum="OptimizationGuidePredictionModelDownloadStatus" expires_after="M106">
   <owner>sophiechang@chromium.org</owner>
@@ -889,6 +915,18 @@
   <token key="OptimizationTarget" variants="OptimizationTarget"/>
 </histogram>
 
+<histogram
+    name="OptimizationGuide.PredictionModelDownloadManager.State.{OptimizationTarget}"
+    enum="OptimizationGuidePredictionModelDownloadState" expires_after="M106">
+  <owner>rajendrant@chromium.org</owner>
+  <owner>sophiechang@chromium.org</owner>
+  <summary>
+    The state of a model download. Recorded multiple times per model file
+    download, as the state is changed.
+  </summary>
+  <token key="OptimizationTarget" variants="OptimizationTarget"/>
+</histogram>
+
 <histogram name="OptimizationGuide.PredictionModelExpired.{OptimizationTarget}"
     enum="BooleanExpired" expires_after="M106">
   <owner>sophiechang@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/others/histograms.xml b/tools/metrics/histograms/metadata/others/histograms.xml
index 7824d9c0..e4fbdd9b 100644
--- a/tools/metrics/histograms/metadata/others/histograms.xml
+++ b/tools/metrics/histograms/metadata/others/histograms.xml
@@ -853,7 +853,7 @@
 </histogram>
 
 <histogram name="AutocompleteActionPredictor.DatabaseAction"
-    enum="AutocompleteActionPredictorDatabaseAction" expires_after="2022-07-03">
+    enum="AutocompleteActionPredictorDatabaseAction" expires_after="2022-09-11">
   <owner>tbansal@chromium.org</owner>
   <owner>chrome-omnibox-team@google.com</owner>
   <owner>jdonnelly@chromium.org</owner>
@@ -873,7 +873,7 @@
 </histogram>
 
 <histogram name="AutocompleteActionPredictor.MatchIsInDb" units="units"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>tbansal@chromium.org</owner>
   <owner>chrome-omnibox-team@google.com</owner>
   <owner>jdonnelly@chromium.org</owner>
@@ -2959,7 +2959,7 @@
 </histogram>
 
 <histogram name="ContextMenu.Shown" enum="BooleanPresent"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>mpearson@chromium.org</owner>
   <owner>twellington@chromium.org</owner>
   <owner>clank-app-team@google.com</owner>
@@ -3032,7 +3032,7 @@
 </histogram>
 
 <histogram name="ContextMenu.ViewsTextServices.Emoji" enum="Boolean"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>yyushkina@chromium.org</owner>
   <summary>
     Number of times the emoji item in the views text services context menu is
@@ -3258,7 +3258,7 @@
 </histogram>
 
 <histogram name="Conversions.RegisteredConversionsPerPage" units="conversions"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>johnidel@chromium.org</owner>
   <owner>csharrison@chromium.org</owner>
   <summary>
@@ -4562,7 +4562,7 @@
 </histogram>
 
 <histogram name="Drive.PushNotificationRegistered" enum="BooleanRegistered"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>simmonsjosh@google.com</owner>
   <owner>src/ui/file_manager/OWNERS</owner>
   <summary>
@@ -5523,7 +5523,7 @@
 </histogram>
 
 <histogram name="Gamepad.UnknownGamepadConnected" enum="GamepadSource"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>mattreynolds@chromium.org</owner>
   <owner>deviceapi-team@google.com</owner>
   <summary>
@@ -5650,7 +5650,7 @@
 </histogram>
 
 <histogram base="true" name="GridTabSwitcher.FramePerSecond" units="frame/sec"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>yusufo@chromium.org</owner>
   <owner>wychen@chromium.org</owner>
   <summary>
@@ -5820,7 +5820,7 @@
 </histogram>
 
 <histogram base="true" name="Hwsec.Attestation.Status"
-    enum="HwsecAttestationOpsStatus" expires_after="2022-07-03">
+    enum="HwsecAttestationOpsStatus" expires_after="2022-09-11">
 <!-- Name completed by histogram_suffixes name="AttestationOps" -->
 
   <owner>garryxiao@chromium.org</owner>
@@ -6627,7 +6627,7 @@
 
 <histogram
     name="Lens.ImageClassification.ClassificationTime.SuccessOnQueryResultMs"
-    units="ms" expires_after="2022-07-03">
+    units="ms" expires_after="2022-09-11">
   <owner>yusuyoutube@google.com</owner>
   <owner>benwgold@google.com</owner>
   <summary>
@@ -7013,7 +7013,7 @@
 </histogram>
 
 <histogram name="Manifest.ParseIdResult" enum="ManifestParseIdResultType"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>phillis@chromium.org</owner>
   <owner>desktop-pwas-team@chromium.org</owner>
   <summary>Tracks the result of parsing id field in the Manifest.</summary>
@@ -7196,7 +7196,7 @@
 </histogram>
 
 <histogram name="MerchantTrust.PageInfo.IsStoreInfoVisible"
-    enum="BooleanVisible" expires_after="2022-07-11">
+    enum="BooleanVisible" expires_after="2022-09-11">
   <owner>zhiyuancai@chromium.org</owner>
   <owner>chrome-shopping@google.com</owner>
   <summary>
@@ -7634,7 +7634,7 @@
 </histogram>
 
 <histogram name="MPArch.ChildProcessLaunchFirst" units="ms"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>pasko@chromium.org</owner>
   <owner>yfriedman@chromium.org</owner>
   <summary>
@@ -8567,7 +8567,7 @@
 </histogram>
 
 <histogram name="PaintHolding.CommitTrigger2" enum="PaintHoldingCommitTrigger2"
-    expires_after="2022-05-29">
+    expires_after="2022-09-11">
   <owner>schenney@chromium.org</owner>
   <owner>paint-dev@chromium.org</owner>
   <summary>
@@ -11239,7 +11239,7 @@
 </histogram>
 
 <histogram name="SB2.DownloadChecks" enum="SB2DownloadChecks"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>xinghuilu@chromium.org</owner>
   <owner>chrome-safebrowsing-alerts@google.com</owner>
   <summary>
@@ -12474,7 +12474,7 @@
 </histogram>
 
 <histogram name="SpellCheck.SpellingService.RequestResultType"
-    enum="ServiceRequestResultType" expires_after="2022-07-03">
+    enum="ServiceRequestResultType" expires_after="2022-09-11">
   <owner>yyushkina@google.com</owner>
   <owner>gujen@google.com</owner>
   <owner>chrome-language@google.com</owner>
@@ -12531,7 +12531,7 @@
 </histogram>
 
 <histogram base="true" name="Spellcheck.Windows.SpellcheckRequestDuration"
-    units="ms" expires_after="2022-07-03">
+    units="ms" expires_after="2022-09-11">
   <owner>gujen@google.com</owner>
   <owner>chrome-language@google.com</owner>
   <summary>
@@ -13356,7 +13356,7 @@
 </histogram>
 
 <histogram name="TouchBar.Default.Metrics" enum="DefaultTouchBarActions"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>ellyjones@chromium.org</owner>
   <owner>chrome-mac-dev@google.com</owner>
   <summary>Tracks the usage of the default touch bar buttons.</summary>
@@ -13645,7 +13645,7 @@
   </summary>
 </histogram>
 
-<histogram name="UI.DeviceScale" units="%" expires_after="2022-07-03">
+<histogram name="UI.DeviceScale" units="%" expires_after="2022-09-11">
   <owner>bsep@chromium.org</owner>
   <summary>
     The device scales available on the system at startup. A system may report
@@ -14290,7 +14290,7 @@
 </histogram>
 
 <histogram name="VoiceInteraction.QueryDuration.Android" units="ms"
-    expires_after="2022-08-28">
+    expires_after="2022-09-11">
   <owner>wylieb@chromium.org</owner>
   <owner>tedchoc@chromium.org</owner>
   <summary>
@@ -14390,7 +14390,7 @@
 </histogram>
 
 <histogram name="VoiceInteraction.VoiceResultConfidenceValue" units="%"
-    expires_after="2022-08-28">
+    expires_after="2022-09-11">
   <owner>wylieb@chromium.org</owner>
   <owner>tedchoc@chromium.org</owner>
   <owner>yusufo@chromium.org</owner>
@@ -14414,7 +14414,7 @@
 </histogram>
 
 <histogram name="VoiceInteraction.VoiceSearchResult" enum="BooleanSuccess"
-    expires_after="2022-08-28">
+    expires_after="2022-09-11">
   <owner>wylieb@chromium.org</owner>
   <owner>tedchoc@chromium.org</owner>
   <owner>yusufo@chromium.org</owner>
@@ -14526,7 +14526,7 @@
 </histogram>
 
 <histogram name="WebFont.BlankTextShownTime" units="ms"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>kenjibaheux@chromium.org</owner>
   <owner>ksakamoto@chromium.org</owner>
   <summary>
@@ -14561,7 +14561,7 @@
 </histogram>
 
 <histogram name="WebFont.DownloadTime.0.Under10KB" units="ms"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>kenjibaheux@chromium.org</owner>
   <owner>ksakamoto@chromium.org</owner>
   <summary>
@@ -14668,7 +14668,7 @@
 </histogram>
 
 <histogram name="WebFont.LocalFontUsed" enum="BooleanUsage"
-    expires_after="2022-07-11">
+    expires_after="2022-09-11">
   <owner>hajimehoshi@chromium.org</owner>
   <owner>kenjibaheux@chromium.org</owner>
   <owner>kouhei@chromium.org</owner>
@@ -14816,7 +14816,7 @@
 </histogram>
 
 <histogram name="WebUITabStrip.CloseTabAction"
-    enum="WebUITabStripCloseTabActions" expires_after="2022-07-03">
+    enum="WebUITabStripCloseTabActions" expires_after="2022-09-11">
   <owner>johntlee@chromium.org</owner>
   <owner>dpapad@chromium.org</owner>
   <summary>
@@ -15027,7 +15027,7 @@
 </histogram>
 
 <histogram name="WhatsNew.LoadResponseCode"
-    enum="CombinedHttpResponseAndNetErrorCode" expires_after="2022-07-03">
+    enum="CombinedHttpResponseAndNetErrorCode" expires_after="2022-09-11">
   <owner>rbpotter@chromium.org</owner>
   <owner>mahmadi@chromium.org</owner>
   <summary>
@@ -15127,7 +15127,7 @@
 </histogram>
 
 <histogram name="WrenchMenu.TimeToAction" units="units"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>ainslie@chromium.org</owner>
   <owner>edwardjung@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/page/histograms.xml b/tools/metrics/histograms/metadata/page/histograms.xml
index ad700519..6133372b 100644
--- a/tools/metrics/histograms/metadata/page/histograms.xml
+++ b/tools/metrics/histograms/metadata/page/histograms.xml
@@ -60,7 +60,7 @@
 </variants>
 
 <histogram name="PageActionController.ExtensionsWithPageActions" units="units"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>rdevlin.cronin@chromium.org</owner>
   <owner>extensions-core@chromium.org</owner>
   <summary>
@@ -95,7 +95,7 @@
 </histogram>
 
 <histogram name="PageLoad.BackForwardCache.Event"
-    enum="PageLoadBackForwardCacheEvent" expires_after="2022-07-03">
+    enum="PageLoadBackForwardCacheEvent" expires_after="2022-09-11">
   <owner>altimin@chromium.org</owner>
   <owner>bmcquade@chromium.org</owner>
   <owner>hajimehoshi@chromium.org</owner>
@@ -157,7 +157,7 @@
 </histogram>
 
 <histogram name="PageLoad.Clients.Ads.AllPages.NonAdNetworkBytes" units="KB"
-    expires_after="2022-05-24">
+    expires_after="2022-09-11">
   <owner>alexmt@chromium.org</owner>
   <owner>johnidel@chromium.org</owner>
   <summary>
@@ -317,7 +317,7 @@
 
 <histogram
     name="PageLoad.Clients.AMP.Experimental.PageTiming.InputToNavigation.Subframe"
-    units="ms" expires_after="2022-07-03">
+    units="ms" expires_after="2022-09-11">
   <owner>bmcquade@chromium.org</owner>
   <owner>sullivan@chromium.org</owner>
   <owner>speed-metrics-dev@chromium.org</owner>
@@ -1411,7 +1411,7 @@
 </histogram>
 
 <histogram name="PageLoad.Experimental.PaintTiming.InputToFirstContentfulPaint"
-    units="ms" expires_after="2022-07-03">
+    units="ms" expires_after="2022-09-11">
   <owner>sullivan@chromium.org</owner>
   <summary>
     The time between the OS-level input event that initiated a navigation, and
@@ -1855,7 +1855,7 @@
 
 <histogram
     name="PageLoad.Internal.PaintTiming.LargestContentfulPaint.ContentType"
-    enum="LargestContentType" expires_after="2022-07-03">
+    enum="LargestContentType" expires_after="2022-09-11">
   <owner>npm@chromium.org</owner>
   <owner>speed-metrics-dev@chromium.org</owner>
   <summary>
@@ -1903,7 +1903,7 @@
 </histogram>
 
 <histogram name="PageLoad.Internal.Renderer.PresentationTime.DeltaFromSwapTime"
-    units="ms" expires_after="2022-07-11">
+    units="ms" expires_after="2022-09-11">
   <owner>jonross@chromium.org</owner>
   <owner>sadrul@chromium.org</owner>
   <summary>
@@ -1923,7 +1923,7 @@
 </histogram>
 
 <histogram name="PageLoad.Internal.Renderer.PresentationTime.Valid"
-    enum="Boolean" expires_after="2022-07-11">
+    enum="Boolean" expires_after="2022-09-11">
   <owner>jonross@chromium.org</owner>
   <owner>sadrul@chromium.org</owner>
   <summary>
@@ -2245,7 +2245,7 @@
 
 <histogram
     name="PageLoad.PaintTiming.NavigationToFirstPaint.AfterBackForwardCacheRestore"
-    units="ms" expires_after="2022-06-19">
+    units="ms" expires_after="2022-09-11">
   <owner>altimin@chromium.org</owner>
   <owner>djw@chromium.org</owner>
   <owner>bfcache-dev@chromium.org</owner>
@@ -2539,7 +2539,7 @@
 </histogram>
 
 <histogram name="PageSerialization.MhtmlLoading.LoadResult"
-    enum="MhtmlLoadResult" expires_after="2022-07-03">
+    enum="MhtmlLoadResult" expires_after="2022-09-11">
   <owner>sclittle@chromium.org</owner>
   <owner>offline-dev@chromium.org</owner>
   <summary>Reports the result of an attempt to load an MHTML archive.</summary>
diff --git a/tools/metrics/histograms/metadata/password/histograms.xml b/tools/metrics/histograms/metadata/password/histograms.xml
index b9439bad..072b188c 100644
--- a/tools/metrics/histograms/metadata/password/histograms.xml
+++ b/tools/metrics/histograms/metadata/password/histograms.xml
@@ -171,7 +171,7 @@
 </histogram>
 
 <histogram name="PasswordBubble.CompromisedBubble.CheckClicked"
-    enum="BooleanClicked" expires_after="2022-07-03">
+    enum="BooleanClicked" expires_after="2022-09-11">
   <owner>vasilii@chromium.org</owner>
   <owner>kazinova@google.com</owner>
   <summary>
@@ -181,7 +181,7 @@
 </histogram>
 
 <histogram name="PasswordBubble.CompromisedBubble.Type"
-    enum="PasswordBubbleFollowupType" expires_after="2022-07-03">
+    enum="PasswordBubbleFollowupType" expires_after="2022-09-11">
   <owner>vasilii@chromium.org</owner>
   <owner>kazinova@google.com</owner>
   <summary>
@@ -297,7 +297,7 @@
 </histogram>
 
 <histogram name="PasswordManager.AcceptedSaveUpdateSubmissionIndicatorEvent"
-    enum="SubmissionIndicatorEvent" expires_after="2022-07-03">
+    enum="SubmissionIndicatorEvent" expires_after="2022-09-11">
   <owner>kazinova@google.com</owner>
   <owner>vasilii@chromium.org</owner>
   <summary>
@@ -436,7 +436,7 @@
 
 <histogram
     name="PasswordManager.AccountStorage.UnsyncedPasswordsFoundDuringSignOut"
-    units="passwords" expires_after="2022-05-31">
+    units="passwords" expires_after="2022-09-11">
   <owner>treib@chromium.org</owner>
   <owner>victorvianna@google.com</owner>
   <owner>mamir@chromium.org</owner>
@@ -572,7 +572,7 @@
 </histogram>
 
 <histogram name="PasswordManager.AddCredentialFromSettings.UserAction"
-    enum="AddCredentialFromSettingsUserInteractions" expires_after="M104">
+    enum="AddCredentialFromSettingsUserInteractions" expires_after="2022-09-11">
   <owner>vidhanj@google.com</owner>
   <owner>lizapopova@google.com</owner>
   <summary>
@@ -593,7 +593,7 @@
 </histogram>
 
 <histogram name="PasswordManager.AffiliationBackend.FirstFetchDelay" units="ms"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>vsemeniuk@google.com</owner>
   <owner>vasilii@chromium.org</owner>
   <summary>
@@ -626,7 +626,7 @@
 </histogram>
 
 <histogram name="PasswordManager.AffiliationFetcher.FetchErrorCode"
-    enum="NetErrorCodes" expires_after="2022-07-03">
+    enum="NetErrorCodes" expires_after="2022-09-11">
   <owner>vsemeniuk@google.com</owner>
   <owner>vasilii@chromium.org</owner>
   <summary>
@@ -637,7 +637,7 @@
 </histogram>
 
 <histogram name="PasswordManager.AffiliationFetcher.FetchHttpResponseCode"
-    enum="HttpResponseCode" expires_after="2022-07-03">
+    enum="HttpResponseCode" expires_after="2022-09-11">
   <owner>vsemeniuk@google.com</owner>
   <owner>vasilii@chromium.org</owner>
   <summary>
@@ -1312,7 +1312,7 @@
 </histogram>
 
 <histogram name="PasswordManager.DeleteUndecryptableLoginsReturnValue"
-    enum="DeleteCorruptedPasswordsResult" expires_after="2022-07-03">
+    enum="DeleteCorruptedPasswordsResult" expires_after="2022-09-11">
   <owner>vasilii@chromium.org</owner>
   <owner>kazinova@google.com</owner>
   <summary>
@@ -1323,7 +1323,7 @@
 </histogram>
 
 <histogram name="PasswordManager.DropdownShown.OffTheRecord" units="units"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>rhalavati@chromium.org</owner>
   <owner>chrome-privacy-core@google.com</owner>
   <summary>
@@ -1503,7 +1503,7 @@
 </histogram>
 
 <histogram name="PasswordManager.GeneratedFormHasNoFormManager"
-    enum="BooleanFormManager" expires_after="2022-07-03">
+    enum="BooleanFormManager" expires_after="2022-09-11">
   <owner>kazinova@google.com</owner>
   <owner>kolos@chromium.org</owner>
   <summary>
@@ -1525,7 +1525,7 @@
 </histogram>
 
 <histogram name="PasswordManager.HttpPasswordMigrationCount"
-    units="saved credentials" expires_after="2022-07-03">
+    units="saved credentials" expires_after="2022-09-11">
   <owner>kazinova@google.com</owner>
   <owner>vasilii@chromium.org</owner>
   <summary>
@@ -1583,7 +1583,7 @@
 </histogram>
 
 <histogram name="PasswordManager.IsSyncPasswordHashSaved"
-    enum="IsSyncPasswordHashSaved" expires_after="2022-07-03">
+    enum="IsSyncPasswordHashSaved" expires_after="2022-09-11">
   <owner>vakh@chromium.org</owner>
   <owner>chrome-safebrowsing-alerts@google.com</owner>
   <summary>
@@ -1605,7 +1605,7 @@
 </histogram>
 
 <histogram name="PasswordManager.ItemSelected.OffTheRecord" units="units"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>rhalavati@chromium.org</owner>
   <owner>chrome-privacy-core@google.com</owner>
   <summary>
@@ -1789,7 +1789,7 @@
 </histogram>
 
 <histogram name="PasswordManager.MediationRequired"
-    enum="CredentialManagerGetResult" expires_after="2022-07-03">
+    enum="CredentialManagerGetResult" expires_after="2022-09-11">
   <owner>kazinova@google.com</owner>
   <owner>vasilii@chromium.org</owner>
   <summary>
@@ -1810,7 +1810,7 @@
 </histogram>
 
 <histogram name="PasswordManager.MergeSyncData.AddLoginSyncError"
-    enum="PasswordAddLoginSyncError" expires_after="2022-05-01">
+    enum="PasswordAddLoginSyncError" expires_after="2022-09-11">
   <owner>mamir@chromium.org</owner>
   <owner>mastiz@chromium.org</owner>
   <summary>
@@ -1978,7 +1978,7 @@
 </histogram>
 
 <histogram name="PasswordManager.PasswordStore.OnLoginsChanged" enum="Boolean"
-    expires_after="2022-06-30">
+    expires_after="2022-09-11">
   <owner>fhorschig@chromium.org</owner>
   <owner>vsemeniuk@google.com</owner>
   <summary>
@@ -1989,7 +1989,7 @@
 </histogram>
 
 <histogram name="PasswordManager.PasswordStore.OnLoginsRetained"
-    enum="LoginsChangedTrigger" expires_after="2022-06-30">
+    enum="LoginsChangedTrigger" expires_after="2022-09-11">
   <owner>fhorschig@chromium.org</owner>
   <owner>vsemeniuk@google.com</owner>
   <summary>
@@ -2004,7 +2004,7 @@
 
 <histogram
     name="PasswordManager.PasswordStoreAndroidBackend.ClearAllLocalPasswords.LoginsToRemove"
-    units="count" expires_after="2022-06-30">
+    units="count" expires_after="2022-09-11">
   <owner>fhorschig@chromium.org</owner>
   <owner>vsemeniuk@google.com</owner>
   <summary>
@@ -2406,7 +2406,7 @@
 </histogram>
 
 <histogram name="PasswordManager.SavingOnUsernameFirstFlow"
-    enum="SavingOnUsernameFirstFlow" expires_after="2022-05-01">
+    enum="SavingOnUsernameFirstFlow" expires_after="2022-09-11">
   <owner>kazinova@google.com</owner>
   <owner>vasilii@chromium.org</owner>
   <summary>
@@ -2417,7 +2417,7 @@
 </histogram>
 
 <histogram name="PasswordManager.StoreDecryptionResult"
-    enum="PasswordDecryptionResult" expires_after="2022-07-03">
+    enum="PasswordDecryptionResult" expires_after="2022-09-11">
   <owner>cfroussios@chromium.org</owner>
   <owner>kazinova@google.com</owner>
   <summary>
@@ -2482,7 +2482,7 @@
 </histogram>
 
 <histogram name="PasswordManager.SuccessfulSubmissionIndicatorEvent"
-    enum="SubmissionIndicatorEvent" expires_after="2022-07-03">
+    enum="SubmissionIndicatorEvent" expires_after="2022-09-11">
   <owner>kazinova@google.com</owner>
   <owner>vasilii@chromium.org</owner>
   <summary>
@@ -2608,7 +2608,7 @@
 </histogram>
 
 <histogram name="PasswordManager.UnifiedPasswordManager.WasMigrationDone"
-    enum="BooleanSuccess" expires_after="2022-06-30">
+    enum="BooleanSuccess" expires_after="2022-09-11">
   <owner>fhorschig@chromium.org</owner>
   <owner>vsemeniuk@google.com</owner>
   <summary>
@@ -2702,7 +2702,7 @@
 </histogram>
 
 <histogram name="PasswordManager.WeakCheck.Time" units="ms"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>kazinova@google.com</owner>
   <owner>vasilii@chromium.org</owner>
   <summary>The time it took to complete the passwords weak check.</summary>
@@ -2934,7 +2934,7 @@
 </histogram>
 
 <histogram name="PasswordProtection.DomFeatureExtractionDuration" units="ms"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>drubery@chromium.org</owner>
   <owner>chrome-safebrowsing-alerts@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/payment/histograms.xml b/tools/metrics/histograms/metadata/payment/histograms.xml
index a31fb32..2f9c555 100644
--- a/tools/metrics/histograms/metadata/payment/histograms.xml
+++ b/tools/metrics/histograms/metadata/payment/histograms.xml
@@ -37,7 +37,7 @@
 </histogram>
 
 <histogram name="PaymentRequest.CheckoutFunnel"
-    enum="PaymentRequestCheckoutFunnelSteps" expires_after="2022-07-11">
+    enum="PaymentRequestCheckoutFunnelSteps" expires_after="2022-09-11">
   <owner>rouslan@chromium.org</owner>
   <owner>web-payments-team@google.com</owner>
   <summary>
@@ -298,7 +298,7 @@
 </histogram>
 
 <histogram name="PaymentRequest.TimeToCheckout.Completed" units="ms"
-    expires_after="2022-07-11">
+    expires_after="2022-09-11">
   <owner>rouslan@chromium.org</owner>
   <owner>web-payments-team@google.com</owner>
   <summary>
@@ -319,7 +319,7 @@
 </histogram>
 
 <histogram name="PaymentRequest.TimeToCheckout.Completed.SkippedShow"
-    units="ms" expires_after="2022-07-11">
+    units="ms" expires_after="2022-09-11">
 <!-- Name completed by histogram_suffixes name="PaymentRequestCompletedInstrument" -->
 
   <owner>rouslan@chromium.org</owner>
@@ -353,7 +353,7 @@
 </histogram>
 
 <histogram name="PaymentRequest.TransactionAmount.Completed"
-    enum="PaymentRequestTransactionSize" expires_after="2022-07-11">
+    enum="PaymentRequestTransactionSize" expires_after="2022-09-11">
   <owner>rouslan@chromium.org</owner>
   <owner>web-payments-team@google.com</owner>
   <summary>
@@ -363,7 +363,7 @@
 </histogram>
 
 <histogram name="PaymentRequest.TransactionAmount.Triggered"
-    enum="PaymentRequestTransactionSize" expires_after="2022-07-11">
+    enum="PaymentRequestTransactionSize" expires_after="2022-09-11">
   <owner>rouslan@chromium.org</owner>
   <owner>web-payments-team@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/permissions/histograms.xml b/tools/metrics/histograms/metadata/permissions/histograms.xml
index 5ed9c1d..6f9d9813 100644
--- a/tools/metrics/histograms/metadata/permissions/histograms.xml
+++ b/tools/metrics/histograms/metadata/permissions/histograms.xml
@@ -294,7 +294,7 @@
 </histogram>
 
 <histogram name="Permissions.CrowdDeny.SafeBrowsing.RequestDuration" units="ms"
-    expires_after="2022-07-11">
+    expires_after="2022-09-11">
   <owner>andypaicu@chromium.org</owner>
   <owner>engedy@chromium.org</owner>
   <owner>hkamila@chromium.org</owner>
@@ -306,7 +306,7 @@
 </histogram>
 
 <histogram name="Permissions.CrowdDeny.SafeBrowsing.Verdict"
-    enum="CrowdDenySafeBrowsingVerdict" expires_after="2022-07-03">
+    enum="CrowdDenySafeBrowsingVerdict" expires_after="2022-09-11">
   <owner>engedy@chromium.org</owner>
   <owner>src/components/permissions/PERMISSIONS_OWNERS</owner>
   <summary>
@@ -317,7 +317,7 @@
 </histogram>
 
 <histogram name="Permissions.DSE.AutoPermissionRevertTransition"
-    enum="AutoDSEPermissionRevertTransition" expires_after="2022-07-01">
+    enum="AutoDSEPermissionRevertTransition" expires_after="2022-09-11">
   <owner>andypaicu@chromium.org</owner>
   <owner>src/components/permissions/PERMISSIONS_OWNERS</owner>
   <summary>
@@ -329,7 +329,7 @@
 </histogram>
 
 <histogram name="Permissions.DSE.EffectiveSetting" enum="ContentSetting"
-    expires_after="2022-07-01">
+    expires_after="2022-09-11">
   <owner>andypaicu@chromium.org</owner>
   <owner>src/components/permissions/PERMISSIONS_OWNERS</owner>
   <summary>
@@ -491,7 +491,7 @@
 </histogram>
 
 <histogram name="Permissions.PredictionService.PredictionSource"
-    enum="PermissionPredictionSource" expires_after="2022-06-30">
+    enum="PermissionPredictionSource" expires_after="2022-09-11">
   <owner>ravjit@chromium.org</owner>
   <owner>src/components/permissions/PERMISSIONS_OWNERS</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/platform/histograms.xml b/tools/metrics/histograms/metadata/platform/histograms.xml
index 10b172a1..969aee1 100644
--- a/tools/metrics/histograms/metadata/platform/histograms.xml
+++ b/tools/metrics/histograms/metadata/platform/histograms.xml
@@ -1261,7 +1261,7 @@
 </histogram>
 
 <histogram name="Platform.TPM.FirmwareUpdate.Result"
-    enum="TPMFirmwareUpdateResult" expires_after="2022-07-01">
+    enum="TPMFirmwareUpdateResult" expires_after="2022-09-11">
   <owner>mnissler@chromium.org</owner>
   <owner>cros-hwsec+uma@chromium.org</owner>
   <summary>Status of a complete TPM firmware update attempt.</summary>
@@ -1601,7 +1601,7 @@
 </histogram>
 
 <histogram name="PlatformThread.Mac.SucceededRealtimePeriod"
-    units="microseconds" expires_after="2022-07-03">
+    units="microseconds" expires_after="2022-09-11">
   <owner>olka@chromium.org</owner>
   <owner>handellm@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/power/histograms.xml b/tools/metrics/histograms/metadata/power/histograms.xml
index 27dc54df..bb0f4814 100644
--- a/tools/metrics/histograms/metadata/power/histograms.xml
+++ b/tools/metrics/histograms/metadata/power/histograms.xml
@@ -1042,7 +1042,7 @@
 </histogram>
 
 <histogram name="Power.IOPMPowerSource.SamplingEventDelta" units="ms"
-    expires_after="2022-07-11">
+    expires_after="2022-09-11">
   <owner>pmonette@chromium.org</owner>
   <owner>catan-team@chromium.org</owner>
   <summary>
@@ -1053,7 +1053,7 @@
 </histogram>
 
 <histogram name="Power.IOPMPowerSource.SamplingEventDelta.MediumTimes"
-    units="ms" expires_after="2022-07-11">
+    units="ms" expires_after="2022-09-11">
   <owner>pmonette@chromium.org</owner>
   <owner>catan-team@chromium.org</owner>
   <summary>
@@ -1136,7 +1136,7 @@
   </summary>
 </histogram>
 
-<histogram base="true" name="Power.Mac" units="mW" expires_after="2022-11-30">
+<histogram base="true" name="Power.Mac" units="mW" expires_after="2022-09-11">
   <owner>olivierli@chromium.org</owner>
   <owner>markchang@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/prefetch/histograms.xml b/tools/metrics/histograms/metadata/prefetch/histograms.xml
index 02b71f6..8ce6b2b 100644
--- a/tools/metrics/histograms/metadata/prefetch/histograms.xml
+++ b/tools/metrics/histograms/metadata/prefetch/histograms.xml
@@ -28,7 +28,7 @@
 </variants>
 
 <histogram name="PrefetchProxy.AfterClick.Mainframe.CookieWaitTime" units="ms"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>robertogden@chromium.org</owner>
   <owner>ryansturm@chromium.org</owner>
   <owner>tbansal@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/print/histograms.xml b/tools/metrics/histograms/metadata/print/histograms.xml
index 5a4bc193..797df9d2 100644
--- a/tools/metrics/histograms/metadata/print/histograms.xml
+++ b/tools/metrics/histograms/metadata/print/histograms.xml
@@ -274,7 +274,7 @@
 </histogram>
 
 <histogram name="PrintPreview.RendererError" enum="PrintPreviewFailureType"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>thestig@chromium.org</owner>
   <owner>dhoss@chromium.org</owner>
   <summary>
@@ -290,7 +290,7 @@
 </histogram>
 
 <histogram name="PrintPreview.RenderToPDFTime" units="ms"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>thestig@chromium.org</owner>
   <owner>awscreen@chromium.org</owner>
   <summary>Time taken to render to PDF for print preview.</summary>
diff --git a/tools/metrics/histograms/metadata/profile/histograms.xml b/tools/metrics/histograms/metadata/profile/histograms.xml
index 3a386b6..7e9966d1 100644
--- a/tools/metrics/histograms/metadata/profile/histograms.xml
+++ b/tools/metrics/histograms/metadata/profile/histograms.xml
@@ -85,7 +85,7 @@
 </histogram>
 
 <histogram name="Profile.Avatar" enum="ProfileAvatar"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>jkrcal@chromium.org</owner>
   <owner>droger@chromium.org</owner>
   <summary>The frequency of selection of each avatar.</summary>
@@ -479,7 +479,7 @@
 </histogram>
 
 <histogram base="true" name="Profile.State.Avatar" enum="ProfileAvatarState"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>jkrcal@chromium.org</owner>
   <owner>droger@chromium.org</owner>
   <summary>
@@ -500,7 +500,7 @@
 </histogram>
 
 <histogram base="true" name="Profile.State.Name" enum="ProfileNameState"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>jkrcal@chromium.org</owner>
   <owner>droger@chromium.org</owner>
   <summary>
@@ -739,7 +739,7 @@
 </histogram>
 
 <histogram name="ProfilePicker.Shown" enum="ProfilePickerEntryPoint"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>msalama@chromium.org</owner>
   <owner>chrome-signin-team@google.com</owner>
   <summary>
@@ -749,7 +749,7 @@
 </histogram>
 
 <histogram name="ProfilePicker.StartupTime.BeforeCreation" units="ms"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>alexilin@chromium.org</owner>
   <owner>dgn@chromium.org</owner>
   <owner>chrome-signin-team@google.com</owner>
@@ -761,7 +761,7 @@
 </histogram>
 
 <histogram name="ProfilePicker.StartupTime.FirstPaint" units="ms"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>alexilin@chromium.org</owner>
   <owner>dgn@chromium.org</owner>
   <owner>chrome-signin-team@google.com</owner>
@@ -787,7 +787,7 @@
 </histogram>
 
 <histogram name="ProfilePicker.StartupTime.MainViewInitialized" units="ms"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>alexilin@chromium.org</owner>
   <owner>dgn@chromium.org</owner>
   <owner>chrome-signin-team@google.com</owner>
@@ -798,7 +798,7 @@
 </histogram>
 
 <histogram name="ProfilePicker.StartupTime.WebViewCreated" units="ms"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>alexilin@chromium.org</owner>
   <owner>dgn@chromium.org</owner>
   <owner>chrome-signin-team@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/quota/histograms.xml b/tools/metrics/histograms/metadata/quota/histograms.xml
index dbd59de..8ecfc359 100644
--- a/tools/metrics/histograms/metadata/quota/histograms.xml
+++ b/tools/metrics/histograms/metadata/quota/histograms.xml
@@ -54,7 +54,7 @@
 </histogram>
 
 <histogram name="Quota.AvailableDiskSpace2" units="MB"
-    expires_after="2022-07-07">
+    expires_after="2022-09-11">
   <owner>jarrydg@chromium.org</owner>
   <owner>chrome-owp-storage@google.com</owner>
   <summary>
@@ -63,6 +63,13 @@
   </summary>
 </histogram>
 
+<histogram name="Quota.DatabaseError" enum="SqliteLoggedResultCode"
+    expires_after="2023-03-14">
+  <owner>ayui@chromium.org</owner>
+  <owner>chrome-owp-storage@google.com</owner>
+  <summary>Errors reported by SQLite while using the quota database.</summary>
+</histogram>
+
 <histogram name="Quota.DiskspaceShortage" units="MB" expires_after="2022-08-21">
   <owner>jarrydg@chromium.org</owner>
   <owner>chrome-owp-storage@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/renderer/histograms.xml b/tools/metrics/histograms/metadata/renderer/histograms.xml
index 1ed5e54..2df6fdf 100644
--- a/tools/metrics/histograms/metadata/renderer/histograms.xml
+++ b/tools/metrics/histograms/metadata/renderer/histograms.xml
@@ -36,7 +36,7 @@
 </variants>
 
 <histogram name="Renderer.BrowserLaunchToRunLoopStart" units="ms"
-    expires_after="M104">
+    expires_after="2022-09-11">
   <owner>sky@chromium.org</owner>
   <owner>jam@chromium.org</owner>
   <summary>
@@ -255,7 +255,7 @@
 </histogram>
 
 <histogram name="Renderer.Font.PrimaryFont.DomContentLoaded.Style" units="ms"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>kojii@chromium.org</owner>
   <owner>tkent@chromium.org</owner>
   <owner>yosin@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/safe_browsing/histograms.xml b/tools/metrics/histograms/metadata/safe_browsing/histograms.xml
index 4c4dc21..8b3e159 100644
--- a/tools/metrics/histograms/metadata/safe_browsing/histograms.xml
+++ b/tools/metrics/histograms/metadata/safe_browsing/histograms.xml
@@ -159,7 +159,7 @@
 
 <histogram
     name="SafeBrowsing.BrowserThrottle.IsCheckCompletedOnProcessResponse"
-    enum="BooleanCompleted" expires_after="2022-07-11">
+    enum="BooleanCompleted" expires_after="2022-09-11">
   <owner>xinghuilu@chromium.org</owner>
   <owner>chrome-safebrowsing-alerts@google.com</owner>
   <summary>
@@ -232,7 +232,7 @@
 </histogram>
 
 <histogram name="SafeBrowsing.CookieAgeHours" units="hours"
-    expires_after="2022-07-07">
+    expires_after="2022-09-11">
   <owner>drubery@chromium.org</owner>
   <owner>chrome-safebrowsing-alerts@google.com</owner>
   <summary>
@@ -362,7 +362,7 @@
 </histogram>
 
 <histogram name="SafeBrowsing.DeepScan.Download.Success.Duration" units="ms"
-    expires_after="2022-06-26">
+    expires_after="2022-09-11">
   <owner>domfc@chromium.org</owner>
   <owner>webprotect-team@google.com</owner>
   <summary>
@@ -999,7 +999,7 @@
 </histogram>
 
 <histogram name="SafeBrowsing.EsbDisabled.LastEnabledInterval" units="days"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>xinghuilu@chromium.org</owner>
   <owner>chrome-safebrowsing-alerts@google.com</owner>
   <summary>
@@ -1434,7 +1434,7 @@
 </histogram>
 
 <histogram name="SafeBrowsing.Pref.Enhanced" enum="BooleanEnabled"
-    expires_after="2022-07-11">
+    expires_after="2022-09-11">
   <owner>drubery@chromium.org</owner>
   <owner>chrome-safebrowsing-alerts@google.com</owner>
   <summary>
@@ -1446,7 +1446,7 @@
 </histogram>
 
 <histogram name="SafeBrowsing.Pref.Extended" enum="BooleanEnabled"
-    expires_after="2022-07-11">
+    expires_after="2022-09-11">
   <owner>vakh@chromium.org</owner>
   <owner>chrome-safebrowsing-alerts@google.com</owner>
   <summary>
@@ -1458,7 +1458,7 @@
 </histogram>
 
 <histogram name="SafeBrowsing.Pref.General" enum="BooleanEnabled"
-    expires_after="2022-07-11">
+    expires_after="2022-09-11">
   <owner>vakh@chromium.org</owner>
   <owner>chrome-safebrowsing-alerts@google.com</owner>
   <summary>
@@ -1528,7 +1528,7 @@
 </histogram>
 
 <histogram name="SafeBrowsing.RT.AllowlistSizeTooSmall"
-    enum="BooleanUnavailable" expires_after="2022-07-03">
+    enum="BooleanUnavailable" expires_after="2022-09-11">
   <owner>xinghuilu@chromium.org</owner>
   <owner>chrome-safebrowsing-alerts@google.com</owner>
   <summary>
@@ -1552,7 +1552,7 @@
 </histogram>
 
 <histogram name="SafeBrowsing.RT.Backoff.State" enum="BooleanEnabled"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>vakh@chromium.org</owner>
   <owner>chrome-safebrowsing-alerts@google.com</owner>
   <summary>
@@ -1606,7 +1606,7 @@
 </histogram>
 
 <histogram name="SafeBrowsing.RT.GetCache.Time" units="ms"
-    expires_after="2022-07-11">
+    expires_after="2022-09-11">
   <owner>xinghuilu@chromium.org</owner>
   <owner>chrome-safebrowsing-alerts@google.com</owner>
   <summary>
@@ -1617,7 +1617,7 @@
 </histogram>
 
 <histogram name="SafeBrowsing.RT.GetCacheResult"
-    enum="SafeBrowsingRTLookupResponseVerdictType" expires_after="2022-07-11">
+    enum="SafeBrowsingRTLookupResponseVerdictType" expires_after="2022-09-11">
   <owner>xinghuilu@chromium.org</owner>
   <owner>chrome-safebrowsing-alerts@google.com</owner>
   <summary>
@@ -1825,7 +1825,7 @@
 </histogram>
 
 <histogram name="SafeBrowsing.RT.SampledRequestSent" units="Boolean"
-    expires_after="2022-07-06">
+    expires_after="2022-09-11">
   <owner>zackhan@chromium.org</owner>
   <owner>chrome-safebrowsing-alerts@google.com</owner>
   <summary>
@@ -1959,7 +1959,7 @@
 </histogram>
 
 <histogram name="SafeBrowsing.TokenFetcher.ErrorType"
-    enum="GoogleServiceAuthError" expires_after="2022-07-11">
+    enum="GoogleServiceAuthError" expires_after="2022-09-11">
   <owner>xinghuilu@chromium.org</owner>
   <owner>chrome-safebrowsing-alerts@google.com</owner>
   <summary>
@@ -1981,7 +1981,7 @@
 </histogram>
 
 <histogram name="SafeBrowsing.Triggers.SuspiciousSite.Event"
-    enum="SuspiciousSiteTriggerEvent" expires_after="2022-07-11">
+    enum="SuspiciousSiteTriggerEvent" expires_after="2022-09-11">
   <owner>vakh@chromium.org</owner>
   <owner>chrome-safebrowsing-alerts@google.com</owner>
   <summary>
@@ -2038,7 +2038,7 @@
 </histogram>
 
 <histogram name="SafeBrowsing.V4GetHash.Network.Result"
-    enum="CombinedHttpResponseAndNetErrorCode" expires_after="2022-07-11">
+    enum="CombinedHttpResponseAndNetErrorCode" expires_after="2022-09-11">
   <owner>vakh@google.com</owner>
   <owner>kcarattini@google.com</owner>
   <owner>chrome-safebrowsing-alerts@google.com</owner>
@@ -2134,7 +2134,7 @@
 </histogram>
 
 <histogram name="SafeBrowsing.V4ProcessFullUpdate.ApplyUpdateDuration"
-    units="ms" expires_after="2022-06-26">
+    units="ms" expires_after="2022-09-11">
   <owner>drubery@chromium.org</owner>
   <owner>chrome-safebrowsing-alerts@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/sb_client/histograms.xml b/tools/metrics/histograms/metadata/sb_client/histograms.xml
index 4955101..d645e083 100644
--- a/tools/metrics/histograms/metadata/sb_client/histograms.xml
+++ b/tools/metrics/histograms/metadata/sb_client/histograms.xml
@@ -35,7 +35,7 @@
 </variants>
 
 <histogram name="SBClientDownload.CheckDownloadStats"
-    enum="SBClientDownloadCheckDownloadStats" expires_after="2022-07-11">
+    enum="SBClientDownloadCheckDownloadStats" expires_after="2022-09-11">
   <owner>vakh@chromium.org</owner>
   <owner>chrome-safebrowsing-alerts@google.com</owner>
   <owner>mattm@chromium.org</owner>
@@ -52,7 +52,7 @@
 </histogram>
 
 <histogram name="SBClientDownload.DeepScanTrigger" enum="SBDeepScanTriggers"
-    expires_after="2022-06-26">
+    expires_after="2022-09-11">
   <owner>drubery@chromium.org</owner>
   <owner>chrome-safebrowsing-alerts@google.com</owner>
   <summary>
@@ -511,7 +511,7 @@
 </histogram>
 
 <histogram name="SBClientPhishing.LocalModelDetectsPhishing"
-    enum="BooleanIsPhishing" expires_after="2022-07-11">
+    enum="BooleanIsPhishing" expires_after="2022-09-11">
   <owner>drubery@chromium.org</owner>
   <owner>chrome-safebrowsing-alerts@google.com</owner>
   <summary>
@@ -547,7 +547,7 @@
 </histogram>
 
 <histogram name="SBClientPhishing.ModelDynamicUpdateSuccess"
-    enum="BooleanSuccess" expires_after="2022-07-11">
+    enum="BooleanSuccess" expires_after="2022-09-11">
   <owner>drubery@chromium.org</owner>
   <owner>chrome-safebrowsing-alerts@google.com</owner>
   <summary>
@@ -557,7 +557,7 @@
 </histogram>
 
 <histogram name="SBClientPhishing.ModelDynamicUpdateVersion"
-    units="client phishing model version" expires_after="2022-07-11">
+    units="client phishing model version" expires_after="2022-09-11">
   <owner>drubery@chromium.org</owner>
   <owner>chrome-safebrowsing-alerts@google.com</owner>
   <summary>
@@ -596,7 +596,7 @@
 </histogram>
 
 <histogram name="SBClientPhishing.PhishingDetectorResult"
-    enum="ClientSidePhishingResult" expires_after="2022-07-11">
+    enum="ClientSidePhishingResult" expires_after="2022-09-11">
   <owner>drubery@chromium.org</owner>
   <owner>chrome-safebrowsing-alerts@google.com</owner>
   <summary>
@@ -653,7 +653,7 @@
 </histogram>
 
 <histogram name="SBClientPhishing.ServerModelDetectsPhishing"
-    enum="BooleanIsPhishing" expires_after="2022-07-11">
+    enum="BooleanIsPhishing" expires_after="2022-09-11">
   <owner>drubery@chromium.org</owner>
   <owner>chrome-safebrowsing-alerts@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/search/histograms.xml b/tools/metrics/histograms/metadata/search/histograms.xml
index 6e6b361b..3fe3a834 100644
--- a/tools/metrics/histograms/metadata/search/histograms.xml
+++ b/tools/metrics/histograms/metadata/search/histograms.xml
@@ -1494,7 +1494,7 @@
 </histogram>
 
 <histogram name="Search.RegionSearch.Lens.RegionAspectRatio"
-    enum="LensRegionSearchAspectRatio" expires_after="2022-07-03">
+    enum="LensRegionSearchAspectRatio" expires_after="2022-09-11">
   <owner>juanmojica@google.com</owner>
   <owner>benwgold@google.com</owner>
   <owner>stanfield@google.com</owner>
@@ -1506,7 +1506,7 @@
 </histogram>
 
 <histogram name="Search.RegionSearch.Lens.RegionViewportProportion" units="pct"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>juanmojica@google.com</owner>
   <owner>benwgold@google.com</owner>
   <owner>stanfield@google.com</owner>
@@ -1518,7 +1518,7 @@
 </histogram>
 
 <histogram name="Search.RegionSearch.Lens.Result"
-    enum="LensRegionSearchCaptureResult" expires_after="2022-07-03">
+    enum="LensRegionSearchCaptureResult" expires_after="2022-09-11">
   <owner>juanmojica@google.com</owner>
   <owner>benwgold@google.com</owner>
   <owner>stanfield@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/security/histograms.xml b/tools/metrics/histograms/metadata/security/histograms.xml
index 7b86c26..f7f5c43 100644
--- a/tools/metrics/histograms/metadata/security/histograms.xml
+++ b/tools/metrics/histograms/metadata/security/histograms.xml
@@ -304,7 +304,7 @@
 </histogram>
 
 <histogram name="Security.SafetyTips.GetDomainInfoTime" units="ms"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>jdeblasio@chromium.org</owner>
   <owner>estark@chromium.org</owner>
   <owner>blundell@chromium.org</owner>
@@ -316,7 +316,7 @@
 </histogram>
 
 <histogram name="Security.SafetyTips.GetReputationStatusWithEngagedSitesTime"
-    units="ms" expires_after="2022-07-03">
+    units="ms" expires_after="2022-09-11">
   <owner>jdeblasio@chromium.org</owner>
   <owner>estark@chromium.org</owner>
   <owner>blundell@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/segmentation_platform/histograms.xml b/tools/metrics/histograms/metadata/segmentation_platform/histograms.xml
index d163190..df95edd 100644
--- a/tools/metrics/histograms/metadata/segmentation_platform/histograms.xml
+++ b/tools/metrics/histograms/metadata/segmentation_platform/histograms.xml
@@ -394,7 +394,7 @@
 </histogram>
 
 <histogram name="SegmentationPlatform.SelectionFailedReason"
-    enum="SegmentationSelectionFailureReason" expires_after="M101">
+    enum="SegmentationSelectionFailureReason" expires_after="2022-09-11">
   <owner>ssid@chromium.org</owner>
   <owner>chrome-segmentation-platform@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/service/histograms.xml b/tools/metrics/histograms/metadata/service/histograms.xml
index 12ea1d6a..1464632 100644
--- a/tools/metrics/histograms/metadata/service/histograms.xml
+++ b/tools/metrics/histograms/metadata/service/histograms.xml
@@ -353,7 +353,7 @@
 </histogram>
 
 <histogram name="ServiceWorker.FetchEvent.MainResource.Status"
-    enum="ServiceWorkerStatusCode" expires_after="2022-07-03">
+    enum="ServiceWorkerStatusCode" expires_after="2022-09-11">
   <owner>wanderview@chromium.org</owner>
   <owner>asamidoi@chromium.org</owner>
   <owner>chrome-worker@google.com</owner>
@@ -377,7 +377,7 @@
 </histogram>
 
 <histogram name="ServiceWorker.FetchEvent.Subresource.Status"
-    enum="ServiceWorkerStatusCode" expires_after="2022-07-03">
+    enum="ServiceWorkerStatusCode" expires_after="2022-09-11">
   <owner>wanderview@chromium.org</owner>
   <owner>asamidoi@chromium.org</owner>
   <owner>chrome-worker@google.com</owner>
@@ -714,7 +714,7 @@
 </histogram>
 
 <histogram name="ServiceWorker.RegisteredStorageKeyCount" units="origins"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>wanderview@chromium.org</owner>
   <owner>asamidoi@chromium.org</owner>
   <owner>chrome-worker@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/settings/histograms.xml b/tools/metrics/histograms/metadata/settings/histograms.xml
index a6a612bb..3b9b225f 100644
--- a/tools/metrics/histograms/metadata/settings/histograms.xml
+++ b/tools/metrics/histograms/metadata/settings/histograms.xml
@@ -203,7 +203,7 @@
 </histogram>
 
 <histogram name="Settings.PrivacyGuide.NextNavigation"
-    enum="SettingsPrivacyGuideInteractions" expires_after="M104">
+    enum="SettingsPrivacyGuideInteractions" expires_after="2022-09-11">
   <owner>harrisonsean@chromium.org</owner>
   <owner>rainhard@chromium.org</owner>
   <owner>chrome-friendly-settings@google.com</owner>
@@ -304,7 +304,7 @@
 </histogram>
 
 <histogram name="Settings.SafetyCheck.Interactions"
-    enum="SettingsSafetyCheckInteractions" expires_after="2022-07-03">
+    enum="SettingsSafetyCheckInteractions" expires_after="2022-09-11">
   <owner>rainhard@chromium.org</owner>
   <owner>msramek@chromium.org</owner>
   <owner>anaudrey@chromium.org</owner>
@@ -337,7 +337,7 @@
 </histogram>
 
 <histogram name="Settings.SearchEngines.Interactions"
-    enum="SettingsSearchEnginesInteractions" expires_after="2022-07-03">
+    enum="SettingsSearchEnginesInteractions" expires_after="2022-09-11">
   <owner>yoangela@chromium.org</owner>
   <owner>chrome-omnibox-team@google.com</owner>
   <summary>
@@ -418,7 +418,7 @@
 </histogram>
 
 <histogram name="Settings.StartupPageEngineTypes"
-    enum="OmniboxSearchEngineType" expires_after="2022-07-03">
+    enum="OmniboxSearchEngineType" expires_after="2022-09-11">
   <owner>mpearson@chromium.org</owner>
   <owner>csharp@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/sharing/histograms.xml b/tools/metrics/histograms/metadata/sharing/histograms.xml
index 5de43cd..5f3cd14b 100644
--- a/tools/metrics/histograms/metadata/sharing/histograms.xml
+++ b/tools/metrics/histograms/metadata/sharing/histograms.xml
@@ -48,7 +48,7 @@
 </histogram>
 
 <histogram name="Sharing.ClickToCallAppsToShow" units="apps"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
 <!-- Name completed by histogram_suffixes name="SharingClickToCallUi" -->
 
   <owner>mvanouwerkerk@chromium.org</owner>
@@ -360,7 +360,7 @@
 </histogram>
 
 <histogram name="Sharing.ScreenshotsAndroid.IsEditorDismissedOnStart"
-    enum="Boolean" expires_after="2022-07-03">
+    enum="Boolean" expires_after="2022-09-11">
   <owner>jeffreycohen@chromium.org</owner>
   <owner>kristipark@chromium.org</owner>
   <owner>src/chrome/browser/share/OWNERS</owner>
@@ -472,7 +472,7 @@
 </histogram>
 
 <histogram name="Sharing.ShareTargetUpdate.DynamicUpdateResult"
-    enum="ShareTargetUpdateResult" expires_after="2022-07-11">
+    enum="ShareTargetUpdateResult" expires_after="2022-09-11">
   <owner>jeffreycohen@chromium.org</owner>
   <owner>kristipark@chromium.org</owner>
   <owner>src/chrome/browser/share/OWNERS</owner>
@@ -487,7 +487,7 @@
 </histogram>
 
 <histogram name="Sharing.ShareTargetUpdate.ResourceBundleResult"
-    enum="ShareTargetUpdateResult" expires_after="2022-07-03">
+    enum="ShareTargetUpdateResult" expires_after="2022-09-11">
   <owner>jeffreycohen@chromium.org</owner>
   <owner>kristipark@chromium.org</owner>
   <owner>src/chrome/browser/share/OWNERS</owner>
@@ -501,7 +501,7 @@
 </histogram>
 
 <histogram name="Sharing.SharingHubAndroid.CloseReason"
-    enum="BottomSheet.StateChangeReason" expires_after="2022-07-03">
+    enum="BottomSheet.StateChangeReason" expires_after="2022-09-11">
   <owner>ellyjones@chromium.org</owner>
   <owner>src/chrome/browser/share/OWNERS</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/signin/histograms.xml b/tools/metrics/histograms/metadata/signin/histograms.xml
index 5c2f8d3a..584da50 100644
--- a/tools/metrics/histograms/metadata/signin/histograms.xml
+++ b/tools/metrics/histograms/metadata/signin/histograms.xml
@@ -834,7 +834,7 @@
 </histogram>
 
 <histogram name="Signin.Reconciler.Operation" enum="SigninReconcilerOperation"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>msarda@chromium.org</owner>
   <owner>droger@chromium.org</owner>
   <summary>
@@ -976,7 +976,7 @@
 </histogram>
 
 <histogram name="Signin.SigninCompletedAccessPoint" enum="SigninAccessPoint"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>msarda@chromium.org</owner>
   <owner>bsazonov@chromium.org</owner>
   <owner>droger@chromium.org</owner>
@@ -985,7 +985,7 @@
 </histogram>
 
 <histogram name="Signin.SigninReason" enum="SigninReason"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>msarda@chromium.org</owner>
   <owner>bsazonov@chromium.org</owner>
   <owner>droger@chromium.org</owner>
@@ -994,7 +994,7 @@
 </histogram>
 
 <histogram name="Signin.SigninStartedAccessPoint" enum="SigninAccessPoint"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>msarda@chromium.org</owner>
   <owner>bsazonov@chromium.org</owner>
   <owner>droger@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/simple/histograms.xml b/tools/metrics/histograms/metadata/simple/histograms.xml
index c8de1e0..b5ff2325 100644
--- a/tools/metrics/histograms/metadata/simple/histograms.xml
+++ b/tools/metrics/histograms/metadata/simple/histograms.xml
@@ -174,7 +174,7 @@
 </histogram>
 
 <histogram name="SimpleCache.FileDescriptorLimiterAction"
-    enum="SimpleCache.FileDescriptorLimiterOp" expires_after="2022-07-01">
+    enum="SimpleCache.FileDescriptorLimiterOp" expires_after="2022-09-11">
   <owner>morlovich@chromium.org</owner>
   <owner>wanderview@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/stability/histograms.xml b/tools/metrics/histograms/metadata/stability/histograms.xml
index 7e380f5d..9dbdd0cb 100644
--- a/tools/metrics/histograms/metadata/stability/histograms.xml
+++ b/tools/metrics/histograms/metadata/stability/histograms.xml
@@ -23,7 +23,7 @@
 <histograms>
 
 <histogram name="Stability.Android.OomKillReverseRank" units="rank"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>boliu@chromium.org</owner>
   <owner>ssid@chromium.org</owner>
   <summary>
@@ -100,7 +100,7 @@
 </histogram>
 
 <histogram base="true" name="Stability.Android.SystemExitReason"
-    enum="AndroidProcessExitReason" expires_after="2022-07-03">
+    enum="AndroidProcessExitReason" expires_after="2022-09-11">
   <owner>boliu@chromium.org</owner>
   <owner>ssid@chromium.org</owner>
   <owner>wnwen@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/startup/histograms.xml b/tools/metrics/histograms/metadata/startup/histograms.xml
index efe4b03..55505efc 100644
--- a/tools/metrics/histograms/metadata/startup/histograms.xml
+++ b/tools/metrics/histograms/metadata/startup/histograms.xml
@@ -221,7 +221,7 @@
 </histogram>
 
 <histogram name="Startup.Android.FeedsLoadingPlaceholderShown.Instant"
-    units="ms" expires_after="2022-07-03">
+    units="ms" expires_after="2022-09-11">
   <owner>hanxi@chromium.org</owner>
   <owner>spdonghao@chromium.org</owner>
   <owner>wychen@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/subresource/histograms.xml b/tools/metrics/histograms/metadata/subresource/histograms.xml
index 6c67877..4df3099 100644
--- a/tools/metrics/histograms/metadata/subresource/histograms.xml
+++ b/tools/metrics/histograms/metadata/subresource/histograms.xml
@@ -23,7 +23,7 @@
 <histograms>
 
 <histogram name="SubresourceFilter.Actions2" enum="SubresourceFilterActions2"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>alexmt@chromium.org</owner>
   <owner>chrome-ads-histograms@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/sync/histograms.xml b/tools/metrics/histograms/metadata/sync/histograms.xml
index 389b7ff..bbd47e9 100644
--- a/tools/metrics/histograms/metadata/sync/histograms.xml
+++ b/tools/metrics/histograms/metadata/sync/histograms.xml
@@ -295,7 +295,7 @@
 </histogram>
 
 <histogram name="Sync.CryptographerPendingKeys"
-    enum="SyncCryptographerPendingKeysState" expires_after="2022-07-03">
+    enum="SyncCryptographerPendingKeysState" expires_after="2022-09-11">
   <owner>mastiz@chromium.org</owner>
   <owner>treib@chromium.org</owner>
   <component>Services&gt;Sync</component>
@@ -316,7 +316,7 @@
 </histogram>
 
 <histogram name="Sync.CustomEncryption" enum="SyncCustomEncryptionEvent"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>mastiz@chromium.org</owner>
   <owner>treib@chromium.org</owner>
   <component>Services&gt;Sync</component>
@@ -474,7 +474,7 @@
   </summary>
 </histogram>
 
-<histogram name="Sync.Local.FileSizeKB" units="KB" expires_after="2022-07-03">
+<histogram name="Sync.Local.FileSizeKB" units="KB" expires_after="2022-09-11">
   <owner>pastarmovj@chromium.org</owner>
   <component>Services&gt;Sync</component>
   <summary>
@@ -505,7 +505,7 @@
 </histogram>
 
 <histogram name="Sync.Local.RoamingProfileUnavailable" enum="BooleanError"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>pastarmovj@chromium.org</owner>
   <component>Services&gt;Sync</component>
   <summary>
@@ -896,7 +896,7 @@
 </histogram>
 
 <histogram name="Sync.RecordedUserEventType" enum="SyncUserEventType"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>mmoskvitin@google.com</owner>
   <owner>treib@chromium.org</owner>
   <component>Services&gt;Sync</component>
@@ -908,7 +908,7 @@
 </histogram>
 
 <histogram name="Sync.RedundantInvalidationPerModelType2" enum="SyncModelTypes"
-    expires_after="2022-05-01">
+    expires_after="2022-09-11">
   <owner>mmoskvitin@google.com</owner>
   <owner>treib@chromium.org</owner>
   <component>Services&gt;Sync</component>
@@ -920,7 +920,7 @@
 </histogram>
 
 <histogram name="Sync.ResetEngineReason" enum="SyncResetEngineReason"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>rushans@google.com</owner>
   <owner>treib@chromium.org</owner>
   <component>Services&gt;Sync</component>
@@ -1079,7 +1079,7 @@
 </histogram>
 
 <histogram name="Sync.TrustedVaultAccessTokenFetchSuccess" enum="Boolean"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>mmoskvitin@google.com</owner>
   <owner>mastiz@chromium.org</owner>
   <component>Services&gt;Sync</component>
@@ -1101,7 +1101,7 @@
 </histogram>
 
 <histogram name="Sync.TrustedVaultDeviceRegistrationState"
-    enum="TrustedVaultDeviceRegistrationState" expires_after="2022-07-03">
+    enum="TrustedVaultDeviceRegistrationState" expires_after="2022-09-11">
   <owner>mmoskvitin@google.com</owner>
   <owner>mastiz@chromium.org</owner>
   <component>Services&gt;Sync</component>
@@ -1113,7 +1113,7 @@
 </histogram>
 
 <histogram name="Sync.TrustedVaultErrorShownOnStartup" enum="Boolean"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>mmoskvitin@google.com</owner>
   <owner>mastiz@chromium.org</owner>
   <component>Services&gt;Sync</component>
@@ -1125,7 +1125,7 @@
 </histogram>
 
 <histogram name="Sync.TrustedVaultFetchedKeysCount" units="keys"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>mmoskvitin@google.com</owner>
   <owner>mastiz@chromium.org</owner>
   <component>Services&gt;Sync</component>
@@ -1135,7 +1135,7 @@
 </histogram>
 
 <histogram name="Sync.TrustedVaultFetchKeysAttempt"
-    enum="TrustedVaultFetchKeysAttempt" expires_after="2022-07-03">
+    enum="TrustedVaultFetchKeysAttempt" expires_after="2022-09-11">
   <owner>mmoskvitin@google.com</owner>
   <owner>mastiz@chromium.org</owner>
   <component>Services&gt;Sync</component>
@@ -1143,7 +1143,7 @@
 </histogram>
 
 <histogram name="Sync.TrustedVaultKeyRetrievalTrigger"
-    enum="TrustedVaultUserActionTrigger" expires_after="2022-05-01">
+    enum="TrustedVaultUserActionTrigger" expires_after="2022-09-11">
   <owner>mmoskvitin@google.com</owner>
   <owner>mastiz@chromium.org</owner>
   <component>Services&gt;Sync</component>
@@ -1155,7 +1155,7 @@
 </histogram>
 
 <histogram name="Sync.TrustedVaultLocalDataDecryptionIsSuccessful"
-    enum="BooleanSuccess" expires_after="2022-07-03">
+    enum="BooleanSuccess" expires_after="2022-09-11">
   <owner>mmoskvitin@google.com</owner>
   <owner>mastiz@chromium.org</owner>
   <component>Services&gt;Sync</component>
@@ -1166,7 +1166,7 @@
 </histogram>
 
 <histogram name="Sync.TrustedVaultLocalDataEncryptionIsSuccessful"
-    enum="BooleanSuccess" expires_after="2022-07-03">
+    enum="BooleanSuccess" expires_after="2022-09-11">
   <owner>mmoskvitin@google.com</owner>
   <owner>mastiz@chromium.org</owner>
   <component>Services&gt;Sync</component>
@@ -1189,7 +1189,7 @@
 </histogram>
 
 <histogram name="Sync.TrustedVaultRecoverabilityDegradedOnStartup"
-    enum="Boolean" expires_after="2022-07-03">
+    enum="Boolean" expires_after="2022-09-11">
   <owner>mmoskvitin@google.com</owner>
   <owner>mastiz@chromium.org</owner>
   <component>Services&gt;Sync</component>
@@ -1202,7 +1202,7 @@
 </histogram>
 
 <histogram name="Sync.TrustedVaultURLFetchResponse"
-    enum="CombinedHttpResponseAndNetErrorCode" expires_after="2022-07-03">
+    enum="CombinedHttpResponseAndNetErrorCode" expires_after="2022-09-11">
   <owner>mmoskvitin@google.com</owner>
   <owner>mastiz@chromium.org</owner>
   <component>Services&gt;Sync</component>
diff --git a/tools/metrics/histograms/metadata/tab/histograms.xml b/tools/metrics/histograms/metadata/tab/histograms.xml
index 804484c..78a11d7 100644
--- a/tools/metrics/histograms/metadata/tab/histograms.xml
+++ b/tools/metrics/histograms/metadata/tab/histograms.xml
@@ -358,7 +358,7 @@
 </histogram>
 
 <histogram name="Tab.CloseAllTabsDialog.ClosedAllTabs" units="Boolean"
-    expires_after="2022-06-30">
+    expires_after="2022-09-11">
   <owner>ckitagawa@chromium.org</owner>
   <owner>fredmello@chromium.org</owner>
   <summary>
@@ -658,7 +658,7 @@
 </histogram>
 
 <histogram name="Tab.Screenshot.Action" enum="TabScreenshotAction"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>skare@chromium.org</owner>
   <summary>
     Records actions taken after one or more screenshots of a page were taken.
@@ -1184,7 +1184,7 @@
 </histogram>
 
 <histogram name="TabManager.Discarding.DiscardToReloadTime" units="ms"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>chrisha@chromium.org</owner>
   <owner>catan-team@chromium.org</owner>
   <summary>
@@ -2847,7 +2847,7 @@
   </summary>
 </histogram>
 
-<histogram name="Tabs.TabState.LoadTime" units="ms" expires_after="2022-07-03">
+<histogram name="Tabs.TabState.LoadTime" units="ms" expires_after="2022-09-11">
   <owner>yusufo@chromium.org</owner>
   <owner>nyquist@chromium.org</owner>
   <owner>dtrainor@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/translate/histograms.xml b/tools/metrics/histograms/metadata/translate/histograms.xml
index 840329f..33f2f31 100644
--- a/tools/metrics/histograms/metadata/translate/histograms.xml
+++ b/tools/metrics/histograms/metadata/translate/histograms.xml
@@ -33,7 +33,7 @@
 </histogram>
 
 <histogram name="Translate.AlwaysTranslateLang" units="units"
-    expires_after="2022-06-30">
+    expires_after="2022-09-11">
   <owner>megjablon@google.com</owner>
   <owner>chrome-language@google.com</owner>
   <summary>
@@ -43,7 +43,7 @@
 </histogram>
 
 <histogram name="Translate.ApplicationStart.AlwaysTranslateLanguage"
-    enum="LocaleCodeISO639" expires_after="2022-06-30">
+    enum="LocaleCodeISO639" expires_after="2022-09-11">
   <owner>curranmax@google.com</owner>
   <owner>megjablon@google.com</owner>
   <owner>chrome-language@google.com</owner>
@@ -71,7 +71,7 @@
 </histogram>
 
 <histogram name="Translate.ApplicationStart.NeverTranslateLanguage"
-    enum="LocaleCodeISO639" expires_after="2022-06-30">
+    enum="LocaleCodeISO639" expires_after="2022-09-11">
   <owner>curranmax@google.com</owner>
   <owner>megjablon@google.com</owner>
   <owner>chrome-language@google.com</owner>
@@ -86,7 +86,7 @@
 </histogram>
 
 <histogram name="Translate.ApplicationStart.NeverTranslateLanguage.Count"
-    units="languages" expires_after="2022-06-30">
+    units="languages" expires_after="2022-09-11">
   <owner>curranmax@google.com</owner>
   <owner>megjablon@google.com</owner>
   <owner>chrome-language@google.com</owner>
@@ -99,7 +99,7 @@
 </histogram>
 
 <histogram name="Translate.ApplicationStart.NeverTranslateSite.Count"
-    units="sites" expires_after="2022-06-30">
+    units="sites" expires_after="2022-09-11">
   <owner>curranmax@google.com</owner>
   <owner>megjablon@google.com</owner>
   <owner>chrome-language@google.com</owner>
@@ -112,13 +112,13 @@
 </histogram>
 
 <histogram name="Translate.BubbleUiEvent" enum="TranslateBubbleUiEvent"
-    expires_after="2022-06-30">
+    expires_after="2022-09-11">
   <owner>groby@google.com</owner>
   <owner>chrome-language@google.com</owner>
   <summary>Tracks UI events related to the translate bubble.</summary>
 </histogram>
 
-<histogram name="Translate.CaptureText" units="ms" expires_after="2022-06-30">
+<histogram name="Translate.CaptureText" units="ms" expires_after="2022-09-11">
   <owner>sclittle@google.com</owner>
   <owner>megjablon@google.com</owner>
   <owner>chrome-language@google.com</owner>
@@ -141,7 +141,7 @@
 </histogram>
 
 <histogram name="Translate.CLD3.LanguagePercentage" units="%"
-    expires_after="2022-06-30">
+    expires_after="2022-09-11">
   <owner>megjablon@google.com</owner>
   <owner>chrome-language@google.com</owner>
   <summary>
@@ -162,14 +162,14 @@
 </histogram>
 
 <histogram name="Translate.CompactInfobar.Event" enum="TranslateCompactUIEvent"
-    expires_after="2022-06-30">
+    expires_after="2022-09-11">
   <owner>megjablon@google.com</owner>
   <owner>chrome-language@google.com</owner>
   <summary>Various user actions performed in the translate infobar.</summary>
 </histogram>
 
 <histogram name="Translate.CompactInfobar.Language.AlwaysTranslate"
-    enum="LocaleCodeISO639" expires_after="2022-06-30">
+    enum="LocaleCodeISO639" expires_after="2022-09-11">
   <owner>megjablon@google.com</owner>
   <owner>chrome-language@google.com</owner>
   <summary>
@@ -179,7 +179,7 @@
 </histogram>
 
 <histogram name="Translate.CompactInfobar.Language.MoreLanguages"
-    enum="LocaleCodeISO639" expires_after="2022-06-30">
+    enum="LocaleCodeISO639" expires_after="2022-09-11">
   <owner>megjablon@google.com</owner>
   <owner>chrome-language@google.com</owner>
   <summary>
@@ -188,7 +188,7 @@
 </histogram>
 
 <histogram name="Translate.CompactInfobar.Language.NeverTranslate"
-    enum="LocaleCodeISO639" expires_after="2022-06-30">
+    enum="LocaleCodeISO639" expires_after="2022-09-11">
   <owner>megjablon@google.com</owner>
   <owner>chrome-language@google.com</owner>
   <summary>
@@ -198,7 +198,7 @@
 </histogram>
 
 <histogram name="Translate.CompactInfobar.Language.PageNotIn"
-    enum="LocaleCodeISO639" expires_after="2022-06-30">
+    enum="LocaleCodeISO639" expires_after="2022-09-11">
   <owner>megjablon@google.com</owner>
   <owner>chrome-language@google.com</owner>
   <summary>
@@ -217,7 +217,7 @@
 </histogram>
 
 <histogram name="Translate.DeclineTranslate" units="units"
-    expires_after="2022-06-30">
+    expires_after="2022-09-11">
   <owner>megjablon@google.com</owner>
   <owner>chrome-language@google.com</owner>
   <summary>
@@ -227,7 +227,7 @@
 </histogram>
 
 <histogram name="Translate.DeclineTranslateCloseInfobar" units="units"
-    expires_after="2022-06-30">
+    expires_after="2022-09-11">
   <owner>megjablon@google.com</owner>
   <owner>chrome-language@google.com</owner>
   <summary>
@@ -237,7 +237,7 @@
 </histogram>
 
 <histogram name="Translate.ExplicitLanguageAsk.Event"
-    enum="TranslateExplicitAskPromptEventType" expires_after="2022-06-30">
+    enum="TranslateExplicitAskPromptEventType" expires_after="2022-09-11">
   <owner>megjablon@google.com</owner>
   <owner>chrome-language@google.com</owner>
   <summary>
@@ -247,7 +247,7 @@
 </histogram>
 
 <histogram name="Translate.ExplicitLanguageAsk.LanguageAdded"
-    enum="LocaleCodeISO639" expires_after="2022-06-30">
+    enum="LocaleCodeISO639" expires_after="2022-09-11">
   <owner>megjablon@google.com</owner>
   <owner>chrome-language@google.com</owner>
   <summary>
@@ -257,7 +257,7 @@
 </histogram>
 
 <histogram name="Translate.ExplicitLanguageAsk.LanguageRemoved"
-    enum="LocaleCodeISO639" expires_after="2022-06-30">
+    enum="LocaleCodeISO639" expires_after="2022-09-11">
   <owner>megjablon@google.com</owner>
   <owner>chrome-language@google.com</owner>
   <summary>
@@ -306,7 +306,7 @@
 </histogram>
 
 <histogram name="Translate.InitiationStatus.v2"
-    enum="TranslateInitiationStatus" expires_after="2022-06-30">
+    enum="TranslateInitiationStatus" expires_after="2022-09-11">
   <owner>megjablon@google.com</owner>
   <owner>chrome-language@google.com</owner>
   <summary>
@@ -317,7 +317,7 @@
 </histogram>
 
 <histogram name="Translate.LanguageDetection.ContentLength" units="characters"
-    expires_after="2022-06-30">
+    expires_after="2022-09-11">
   <owner>sclittle@chromium.org</owner>
   <owner>megjablon@chromium.org</owner>
   <owner>chrome-language@google.com</owner>
@@ -327,7 +327,7 @@
 </histogram>
 
 <histogram name="Translate.LanguageDetection.LanguageVerification"
-    enum="TranslateLanguageVerification" expires_after="2022-06-30">
+    enum="TranslateLanguageVerification" expires_after="2022-09-11">
   <owner>curranmax@google.com</owner>
   <owner>megjablon@google.com</owner>
   <owner>chrome-language@google.com</owner>
@@ -373,7 +373,7 @@
 </histogram>
 
 <histogram name="Translate.MenuTranslation.IsAvailable" enum="BooleanAvailable"
-    expires_after="2022-06-30">
+    expires_after="2022-09-11">
   <owner>cuianthony@google.com</owner>
   <owner>chrome-language@google.com</owner>
   <summary>
@@ -383,7 +383,7 @@
 </histogram>
 
 <histogram name="Translate.MenuTranslation.UnavailableReasons"
-    enum="MenuTranslationUnavailableReason" expires_after="2022-06-30">
+    enum="MenuTranslationUnavailableReason" expires_after="2022-09-11">
   <owner>cuianthony@google.com</owner>
   <owner>chrome-language@google.com</owner>
   <summary>
@@ -396,7 +396,7 @@
 </histogram>
 
 <histogram name="Translate.ModifyOriginalLang" units="units"
-    expires_after="2022-06-30">
+    expires_after="2022-09-11">
   <owner>megjablon@google.com</owner>
   <owner>chrome-language@google.com</owner>
   <summary>
@@ -406,7 +406,7 @@
 </histogram>
 
 <histogram name="Translate.ModifyTargetLang" units="units"
-    expires_after="2022-06-30">
+    expires_after="2022-09-11">
   <owner>megjablon@google.com</owner>
   <owner>chrome-language@google.com</owner>
   <summary>
@@ -416,7 +416,7 @@
 </histogram>
 
 <histogram name="Translate.NeverTranslateLang" units="units"
-    expires_after="2022-06-30">
+    expires_after="2022-09-11">
   <owner>megjablon@google.com</owner>
   <owner>chrome-language@google.com</owner>
   <summary>
@@ -426,7 +426,7 @@
 </histogram>
 
 <histogram name="Translate.NeverTranslateSite" units="units"
-    expires_after="2022-06-30">
+    expires_after="2022-09-11">
   <owner>megjablon@google.com</owner>
   <owner>chrome-language@google.com</owner>
   <summary>
@@ -463,7 +463,7 @@
 </histogram>
 
 <histogram name="Translate.PageLoad.FinalState" enum="TranslateState"
-    expires_after="2022-06-30">
+    expires_after="2022-09-11">
   <owner>curranmax@google.com</owner>
   <owner>megjablon@google.com</owner>
   <owner>chrome-language@google.com</owner>
@@ -477,7 +477,7 @@
 </histogram>
 
 <histogram name="Translate.PageLoad.FinalTargetLanguage"
-    enum="LocaleCodeISO639" expires_after="2022-06-30">
+    enum="LocaleCodeISO639" expires_after="2022-09-11">
   <owner>curranmax@google.com</owner>
   <owner>megjablon@google.com</owner>
   <owner>chrome-language@google.com</owner>
@@ -518,7 +518,7 @@
 </histogram>
 
 <histogram name="Translate.PageLoad.InitialState" enum="TranslateState"
-    expires_after="2022-06-30">
+    expires_after="2022-09-11">
   <owner>curranmax@google.com</owner>
   <owner>megjablon@google.com</owner>
   <owner>chrome-language@google.com</owner>
@@ -546,7 +546,7 @@
 </histogram>
 
 <histogram name="Translate.PageLoad.InitialTargetLanguage.Origin"
-    enum="TranslateTargetLanguageOrigin" expires_after="2022-06-30">
+    enum="TranslateTargetLanguageOrigin" expires_after="2022-09-11">
   <owner>curranmax@google.com</owner>
   <owner>megjablon@google.com</owner>
   <owner>chrome-language@google.com</owner>
@@ -561,7 +561,7 @@
 
 <histogram
     name="Translate.PageLoad.IsInitialSourceLanguageInUsersContentLanguages"
-    enum="Boolean" expires_after="2022-06-30">
+    enum="Boolean" expires_after="2022-09-11">
   <owner>curranmax@google.com</owner>
   <owner>megjablon@google.com</owner>
   <owner>chrome-language@google.com</owner>
@@ -576,7 +576,7 @@
 </histogram>
 
 <histogram name="Translate.PageLoad.NumReversions" units="reversions"
-    expires_after="2022-06-30">
+    expires_after="2022-09-11">
   <owner>curranmax@google.com</owner>
   <owner>megjablon@google.com</owner>
   <owner>chrome-language@google.com</owner>
@@ -589,7 +589,7 @@
 </histogram>
 
 <histogram name="Translate.PageLoad.NumTargetLanguageChanges"
-    units="target language changes" expires_after="2022-06-30">
+    units="target language changes" expires_after="2022-09-11">
   <owner>curranmax@google.com</owner>
   <owner>megjablon@google.com</owner>
   <owner>chrome-language@google.com</owner>
@@ -602,7 +602,7 @@
 </histogram>
 
 <histogram name="Translate.PageLoad.NumTranslations" units="translations"
-    expires_after="2022-06-30">
+    expires_after="2022-09-11">
   <owner>curranmax@google.com</owner>
   <owner>megjablon@google.com</owner>
   <owner>chrome-language@google.com</owner>
@@ -614,7 +614,7 @@
 </histogram>
 
 <histogram name="Translate.PageLoad.Ranker.Decision"
-    enum="TranslateRankerDecision" expires_after="2022-06-30">
+    enum="TranslateRankerDecision" expires_after="2022-09-11">
   <owner>curranmax@google.com</owner>
   <owner>megjablon@google.com</owner>
   <owner>chrome-language@google.com</owner>
@@ -653,7 +653,7 @@
 </histogram>
 
 <histogram name="Translate.PageLoad.TriggerDecision"
-    enum="TranslateTriggerDecision" expires_after="2022-06-30">
+    enum="TranslateTriggerDecision" expires_after="2022-09-11">
   <owner>curranmax@google.com</owner>
   <owner>megjablon@google.com</owner>
   <owner>chrome-language@google.com</owner>
@@ -740,7 +740,7 @@
 </histogram>
 
 <histogram name="Translate.RevertTranslation" units="units"
-    expires_after="2022-06-30">
+    expires_after="2022-09-11">
   <owner>megjablon@google.com</owner>
   <owner>chrome-language@google.com</owner>
   <summary>
@@ -750,7 +750,7 @@
 </histogram>
 
 <histogram name="Translate.ShowErrorUI" enum="TranslateError"
-    expires_after="2022-06-30">
+    expires_after="2022-09-11">
   <owner>megjablon@google.com</owner>
   <owner>chrome-language@google.com</owner>
   <summary>
@@ -761,7 +761,7 @@
 </histogram>
 
 <histogram name="Translate.SimilarLanguageMatch" enum="BooleanMatched"
-    expires_after="2022-06-30">
+    expires_after="2022-09-11">
   <owner>megjablon@google.com</owner>
   <owner>chrome-language@google.com</owner>
   <summary>
@@ -775,7 +775,7 @@
 </histogram>
 
 <histogram name="Translate.SourceLanguage" enum="LocaleCodeISO639"
-    expires_after="2022-06-30">
+    expires_after="2022-09-11">
   <owner>megjablon@google.com</owner>
   <owner>chrome-language@google.com</owner>
   <summary>
@@ -785,7 +785,7 @@
 </histogram>
 
 <histogram name="Translate.TargetLanguage" enum="LocaleCodeISO639"
-    expires_after="2022-06-30">
+    expires_after="2022-09-11">
   <owner>megjablon@google.com</owner>
   <owner>chrome-language@google.com</owner>
   <summary>
@@ -795,7 +795,7 @@
 </histogram>
 
 <histogram name="Translate.TargetLanguage.Origin"
-    enum="TranslateTargetLanguageOrigin" expires_after="2022-06-30">
+    enum="TranslateTargetLanguageOrigin" expires_after="2022-09-11">
   <owner>megjablon@google.com</owner>
   <owner>chrome-language@google.com</owner>
   <summary>
@@ -806,7 +806,7 @@
 </histogram>
 
 <histogram name="Translate.Translate" enum="BooleanTranslate"
-    expires_after="2022-06-30">
+    expires_after="2022-09-11">
   <owner>megjablon@google.com</owner>
   <owner>chrome-language@google.com</owner>
   <summary>
@@ -816,7 +816,7 @@
 </histogram>
 
 <histogram name="Translate.Translate.AMPCacheURL" enum="BooleanTranslate"
-    expires_after="2022-06-30">
+    expires_after="2022-09-11">
   <owner>sclittle@google.com</owner>
   <owner>megjablon@google.com</owner>
   <owner>chrome-language@google.com</owner>
@@ -885,7 +885,7 @@
 </histogram>
 
 <histogram name="Translate.Translation.LanguageDetection.ContentLength"
-    units="characters" expires_after="2022-06-30">
+    units="characters" expires_after="2022-09-11">
   <owner>megjablon@chromium.org</owner>
   <owner>chrome-language@google.com</owner>
   <summary>
@@ -896,7 +896,7 @@
 </histogram>
 
 <histogram name="Translate.Translation.SourceLanguage" enum="LocaleCodeISO639"
-    expires_after="2022-06-30">
+    expires_after="2022-09-11">
   <owner>curranmax@google.com</owner>
   <owner>megjablon@google.com</owner>
   <owner>chrome-language@google.com</owner>
@@ -909,7 +909,7 @@
 </histogram>
 
 <histogram name="Translate.Translation.Status" enum="TranslationStatus"
-    expires_after="2022-06-30">
+    expires_after="2022-09-11">
   <owner>curranmax@google.com</owner>
   <owner>megjablon@google.com</owner>
   <owner>chrome-language@google.com</owner>
@@ -942,7 +942,7 @@
 </histogram>
 
 <histogram name="Translate.Translation.TargetLanguage.Origin"
-    enum="TranslateTargetLanguageOrigin" expires_after="2022-06-30">
+    enum="TranslateTargetLanguageOrigin" expires_after="2022-09-11">
   <owner>curranmax@google.com</owner>
   <owner>megjablon@google.com</owner>
   <owner>chrome-language@google.com</owner>
@@ -980,7 +980,7 @@
 </histogram>
 
 <histogram name="Translate.Translation.TimeToTranslate" units="ms"
-    expires_after="2022-06-30">
+    expires_after="2022-09-11">
   <owner>curranmax@google.com</owner>
   <owner>megjablon@google.com</owner>
   <owner>chrome-language@google.com</owner>
@@ -993,7 +993,7 @@
 </histogram>
 
 <histogram name="Translate.Translation.Type" enum="TranslationType"
-    expires_after="2022-06-30">
+    expires_after="2022-09-11">
   <owner>curranmax@google.com</owner>
   <owner>megjablon@google.com</owner>
   <owner>chrome-language@google.com</owner>
@@ -1007,7 +1007,7 @@
 </histogram>
 
 <histogram name="Translate.UiInteraction.Event" enum="TranslateUIInteraction"
-    expires_after="2022-06-30">
+    expires_after="2022-09-11">
   <owner>curranmax@google.com</owner>
   <owner>megjablon@google.com</owner>
   <owner>chrome-language@google.com</owner>
@@ -1035,7 +1035,7 @@
 </histogram>
 
 <histogram name="Translate.UserActionDuration" units="ms"
-    expires_after="2022-06-30">
+    expires_after="2022-09-11">
   <owner>megjablon@google.com</owner>
   <owner>chrome-language@google.com</owner>
   <summary>
@@ -1046,7 +1046,7 @@
 
 <histogram
     name="TranslateModelService.LanguageDetectionModel.PendingRequestCallbacks"
-    units="requests" expires_after="2022-06-30">
+    units="requests" expires_after="2022-09-11">
   <owner>mcrouse@chromium.org</owner>
   <owner>chrome-language@chromium.org</owner>
   <summary>
@@ -1057,7 +1057,7 @@
 </histogram>
 
 <histogram name="TranslateModelService.LanguageDetectionModel.WasLoaded"
-    enum="BooleanLoaded" expires_after="2022-06-30">
+    enum="BooleanLoaded" expires_after="2022-09-11">
   <owner>mcrouse@chromium.org</owner>
   <owner>chrome-language@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/ukm/histograms.xml b/tools/metrics/histograms/metadata/ukm/histograms.xml
index 3449d15..78c3750 100644
--- a/tools/metrics/histograms/metadata/ukm/histograms.xml
+++ b/tools/metrics/histograms/metadata/ukm/histograms.xml
@@ -34,7 +34,7 @@
 </histogram>
 
 <histogram name="UKM.BuildAndStoreLogIsEmpty" enum="Boolean"
-    expires_after="2022-07-01">
+    expires_after="2022-09-11">
   <owner>rkaplow@chromium.org</owner>
   <owner>ukm-team@google.com</owner>
   <summary>
@@ -59,7 +59,7 @@
 </histogram>
 
 <histogram name="UKM.ConsentObserver.Purge" enum="Boolean"
-    expires_after="2022-07-01">
+    expires_after="2022-09-11">
   <owner>bcwhite@chromium.org</owner>
   <owner>rkaplow@chromium.org</owner>
   <owner>ukm-team@google.com</owner>
@@ -175,7 +175,7 @@
 </histogram>
 
 <histogram name="UKM.LogUpload.ResponseOrErrorCode"
-    enum="CombinedHttpResponseAndNetErrorCode" expires_after="2022-07-03">
+    enum="CombinedHttpResponseAndNetErrorCode" expires_after="2022-09-11">
   <owner>rkaplow@chromium.org</owner>
   <owner>ukm-team@google.com</owner>
   <summary>
@@ -304,7 +304,7 @@
 </histogram>
 
 <histogram name="UKM.Sources.UnsentSourcesCount" units="sources"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>rkaplow@chromium.org</owner>
   <owner>ukm-team@google.com</owner>
   <summary>
@@ -314,7 +314,7 @@
 </histogram>
 
 <histogram name="UKM.UnsentLogs.DroppedSize" units="bytes"
-    expires_after="2022-07-11">
+    expires_after="2022-09-11">
   <owner>rkaplow@chromium.org</owner>
   <owner>ukm-team@google.com</owner>
   <summary>
@@ -324,7 +324,7 @@
 </histogram>
 
 <histogram name="UKM.UnsentLogs.NumDropped" units="units"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>rkaplow@chromium.org</owner>
   <owner>ukm-team@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/v8/histograms.xml b/tools/metrics/histograms/metadata/v8/histograms.xml
index 89940c8..12718b0 100644
--- a/tools/metrics/histograms/metadata/v8/histograms.xml
+++ b/tools/metrics/histograms/metadata/v8/histograms.xml
@@ -23,7 +23,7 @@
 <histograms>
 
 <histogram name="V8.ArrayBufferLargeAllocations" units="MB"
-    expires_after="2022-06-30">
+    expires_after="2022-09-11">
   <owner>gdeepti@chromium.org</owner>
   <owner>ecmziegler@chromium.org</owner>
   <owner>clemensb@chromium.org</owner>
@@ -34,7 +34,7 @@
 </histogram>
 
 <histogram name="V8.ArrayBufferNewSizeFailures" units="MB"
-    expires_after="2022-06-30">
+    expires_after="2022-09-11">
   <owner>gdeepti@chromium.org</owner>
   <owner>ecmziegler@chromium.org</owner>
   <owner>clemensb@chromium.org</owner>
@@ -151,7 +151,7 @@
 </histogram>
 
 <histogram name="V8.CompileScript.CacheBehaviour" enum="V8CacheBehaviour"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>leszeks@chromium.org</owner>
   <owner>v8-runtime@google.com</owner>
   <summary>
@@ -1548,7 +1548,7 @@
   </summary>
 </histogram>
 
-<histogram name="V8.WasmCacheCount" units="count" expires_after="2022-06-30">
+<histogram name="V8.WasmCacheCount" units="count" expires_after="2022-09-11">
   <owner>ahaas@chromium.org</owner>
   <owner>ecmziegler@chromium.org</owner>
   <owner>wasm-v8@google.com</owner>
@@ -1572,7 +1572,7 @@
 </histogram>
 
 <histogram name="V8.WasmCompileAfterDeserializeMilliSeconds" units="ms"
-    expires_after="2022-06-30">
+    expires_after="2022-09-11">
   <owner>ahaas@chromium.org</owner>
   <owner>ecmziegler@chromium.org</owner>
   <owner>wasm-v8@google.com</owner>
@@ -1685,7 +1685,7 @@
 </histogram>
 
 <histogram name="V8.WasmDeserializationTimeMilliSeconds" units="ms"
-    expires_after="2022-06-30">
+    expires_after="2022-09-11">
   <owner>ahaas@chromium.org</owner>
   <owner>clemensb@chromium.org</owner>
   <owner>ecmziegler@chromium.org</owner>
@@ -1757,7 +1757,7 @@
 </histogram>
 
 <histogram name="V8.WasmLazyCompileTimeMicroSeconds" units="microseconds"
-    expires_after="2022-06-30">
+    expires_after="2022-09-11">
   <owner>clemensb@chromium.org</owner>
   <owner>ecmziegler@chromium.org</owner>
   <owner>wasm-v8@google.com</owner>
@@ -1834,7 +1834,7 @@
 </histogram>
 
 <histogram name="V8.WasmModuleCodeSizeMiB" units="MB"
-    expires_after="2022-07-11">
+    expires_after="2022-09-11">
   <owner>ecmziegler@chromium.org</owner>
   <owner>adamk@chromium.org</owner>
   <owner>clemensb@chromium.org</owner>
@@ -1940,7 +1940,7 @@
 </histogram>
 
 <histogram name="V8.WasmSerializationTimeMilliSeconds" units="ms"
-    expires_after="2022-06-30">
+    expires_after="2022-09-11">
   <owner>ahaas@chromium.org</owner>
   <owner>clemensb@chromium.org</owner>
   <owner>ecmziegler@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/variations/histograms.xml b/tools/metrics/histograms/metadata/variations/histograms.xml
index 737448e..42a2f2b 100644
--- a/tools/metrics/histograms/metadata/variations/histograms.xml
+++ b/tools/metrics/histograms/metadata/variations/histograms.xml
@@ -184,7 +184,7 @@
 </histogram>
 
 <histogram name="Variations.Headers.ExperimentCount" units="units"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>asvitkine@chromium.org</owner>
   <owner>rkaplow@chromium.org</owner>
   <owner>src/base/metrics/OWNERS</owner>
@@ -355,7 +355,7 @@
 </histogram>
 
 <histogram name="Variations.SafeMode.Streak.Crashes" units="crashes"
-    expires_after="2022-07-11">
+    expires_after="2022-09-11">
   <owner>isherman@chromium.org</owner>
   <owner>asvitkine@chromium.org</owner>
   <owner>src/base/metrics/OWNERS</owner>
@@ -369,7 +369,7 @@
 </histogram>
 
 <histogram name="Variations.SafeMode.Streak.FetchFailures" units="failures"
-    expires_after="2022-07-11">
+    expires_after="2022-09-11">
   <owner>isherman@chromium.org</owner>
   <owner>asvitkine@chromium.org</owner>
   <owner>src/base/metrics/OWNERS</owner>
@@ -456,7 +456,7 @@
 </histogram>
 
 <histogram name="Variations.SeedFreshness" units="minutes"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>asvitkine@chromium.org</owner>
   <owner>rkaplow@chromium.org</owner>
   <owner>src/base/metrics/OWNERS</owner>
@@ -501,7 +501,7 @@
 </histogram>
 
 <histogram name="Variations.SeedLoadResult" enum="VariationsSeedLoadResult"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>asvitkine@chromium.org</owner>
   <owner>rkaplow@chromium.org</owner>
   <owner>src/base/metrics/OWNERS</owner>
@@ -647,7 +647,7 @@
 </histogram>
 
 <histogram name="Variations.UserChannel" enum="UserChannels"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>asvitkine@chromium.org</owner>
   <owner>rkaplow@chromium.org</owner>
   <owner>src/base/metrics/OWNERS</owner>
diff --git a/tools/metrics/histograms/metadata/web_apk/histograms.xml b/tools/metrics/histograms/metadata/web_apk/histograms.xml
index 3bedd38..94c2b37f 100644
--- a/tools/metrics/histograms/metadata/web_apk/histograms.xml
+++ b/tools/metrics/histograms/metadata/web_apk/histograms.xml
@@ -326,7 +326,7 @@
 </histogram>
 
 <histogram name="WebApk.WebappRegistry.NumberOfOrigins" units="count"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>eirage@chromium.org</owner>
   <owner>
     src/chrome/android/java/src/org/chromium/chrome/browser/webapps/OWNERS
diff --git a/tools/metrics/histograms/metadata/web_audio/histograms.xml b/tools/metrics/histograms/metadata/web_audio/histograms.xml
index e5ecd4a..8d99d7a3 100644
--- a/tools/metrics/histograms/metadata/web_audio/histograms.xml
+++ b/tools/metrics/histograms/metadata/web_audio/histograms.xml
@@ -23,7 +23,7 @@
 <histograms>
 
 <histogram name="WebAudio.AudioBuffer.Length" units="frames"
-    expires_after="2022-06-30">
+    expires_after="2022-09-11">
   <owner>hongchan@chromium.org</owner>
   <summary>
     The length (in frames) requested by createBuffer(). Recorded for every call
@@ -82,7 +82,7 @@
 </histogram>
 
 <histogram name="WebAudio.AudioContext.latencyHintCategory" units="units"
-    expires_after="2022-06-30">
+    expires_after="2022-09-11">
   <owner>hongchan@chromium.org</owner>
   <summary>
     If provided, the latencyHint option category of &quot;interactive&quot;,
@@ -94,7 +94,7 @@
 </histogram>
 
 <histogram name="WebAudio.AudioContext.latencyHintMilliSeconds" units="ms"
-    expires_after="2022-06-30">
+    expires_after="2022-09-11">
   <owner>hongchan@chromium.org</owner>
   <summary>
     If the latencyHint is provided and is a floating-point number, the value in
@@ -156,7 +156,7 @@
 </histogram>
 
 <histogram name="WebAudio.AudioDestination.HardwareOutputLatency" units="ms"
-    expires_after="2022-06-30">
+    expires_after="2022-09-11">
   <owner>hongchan@chromium.org</owner>
   <owner>src/third_party/blink/renderer/modules/webaudio/OWNERS</owner>
   <summary>
@@ -208,7 +208,7 @@
 </histogram>
 
 <histogram name="WebAudio.ConvolverNode.ImpulseResponseLength" units="ms"
-    expires_after="2022-06-30">
+    expires_after="2022-09-11">
   <owner>hongchan@chromium.org</owner>
   <summary>
     The duration in millisec of impulse responses for a ConvolverNode. Recorded
diff --git a/tools/metrics/histograms/metadata/web_rtc/histograms.xml b/tools/metrics/histograms/metadata/web_rtc/histograms.xml
index 4f4c6dc..f4b4e32 100644
--- a/tools/metrics/histograms/metadata/web_rtc/histograms.xml
+++ b/tools/metrics/histograms/metadata/web_rtc/histograms.xml
@@ -393,7 +393,7 @@
 </histogram>
 
 <histogram name="WebRTC.Audio.ApmCaptureOutputLevelAverageRms"
-    units="dBFS (negated)" expires_after="2022-07-11">
+    units="dBFS (negated)" expires_after="2022-09-11">
   <owner>peah@chromium.org</owner>
   <summary>
     This histogram reports the average RMS of the signal in the output of
@@ -405,7 +405,7 @@
 </histogram>
 
 <histogram name="WebRTC.Audio.ApmCaptureOutputLevelPeakRms"
-    units="dBFS (negated)" expires_after="2022-07-11">
+    units="dBFS (negated)" expires_after="2022-09-11">
   <owner>peah@chromium.org</owner>
   <summary>
     This histogram reports the peak RMS of the signal in the output of WebRTC's
@@ -546,7 +546,7 @@
 </histogram>
 
 <histogram name="WebRTC.Audio.DelayedPacketOutageEventsPerMinute"
-    units="events/minute" expires_after="2022-07-11">
+    units="events/minute" expires_after="2022-09-11">
   <owner>hlundin@chromium.org</owner>
   <summary>
     Counts the number of delayed packet outage events per minute. The range is
@@ -796,7 +796,7 @@
 </histogram>
 
 <histogram name="WebRTC.Audio.ReceiverDeviceDelayMs" units="ms"
-    expires_after="2022-07-11">
+    expires_after="2022-09-11">
   <owner>hlundin@chromium.org</owner>
   <summary>
     The sound card's buffering delay for the receiving side. Sampled once every
@@ -805,7 +805,7 @@
 </histogram>
 
 <histogram name="WebRTC.Audio.ReceiverJitterBufferDelayMs" units="ms"
-    expires_after="2022-07-11">
+    expires_after="2022-09-11">
   <owner>hlundin@chromium.org</owner>
   <summary>
     The jitter buffer delay for the receiving side. Sampled once every 10 ms
@@ -1000,7 +1000,7 @@
 </histogram>
 
 <histogram name="WebRTC.BWE.RampUpTimeTo2000kbpsInMs" units="ms"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>holmer@chromium.org</owner>
   <summary>
     The time it takes the estimated bandwidth to reach 2000 kbps from the first
@@ -1038,7 +1038,7 @@
 </histogram>
 
 <histogram name="WebRTC.Call.BitrateReceivedInKbps" units="kbps"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>holmer@chromium.org</owner>
   <summary>
     Average total bitrate received during a call (audio + video + RTCP), counted
@@ -1066,7 +1066,7 @@
 </histogram>
 
 <histogram name="WebRTC.Call.PacerBitrateInKbps" units="kbps"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>holmer@chromium.org</owner>
   <summary>
     Average pacer bitrate during a call, counted from first packet sent until
@@ -1110,7 +1110,7 @@
 </histogram>
 
 <histogram name="WebRTC.Call.VideoBitrateReceivedInKbps" units="kbps"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>holmer@chromium.org</owner>
   <summary>
     Average video bitrate received during a call, counted from first packet
@@ -1120,7 +1120,7 @@
 </histogram>
 
 <histogram name="WebRTC.DataChannelAggregateType"
-    enum="DataChannelAggregateType" expires_after="2022-06-30">
+    enum="DataChannelAggregateType" expires_after="2022-09-11">
   <owner>orphis@chromium.org</owner>
   <owner>toprice@chromium.org</owner>
   <summary>
@@ -1130,7 +1130,7 @@
 </histogram>
 
 <histogram name="WebRTC.DataChannelCounters" enum="DataChannelCounters"
-    expires_after="2022-06-30">
+    expires_after="2022-09-11">
   <owner>orphis@chromium.org</owner>
   <owner>toprice@chromium.org</owner>
   <summary>
@@ -1164,7 +1164,7 @@
 </histogram>
 
 <histogram name="WebRTC.DataChannelSctpErrorCode"
-    enum="DataChannelSctpErrorCode" expires_after="2022-06-30">
+    enum="DataChannelSctpErrorCode" expires_after="2022-09-11">
   <owner>orphis@chromium.org</owner>
   <owner>toprice@chromium.org</owner>
   <summary>
@@ -1691,7 +1691,7 @@
 </histogram>
 
 <histogram name="WebRTC.PeerConnection.SdpFormatReceivedAnswer"
-    enum="PeerConnectionSdpFormatReceived" expires_after="2022-07-03">
+    enum="PeerConnectionSdpFormatReceived" expires_after="2022-09-11">
   <owner>steveanton@chromium.org</owner>
   <owner>hta@chromium.org</owner>
   <owner>webrtc-dev@chromium.org</owner>
@@ -1703,7 +1703,7 @@
 </histogram>
 
 <histogram name="WebRTC.PeerConnection.SdpSemanticNegotiated"
-    enum="PeerConnectionSdpSemanticNegotiated" expires_after="2022-07-03">
+    enum="PeerConnectionSdpSemanticNegotiated" expires_after="2022-09-11">
   <owner>hta@chromium.org</owner>
   <owner>webrtc-dev@chromium.org</owner>
   <summary>
@@ -1716,7 +1716,7 @@
 </histogram>
 
 <histogram name="WebRTC.PeerConnection.SdpSemanticRequested"
-    enum="PeerConnectionSdpSemanticRequested" expires_after="2022-07-03">
+    enum="PeerConnectionSdpSemanticRequested" expires_after="2022-09-11">
   <owner>hta@chromium.org</owner>
   <owner>webrtc-dev@chromium.org</owner>
   <summary>
@@ -4010,7 +4010,7 @@
 </histogram>
 
 <histogram name="WebRtcTextLogging.UploadFailureReason"
-    enum="WebRtcLoggingUploadFailureReason" expires_after="2022-07-03">
+    enum="WebRtcLoggingUploadFailureReason" expires_after="2022-09-11">
   <owner>toprice@chromium.org</owner>
   <owner>olka@chromium.org</owner>
   <owner>webrtc-dev@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/webapps/histograms.xml b/tools/metrics/histograms/metadata/webapps/histograms.xml
index 5c5d890..087147d 100644
--- a/tools/metrics/histograms/metadata/webapps/histograms.xml
+++ b/tools/metrics/histograms/metadata/webapps/histograms.xml
@@ -62,7 +62,7 @@
 </histogram>
 
 <histogram name="AppBanners.InstallableStatusCode"
-    enum="AppBannersInstallableStatusCode" expires_after="2022-07-03">
+    enum="AppBannersInstallableStatusCode" expires_after="2022-09-11">
   <owner>dominickn@chromium.org</owner>
   <owner>pjmclachlan@google.com</owner>
   <summary>
@@ -75,7 +75,7 @@
 </histogram>
 
 <histogram name="AppBanners.InstallEvent" enum="AppBannersInstallEvent"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>dominickn@chromium.org</owner>
   <owner>pjmclachlan@google.com</owner>
   <owner>pcovell@google.com</owner>
@@ -157,7 +157,7 @@
 </histogram>
 
 <histogram name="Webapp.AddToHomescreenDialog.Timeout" units="ms"
-    expires_after="2022-06-19">
+    expires_after="2022-09-11">
   <owner>dominickn@chromium.org</owner>
   <owner>peconn@chromium.org</owner>
   <summary>
@@ -471,7 +471,7 @@
 </histogram>
 
 <histogram name="WebApp.Launcher.LaunchResult"
-    enum="WebAppLauncherLaunchResult" expires_after="2022-07-01">
+    enum="WebAppLauncherLaunchResult" expires_after="2022-09-11">
   <owner>davidbienvenu@chromium.org</owner>
   <owner>jessemckenna@google.com</owner>
   <summary>
@@ -598,7 +598,7 @@
 </histogram>
 
 <histogram name="WebApp.Preinstalled.EnabledCount" units="apps"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>alancutter@chromium.org</owner>
   <owner>desktop-pwas-team@google.com</owner>
   <summary>
@@ -608,7 +608,7 @@
 </histogram>
 
 <histogram name="WebApp.Preinstalled.MigratingWebAppAbsentChromeAppAbsent"
-    units="apps" expires_after="2022-07-03">
+    units="apps" expires_after="2022-09-11">
   <owner>alancutter@chromium.org</owner>
   <owner>desktop-pwas-team@google.com</owner>
   <summary>
@@ -619,7 +619,7 @@
 </histogram>
 
 <histogram name="WebApp.Preinstalled.MigratingWebAppAbsentChromeAppPresent"
-    units="apps" expires_after="2022-07-03">
+    units="apps" expires_after="2022-09-11">
   <owner>alancutter@chromium.org</owner>
   <owner>desktop-pwas-team@google.com</owner>
   <summary>
@@ -630,7 +630,7 @@
 </histogram>
 
 <histogram name="WebApp.Preinstalled.MigratingWebAppPresentChromeAppAbsent"
-    units="apps" expires_after="2022-07-03">
+    units="apps" expires_after="2022-09-11">
   <owner>alancutter@chromium.org</owner>
   <owner>desktop-pwas-team@google.com</owner>
   <summary>
@@ -641,7 +641,7 @@
 </histogram>
 
 <histogram name="WebApp.Preinstalled.MigratingWebAppPresentChromeAppPresent"
-    units="apps" expires_after="2022-07-03">
+    units="apps" expires_after="2022-09-11">
   <owner>alancutter@chromium.org</owner>
   <owner>desktop-pwas-team@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/webauthn/histograms.xml b/tools/metrics/histograms/metadata/webauthn/histograms.xml
index f21c1930..09bc572f 100644
--- a/tools/metrics/histograms/metadata/webauthn/histograms.xml
+++ b/tools/metrics/histograms/metadata/webauthn/histograms.xml
@@ -51,7 +51,7 @@
 </histogram>
 
 <histogram name="WebAuthentication.CableV2.GetAssertionTime" units="ms"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>agl@chromium.org</owner>
   <owner>martinkr@google.com</owner>
   <summary>
@@ -190,7 +190,7 @@
 </histogram>
 
 <histogram name="WebAuthentication.IsUVPlatformAuthenticatorAvailable2"
-    enum="Boolean" expires_after="2022-07-03">
+    enum="Boolean" expires_after="2022-09-11">
   <owner>kenrb@chromium.org</owner>
   <owner>martinkr@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/windows/histograms.xml b/tools/metrics/histograms/metadata/windows/histograms.xml
index d4aa525..d0af21a 100644
--- a/tools/metrics/histograms/metadata/windows/histograms.xml
+++ b/tools/metrics/histograms/metadata/windows/histograms.xml
@@ -273,7 +273,7 @@
 </histogram>
 
 <histogram name="Windows.ProcessorFamily" enum="ProcessorFamily"
-    expires_after="2022-07-03">
+    expires_after="2022-09-11">
   <owner>rkc@chromium.org</owner>
   <owner>rockot@chromium.org</owner>
   <summary>
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps.json b/tools/perf/core/perfetto_binary_roller/binary_deps.json
index cdb9ca7..128de6a 100644
--- a/tools/perf/core/perfetto_binary_roller/binary_deps.json
+++ b/tools/perf/core/perfetto_binary_roller/binary_deps.json
@@ -5,24 +5,24 @@
             "remote_path": "perfetto_binaries/trace_processor_shell/linux_arm64/49b4b5dcbc312d8d2c3751cf29238b8efeb4e494/trace_processor_shell"
         },
         "win": {
-            "hash": "6f2d55046183bf7228946d4df574445e044c866e",
-            "remote_path": "perfetto_binaries/trace_processor_shell/win/e26fd0a8a5e96b82f71b3c86240d8c8c0429245e/trace_processor_shell.exe"
+            "hash": "f253938ba7c08d318e2d1e23e8a60e95270c3070",
+            "remote_path": "perfetto_binaries/trace_processor_shell/win/ff1a1bfefc50ed1d9295e674110f178f1faf4776/trace_processor_shell.exe"
         },
         "linux_arm": {
             "hash": "58893933be305d3bfe0a72ebebcacde2ac3ca893",
             "remote_path": "perfetto_binaries/trace_processor_shell/linux_arm/49b4b5dcbc312d8d2c3751cf29238b8efeb4e494/trace_processor_shell"
         },
         "mac": {
-            "hash": "d061a6f8b30142d4747c6f0ef7e4e98bd3495312",
-            "remote_path": "perfetto_binaries/trace_processor_shell/mac/e26fd0a8a5e96b82f71b3c86240d8c8c0429245e/trace_processor_shell"
+            "hash": "797498a8bbe266429d24bd17c593c8ca6e1a011f",
+            "remote_path": "perfetto_binaries/trace_processor_shell/mac/ff1a1bfefc50ed1d9295e674110f178f1faf4776/trace_processor_shell"
         },
         "mac_arm64": {
             "hash": "c0397e87456ad6c6a7aa0133e5b81c97adbab4ab",
             "remote_path": "perfetto_binaries/trace_processor_shell/mac_arm64/cefb3e0ec3a0580c996f801e854fe02963c03d5c/trace_processor_shell"
         },
         "linux": {
-            "hash": "4796f7ebe3ae11e2bb8e59c7dac9a91dcc450bb8",
-            "remote_path": "perfetto_binaries/trace_processor_shell/linux/e26fd0a8a5e96b82f71b3c86240d8c8c0429245e/trace_processor_shell"
+            "hash": "81a2792186feba8ff3b4a398436677f92a602e22",
+            "remote_path": "perfetto_binaries/trace_processor_shell/linux/ff1a1bfefc50ed1d9295e674110f178f1faf4776/trace_processor_shell"
         }
     },
     "power_profile.sql": {
diff --git a/ui/chromeos/file_manager_strings.grdp b/ui/chromeos/file_manager_strings.grdp
index 51343bec..97aedc3ea 100644
--- a/ui/chromeos/file_manager_strings.grdp
+++ b/ui/chromeos/file_manager_strings.grdp
@@ -670,12 +670,18 @@
   <message name="IDS_FILE_BROWSER_FILE_COPIED" desc="File Manager status message. 'Item' is used here as a generic term for file or directory.">
     <ph name="FILE_NAME">$1<ex>big.xz</ex></ph> copied.
   </message>
+  <message name="IDS_FILE_BROWSER_FILE_EXTRACTED" desc="File Manager status message. 'Item' is used here as a generic term for file or directory.">
+    <ph name="FILE_NAME">$1<ex>big.xz</ex></ph> extracted.
+  </message>
   <message name="IDS_FILE_BROWSER_FILE_ITEMS" desc="File Manager status message. 'Item' is used here as a generic term for file or directory.">
     <ph name="NUMBER_OF_ITEMS">$1<ex>3</ex></ph> items
   </message>
   <message name="IDS_FILE_BROWSER_FILE_ITEMS_COPIED" desc="File Manager status message. 'Item' is used here as a generic term for file or directory.">
     <ph name="NUMBER_OF_ITEMS">$1<ex>3</ex></ph> items copied.
   </message>
+  <message name="IDS_FILE_BROWSER_FILE_ITEMS_EXTRACTED" desc="File Manager status message. 'Item' is used here as a generic term for file or directory.">
+    <ph name="NUMBER_OF_ITEMS">$1<ex>3</ex></ph> items extracted.
+  </message>
   <message name="IDS_FILE_BROWSER_FILE_ITEMS_MOVED" desc="File Manager status message. 'Item' is used here as a generic term for file or directory.">
     <ph name="NUMBER_OF_ITEMS">$1<ex>3</ex></ph> items moved.
   </message>
diff --git a/ui/chromeos/file_manager_strings_grdp/IDS_FILE_BROWSER_FILE_EXTRACTED.png.sha1 b/ui/chromeos/file_manager_strings_grdp/IDS_FILE_BROWSER_FILE_EXTRACTED.png.sha1
new file mode 100644
index 0000000..8cb5ce1d
--- /dev/null
+++ b/ui/chromeos/file_manager_strings_grdp/IDS_FILE_BROWSER_FILE_EXTRACTED.png.sha1
@@ -0,0 +1 @@
+52338dc76f27c629cc8d9323119bdde1d183e5d1
\ No newline at end of file
diff --git a/ui/chromeos/file_manager_strings_grdp/IDS_FILE_BROWSER_FILE_ITEMS_EXTRACTED.png.sha1 b/ui/chromeos/file_manager_strings_grdp/IDS_FILE_BROWSER_FILE_ITEMS_EXTRACTED.png.sha1
new file mode 100644
index 0000000..5524f1f1
--- /dev/null
+++ b/ui/chromeos/file_manager_strings_grdp/IDS_FILE_BROWSER_FILE_ITEMS_EXTRACTED.png.sha1
@@ -0,0 +1 @@
+4d193313bb7b6fab0e387f6674af11c23da2ad5f
\ No newline at end of file
diff --git a/ui/file_manager/file_manager/common/js/file_type.js b/ui/file_manager/file_manager/common/js/file_type.js
index dcb771762..62d2f6c 100644
--- a/ui/file_manager/file_manager/common/js/file_type.js
+++ b/ui/file_manager/file_manager/common/js/file_type.js
@@ -17,6 +17,9 @@
 
 export {FileExtensionType};
 
+// All supported file types are now defined in
+// ui/file_manager/base/gn/file_types.json5.
+
 /**
  * A special type for directory.
  * @type{!FileExtensionType}
diff --git a/ui/file_manager/file_manager/foreground/js/ui/progress_center_panel.js b/ui/file_manager/file_manager/foreground/js/ui/progress_center_panel.js
index 1891fb1..e7c7cc83 100644
--- a/ui/file_manager/file_manager/foreground/js/ui/progress_center_panel.js
+++ b/ui/file_manager/file_manager/foreground/js/ui/progress_center_panel.js
@@ -150,6 +150,9 @@
                 strf('COPY_FILE_NAME_LONG', source, destination) :
                 strf('FILE_COPIED', source);
           }
+          if (item.type === ProgressItemType.EXTRACT) {
+            return strf('FILE_EXTRACTED', source);
+          }
           if (item.type === ProgressItemType.MOVE) {
             return hasDestination ?
                 strf('MOVE_FILE_NAME_LONG', source, destination) :
@@ -170,6 +173,9 @@
               strf('COPY_ITEMS_REMAINING_LONG', count, destination) :
               strf('FILE_ITEMS_COPIED', source);
         }
+        if (item.type === ProgressItemType.EXTRACT) {
+          return strf('FILE_ITEMS_EXTRACTED', count);
+        }
         if (item.type === ProgressItemType.MOVE) {
           return hasDestination ?
               strf('MOVE_ITEMS_REMAINING_LONG', count, destination) :
@@ -311,6 +317,7 @@
         case ProgressItemState.COMPLETED:
           // Create a completed panel for copies, moves, deletes and formats.
           if (item.type === ProgressItemType.COPY ||
+              item.type === ProgressItemType.EXTRACT ||
               item.type === ProgressItemType.MOVE ||
               item.type === ProgressItemType.FORMAT ||
               item.type === ProgressItemType.ZIP ||
diff --git a/ui/file_manager/integration_tests/test_util.js b/ui/file_manager/integration_tests/test_util.js
index df36c87..716c7eff3 100644
--- a/ui/file_manager/integration_tests/test_util.js
+++ b/ui/file_manager/integration_tests/test_util.js
@@ -820,73 +820,72 @@
     type: EntryType.FILE,
     sourceFileName: 'text.docx',
     targetPath: 'text.docx',
-    mimeType: `application/vnd.openxmlformats-officedocument.wordprocessingml\
-.document`,
+    mimeType: 'application/vnd.openxmlformats-officedocument' +
+        '.wordprocessingml.document',
     lastModifiedTime: 'Jan 4, 2019, 10:57 AM',
     nameText: 'text.docx',
     sizeText: '8.7 KB',
     typeText: 'Office document',
-    alternateUrl: 'https://drive.google.com/open?id=smalldocxid&\
-usp=drive_fs',
+    alternateUrl: 'https://drive.google.com/open?id=smalldocxid&usp=drive_fs',
   }),
 
   smallDocxHosted: new TestEntryInfo({
     type: EntryType.FILE,
     sourceFileName: 'text.docx',
     targetPath: 'synced.docx',
-    mimeType: `application/vnd.openxmlformats-officedocument.wordprocessingml\
-.document`,
+    mimeType: 'application/vnd.openxmlformats-officedocument' +
+        '.wordprocessingml.document',
     lastModifiedTime: 'Jan 4, 2019, 10:57 AM',
     nameText: 'synced.docx',
     sizeText: '8.7 KB',
     typeText: 'Office document',
-    alternateUrl: 'https://docs.google.com/document/d/smalldocxid?rtpof=true&\
-usp=drive_fs',
+    alternateUrl: 'https://docs.google.com/document/d/smalldocxid' +
+        '?rtpof=true&usp=drive_fs',
   }),
 
   smallDocxPinned: new TestEntryInfo({
     type: EntryType.FILE,
     sourceFileName: 'text.docx',
     targetPath: 'pinned.docx',
-    mimeType: `application/vnd.openxmlformats-officedocument.wordprocessingml\
-.document`,
+    mimeType: 'application/vnd.openxmlformats-officedocument' +
+        '.wordprocessingml.document',
     lastModifiedTime: 'Jan 4, 2019, 10:57 AM',
     nameText: 'pinned.docx',
     sizeText: '8.7 KB',
     typeText: 'Office document',
     pinned: true,
-    alternateUrl: 'https://docs.google.com/document/d/pinneddocxid?rtpof=true&\
-usp=drive_fs',
+    alternateUrl: 'https://docs.google.com/document/d/pinneddocxid' +
+        '?rtpof=true&usp=drive_fs',
   }),
 
   smallXlsxPinned: new TestEntryInfo({
     type: EntryType.FILE,
     sourceFileName: 'sheet.xlsx',
     targetPath: 'pinned.xlsx',
-    mimeType: `application/vnd.openxmlformats-officedocument.spreadsheetml\
-.sheet`,
+    mimeType: 'application/vnd.openxmlformats-officedocument' +
+        '.spreadsheetml.sheet',
     lastModifiedTime: 'Jan 10, 2020, 11:58 PM',
     nameText: 'pinned.xlsx',
     sizeText: '5.7 KB',
     typeText: 'Office spreadsheet',
     pinned: true,
-    alternateUrl: 'https://docs.google.com/document/d/pinnedxlsxid?rtpof=true&\
-usp=drive_fs',
+    alternateUrl: 'https://docs.google.com/document/d/pinnedxlsxid' +
+        '?rtpof=true&usp=drive_fs',
   }),
 
   smallPptxPinned: new TestEntryInfo({
     type: EntryType.FILE,
     sourceFileName: 'presentation.pptx',
     targetPath: 'pinned.pptx',
-    mimeType: `application/vnd.openxmlformats-officedocument.presentationml\
-.presentation`,
+    mimeType: 'application/vnd.openxmlformats-officedocument' +
+        '.presentationml.presentation',
     lastModifiedTime: 'Jan 14, 2020, 10:15 AM',
     nameText: 'pinned.pptx',
     sizeText: '35.2 KB',
     typeText: 'Office document',
     pinned: true,
-    alternateUrl: 'https://docs.google.com/document/d/pinnedpptxid?rtpof=true&\
-usp=drive_fs',
+    alternateUrl: 'https://docs.google.com/document/d/pinnedpptxid' +
+        '?rtpof=true&usp=drive_fs',
   }),
 
   pinned: new TestEntryInfo({
diff --git a/ui/views/BUILD.gn b/ui/views/BUILD.gn
index ced5ff3..b9a8822 100644
--- a/ui/views/BUILD.gn
+++ b/ui/views/BUILD.gn
@@ -1284,6 +1284,7 @@
       "cocoa/bridged_native_widget_unittest.mm",
       "cocoa/cocoa_mouse_capture_unittest.mm",
       "cocoa/drag_drop_client_mac_unittest.mm",
+      "cocoa/fullscreen_controller_unittest.cc",
       "controls/menu/menu_closure_animation_mac_unittest.cc",
       "controls/menu/menu_runner_cocoa_unittest.mm",
       "controls/native/native_view_host_mac_unittest.mm",
diff --git a/ui/views/cocoa/fullscreen_controller_unittest.cc b/ui/views/cocoa/fullscreen_controller_unittest.cc
new file mode 100644
index 0000000..7c78ad1
--- /dev/null
+++ b/ui/views/cocoa/fullscreen_controller_unittest.cc
@@ -0,0 +1,693 @@
+// 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 "components/remote_cocoa/app_shim/native_widget_ns_window_fullscreen_controller.h"
+
+#include "base/test/task_environment.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace remote_cocoa {
+namespace test {
+
+using testing::_;
+using testing::Invoke;
+using testing::Return;
+
+class MockClient : public NativeWidgetNSWindowFullscreenController::Client {
+ public:
+  MOCK_METHOD1(FullscreenControllerTransitionStart, void(bool));
+  MOCK_METHOD1(FullscreenControllerTransitionComplete, void(bool));
+  MOCK_METHOD2(FullscreenControllerSetFrame,
+               void(const gfx::Rect&, base::TimeDelta&));
+
+  MOCK_METHOD0(FullscreenControllerToggleFullscreen, void());
+  MOCK_METHOD0(FullscreenControllerCloseWindow, void());
+  MOCK_CONST_METHOD0(FullscreenControllerGetDisplayId, int64_t());
+  MOCK_CONST_METHOD1(FullscreenControllerGetFrameForDisplay,
+                     gfx::Rect(int64_t display_id));
+  MOCK_CONST_METHOD0(FullscreenControllerGetFrame, gfx::Rect());
+};
+
+class MacFullscreenControllerTest : public testing::Test {
+ public:
+  void SetUp() override {
+    EXPECT_CALL(mock_client_, FullscreenControllerToggleFullscreen()).Times(0);
+    EXPECT_CALL(mock_client_, FullscreenControllerSetFrame(_, _)).Times(0);
+    EXPECT_CALL(mock_client_, FullscreenControllerCloseWindow()).Times(0);
+  }
+
+ protected:
+  const gfx::Rect kWindowRect{1, 2, 3, 4};
+  const int64_t kDisplay0Id = 0;
+  const int64_t kDisplay1Id = 1;
+  const gfx::Rect kDisplay0Frame{9, 10, 11, 12};
+  const gfx::Rect kDisplay1Frame{13, 14, 15, 16};
+
+  MockClient mock_client_;
+  NativeWidgetNSWindowFullscreenController controller_{&mock_client_};
+  base::test::SingleThreadTaskEnvironment task_environment_;
+};
+
+// Simple enter-and-exit fullscreen via the green traffic light button.
+TEST_F(MacFullscreenControllerTest, SimpleUserInitiated) {
+  // Enter fullscreen the way that clicking the green traffic light does it.
+  EXPECT_CALL(mock_client_, FullscreenControllerGetFrame())
+      .Times(1)
+      .WillOnce(Return(kWindowRect));
+  EXPECT_CALL(mock_client_, FullscreenControllerTransitionStart(true)).Times(1);
+  controller_.OnWindowWillEnterFullscreen();
+  EXPECT_TRUE(controller_.IsInFullscreenTransition());
+  EXPECT_TRUE(controller_.GetTargetFullscreenState());
+
+  // The controller should do nothing, and wait until it receives
+  // OnWindowDidEnterFullscreen.
+  task_environment_.RunUntilIdle();
+  EXPECT_TRUE(controller_.IsInFullscreenTransition());
+
+  // Calling EnterFullscreen inside the transition will do nothing.
+  controller_.EnterFullscreen(display::kInvalidDisplayId);
+  task_environment_.RunUntilIdle();
+  EXPECT_TRUE(controller_.IsInFullscreenTransition());
+  EXPECT_TRUE(controller_.GetTargetFullscreenState());
+
+  // Complete the transition.
+  EXPECT_CALL(mock_client_, FullscreenControllerTransitionComplete(true))
+      .Times(1);
+  controller_.OnWindowDidEnterFullscreen();
+  EXPECT_FALSE(controller_.IsInFullscreenTransition());
+  EXPECT_TRUE(controller_.GetTargetFullscreenState());
+
+  // Calling EnterFullscreen while fullscreen should do nothing.
+  controller_.EnterFullscreen(display::kInvalidDisplayId);
+  EXPECT_FALSE(controller_.IsInFullscreenTransition());
+  task_environment_.RunUntilIdle();
+  EXPECT_FALSE(controller_.IsInFullscreenTransition());
+  EXPECT_TRUE(controller_.GetTargetFullscreenState());
+
+  // Calling EnterFullscreen specifying the display that we are currently on
+  // will also be a no-op.
+  EXPECT_CALL(mock_client_, FullscreenControllerGetDisplayId())
+      .WillOnce(Return(kDisplay0Id));
+  controller_.EnterFullscreen(kDisplay0Id);
+  task_environment_.RunUntilIdle();
+  EXPECT_FALSE(controller_.IsInFullscreenTransition());
+  EXPECT_TRUE(controller_.GetTargetFullscreenState());
+
+  // Exit fullscreen via the green traffic light.
+  EXPECT_CALL(mock_client_, FullscreenControllerTransitionStart(false))
+      .Times(1);
+  controller_.OnWindowWillExitFullscreen();
+  EXPECT_TRUE(controller_.IsInFullscreenTransition());
+  EXPECT_FALSE(controller_.GetTargetFullscreenState());
+
+  // Calling ExitFullscreen inside the transition will do nothing.
+  controller_.ExitFullscreen();
+  task_environment_.RunUntilIdle();
+  EXPECT_TRUE(controller_.IsInFullscreenTransition());
+  EXPECT_FALSE(controller_.GetTargetFullscreenState());
+
+  // Complete the transition.
+  EXPECT_CALL(mock_client_, FullscreenControllerTransitionComplete(false))
+      .Times(1);
+  controller_.OnWindowDidExitFullscreen();
+  EXPECT_FALSE(controller_.IsInFullscreenTransition());
+  EXPECT_FALSE(controller_.GetTargetFullscreenState());
+  task_environment_.RunUntilIdle();
+}
+
+// Simple enter-and-exit fullscreen programmatically.
+TEST_F(MacFullscreenControllerTest, SimpleProgrammatic) {
+  // Enter fullscreen.
+  EXPECT_CALL(mock_client_, FullscreenControllerGetFrame())
+      .Times(1)
+      .WillOnce(Return(kWindowRect));
+  EXPECT_CALL(mock_client_, FullscreenControllerTransitionStart(true)).Times(1);
+  controller_.EnterFullscreen(display::kInvalidDisplayId);
+  EXPECT_TRUE(controller_.IsInFullscreenTransition());
+
+  // The call to ToggleFullscreen is sent via a posted task, so it shouldn't
+  // happen until after the run loop is pumped. The function
+  // OnWindowWillEnterFullscreen is called from within ToggleFullscreen.
+  EXPECT_CALL(mock_client_, FullscreenControllerToggleFullscreen())
+      .WillOnce(Invoke(&controller_, &NativeWidgetNSWindowFullscreenController::
+                                         OnWindowWillEnterFullscreen));
+  task_environment_.RunUntilIdle();
+  EXPECT_TRUE(controller_.IsInFullscreenTransition());
+
+  // Complete the transition.
+  EXPECT_CALL(mock_client_, FullscreenControllerTransitionComplete(true))
+      .Times(1);
+  controller_.OnWindowDidEnterFullscreen();
+  EXPECT_FALSE(controller_.IsInFullscreenTransition());
+
+  // Exit fullscreen.
+  EXPECT_CALL(mock_client_, FullscreenControllerTransitionStart(false))
+      .Times(1);
+  controller_.ExitFullscreen();
+  EXPECT_TRUE(controller_.IsInFullscreenTransition());
+
+  // Make ToggleFullscreen happen, and invoke OnWindowWillExitFullscreen.
+  EXPECT_CALL(mock_client_, FullscreenControllerToggleFullscreen())
+      .WillOnce(Invoke(&controller_, &NativeWidgetNSWindowFullscreenController::
+                                         OnWindowWillExitFullscreen));
+  task_environment_.RunUntilIdle();
+  EXPECT_TRUE(controller_.IsInFullscreenTransition());
+
+  // Complete the transition.
+  EXPECT_CALL(mock_client_, FullscreenControllerTransitionComplete(false))
+      .Times(1);
+  controller_.OnWindowDidExitFullscreen();
+  EXPECT_FALSE(controller_.IsInFullscreenTransition());
+  task_environment_.RunUntilIdle();
+}
+
+// A transition that fails to enter fullscreen.
+TEST_F(MacFullscreenControllerTest, FailEnterFullscreenSimple) {
+  // Enter fullscreen.
+  EXPECT_CALL(mock_client_, FullscreenControllerGetFrame())
+      .Times(1)
+      .WillOnce(Return(kWindowRect));
+  EXPECT_CALL(mock_client_, FullscreenControllerTransitionStart(true)).Times(1);
+  controller_.EnterFullscreen(display::kInvalidDisplayId);
+  EXPECT_CALL(mock_client_, FullscreenControllerToggleFullscreen())
+      .WillOnce(Invoke(&controller_, &NativeWidgetNSWindowFullscreenController::
+                                         OnWindowWillEnterFullscreen));
+  task_environment_.RunUntilIdle();
+
+  // Fail the transition.
+  EXPECT_CALL(mock_client_, FullscreenControllerTransitionComplete(false))
+      .Times(1);
+  controller_.OnWindowDidExitFullscreen();
+  EXPECT_FALSE(controller_.IsInFullscreenTransition());
+  EXPECT_FALSE(controller_.GetTargetFullscreenState());
+  task_environment_.RunUntilIdle();
+  EXPECT_FALSE(controller_.IsInFullscreenTransition());
+  EXPECT_FALSE(controller_.GetTargetFullscreenState());
+}
+
+// A transition that fails to exit fullscreen.
+TEST_F(MacFullscreenControllerTest, FailExitFullscreen) {
+  // Enter fullscreen.
+  EXPECT_CALL(mock_client_, FullscreenControllerGetFrame())
+      .Times(1)
+      .WillOnce(Return(kWindowRect));
+  EXPECT_CALL(mock_client_, FullscreenControllerTransitionStart(true)).Times(1);
+  controller_.EnterFullscreen(display::kInvalidDisplayId);
+  EXPECT_CALL(mock_client_, FullscreenControllerToggleFullscreen())
+      .WillOnce(Invoke(&controller_, &NativeWidgetNSWindowFullscreenController::
+                                         OnWindowWillEnterFullscreen));
+  task_environment_.RunUntilIdle();
+  EXPECT_CALL(mock_client_, FullscreenControllerTransitionComplete(true))
+      .Times(1);
+  controller_.OnWindowDidEnterFullscreen();
+  EXPECT_FALSE(controller_.IsInFullscreenTransition());
+  EXPECT_TRUE(controller_.GetTargetFullscreenState());
+
+  // Start to exit fullscreen, through OnWindowWillExitFullscreen.
+  EXPECT_CALL(mock_client_, FullscreenControllerTransitionStart(false))
+      .Times(1);
+  controller_.ExitFullscreen();
+  EXPECT_TRUE(controller_.IsInFullscreenTransition());
+  EXPECT_CALL(mock_client_, FullscreenControllerToggleFullscreen())
+      .WillOnce(Invoke(&controller_, &NativeWidgetNSWindowFullscreenController::
+                                         OnWindowWillExitFullscreen));
+  task_environment_.RunUntilIdle();
+  EXPECT_TRUE(controller_.IsInFullscreenTransition());
+
+  // Fail the transition.
+  EXPECT_CALL(mock_client_, FullscreenControllerTransitionComplete(true))
+      .Times(1);
+  controller_.OnWindowDidEnterFullscreen();
+  EXPECT_FALSE(controller_.IsInFullscreenTransition());
+  EXPECT_TRUE(controller_.GetTargetFullscreenState());
+  task_environment_.RunUntilIdle();
+  EXPECT_FALSE(controller_.IsInFullscreenTransition());
+  EXPECT_TRUE(controller_.GetTargetFullscreenState());
+}
+
+// A simple cross-screen transition.
+TEST_F(MacFullscreenControllerTest, SimpleCrossScreen) {
+  // Enter fullscreen to display 0, from display 0.
+  EXPECT_CALL(mock_client_, FullscreenControllerGetFrame())
+      .Times(1)
+      .WillOnce(Return(kWindowRect));
+  EXPECT_CALL(mock_client_, FullscreenControllerTransitionStart(true)).Times(1);
+  controller_.EnterFullscreen(kDisplay1Id);
+  EXPECT_TRUE(controller_.IsInFullscreenTransition());
+
+  // This will trigger a call to MoveToTargetDisplayThenToggleFullscreen, even
+  // though we are not actually moving displays. It will also then post a task
+  // To ToggleFullscreen (because RunUntilIdle will pick up that posted task).
+  EXPECT_CALL(mock_client_, FullscreenControllerGetFrameForDisplay(kDisplay1Id))
+      .Times(1)
+      .WillOnce(Return(kDisplay1Frame));
+  EXPECT_CALL(mock_client_, FullscreenControllerSetFrame(kDisplay1Frame, _))
+      .Times(1);
+  EXPECT_CALL(mock_client_, FullscreenControllerToggleFullscreen())
+      .WillOnce(Invoke(&controller_, &NativeWidgetNSWindowFullscreenController::
+                                         OnWindowWillEnterFullscreen));
+  task_environment_.RunUntilIdle();
+  EXPECT_TRUE(controller_.IsInFullscreenTransition());
+  EXPECT_TRUE(controller_.GetTargetFullscreenState());
+
+  // Complete the transition to fullscreen on the new display.
+  EXPECT_CALL(mock_client_, FullscreenControllerTransitionComplete(true))
+      .Times(1);
+  controller_.OnWindowDidEnterFullscreen();
+  EXPECT_FALSE(controller_.IsInFullscreenTransition());
+  EXPECT_TRUE(controller_.GetTargetFullscreenState());
+
+  // Re-entering fullscreen on our current display should be a no-op.
+  EXPECT_CALL(mock_client_, FullscreenControllerGetDisplayId())
+      .WillOnce(Return(kDisplay1Id));
+  controller_.EnterFullscreen(kDisplay1Id);
+  task_environment_.RunUntilIdle();
+  EXPECT_FALSE(controller_.IsInFullscreenTransition());
+  EXPECT_TRUE(controller_.GetTargetFullscreenState());
+
+  // Exit fullscreen. This will post a task to restore bounds.
+  EXPECT_CALL(mock_client_, FullscreenControllerTransitionStart(false))
+      .Times(1);
+  controller_.ExitFullscreen();
+  EXPECT_CALL(mock_client_, FullscreenControllerToggleFullscreen())
+      .WillOnce(Invoke(&controller_, &NativeWidgetNSWindowFullscreenController::
+                                         OnWindowWillExitFullscreen));
+  task_environment_.RunUntilIdle();
+  controller_.OnWindowDidExitFullscreen();
+  EXPECT_TRUE(controller_.IsInFullscreenTransition());
+  EXPECT_FALSE(controller_.GetTargetFullscreenState());
+
+  // Let the run loop run, it will restore the bounds.
+  EXPECT_CALL(mock_client_, FullscreenControllerSetFrame(kWindowRect, _))
+      .Times(1);
+  EXPECT_CALL(mock_client_, FullscreenControllerTransitionComplete(false))
+      .Times(1);
+  task_environment_.RunUntilIdle();
+  EXPECT_FALSE(controller_.IsInFullscreenTransition());
+  EXPECT_FALSE(controller_.GetTargetFullscreenState());
+}
+
+// A cross-screen transition after having entered fullscreen.
+TEST_F(MacFullscreenControllerTest, CrossScreenFromFullscreen) {
+  // Enter fullscreen the way that clicking the green traffic light does it.
+  EXPECT_CALL(mock_client_, FullscreenControllerGetFrame())
+      .Times(1)
+      .WillOnce(Return(kWindowRect));
+  EXPECT_CALL(mock_client_, FullscreenControllerTransitionStart(true)).Times(1);
+  controller_.OnWindowWillEnterFullscreen();
+  EXPECT_CALL(mock_client_, FullscreenControllerTransitionComplete(true))
+      .Times(1);
+  controller_.OnWindowDidEnterFullscreen();
+  EXPECT_FALSE(controller_.IsInFullscreenTransition());
+  EXPECT_TRUE(controller_.GetTargetFullscreenState());
+
+  // Enter fullscreen on a different display.
+  EXPECT_CALL(mock_client_, FullscreenControllerTransitionStart(true)).Times(1);
+  EXPECT_CALL(mock_client_, FullscreenControllerGetDisplayId())
+      .WillRepeatedly(Return(kDisplay0Id));
+  controller_.EnterFullscreen(kDisplay1Id);
+  EXPECT_TRUE(controller_.IsInFullscreenTransition());
+  EXPECT_TRUE(controller_.GetTargetFullscreenState());
+
+  // Execute the task posted to ToggleFullscreen.
+  EXPECT_CALL(mock_client_, FullscreenControllerToggleFullscreen())
+      .WillOnce(Invoke(&controller_, &NativeWidgetNSWindowFullscreenController::
+                                         OnWindowWillExitFullscreen));
+  task_environment_.RunUntilIdle();
+  EXPECT_TRUE(controller_.IsInFullscreenTransition());
+  EXPECT_TRUE(controller_.GetTargetFullscreenState());
+
+  // Complete the transition to windowed. This will post a task to move to the
+  // target display's frame.
+  EXPECT_CALL(mock_client_, FullscreenControllerGetFrameForDisplay(kDisplay1Id))
+      .Times(1)
+      .WillOnce(Return(kDisplay1Frame));
+  controller_.OnWindowDidExitFullscreen();
+  EXPECT_TRUE(controller_.IsInFullscreenTransition());
+  EXPECT_TRUE(controller_.GetTargetFullscreenState());
+
+  // Execute the posted task to set the new frame. This will also re-toggle
+  // fullscreen in the RunUntilIdle.
+  EXPECT_CALL(mock_client_, FullscreenControllerSetFrame(kDisplay1Frame, _))
+      .Times(1);
+  EXPECT_CALL(mock_client_, FullscreenControllerToggleFullscreen())
+      .WillOnce(Invoke(&controller_, &NativeWidgetNSWindowFullscreenController::
+                                         OnWindowWillEnterFullscreen));
+  task_environment_.RunUntilIdle();
+  EXPECT_TRUE(controller_.GetTargetFullscreenState());
+
+  // Complete the transition.
+  EXPECT_CALL(mock_client_, FullscreenControllerTransitionComplete(true))
+      .Times(1);
+  controller_.OnWindowDidEnterFullscreen();
+  task_environment_.RunUntilIdle();
+  EXPECT_FALSE(controller_.IsInFullscreenTransition());
+  EXPECT_TRUE(controller_.GetTargetFullscreenState());
+
+  // Exit fullscreen. This will restore bounds.
+  EXPECT_CALL(mock_client_, FullscreenControllerTransitionStart(false))
+      .Times(1);
+  controller_.ExitFullscreen();
+  EXPECT_CALL(mock_client_, FullscreenControllerToggleFullscreen())
+      .WillOnce(Invoke(&controller_, &NativeWidgetNSWindowFullscreenController::
+                                         OnWindowWillExitFullscreen));
+  task_environment_.RunUntilIdle();
+  controller_.OnWindowDidExitFullscreen();
+  EXPECT_CALL(mock_client_, FullscreenControllerSetFrame(kWindowRect, _))
+      .Times(1);
+  EXPECT_CALL(mock_client_, FullscreenControllerTransitionComplete(false))
+      .Times(1);
+  task_environment_.RunUntilIdle();
+  EXPECT_FALSE(controller_.IsInFullscreenTransition());
+  EXPECT_FALSE(controller_.GetTargetFullscreenState());
+}
+
+// Fail a cross-screen transition when exiting fullscreen.
+TEST_F(MacFullscreenControllerTest, CrossScreenFromFullscreenFailExit) {
+  // Enter fullscreen the way that clicking the green traffic light does it.
+  EXPECT_CALL(mock_client_, FullscreenControllerGetFrame())
+      .Times(1)
+      .WillOnce(Return(kWindowRect));
+  EXPECT_CALL(mock_client_, FullscreenControllerTransitionStart(true)).Times(1);
+  controller_.OnWindowWillEnterFullscreen();
+  EXPECT_CALL(mock_client_, FullscreenControllerTransitionComplete(true))
+      .Times(1);
+  controller_.OnWindowDidEnterFullscreen();
+  EXPECT_FALSE(controller_.IsInFullscreenTransition());
+  EXPECT_TRUE(controller_.GetTargetFullscreenState());
+
+  // Attempt to enter fullscreen on a different display. Get as far as toggling
+  // fullscreen to get back to windowed mode.
+  EXPECT_CALL(mock_client_, FullscreenControllerTransitionStart(true)).Times(1);
+  EXPECT_CALL(mock_client_, FullscreenControllerGetDisplayId())
+      .WillRepeatedly(Return(kDisplay0Id));
+  controller_.EnterFullscreen(kDisplay1Id);
+  EXPECT_CALL(mock_client_, FullscreenControllerToggleFullscreen())
+      .WillOnce(Invoke(&controller_, &NativeWidgetNSWindowFullscreenController::
+                                         OnWindowWillExitFullscreen));
+  task_environment_.RunUntilIdle();
+  EXPECT_TRUE(controller_.IsInFullscreenTransition());
+  EXPECT_TRUE(controller_.GetTargetFullscreenState());
+
+  // Fail the transition back to windowed. This should just leave us as being
+  // fullscreen. It will issue the TransitionComplete notification indicating
+  // that the transition is over, and that we're in fullscreen mode (no mention
+  // of which display).
+  EXPECT_CALL(mock_client_, FullscreenControllerTransitionComplete(true))
+      .Times(1);
+  controller_.OnWindowDidEnterFullscreen();
+  EXPECT_FALSE(controller_.IsInFullscreenTransition());
+  EXPECT_TRUE(controller_.GetTargetFullscreenState());
+
+  // No further tasks should run.
+  task_environment_.RunUntilIdle();
+  EXPECT_FALSE(controller_.IsInFullscreenTransition());
+  EXPECT_TRUE(controller_.GetTargetFullscreenState());
+}
+
+// Fail a cross-screen transition when entering fullscreen.
+TEST_F(MacFullscreenControllerTest, CrossScreenFromFullscreenFailEnter) {
+  // Enter fullscreen the way that clicking the green traffic light does it.
+  EXPECT_CALL(mock_client_, FullscreenControllerGetFrame())
+      .Times(1)
+      .WillOnce(Return(kWindowRect));
+  EXPECT_CALL(mock_client_, FullscreenControllerTransitionStart(true)).Times(1);
+  controller_.OnWindowWillEnterFullscreen();
+  EXPECT_CALL(mock_client_, FullscreenControllerTransitionComplete(true))
+      .Times(1);
+  controller_.OnWindowDidEnterFullscreen();
+  EXPECT_FALSE(controller_.IsInFullscreenTransition());
+  EXPECT_TRUE(controller_.GetTargetFullscreenState());
+
+  // Enter fullscreen on a different display. Get as far as toggling fullscreen
+  // to get back to windowed mode, moving the window, and then toggling
+  // fullscreen on the new display.
+  EXPECT_CALL(mock_client_, FullscreenControllerTransitionStart(true)).Times(1);
+  EXPECT_CALL(mock_client_, FullscreenControllerGetDisplayId())
+      .WillRepeatedly(Return(kDisplay0Id));
+  controller_.EnterFullscreen(kDisplay1Id);
+  EXPECT_CALL(mock_client_, FullscreenControllerToggleFullscreen())
+      .WillOnce(Invoke(&controller_, &NativeWidgetNSWindowFullscreenController::
+                                         OnWindowWillExitFullscreen));
+  task_environment_.RunUntilIdle();
+  EXPECT_CALL(mock_client_, FullscreenControllerGetFrameForDisplay(kDisplay1Id))
+      .Times(1)
+      .WillOnce(Return(kDisplay1Frame));
+  controller_.OnWindowDidExitFullscreen();
+  EXPECT_CALL(mock_client_, FullscreenControllerSetFrame(kDisplay1Frame, _))
+      .Times(1);
+  EXPECT_CALL(mock_client_, FullscreenControllerToggleFullscreen())
+      .WillOnce(Invoke(&controller_, &NativeWidgetNSWindowFullscreenController::
+                                         OnWindowWillEnterFullscreen));
+  task_environment_.RunUntilIdle();
+  EXPECT_TRUE(controller_.IsInFullscreenTransition());
+  EXPECT_TRUE(controller_.GetTargetFullscreenState());
+
+  // Fail the transition to fullscreen mode. This will cause us to post tasks
+  // to stay in windowed mode.
+  controller_.OnWindowDidExitFullscreen();
+  EXPECT_TRUE(controller_.IsInFullscreenTransition());
+  EXPECT_FALSE(controller_.GetTargetFullscreenState());
+
+  // This will cause us to restore the window's original frame, and then declare
+  // the transition complete, with the final state after transition as being
+  // windowed.
+  EXPECT_CALL(mock_client_, FullscreenControllerSetFrame(kWindowRect, _))
+      .Times(1);
+  EXPECT_CALL(mock_client_, FullscreenControllerTransitionComplete(false))
+      .Times(1);
+  task_environment_.RunUntilIdle();
+  EXPECT_FALSE(controller_.IsInFullscreenTransition());
+  EXPECT_FALSE(controller_.GetTargetFullscreenState());
+}
+
+// Be instructed to exit fullscreen while entering fullscreen.
+TEST_F(MacFullscreenControllerTest, ExitWhileEntering) {
+  // Enter fullscreen the way that clicking the green traffic light does it.
+  EXPECT_CALL(mock_client_, FullscreenControllerGetFrame())
+      .Times(1)
+      .WillOnce(Return(kWindowRect));
+  EXPECT_CALL(mock_client_, FullscreenControllerTransitionStart(true)).Times(1);
+  controller_.OnWindowWillEnterFullscreen();
+  task_environment_.RunUntilIdle();
+  EXPECT_TRUE(controller_.IsInFullscreenTransition());
+
+  // Call ExitFullscreen inside the transition.
+  controller_.ExitFullscreen();
+  task_environment_.RunUntilIdle();
+  EXPECT_TRUE(controller_.IsInFullscreenTransition());
+  EXPECT_FALSE(controller_.GetTargetFullscreenState());
+
+  // Complete the transition to fullscreen. This will report that it is still
+  // in transition to windowed.
+  controller_.OnWindowDidEnterFullscreen();
+  EXPECT_TRUE(controller_.IsInFullscreenTransition());
+  EXPECT_FALSE(controller_.GetTargetFullscreenState());
+
+  // The above call will have posted a ToggleFullscreen task.
+  EXPECT_CALL(mock_client_, FullscreenControllerToggleFullscreen())
+      .WillOnce(Invoke(&controller_, &NativeWidgetNSWindowFullscreenController::
+                                         OnWindowWillExitFullscreen));
+  task_environment_.RunUntilIdle();
+
+  // Complete the transition to windowed mode.
+  EXPECT_CALL(mock_client_, FullscreenControllerTransitionComplete(false))
+      .Times(1);
+  controller_.OnWindowDidExitFullscreen();
+  EXPECT_FALSE(controller_.IsInFullscreenTransition());
+  EXPECT_FALSE(controller_.GetTargetFullscreenState());
+
+  // Nothing more should happen.
+  task_environment_.RunUntilIdle();
+}
+
+// Be instructed to enter fullscreen while exiting fullscreen.
+TEST_F(MacFullscreenControllerTest, EnterWhileExiting) {
+  // Enter fullscreen the way that clicking the green traffic light does it.
+  EXPECT_CALL(mock_client_, FullscreenControllerGetFrame())
+      .Times(1)
+      .WillOnce(Return(kWindowRect));
+  EXPECT_CALL(mock_client_, FullscreenControllerTransitionStart(true)).Times(1);
+  controller_.OnWindowWillEnterFullscreen();
+  task_environment_.RunUntilIdle();
+  EXPECT_TRUE(controller_.IsInFullscreenTransition());
+  EXPECT_CALL(mock_client_, FullscreenControllerTransitionComplete(true))
+      .Times(1);
+  controller_.OnWindowDidEnterFullscreen();
+  EXPECT_FALSE(controller_.IsInFullscreenTransition());
+  EXPECT_TRUE(controller_.GetTargetFullscreenState());
+
+  // Exit fullscreen.
+  EXPECT_CALL(mock_client_, FullscreenControllerTransitionStart(false))
+      .Times(1);
+  controller_.OnWindowWillExitFullscreen();
+  EXPECT_TRUE(controller_.IsInFullscreenTransition());
+  EXPECT_FALSE(controller_.GetTargetFullscreenState());
+
+  // Call EnterFullscreen inside the transition.
+  controller_.EnterFullscreen(display::kInvalidDisplayId);
+  EXPECT_TRUE(controller_.IsInFullscreenTransition());
+  EXPECT_TRUE(controller_.GetTargetFullscreenState());
+
+  // Complete the transition to windowed mode. This will report that it is
+  // still in transition to fullscreen.
+  controller_.OnWindowDidExitFullscreen();
+  EXPECT_TRUE(controller_.IsInFullscreenTransition());
+  EXPECT_TRUE(controller_.GetTargetFullscreenState());
+
+  // The above call will have posted a ToggleFullscreen task.
+  EXPECT_CALL(mock_client_, FullscreenControllerToggleFullscreen())
+      .WillOnce(Invoke(&controller_, &NativeWidgetNSWindowFullscreenController::
+                                         OnWindowWillEnterFullscreen));
+  task_environment_.RunUntilIdle();
+  EXPECT_TRUE(controller_.IsInFullscreenTransition());
+  EXPECT_TRUE(controller_.GetTargetFullscreenState());
+
+  // Complete the transition to fullscreen mode.
+  EXPECT_CALL(mock_client_, FullscreenControllerTransitionComplete(true))
+      .Times(1);
+  controller_.OnWindowDidEnterFullscreen();
+  EXPECT_FALSE(controller_.IsInFullscreenTransition());
+  EXPECT_TRUE(controller_.GetTargetFullscreenState());
+
+  // Nothing more should happen.
+  task_environment_.RunUntilIdle();
+}
+
+// Be instructed to enter fullscreen on a different screen, while exiting
+// fullscreen.
+TEST_F(MacFullscreenControllerTest, EnterCrossScreenWhileExiting) {
+  // Enter fullscreen the way that clicking the green traffic light does it.
+  EXPECT_CALL(mock_client_, FullscreenControllerGetFrame())
+      .Times(1)
+      .WillOnce(Return(kWindowRect));
+  EXPECT_CALL(mock_client_, FullscreenControllerTransitionStart(true)).Times(1);
+  controller_.OnWindowWillEnterFullscreen();
+  task_environment_.RunUntilIdle();
+  EXPECT_TRUE(controller_.IsInFullscreenTransition());
+  EXPECT_CALL(mock_client_, FullscreenControllerTransitionComplete(true))
+      .Times(1);
+  controller_.OnWindowDidEnterFullscreen();
+  EXPECT_FALSE(controller_.IsInFullscreenTransition());
+  EXPECT_TRUE(controller_.GetTargetFullscreenState());
+
+  // Exit fullscreen.
+  EXPECT_CALL(mock_client_, FullscreenControllerTransitionStart(false))
+      .Times(1);
+  controller_.OnWindowWillExitFullscreen();
+  EXPECT_TRUE(controller_.IsInFullscreenTransition());
+  EXPECT_FALSE(controller_.GetTargetFullscreenState());
+
+  // Call EnterFullscreen inside the transition.
+  controller_.EnterFullscreen(kDisplay1Id);
+  EXPECT_TRUE(controller_.IsInFullscreenTransition());
+  EXPECT_TRUE(controller_.GetTargetFullscreenState());
+
+  // Complete the transition to windowed mode. This will report that it is
+  // still in transition to fullscreen.
+  controller_.OnWindowDidExitFullscreen();
+  EXPECT_TRUE(controller_.IsInFullscreenTransition());
+  EXPECT_TRUE(controller_.GetTargetFullscreenState());
+
+  // The above call will have posted a MoveToTargetDisplayThenToggleFullscreen
+  // task, which will then post a ToggleFullscreen task.
+  EXPECT_CALL(mock_client_, FullscreenControllerGetFrameForDisplay(kDisplay1Id))
+      .Times(1)
+      .WillOnce(Return(kDisplay1Frame));
+  EXPECT_CALL(mock_client_, FullscreenControllerSetFrame(kDisplay1Frame, _))
+      .Times(1);
+  EXPECT_CALL(mock_client_, FullscreenControllerToggleFullscreen())
+      .WillOnce(Invoke(&controller_, &NativeWidgetNSWindowFullscreenController::
+                                         OnWindowWillEnterFullscreen));
+  task_environment_.RunUntilIdle();
+  EXPECT_TRUE(controller_.IsInFullscreenTransition());
+  EXPECT_TRUE(controller_.GetTargetFullscreenState());
+
+  // Complete the transition to fullscreen mode.
+  EXPECT_CALL(mock_client_, FullscreenControllerTransitionComplete(true))
+      .Times(1);
+  controller_.OnWindowDidEnterFullscreen();
+  EXPECT_FALSE(controller_.IsInFullscreenTransition());
+  EXPECT_TRUE(controller_.GetTargetFullscreenState());
+
+  // Nothing more should happen.
+  task_environment_.RunUntilIdle();
+}
+
+// Be instructed to enter fullscreen on a different screen, while entering
+// fullscreen.
+TEST_F(MacFullscreenControllerTest, EnterCrossScreenWhileEntering) {
+  // Enter fullscreen the way that clicking the green traffic light does it.
+  EXPECT_CALL(mock_client_, FullscreenControllerGetFrame())
+      .Times(1)
+      .WillOnce(Return(kWindowRect));
+  EXPECT_CALL(mock_client_, FullscreenControllerTransitionStart(true)).Times(1);
+  controller_.OnWindowWillEnterFullscreen();
+  task_environment_.RunUntilIdle();
+
+  // Call EnterFullscreen inside the transition.
+  controller_.EnterFullscreen(kDisplay1Id);
+  EXPECT_TRUE(controller_.IsInFullscreenTransition());
+  EXPECT_TRUE(controller_.GetTargetFullscreenState());
+
+  // Complete the original fullscreen transition. This will check to see if
+  // the display we are on is the display we wanted to be on. Seeing that it
+  // isn't it will post a task to exit fullscreen to move to the correct
+  // display.
+  EXPECT_CALL(mock_client_, FullscreenControllerGetDisplayId())
+      .WillOnce(Return(kDisplay0Id));
+  controller_.OnWindowDidEnterFullscreen();
+  EXPECT_TRUE(controller_.IsInFullscreenTransition());
+  EXPECT_TRUE(controller_.GetTargetFullscreenState());
+
+  // The above will have posted a task to ToggleFullscreen.
+  EXPECT_CALL(mock_client_, FullscreenControllerToggleFullscreen())
+      .WillOnce(Invoke(&controller_, &NativeWidgetNSWindowFullscreenController::
+                                         OnWindowWillExitFullscreen));
+  task_environment_.RunUntilIdle();
+
+  // Complete the transition to windowed mode. This will then move the window
+  // and toggle fullscreen.
+  controller_.OnWindowDidExitFullscreen();
+  EXPECT_CALL(mock_client_, FullscreenControllerGetFrameForDisplay(kDisplay1Id))
+      .Times(1)
+      .WillOnce(Return(kDisplay1Frame));
+  EXPECT_CALL(mock_client_, FullscreenControllerSetFrame(kDisplay1Frame, _))
+      .Times(1);
+  EXPECT_CALL(mock_client_, FullscreenControllerToggleFullscreen())
+      .WillOnce(Invoke(&controller_, &NativeWidgetNSWindowFullscreenController::
+                                         OnWindowWillEnterFullscreen));
+  task_environment_.RunUntilIdle();
+  EXPECT_TRUE(controller_.IsInFullscreenTransition());
+  EXPECT_TRUE(controller_.GetTargetFullscreenState());
+
+  // Complete the transition to fullscreen.
+  EXPECT_CALL(mock_client_, FullscreenControllerTransitionComplete(true))
+      .Times(1);
+  controller_.OnWindowDidEnterFullscreen();
+  EXPECT_FALSE(controller_.IsInFullscreenTransition());
+  EXPECT_TRUE(controller_.GetTargetFullscreenState());
+
+  // Exit fullscreen.
+  EXPECT_CALL(mock_client_, FullscreenControllerTransitionStart(false))
+      .Times(1);
+  controller_.OnWindowWillExitFullscreen();
+  EXPECT_TRUE(controller_.IsInFullscreenTransition());
+  EXPECT_FALSE(controller_.GetTargetFullscreenState());
+
+  // Complete the transition to windowed mode. The frame frame should be reset.
+  controller_.OnWindowDidExitFullscreen();
+  EXPECT_TRUE(controller_.IsInFullscreenTransition());
+  EXPECT_CALL(mock_client_, FullscreenControllerSetFrame(kWindowRect, _))
+      .Times(1);
+  EXPECT_CALL(mock_client_, FullscreenControllerTransitionComplete(false))
+      .Times(1);
+  task_environment_.RunUntilIdle();
+  EXPECT_FALSE(controller_.IsInFullscreenTransition());
+  EXPECT_FALSE(controller_.GetTargetFullscreenState());
+}
+
+}  // namespace test
+}  // namespace remote_cocoa
diff --git a/weblayer/browser/content_browser_client_impl.cc b/weblayer/browser/content_browser_client_impl.cc
index 8c31bf2..070d682 100644
--- a/weblayer/browser/content_browser_client_impl.cc
+++ b/weblayer/browser/content_browser_client_impl.cc
@@ -1209,7 +1209,6 @@
     content::RenderFrameHost* render_frame_host) {
   DCHECK(render_frame_host);
 
-  const GURL& url = render_frame_host->GetLastCommittedOrigin().GetURL();
   content::BrowserContext* browser_context =
       render_frame_host->GetBrowserContext();
   DCHECK(browser_context);
@@ -1217,9 +1216,8 @@
   content::PermissionController* permission_controller =
       browser_context->GetPermissionController();
   blink::mojom::PermissionStatus status =
-      permission_controller->GetPermissionStatusForFrame(
-          content::PermissionType::CLIPBOARD_READ_WRITE, render_frame_host,
-          url);
+      permission_controller->GetPermissionStatusForCurrentDocument(
+          content::PermissionType::CLIPBOARD_READ_WRITE, render_frame_host);
 
   if (!render_frame_host->HasTransientUserActivation() &&
       status != blink::mojom::PermissionStatus::GRANTED) {