diff --git a/DEPS b/DEPS
index 425e4f4b..d6616ff 100644
--- a/DEPS
+++ b/DEPS
@@ -177,7 +177,7 @@
   # luci-go CIPD package version.
   # Make sure the revision is uploaded by infra-packagers builder.
   # https://ci.chromium.org/p/infra-internal/g/infra-packagers/console
-  'luci_go': 'git_revision:abd26383ec208bf080ee8585d1495e7f75483ec9',
+  'luci_go': 'git_revision:c9957ed0ce0fd363aac127056344eba1b873bad0',
 
   # This can be overridden, e.g. with custom_vars, to build clang from HEAD
   # instead of downloading the prebuilt pinned revision.
@@ -209,7 +209,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Skia
   # and whatever else without interference from each other.
-  'skia_revision': '6f4bacb9df54d7be6edee0140da595139fb820ca',
+  'skia_revision': '357e67e7af3b95411f3f8426ca5de4767eb85d07',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
@@ -221,7 +221,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ANGLE
   # and whatever else without interference from each other.
-  'angle_revision': 'be873929d707de54dd3d0fae31acdeea6ec8c8c2',
+  'angle_revision': 'fec39fa1fc5985d07d6ae5fb39121b695d553488',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
@@ -280,7 +280,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling catapult
   # and whatever else without interference from each other.
-  'catapult_revision': 'ec690bb8debdc5c03a90654a9eb6d8d762dd26ee',
+  'catapult_revision': '712eb08096321db1e89699f933c14fbd11fcb0ed',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
@@ -288,7 +288,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling devtools-frontend
   # and whatever else without interference from each other.
-  'devtools_frontend_revision': 'ad4964c542770fba75466e66ca63903d4e2f0722',
+  'devtools_frontend_revision': '49b68def657abf35d54d044317854c5d66973a5e',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libprotobuf-mutator
   # and whatever else without interference from each other.
@@ -328,7 +328,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'dawn_revision': '661928894ec9d4d0024e5444bc3ab86575679400',
+  'dawn_revision': 'b3c371031cc85b7af53d50386c7865fcb35a0a6a',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -643,7 +643,7 @@
       'packages': [
         {
           'package': 'chromium/rts/model/linux-amd64',
-          'version': 'FhI-EilC0HUglPzO3z1pvUzIutL1h_JytCiVgyxN0BYC',
+          'version': 'AuRsBzJA9Y6af0fcM4IGlO6_aewhxhwOsNR1GkV39aYC',
         },
       ],
       'dep_type': 'cipd',
@@ -654,7 +654,7 @@
       'packages': [
         {
           'package': 'chromium/rts/model/mac-amd64',
-          'version': 'jbwok3INQjzMDqZtOLyPpUvWdMrA2Ik6lJ-pjM8bvosC',
+          'version': 'WOrwX1yQ3gpyGuJlcfl4bzOHIPiGcSr3FBvYYMs-DW8C',
         },
       ],
       'dep_type': 'cipd',
@@ -665,7 +665,7 @@
       'packages': [
         {
           'package': 'chromium/rts/model/windows-amd64',
-          'version': 'C7ucDXsn1avmVkd2AmtllTpESb4IeFnsWdx5Ry95sXkC',
+          'version': '6EZxobRsu9uWCW3TGRN0cj_8H388lPgt2e2AV0NJEcoC',
         },
       ],
       'dep_type': 'cipd',
@@ -946,7 +946,7 @@
   # Tools used when building Chrome for Chrome OS. This affects both the Simple
   # Chrome workflow, as well as the chromeos-chrome ebuild.
   'src/third_party/chromite': {
-      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + 'b4d5ad4a85af28a8151c427329984cfa29bbca46',
+      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + 'ad4379840822c3831e1d3c00d613d67012fdf811',
       'condition': 'checkout_chromeos',
   },
 
@@ -966,7 +966,7 @@
   },
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + 'c8f63d390cfda6001a67316869914dca977770b7',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '85557a08f4918c65b5bc18868eaeb18e19800983',
 
   'src/third_party/devtools-frontend/src':
     Var('chromium_git') + '/devtools/devtools-frontend' + '@' + Var('devtools_frontend_revision'),
@@ -1349,7 +1349,7 @@
   },
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' + '8545284860afe328993751b80a9cf856872463df',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' + 'd355130be3c1738804c6cf787a1ff31886f539db',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '6f3e5028eb65d0b4c5fdd792106ac4c84eee1eb3',
@@ -1547,7 +1547,7 @@
   'src/third_party/usrsctp/usrsctplib':
     Var('chromium_git') + '/external/github.com/sctplab/usrsctp' + '@' + '22ba62ffe79c3881581ab430368bf3764d9533eb',
 
-  'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@e00902ff0f362653dbfc2c6220b11ff8f91a5f20',
+  'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@d39bb223cf04dea5d35cbd982e140d1540f96352',
 
   'src/third_party/vulkan_memory_allocator':
     Var('chromium_git') + '/external/github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git' + '@' + '732a76d9d3c70d6aa487216495eeb28518349c3a',
@@ -1574,7 +1574,7 @@
     Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + '7c0e4f92088f8230bd63a81b9b924fb9dadc5be8',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + 'fccb052ee38f4f5e7c5bf1666954b0dedef4223e',
+    Var('webrtc_git') + '/src.git' + '@' + '2a25a969731da40dbcf3468ff58713ea5c643484',
 
   'src/third_party/libgifcodec':
      Var('skia_git') + '/libgifcodec' + '@'+  Var('libgifcodec_revision'),
@@ -1601,7 +1601,7 @@
       'packages': [
         {
           'package': 'skia/tools/goldctl/linux-amd64',
-          'version': 'SIbSqtgKfmhBASeojfVyHGkIx2ZItagJYLeJt9yef1oC',
+          'version': 'v0j61b0DK560O1WaRwZ_kGgqR39JhxCdufdFIIheSfAC',
         },
       ],
       'dep_type': 'cipd',
@@ -1635,7 +1635,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@c48adaed513745039abef53b2ea5a0abd3057fc0',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@0643b2b983dd1126e4702664fde75dce35f06f2f',
     'condition': 'checkout_src_internal',
   },
 
@@ -1654,7 +1654,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/help_app/app',
-        'version': 'KXn8zZ3-mP6fa0dmjSEmUcmhZ-1rDhqe1djW922_BJQC',
+        'version': '1buQBclCFtgWF-REQWmMlg1WMXSX6y03pzFmZybJuIAC',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
diff --git a/android_webview/browser/component_updater/OWNERS b/android_webview/browser/component_updater/OWNERS
new file mode 100644
index 0000000..15667db
--- /dev/null
+++ b/android_webview/browser/component_updater/OWNERS
@@ -0,0 +1 @@
+file://android_webview/nonembedded/component_updater/OWNERS
diff --git a/android_webview/nonembedded/component_updater/installer_policies/aw_package_names_allowlist_component_installer_policy.cc b/android_webview/nonembedded/component_updater/installer_policies/aw_package_names_allowlist_component_installer_policy.cc
index 57a30f9..357e4574 100644
--- a/android_webview/nonembedded/component_updater/installer_policies/aw_package_names_allowlist_component_installer_policy.cc
+++ b/android_webview/nonembedded/component_updater/installer_policies/aw_package_names_allowlist_component_installer_policy.cc
@@ -87,11 +87,10 @@
   return false;
 }
 
-// Called during startup and installation before ComponentReady().
 bool AwPackageNamesAllowlistComponentInstallerPolicy::VerifyInstallation(
     const base::DictionaryValue& manifest,
     const base::FilePath& install_dir) const {
-  return manifest.HasKey(kWebViewAppsPackageNamesAllowlistName);
+  return true;
 }
 
 base::FilePath
diff --git a/ash/app_list/bubble/scrollable_apps_grid_view.cc b/ash/app_list/bubble/scrollable_apps_grid_view.cc
index 97bdfc7..9b9ec41 100644
--- a/ash/app_list/bubble/scrollable_apps_grid_view.cc
+++ b/ash/app_list/bubble/scrollable_apps_grid_view.cc
@@ -87,6 +87,10 @@
   return 0;
 }
 
+bool ScrollableAppsGridView::IsScrollAxisVertical() const {
+  return true;
+}
+
 void ScrollableAppsGridView::CalculateIdealBounds() {
   DCHECK(!is_in_folder());
 
diff --git a/ash/app_list/bubble/scrollable_apps_grid_view.h b/ash/app_list/bubble/scrollable_apps_grid_view.h
index 58602e2..052f7a8 100644
--- a/ash/app_list/bubble/scrollable_apps_grid_view.h
+++ b/ash/app_list/bubble/scrollable_apps_grid_view.h
@@ -30,6 +30,7 @@
   gfx::Insets GetTilePadding() const override;
   gfx::Size GetTileGridSize() const override;
   int GetPaddingBetweenPages() const override;
+  bool IsScrollAxisVertical() const override;
   void CalculateIdealBounds() override;
 };
 
diff --git a/ash/app_list/views/apps_grid_view.cc b/ash/app_list/views/apps_grid_view.cc
index 050bc25..16824a57 100644
--- a/ash/app_list/views/apps_grid_view.cc
+++ b/ash/app_list/views/apps_grid_view.cc
@@ -32,7 +32,6 @@
 #include "ash/public/cpp/app_list/app_list_features.h"
 #include "ash/public/cpp/app_list/app_list_switches.h"
 #include "ash/public/cpp/metrics_util.h"
-#include "ash/public/cpp/pagination/pagination_controller.h"
 #include "base/bind.h"
 #include "base/callback_helpers.h"
 #include "base/guid.h"
@@ -302,14 +301,6 @@
       GetAppListConfig().page_transition_duration(),
       GetAppListConfig().overscroll_page_transition_duration());
 
-  pagination_controller_ = std::make_unique<PaginationController>(
-      &pagination_model_,
-      folder_delegate_ ? PaginationController::SCROLL_AXIS_HORIZONTAL
-                       : PaginationController::SCROLL_AXIS_VERTICAL,
-      folder_delegate_
-          ? base::DoNothing()
-          : base::BindRepeating(&AppListRecordPageSwitcherSourceByEventType),
-      IsTabletMode());
   bounds_animator_->AddObserver(this);
 }
 
@@ -409,20 +400,6 @@
       ax::mojom::Event::kTreeChanged);
 }
 
-void AppsGridView::OnTabletModeChanged(bool started) {
-  pagination_controller_->set_is_tablet_mode(started);
-
-  // Enable/Disable folder icons's background blur based on tablet mode.
-  for (const auto& entry : view_model_.entries()) {
-    auto* item_view = static_cast<AppListItemView*>(entry.view);
-    if (item_view->item()->is_folder())
-      item_view->SetBackgroundBlurEnabled(started);
-  }
-
-  // Prevent context menus from remaining open after a transition
-  CancelContextMenusOnCurrentPage();
-}
-
 void AppsGridView::SetModel(AppListModel* model) {
   if (model_)
     model_->RemoveObserver(this);
@@ -1166,16 +1143,15 @@
     }
   }
 
-  if (pagination_controller_->scroll_axis() ==
-      PaginationController::SCROLL_AXIS_HORIZONTAL) {
-    // Page size including padding pixels. A tile.x + page_width means the same
-    // tile slot in the next page.
-    const int page_width = grid_size.width() + GetPaddingBetweenPages();
-    return gfx::Vector2d(page_width * multiplier, 0);
+  if (IsScrollAxisVertical()) {
+    const int page_height = grid_size.height() + GetPaddingBetweenPages();
+    return gfx::Vector2d(0, page_height * multiplier);
   }
 
-  const int page_height = grid_size.height() + GetPaddingBetweenPages();
-  return gfx::Vector2d(0, page_height * multiplier);
+  // Page size including padding pixels. A tile.x + page_width means the same
+  // tile slot in the next page.
+  const int page_width = grid_size.width() + GetPaddingBetweenPages();
+  return gfx::Vector2d(page_width * multiplier, 0);
 }
 
 void AppsGridView::CalculateIdealBoundsForFolder() {
@@ -3171,8 +3147,7 @@
   int new_page_flip_target = -1;
 
   // Drag zones are at the edges of the scroll axis.
-  if (pagination_controller_->scroll_axis() ==
-      PaginationController::SCROLL_AXIS_VERTICAL) {
+  if (IsScrollAxisVertical()) {
     if (drag_point.y() <
         GetAppListConfig().page_flip_zone_size() + GetInsets().top()) {
       new_page_flip_target = pagination_model_.selected_page() - 1;
diff --git a/ash/app_list/views/apps_grid_view.h b/ash/app_list/views/apps_grid_view.h
index 405de59..36da4ecd 100644
--- a/ash/app_list/views/apps_grid_view.h
+++ b/ash/app_list/views/apps_grid_view.h
@@ -46,7 +46,6 @@
 class AppListViewDelegate;
 class AppsGridViewFolderDelegate;
 class ContentsView;
-class PaginationController;
 class PulsingBlockView;
 class GhostImageView;
 
@@ -124,9 +123,6 @@
   // used to trap focus within the folder when it is opened.
   void DisableFocusForShowingActiveFolder(bool disabled);
 
-  // Called when tablet mode starts and ends.
-  void OnTabletModeChanged(bool started);
-
   // Sets |model| to use. Note this does not take ownership of |model|.
   void SetModel(AppListModel* model);
 
@@ -342,6 +338,10 @@
   // does not use pages.
   virtual int GetPaddingBetweenPages() const = 0;
 
+  // Returns true if scrolling is vertical (the common case). Folders may scroll
+  // horizontally.
+  virtual bool IsScrollAxisVertical() const = 0;
+
   // Starts the "cardified" state if the subclass supports it.
   virtual void MaybeStartCardifiedView() {}
 
@@ -403,9 +403,6 @@
   // TODO(crbug.com/1211608): Move these member variables to PagedAppsGridView.
   PaginationModel pagination_model_{this};
 
-  // Must appear after |pagination_model_|.
-  std::unique_ptr<PaginationController> pagination_controller_;
-
   // View structure used only for non-folder.
   PagedViewStructure view_structure_{this};
 
diff --git a/ash/app_list/views/paged_apps_grid_view.cc b/ash/app_list/views/paged_apps_grid_view.cc
index c4c944e..d75b04f 100644
--- a/ash/app_list/views/paged_apps_grid_view.cc
+++ b/ash/app_list/views/paged_apps_grid_view.cc
@@ -7,6 +7,8 @@
 #include <algorithm>
 #include <utility>
 
+#include "ash/app_list/app_list_metrics.h"
+#include "ash/app_list/model/app_list_item.h"
 #include "ash/app_list/views/app_list_item_view.h"
 #include "ash/app_list/views/app_list_main_view.h"
 #include "ash/app_list/views/contents_view.h"
@@ -168,12 +170,35 @@
       contents_view_(contents_view) {
   DCHECK(contents_view_);
   pagination_model_.AddObserver(this);
+
+  pagination_controller_ = std::make_unique<PaginationController>(
+      &pagination_model_,
+      is_in_folder() ? PaginationController::SCROLL_AXIS_HORIZONTAL
+                     : PaginationController::SCROLL_AXIS_VERTICAL,
+      is_in_folder()
+          ? base::DoNothing()
+          : base::BindRepeating(&AppListRecordPageSwitcherSourceByEventType),
+      IsTabletMode());
 }
 
 PagedAppsGridView::~PagedAppsGridView() {
   pagination_model_.RemoveObserver(this);
 }
 
+void PagedAppsGridView::OnTabletModeChanged(bool started) {
+  pagination_controller_->set_is_tablet_mode(started);
+
+  // Enable/Disable folder icons's background blur based on tablet mode.
+  for (const auto& entry : view_model()->entries()) {
+    auto* item_view = static_cast<AppListItemView*>(entry.view);
+    if (item_view->item()->is_folder())
+      item_view->SetBackgroundBlurEnabled(started);
+  }
+
+  // Prevent context menus from remaining open after a transition
+  CancelContextMenusOnCurrentPage();
+}
+
 void PagedAppsGridView::HandleScrollFromAppListView(const gfx::Vector2d& offset,
                                                     ui::EventType type) {
   // If |pagination_model_| is empty, don't handle scroll events.
@@ -474,6 +499,11 @@
              : GetAppListConfig().page_spacing();
 }
 
+bool PagedAppsGridView::IsScrollAxisVertical() const {
+  return pagination_controller_->scroll_axis() ==
+         PaginationController::SCROLL_AXIS_VERTICAL;
+}
+
 void PagedAppsGridView::MaybeStartCardifiedView() {
   if (!cardified_state_)
     StartAppsGridCardifiedView();
diff --git a/ash/app_list/views/paged_apps_grid_view.h b/ash/app_list/views/paged_apps_grid_view.h
index e4b98b3..dfc7c5b5 100644
--- a/ash/app_list/views/paged_apps_grid_view.h
+++ b/ash/app_list/views/paged_apps_grid_view.h
@@ -28,6 +28,7 @@
 namespace ash {
 
 class ContentsView;
+class PaginationController;
 
 // An apps grid that shows the apps on a series of fixed-size pages.
 // Used for the peeking/fullscreen launcher, home launcher and folders.
@@ -41,6 +42,9 @@
   PagedAppsGridView& operator=(const PagedAppsGridView&) = delete;
   ~PagedAppsGridView() override;
 
+  // Called when tablet mode starts and ends.
+  void OnTabletModeChanged(bool started);
+
   // Passes scroll information from AppListView to the PaginationController,
   // which may switch pages.
   void HandleScrollFromAppListView(const gfx::Vector2d& offset,
@@ -63,6 +67,7 @@
   gfx::Insets GetTilePadding() const override;
   gfx::Size GetTileGridSize() const override;
   int GetPaddingBetweenPages() const override;
+  bool IsScrollAxisVertical() const override;
   void MaybeStartCardifiedView() override;
   void MaybeEndCardifiedView() override;
 
@@ -125,6 +130,9 @@
   // Created by AppListMainView, owned by views hierarchy.
   ContentsView* const contents_view_;
 
+  // Depends on |pagination_model_|.
+  std::unique_ptr<PaginationController> pagination_controller_;
+
   // Whether the grid is in mouse drag. Used for between-item drags that move
   // the entire grid, not for app icon drags.
   bool is_in_mouse_drag_ = false;
diff --git a/ash/public/cpp/holding_space/holding_space_client.h b/ash/public/cpp/holding_space/holding_space_client.h
index fc9a0c16..5d7ce547 100644
--- a/ash/public/cpp/holding_space/holding_space_client.h
+++ b/ash/public/cpp/holding_space/holding_space_client.h
@@ -25,6 +25,9 @@
  public:
   using SuccessCallback = base::OnceCallback<void(bool)>;
 
+  // Adds a diagnostics log item backed by the provided `file_path`.
+  virtual void AddDiagnosticsLog(const base::FilePath& file_path) = 0;
+
   // Adds a screenshot item backed by the provided `file_path`.
   virtual void AddScreenshot(const base::FilePath& file_path) = 0;
 
diff --git a/ash/public/cpp/holding_space/mock_holding_space_client.h b/ash/public/cpp/holding_space/mock_holding_space_client.h
index 5bdd725..3ad946d0 100644
--- a/ash/public/cpp/holding_space/mock_holding_space_client.h
+++ b/ash/public/cpp/holding_space/mock_holding_space_client.h
@@ -24,6 +24,10 @@
 
   // HoldingSpaceClient:
   MOCK_METHOD(void,
+              AddDiagnosticsLog,
+              (const base::FilePath& file_path),
+              (override));
+  MOCK_METHOD(void,
               AddScreenshot,
               (const base::FilePath& file_path),
               (override));
diff --git a/ash/public/cpp/pagination/pagination_controller.cc b/ash/public/cpp/pagination/pagination_controller.cc
index 663c32d3..7e302a2 100644
--- a/ash/public/cpp/pagination/pagination_controller.cc
+++ b/ash/public/cpp/pagination/pagination_controller.cc
@@ -5,6 +5,7 @@
 #include "ash/public/cpp/pagination/pagination_controller.h"
 
 #include "ash/public/cpp/pagination/pagination_model.h"
+#include "base/check.h"
 #include "ui/gfx/geometry/rect.h"
 #include "ui/gfx/geometry/vector2d.h"
 
@@ -28,6 +29,7 @@
       scroll_axis_(scroll_axis),
       record_metrics_(record_metrics),
       is_tablet_mode_(is_tablet_mode) {
+  DCHECK(pagination_model_);
   DCHECK(record_metrics_);
 }
 
diff --git a/ash/public/cpp/pagination/pagination_controller.h b/ash/public/cpp/pagination/pagination_controller.h
index 9ab49b5..6832808 100644
--- a/ash/public/cpp/pagination/pagination_controller.h
+++ b/ash/public/cpp/pagination/pagination_controller.h
@@ -65,8 +65,8 @@
   // Helper function to change the page and callback record_metrics_.
   void SelectPageAndRecordMetric(int delta, ui::EventType type);
 
-  PaginationModel* pagination_model_;  // Not owned.
-  ScrollAxis scroll_axis_;
+  PaginationModel* const pagination_model_;  // Not owned.
+  const ScrollAxis scroll_axis_;
 
   const RecordMetrics record_metrics_;
 
diff --git a/ash/shortcut_viewer/keyboard_shortcut_viewer_metadata.cc b/ash/shortcut_viewer/keyboard_shortcut_viewer_metadata.cc
index 3c417f294..7fac2057 100644
--- a/ash/shortcut_viewer/keyboard_shortcut_viewer_metadata.cc
+++ b/ash/shortcut_viewer/keyboard_shortcut_viewer_metadata.cc
@@ -906,8 +906,7 @@
        // |accelerator_ids|
        {},
        // |shortcut_key_codes|
-       {ui::VKEY_CONTROL, ui::VKEY_UNKNOWN, ui::VKEY_LMENU, ui::VKEY_UNKNOWN,
-        ui::VKEY_UP}},
+       {ui::VKEY_COMMAND, ui::VKEY_UNKNOWN, ui::VKEY_LEFT}},
 
       {// |categories|
        {ShortcutCategory::kPageAndBrowser},
@@ -916,8 +915,7 @@
        // |accelerator_ids|
        {},
        // |shortcut_key_codes|
-       {ui::VKEY_CONTROL, ui::VKEY_UNKNOWN, ui::VKEY_LMENU, ui::VKEY_UNKNOWN,
-        ui::VKEY_DOWN}},
+       {ui::VKEY_COMMAND, ui::VKEY_UNKNOWN, ui::VKEY_RIGHT}},
 
       {// |categories|
        {ShortcutCategory::kPageAndBrowser},
@@ -979,15 +977,6 @@
        {ui::VKEY_LMENU, ui::VKEY_UNKNOWN}},
 
       {// |categories|
-       {ShortcutCategory::kSystemAndDisplay},
-       IDS_KSV_DESCRIPTION_USE_F_KEYS,
-       IDS_KSV_SHORTCUT_USE_F_KEYS,
-       // |accelerator_ids|
-       {},
-       // |shortcut_key_codes|
-       {ui::VKEY_COMMAND, ui::VKEY_UNKNOWN}},
-
-      {// |categories|
        {ShortcutCategory::kTextEditing},
        IDS_KSV_DESCRIPTION_SELECT_ADDRESS_BAR,
        IDS_KSV_SHORTCUT_SELECT_ADDRESS_BAR,
diff --git a/base/BUILD.gn b/base/BUILD.gn
index 06bc03e..fad1f1b0 100644
--- a/base/BUILD.gn
+++ b/base/BUILD.gn
@@ -2297,13 +2297,11 @@
       "tracing/tracing_tls.h",
     ]
 
-    public_deps += [
-      "//base/tracing/protos:chrome_track_event_zero",
-      "//third_party/perfetto:libperfetto",
-    ]
+    public_deps += [ "//third_party/perfetto:libperfetto" ]
 
     deps += [
       "//base/tracing/protos:chrome_track_event",
+      "//base/tracing/protos:chrome_track_event_zero",
       "//third_party/perfetto/include/perfetto/protozero",
     ]
 
diff --git a/base/tracing/protos/chrome_track_event.proto b/base/tracing/protos/chrome_track_event.proto
index 311ccd6..54e11ce 100644
--- a/base/tracing/protos/chrome_track_event.proto
+++ b/base/tracing/protos/chrome_track_event.proto
@@ -34,8 +34,7 @@
 }
 
 message ChromeBrowserContext {
-  reserved 1;
-  optional string id = 2;
+  optional fixed64 ptr = 1;
 }
 
 message ChromeProfileDestroyer {
@@ -143,50 +142,6 @@
   optional uint32 resource_id = 1;
 }
 
-// Information about RenderProcessHost.
-message RenderProcessHost {
-  // Unique Id to identify the RenderProcessHost. This is the browser-side,
-  // persistent id for this RenderProcessHost that stays constant even across OS
-  // layer processes managed by this RenderProcessHost.
-  optional uint32 id = 1;
-  // See ProcessLock::ToString().
-  optional string process_lock = 2;
-  // The PID of the child process.
-  optional int32 child_process_id = 3;
-
-  // Details about the associated browser context.
-  optional ChromeBrowserContext browser_context = 4;
-}
-
-message RenderProcessHostListener {
-  // Routing ID of the listener to the RenderProcessHost, recorded when a new ID
-  // is added or when an ID is removed.
-  optional uint32 routing_id = 1;
-}
-
-message RenderProcessHostCleanup {
-  // Number of IPC listeners registered to the host when Cleanup() was called.
-  optional uint32 listener_count = 1;
-  // Number of "keep alive" references active in the RenderProcessHost, recorded
-  // when Cleanup() was called.
-  optional uint32 keep_alive_ref_count = 2;
-}
-
-message ChildProcessLauncherPriority {
-  // True if the new priority set to background.
-  optional bool is_backgrounded = 1;
-  // True if the renderer proecss has pending views.
-  optional bool has_pending_views = 2;
-
-  // Importance of the child process in Android.
-  enum Importance {
-    IMPORTANCE_NORMAL = 1;
-    IMPORTANCE_MODERATE = 2;
-    IMPORTANCE_IMPORTANT = 3;
-  }
-  optional Importance importance = 3;
-}
-
 message ChromeTrackEvent {
   // Extension range for Chrome: 1000-1999
   // Next ID: 1017
@@ -218,12 +173,7 @@
 
     optional ChromeHashedPerformanceMark chrome_hashed_performance_mark = 1011;
 
-    optional RenderProcessHost render_process_host = 1012;
-    optional RenderProcessHostCleanup render_process_host_cleanup = 1013;
-    optional RenderProcessHostListener render_process_host_listener_changed =
-        1014;
-    optional ChildProcessLauncherPriority child_process_launcher_priority =
-        1015;
+    // reserved 1012 to 1015.
 
     optional ResourceBundle resource_bundle = 1016;
   }
diff --git a/base/values_unittest.cc b/base/values_unittest.cc
index c5cab6d4..468596a 100644
--- a/base/values_unittest.cc
+++ b/base/values_unittest.cc
@@ -2414,14 +2414,13 @@
   EXPECT_TRUE(main_dict.FindKey("list"));
   EXPECT_FALSE(main_dict.FindKey("DNE"));
 
-  EXPECT_TRUE(main_dict.GetBooleanWithoutPathExpansion("bool", nullptr));
-  EXPECT_FALSE(main_dict.GetBooleanWithoutPathExpansion("int", nullptr));
-  EXPECT_FALSE(main_dict.GetBooleanWithoutPathExpansion("double", nullptr));
-  EXPECT_FALSE(main_dict.GetBooleanWithoutPathExpansion("string", nullptr));
-  EXPECT_FALSE(main_dict.GetBooleanWithoutPathExpansion("binary", nullptr));
-  EXPECT_FALSE(main_dict.GetBooleanWithoutPathExpansion("dict", nullptr));
-  EXPECT_FALSE(main_dict.GetBooleanWithoutPathExpansion("list", nullptr));
-  EXPECT_FALSE(main_dict.GetBooleanWithoutPathExpansion("DNE", nullptr));
+  EXPECT_NE(absl::nullopt, main_dict.FindBoolKey("bool"));
+  EXPECT_EQ(absl::nullopt, main_dict.FindBoolKey("int"));
+  EXPECT_EQ(absl::nullopt, main_dict.FindBoolKey("double"));
+  EXPECT_EQ(absl::nullopt, main_dict.FindBoolKey("string"));
+  EXPECT_EQ(absl::nullopt, main_dict.FindBoolKey("binary"));
+  EXPECT_EQ(absl::nullopt, main_dict.FindBoolKey("list"));
+  EXPECT_EQ(absl::nullopt, main_dict.FindBoolKey("DNE"));
 
   EXPECT_EQ(absl::nullopt, main_dict.FindIntKey("bool"));
   EXPECT_NE(absl::nullopt, main_dict.FindIntKey("int"));
diff --git a/cc/paint/paint_op_reader.cc b/cc/paint/paint_op_reader.cc
index 79b0cfe..7040624 100644
--- a/cc/paint/paint_op_reader.cc
+++ b/cc/paint/paint_op_reader.cc
@@ -1086,9 +1086,10 @@
 void PaintOpReader::ReadRecordPaintFilter(
     sk_sp<PaintFilter>* filter,
     const absl::optional<PaintFilter::CropRect>& crop_rect) {
-  SkRect record_bounds;
-  gfx::SizeF raster_scale;
-  PaintShader::ScalingBehavior scaling_behavior;
+  SkRect record_bounds = SkRect::MakeEmpty();
+  gfx::SizeF raster_scale = {0.f, 0.f};
+  PaintShader::ScalingBehavior scaling_behavior =
+      PaintShader::ScalingBehavior::kRasterAtScale;
   sk_sp<PaintRecord> record;
   ReadSimple(&record_bounds);
   ReadSimple(&raster_scale);
diff --git a/chrome/android/chrome_java_resources.gni b/chrome/android/chrome_java_resources.gni
index 5b5ed48..3046f31 100644
--- a/chrome/android/chrome_java_resources.gni
+++ b/chrome/android/chrome_java_resources.gni
@@ -564,7 +564,6 @@
   "java/res/drawable/google_pay_plex.xml",
   "java/res/drawable/google_pay_with_divider.xml",
   "java/res/drawable/ic_add_box_rounded_corner.xml",
-  "java/res/drawable/ic_add_circle_40dp.xml",
   "java/res/drawable/ic_add_to_home_screen.xml",
   "java/res/drawable/ic_arrow_back_24dp.xml",
   "java/res/drawable/ic_arrow_forward_blue_24dp.xml",
diff --git a/chrome/android/chrome_junit_test_java_sources.gni b/chrome/android/chrome_junit_test_java_sources.gni
index cdd91a2..16963be3 100644
--- a/chrome/android/chrome_junit_test_java_sources.gni
+++ b/chrome/android/chrome_junit_test_java_sources.gni
@@ -77,6 +77,7 @@
   "junit/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityTabControllerUnitTest.java",
   "junit/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityUrlLoadingTest.java",
   "junit/src/org/chromium/chrome/browser/customtabs/features/ImmersiveModeControllerTest.java",
+  "junit/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbarUnitTest.java",
   "junit/src/org/chromium/chrome/browser/customtabs/shadows/ShadowExternalNavigationDelegateImpl.java",
   "junit/src/org/chromium/chrome/browser/directactions/FindInPageDirectActionHandlerTest.java",
   "junit/src/org/chromium/chrome/browser/directactions/GoBackDirectActionHandlerTest.java",
diff --git a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AssistantCoordinator.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AssistantCoordinator.java
index d8e53ec..6da2f55 100644
--- a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AssistantCoordinator.java
+++ b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AssistantCoordinator.java
@@ -103,7 +103,7 @@
         Profile profile = Profile.fromWebContents(currentTab.getWebContents());
 
         HelpAndFeedbackLauncherImpl.getInstance().showFeedback(mActivity, profile,
-                currentTab.getUrlString(), FEEDBACK_CATEGORY_TAG, screenshotMode, debugContext);
+                currentTab.getUrl().getSpec(), FEEDBACK_CATEGORY_TAG, screenshotMode, debugContext);
     }
 
     public void show() {
diff --git a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantPersonalDataManagerTest.java b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantPersonalDataManagerTest.java
index 115c8600..23f3caaa 100644
--- a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantPersonalDataManagerTest.java
+++ b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantPersonalDataManagerTest.java
@@ -60,6 +60,7 @@
 import org.junit.runner.RunWith;
 
 import org.chromium.base.test.util.CommandLineFlags;
+import org.chromium.base.test.util.DisabledTest;
 import org.chromium.base.test.util.FlakyTest;
 import org.chromium.chrome.autofill_assistant.R;
 import org.chromium.chrome.browser.autofill.PersonalDataManager;
@@ -992,6 +993,7 @@
      */
     @Test
     @MediumTest
+    @DisabledTest(message = "Flaky - https://crbug.com/1215465")
     public void testCreateAndEnterShippingAddress() throws Exception {
         ArrayList<ActionProto> list = new ArrayList<>();
         list.add((ActionProto) ActionProto.newBuilder()
diff --git a/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceNoTabsTest.java b/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceNoTabsTest.java
index fcd95a59..a6f57fd8 100644
--- a/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceNoTabsTest.java
+++ b/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceNoTabsTest.java
@@ -48,7 +48,6 @@
 import java.io.IOException;
 import java.util.Arrays;
 import java.util.List;
-import java.util.concurrent.TimeoutException;
 
 /**
  * Integration tests of the {@link StartSurface} for cases where there are no tabs. See {@link
@@ -113,7 +112,7 @@
     @Feature({"StartSurface"})
     // clang-format off
     @CommandLineFlags.Add({BASE_PARAMS + "/single"})
-    public void testShow_SingleAsHomepage_NoTabs() throws TimeoutException {
+    public void testShow_SingleAsHomepage_NoTabs() {
         // clang-format on
         CriteriaHelper.pollUiThread(
                 ()
diff --git a/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTest.java b/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTest.java
index 5c0cd615..52550a3 100644
--- a/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTest.java
+++ b/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTest.java
@@ -38,6 +38,8 @@
 import static org.chromium.chrome.test.util.ViewUtils.waitForView;
 
 import android.os.Build;
+import android.os.Build.VERSION;
+import android.os.Build.VERSION_CODES;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.runner.lifecycle.ActivityLifecycleMonitorRegistry;
 import android.support.test.runner.lifecycle.Stage;
@@ -230,12 +232,8 @@
                 mLayoutChangedCallbackHelper.notifyCalled();
             }
         };
-        mActivityTestRule.getActivity().getLayoutManagerSupplier().addObserver((manager) -> {
-            if (manager.getActiveLayout() != null) {
-                mCurrentlyActiveLayout = manager.getActiveLayout().getLayoutType();
-            }
-            manager.addObserver(mLayoutObserver);
-        });
+        mActivityTestRule.getActivity().getLayoutManagerSupplier().addObserver(
+                (obs) -> { obs.addObserver(mLayoutObserver); });
     }
 
     @Test
diff --git a/chrome/android/features/vr/BUILD.gn b/chrome/android/features/vr/BUILD.gn
index 607f3f13..bd5556e 100644
--- a/chrome/android/features/vr/BUILD.gn
+++ b/chrome/android/features/vr/BUILD.gn
@@ -100,6 +100,7 @@
     "//third_party/gvr-android-sdk:gvr_common_java",
     "//ui/android:ui_full_java",
     "//ui/android:ui_utils_java",
+    "//url:gurl_java",
   ]
 
   annotation_processor_deps = [ "//base/android/jni_generator:jni_processor" ]
diff --git a/chrome/android/features/vr/java/src/org/chromium/chrome/browser/vr/VrShellDelegate.java b/chrome/android/features/vr/java/src/org/chromium/chrome/browser/vr/VrShellDelegate.java
index b25237b..8c410f4 100644
--- a/chrome/android/features/vr/java/src/org/chromium/chrome/browser/vr/VrShellDelegate.java
+++ b/chrome/android/features/vr/java/src/org/chromium/chrome/browser/vr/VrShellDelegate.java
@@ -731,7 +731,7 @@
         // TODO(ymalik): This call will connect to the Google Services api which can be slow. Can we
         // connect to it beforehand when we know that we'll be prompting for feedback?
         HelpAndFeedbackLauncherImpl.getInstance().showFeedback(TabUtils.getActivity(tab),
-                Profile.fromWebContents(tab.getWebContents()), tab.getUrlString(),
+                Profile.fromWebContents(tab.getWebContents()), tab.getUrl().getSpec(),
                 ContextUtils.getApplicationContext().getPackageName() + "." + FEEDBACK_REPORT_TYPE);
     }
 
diff --git a/chrome/android/java/res/drawable/ic_add_circle_40dp.xml b/chrome/android/java/res/drawable/ic_add_circle_40dp.xml
deleted file mode 100644
index ae877469..0000000
--- a/chrome/android/java/res/drawable/ic_add_circle_40dp.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright 2019 The Chromium Authors. All rights reserved.
-     Use of this source code is governed by a BSD-style license that can be
-     found in the LICENSE file. -->
-
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:tools="http://schemas.android.com/tools"
-    tools:targetApi="21"
-    android:height="40dp"
-    android:width="40dp"
-    android:viewportHeight="40.0"
-    android:viewportWidth="40.0">
-
-    <path
-        android:fillColor="@color/default_control_color_active"
-        android:pathData="M20,20m-20,0a20,20 0,1 1,40 0a20,20 0,1 1,-40 0" />
-    <path
-        android:fillColor="@color/default_icon_color_inverse"
-        android:pathData="M27,21l-6,0l0,6l-2,0l0,-6l-6,0l0,-2l6,0l0,-6l2,0l0,6l6,0z" />
-</vector>
-
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
index 1825f9c..8f84d93 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
@@ -667,9 +667,8 @@
                         if (getCompositorViewHolder() == null) return null;
                         return getCompositorViewHolder().getLayerTitleCache();
                     },
-                    mOverviewModeBehaviorSupplier,
+                    mOverviewModeBehaviorSupplier, mLayoutStateProviderOneshotSupplier,
                     mRootUiCoordinator::getTopUiThemeColorProvider);
-            mLayoutStateProviderOneshotSupplier.set(mLayoutManager);
             // clang-format on
             mOverviewModeController = mLayoutManager;
         }
@@ -687,9 +686,8 @@
                         if (getCompositorViewHolder() == null) return null;
                         return getCompositorViewHolder().getLayerTitleCache();
                     },
-                    mOverviewModeBehaviorSupplier,
+                    mOverviewModeBehaviorSupplier, mLayoutStateProviderOneshotSupplier,
                     mRootUiCoordinator::getTopUiThemeColorProvider);
-            mLayoutStateProviderOneshotSupplier.set(mLayoutManager);
             // clang-format on
             mOverviewModeController = mLayoutManager;
         }
@@ -1409,8 +1407,8 @@
                         boolean loaded = false;
                         if (matchingTabIndex != TabModel.INVALID_TAB_INDEX) {
                             Tab tab = tabModel.getTabAt(matchingTabIndex);
-                            if (tab.getUrlString().equals(url)
-                                    || tab.getUrlString().equals(IntentUtils.safeGetStringExtra(
+                            if (tab.getUrl().getSpec().equals(url)
+                                    || tab.getUrl().getSpec().equals(IntentUtils.safeGetStringExtra(
                                             intent, TabOpenType.REUSE_TAB_ORIGINAL_URL_STRING))) {
                                 tabModel.setIndex(matchingTabIndex, TabSelectionType.FROM_USER);
                                 LoadUrlParams loadUrlParams =
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/tab_activity_glue/ReparentingTask.java b/chrome/android/java/src/org/chromium/chrome/browser/app/tab_activity_glue/ReparentingTask.java
index eb4e9ac9..f247e951 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/app/tab_activity_glue/ReparentingTask.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/app/tab_activity_glue/ReparentingTask.java
@@ -105,7 +105,7 @@
         }
         intent.setAction(Intent.ACTION_VIEW);
         if (TextUtils.isEmpty(intent.getDataString())) {
-            intent.setData(Uri.parse(mTab.getUrlString()));
+            intent.setData(Uri.parse(mTab.getUrl().getSpec()));
         }
         if (mTab.isIncognito()) {
             intent.putExtra(Browser.EXTRA_APPLICATION_ID,
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/browserservices/SessionDataHolder.java b/chrome/android/java/src/org/chromium/chrome/browser/browserservices/SessionDataHolder.java
index d9b5e16d..f23978e8 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/browserservices/SessionDataHolder.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/browserservices/SessionDataHolder.java
@@ -157,30 +157,6 @@
         return handler != null && handler.canUseReferrer(referrer);
     }
 
-    /**
-     * @return The url for the page displayed using the current {@link
-     * SessionHandler}.
-     *
-     * @deprecated This will be removed once downstream usages change.
-     */
-    @Deprecated
-    public String getCurrentUrlForActiveBrowserSession() {
-        if (mActiveSessionHandler == null) return null;
-        return mActiveSessionHandler.getCurrentUrl();
-    }
-
-    /**
-     * @return The pending url for the page about to be displayed using the current {@link
-     * SessionHandler}.
-     *
-     * @deprecated This will be removed once downstream usages change.
-     */
-    @Deprecated
-    public String getPendingUrlForActiveBrowserSession() {
-        if (mActiveSessionHandler == null) return null;
-        return mActiveSessionHandler.getPendingUrl();
-    }
-
     private void ensureSessionCleanUpOnDisconnects() {
         if (mSessionDisconnectCallback != null) return;
         mSessionDisconnectCallback = (session) -> {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/LayerTitleCache.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/LayerTitleCache.java
index f5f6e5d8..33c7e64c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/LayerTitleCache.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/LayerTitleCache.java
@@ -174,7 +174,7 @@
     private String getTitleForTab(Tab tab, String defaultTitle) {
         String title = tab.getTitle();
         if (TextUtils.isEmpty(title)) {
-            title = tab.getUrlString();
+            title = tab.getUrl().getSpec();
             if (TextUtils.isEmpty(title)) {
                 title = defaultTitle;
                 if (TextUtils.isEmpty(title)) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/LayoutManagerChrome.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/LayoutManagerChrome.java
index f039106..3839a19 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/LayoutManagerChrome.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/LayoutManagerChrome.java
@@ -26,6 +26,7 @@
 import org.chromium.chrome.browser.compositor.overlays.strip.StripLayoutHelperManager;
 import org.chromium.chrome.browser.device.DeviceClassManager;
 import org.chromium.chrome.browser.fullscreen.FullscreenManager;
+import org.chromium.chrome.browser.layouts.LayoutStateProvider;
 import org.chromium.chrome.browser.layouts.components.VirtualView;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tab.TabLaunchType;
@@ -85,6 +86,7 @@
      * @param tabContentManagerSupplier Supplier of the {@link TabContentManager} instance.
      * @param layerTitleCacheSupplier Supplier of the {@link LayerTitleCache}.
      * @param overviewModeBehaviorSupplier Supplier of the {@link OverviewModeBehavior}.
+     * @param layoutStateProviderOneshotSupplier Supplier of the {@link LayoutStateProvider}.
      * @param topUiThemeColorProvider {@link ThemeColorProvider} for top UI.
      */
     public LayoutManagerChrome(LayoutManagerHost host, ViewGroup contentContainer,
@@ -92,9 +94,10 @@
             ObservableSupplier<TabContentManager> tabContentManagerSupplier,
             Supplier<LayerTitleCache> layerTitleCacheSupplier,
             OneshotSupplierImpl<OverviewModeBehavior> overviewModeBehaviorSupplier,
+            OneshotSupplierImpl<LayoutStateProvider> layoutStateProviderOneshotSupplier,
             Supplier<TopUiThemeColorProvider> topUiThemeColorProvider) {
         super(host, contentContainer, tabContentManagerSupplier, layerTitleCacheSupplier,
-                topUiThemeColorProvider);
+                layoutStateProviderOneshotSupplier, topUiThemeColorProvider);
         Context context = host.getContext();
         LayoutRenderHost renderHost = host.getLayoutRenderHost();
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/LayoutManagerChromePhone.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/LayoutManagerChromePhone.java
index 02941e8..866caea4 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/LayoutManagerChromePhone.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/LayoutManagerChromePhone.java
@@ -14,6 +14,7 @@
 import org.chromium.chrome.browser.compositor.layouts.content.TabContentManager;
 import org.chromium.chrome.browser.compositor.layouts.phone.SimpleAnimationLayout;
 import org.chromium.chrome.browser.device.DeviceClassManager;
+import org.chromium.chrome.browser.layouts.LayoutStateProvider;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tab.TabLaunchType;
 import org.chromium.chrome.browser.tabmodel.TabCreatorManager;
@@ -41,6 +42,7 @@
      * @param tabContentManagerSupplier Supplier of the {@link TabContentManager} instance.
      * @param layerTitleCacheSupplier Supplier of the {@link LayerTitleCache}.
      * @param overviewModeBehaviorSupplier Supplier of the {@link OverviewModeBehavior}.
+     * @param layoutStateProviderOneshotSupplier Supplier of the {@link LayoutStateProvider}.
      * @param topUiThemeColorProvider {@link ThemeColorProvider} for top UI.
      */
     public LayoutManagerChromePhone(LayoutManagerHost host, ViewGroup contentContainer,
@@ -48,9 +50,11 @@
             ObservableSupplier<TabContentManager> tabContentManagerSupplier,
             Supplier<LayerTitleCache> layerTitleCacheSupplier,
             OneshotSupplierImpl<OverviewModeBehavior> overviewModeBehaviorSupplier,
+            OneshotSupplierImpl<LayoutStateProvider> layoutStateProviderOneshotSupplier,
             Supplier<TopUiThemeColorProvider> topUiThemeColorProvider) {
         super(host, contentContainer, true, startSurface, tabContentManagerSupplier,
-                layerTitleCacheSupplier, overviewModeBehaviorSupplier, topUiThemeColorProvider);
+                layerTitleCacheSupplier, overviewModeBehaviorSupplier,
+                layoutStateProviderOneshotSupplier, topUiThemeColorProvider);
     }
 
     @Override
@@ -135,7 +139,7 @@
     }
 
     @Override
-    protected void tabCreating(int sourceId, String url, boolean isIncognito) {
+    protected void tabCreating(int sourceId, boolean isIncognito) {
         if (getActiveLayout() != null && !getActiveLayout().isStartingToHide()
                 && overlaysHandleTabCreating() && getActiveLayout().handlesTabCreating()) {
             // If the current layout in the foreground, let it handle the tab creation animation.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/LayoutManagerChromeTablet.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/LayoutManagerChromeTablet.java
index 556b5bc..80fc087 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/LayoutManagerChromeTablet.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/LayoutManagerChromeTablet.java
@@ -13,6 +13,7 @@
 import org.chromium.chrome.browser.compositor.LayerTitleCache;
 import org.chromium.chrome.browser.compositor.layouts.content.TabContentManager;
 import org.chromium.chrome.browser.compositor.overlays.strip.StripLayoutHelperManager;
+import org.chromium.chrome.browser.layouts.LayoutStateProvider;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tab.TabLaunchType;
 import org.chromium.chrome.browser.tabmodel.TabCreatorManager;
@@ -39,15 +40,18 @@
      * @param tabContentManagerSupplier Supplier of the {@link TabContentManager} instance.
      * @param layerTitleCacheSupplier Supplier of the {@link LayerTitleCache}.
      * @param overviewModeBehaviorSupplier Supplier of the {@link OverviewModeBehavior}.
+     * @param layoutStateProviderOneshotSupplier Supplier of the {@link LayoutStateProvider}.
      * @param topUiThemeColorProvider {@link ThemeColorProvider} for top UI.
      */
     public LayoutManagerChromeTablet(LayoutManagerHost host, ViewGroup contentContainer,
             ObservableSupplier<TabContentManager> tabContentManagerSupplier,
             Supplier<LayerTitleCache> layerTitleCacheSupplier,
             OneshotSupplierImpl<OverviewModeBehavior> overviewModeBehaviorSupplier,
+            OneshotSupplierImpl<LayoutStateProvider> layoutStateProviderOneshotSupplier,
             Supplier<TopUiThemeColorProvider> topUiThemeColorProvider) {
         super(host, contentContainer, false, null, tabContentManagerSupplier,
-                layerTitleCacheSupplier, overviewModeBehaviorSupplier, topUiThemeColorProvider);
+                layerTitleCacheSupplier, overviewModeBehaviorSupplier,
+                layoutStateProviderOneshotSupplier, topUiThemeColorProvider);
 
         mTabStripLayoutHelperManager = new StripLayoutHelperManager(host.getContext(), this,
                 mHost.getLayoutRenderHost(), () -> mTitleCache, layerTitleCacheSupplier);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/LayoutManagerImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/LayoutManagerImpl.java
index 4c97f1d..e6ed389e 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/LayoutManagerImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/LayoutManagerImpl.java
@@ -20,6 +20,7 @@
 import org.chromium.base.TraceEvent;
 import org.chromium.base.supplier.ObservableSupplier;
 import org.chromium.base.supplier.ObservableSupplierImpl;
+import org.chromium.base.supplier.OneshotSupplierImpl;
 import org.chromium.base.supplier.Supplier;
 import org.chromium.chrome.browser.browser_controls.BrowserControlsStateProvider;
 import org.chromium.chrome.browser.browser_controls.BrowserControlsUtils;
@@ -36,6 +37,7 @@
 import org.chromium.chrome.browser.gesturenav.HistoryNavigationCoordinator;
 import org.chromium.chrome.browser.layouts.CompositorModelChangeProcessor;
 import org.chromium.chrome.browser.layouts.EventFilter;
+import org.chromium.chrome.browser.layouts.LayoutStateProvider;
 import org.chromium.chrome.browser.layouts.LayoutType;
 import org.chromium.chrome.browser.layouts.ManagedLayoutManager;
 import org.chromium.chrome.browser.layouts.SceneOverlay;
@@ -167,6 +169,9 @@
     /** A map of {@link SceneOverlay} to its position relative to the others. */
     private Map<Class, Integer> mOverlayOrderMap = new HashMap<>();
 
+    /** The supplier used to supply the LayoutStateProvider. */
+    private final OneshotSupplierImpl<LayoutStateProvider> mLayoutStateProviderOneshotSupplier;
+
     /** The supplier of {@link ThemeColorProvider} for top UI. */
     private final Supplier<TopUiThemeColorProvider> mTopUiThemeColorProvider;
 
@@ -197,8 +202,7 @@
                 return;
             }
 
-            tabCreating(
-                    getTabModelSelector().getCurrentTabId(), tab.getUrlString(), tab.isIncognito());
+            tabCreating(getTabModelSelector().getCurrentTabId(), tab.isIncognito());
         }
 
         @Override
@@ -252,15 +256,19 @@
      * @param contentContainer A {@link ViewGroup} for Android views to be bound to.
      * @param tabContentManagerSupplier Supplier of the {@link TabContentManager} instance.
      * @param layerTitleCacheSupplier A supplier of the cache of title textures.
+     * @param layoutStateProviderOneshotSupplier Supplier used to supply the {@link
+     *         LayoutStateProvider}.
      * @param topUiThemeColorProvider {@link ThemeColorProvider} for top UI.
      */
     public LayoutManagerImpl(LayoutManagerHost host, ViewGroup contentContainer,
             ObservableSupplier<TabContentManager> tabContentManagerSupplier,
             Supplier<LayerTitleCache> layerTitleCacheSupplier,
+            OneshotSupplierImpl<LayoutStateProvider> layoutStateProviderOneshotSupplier,
             Supplier<TopUiThemeColorProvider> topUiThemeColorProvider) {
         mHost = host;
         mPxToDp = 1.f / mHost.getContext().getResources().getDisplayMetrics().density;
         mTabContentManagerSupplier = tabContentManagerSupplier;
+        mLayoutStateProviderOneshotSupplier = layoutStateProviderOneshotSupplier;
         mLayerTitleCacheSupplier = layerTitleCacheSupplier;
         mTopUiThemeColorProvider = topUiThemeColorProvider;
         mContext = host.getContext();
@@ -289,6 +297,8 @@
 
         mFrameRequestSupplier =
                 new CompositorModelChangeProcessor.FrameRequestSupplier(this::requestUpdate);
+
+        mLayoutStateProviderOneshotSupplier.set(this);
     }
 
     /**
@@ -699,7 +709,7 @@
      * @param url         The url of the created tab.
      * @param isIncognito Whether or not created tab will be incognito.
      */
-    protected void tabCreating(int sourceId, String url, boolean isIncognito) {
+    protected void tabCreating(int sourceId, boolean isIncognito) {
         if (getActiveLayout() != null) getActiveLayout().onTabCreating(sourceId);
     }
 
@@ -797,7 +807,7 @@
     // Whether the tab is ready to display or it should be faded in as it loads.
     private static boolean shouldStall(Tab tab) {
         return (tab.isFrozen() || tab.needsReload())
-                && !NativePage.isNativePageUrl(tab.getUrlString(), tab.isIncognito());
+                && !NativePage.isNativePageUrl(tab.getUrl(), tab.isIncognito());
     }
 
     @Override
@@ -894,22 +904,18 @@
     public void startHiding(int nextTabId, boolean hintAtTabSelection) {
         requestUpdate();
         if (hintAtTabSelection) {
+            notifyObserversOnTabSelectionHinted(nextTabId);
+
             // TODO(crbug.com/1108496): Remove after migrates to LayoutStateObserver.
             for (SceneChangeObserver observer : mSceneChangeObservers) {
                 observer.onTabSelectionHinted(nextTabId);
             }
-
-            for (LayoutStateObserver observer : mLayoutObservers) {
-                observer.onTabSelectionHinted(nextTabId);
-            }
         }
 
         Layout layoutBeingHidden = getActiveLayout();
-        for (LayoutStateObserver observer : mLayoutObservers) {
-            observer.onStartedHiding(layoutBeingHidden.getLayoutType(),
-                    shouldShowToolbarAnimationOnHide(layoutBeingHidden, nextTabId),
-                    shouldDelayHideAnimation(layoutBeingHidden));
-        }
+        notifyObserversLayoutStartedHiding(layoutBeingHidden.getLayoutType(),
+                shouldShowToolbarAnimationOnHide(layoutBeingHidden, nextTabId),
+                shouldDelayHideAnimation(layoutBeingHidden));
     }
 
     @Override
@@ -919,9 +925,7 @@
         assert mNextActiveLayout != null : "Need to have a next active layout.";
         if (mNextActiveLayout != null) {
             // Notify LayoutObservers the active layout is finished hiding.
-            for (LayoutStateObserver observer : mLayoutObservers) {
-                observer.onFinishedHiding(getActiveLayout().getLayoutType());
-            }
+            notifyObserversLayoutFinishedHiding(getActiveLayout().getLayoutType());
 
             startShowing(mNextActiveLayout, true);
         }
@@ -930,9 +934,7 @@
     @Override
     public void doneShowing() {
         // Notify LayoutObservers the active layout is finished showing.
-        for (LayoutStateObserver observer : mLayoutObservers) {
-            observer.onFinishedShowing(getActiveLayout().getLayoutType());
-        }
+        notifyObserversLayoutFinishedShowing(getActiveLayout().getLayoutType());
     }
 
     /**
@@ -991,10 +993,8 @@
             observer.onSceneChange(getActiveLayout());
         }
 
-        for (LayoutStateObserver observer : mLayoutObservers) {
-            observer.onStartedShowing(
-                    layout.getLayoutType(), shouldShowToolbarAnimationOnShow(animate));
-        }
+        notifyObserversLayoutStartedShowing(
+                layout.getLayoutType(), shouldShowToolbarAnimationOnShow(animate));
     }
 
     /**
@@ -1011,11 +1011,6 @@
         return layout == mActiveLayout;
     }
 
-    @Override
-    public int getActiveLayoutType() {
-        return getActiveLayout() != null ? getActiveLayout().getLayoutType() : LayoutType.NONE;
-    }
-
     /**
      * Get a list of virtual views for accessibility.
      *
@@ -1129,6 +1124,48 @@
         mLayoutObservers.removeObserver(listener);
     }
 
+    protected final void notifyObserversLayoutStartedShowing(
+            @LayoutType int layoutType, boolean showToolbar) {
+        mLayoutStateProviderOneshotSupplier.onAvailable((unused) -> {
+            for (LayoutStateObserver observer : mLayoutObservers) {
+                observer.onStartedShowing(layoutType, showToolbar);
+            }
+        });
+    }
+
+    protected final void notifyObserversLayoutFinishedShowing(@LayoutType int layoutType) {
+        mLayoutStateProviderOneshotSupplier.onAvailable((unused) -> {
+            for (LayoutStateObserver observer : mLayoutObservers) {
+                observer.onFinishedShowing(layoutType);
+            }
+        });
+    }
+
+    protected final void notifyObserversLayoutStartedHiding(
+            @LayoutType int layoutType, boolean showToolbar, boolean delayAnimation) {
+        mLayoutStateProviderOneshotSupplier.onAvailable((unused) -> {
+            for (LayoutStateObserver observer : mLayoutObservers) {
+                observer.onStartedHiding(layoutType, showToolbar, delayAnimation);
+            }
+        });
+    }
+
+    protected final void notifyObserversLayoutFinishedHiding(@LayoutType int layoutType) {
+        mLayoutStateProviderOneshotSupplier.onAvailable((unused) -> {
+            for (LayoutStateObserver observer : mLayoutObservers) {
+                observer.onFinishedHiding(layoutType);
+            }
+        });
+    }
+
+    protected final void notifyObserversOnTabSelectionHinted(int tabId) {
+        mLayoutStateProviderOneshotSupplier.onAvailable((unused) -> {
+            for (LayoutStateObserver observer : mLayoutObservers) {
+                observer.onTabSelectionHinted(tabId);
+            }
+        });
+    }
+
     protected boolean shouldShowToolbarAnimationOnShow(boolean isAnimate) {
         return false;
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/StaticLayout.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/StaticLayout.java
index f373d09..6fe3520 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/StaticLayout.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/StaticLayout.java
@@ -396,7 +396,7 @@
     // Whether the tab is ready to display or it should be faded in as it loads.
     private boolean shouldStall(Tab tab) {
         return (tab.isFrozen() || tab.needsReload())
-                && !NativePage.isNativePageUrl(tab.getUrlString(), tab.isIncognito());
+                && !NativePage.isNativePageUrl(tab.getUrl(), tab.isIncognito());
     }
 
     @Override
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabActivity.java
index 729e2b3..1a8041da 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabActivity.java
@@ -189,8 +189,8 @@
                 CustomTabAppMenuPropertiesDelegate.getIndexOfMenuItemFromBundle(menuItemData);
         if (menuIndex >= 0) {
             ((CustomTabIntentDataProvider) mIntentDataProvider)
-                    .clickMenuItemWithUrlAndTitle(this, menuIndex, getActivityTab().getUrlString(),
-                            getActivityTab().getTitle());
+                    .clickMenuItemWithUrlAndTitle(this, menuIndex,
+                            getActivityTab().getUrl().getSpec(), getActivityTab().getTitle());
             RecordUserAction.record("CustomTabsMenuCustomMenuItem");
             return true;
         }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabBottomBarDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabBottomBarDelegate.java
index 2a83a89..9b04cc7 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabBottomBarDelegate.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabBottomBarDelegate.java
@@ -345,7 +345,7 @@
             Activity activity, CustomTabActivityTabProvider tabProvider) {
         Intent addedIntent = extraIntent == null ? new Intent() : new Intent(extraIntent);
         Tab tab = tabProvider.getTab();
-        if (tab != null) addedIntent.setData(Uri.parse(tab.getUrlString()));
+        if (tab != null) addedIntent.setData(Uri.parse(tab.getUrl().getSpec()));
         try {
             pendingIntent.send(activity, 0, addedIntent, null, null);
         } catch (CanceledException e) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabCompositorContentInitializer.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabCompositorContentInitializer.java
index abc768b4..174a8267 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabCompositorContentInitializer.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabCompositorContentInitializer.java
@@ -9,6 +9,7 @@
 
 import org.chromium.base.Callback;
 import org.chromium.base.supplier.ObservableSupplier;
+import org.chromium.base.supplier.OneshotSupplierImpl;
 import org.chromium.chrome.browser.compositor.CompositorViewHolder;
 import org.chromium.chrome.browser.compositor.layouts.LayoutManagerImpl;
 import org.chromium.chrome.browser.compositor.layouts.content.TabContentManager;
@@ -78,7 +79,7 @@
                     if (mCompositorViewHolder.get() == null) return null;
                     return mCompositorViewHolder.get().getLayerTitleCache();
                 },
-                () -> mTopUiThemeColorProvider);
+                new OneshotSupplierImpl<>(), () -> mTopUiThemeColorProvider);
         // clang-format on
 
         mCompositorViewHolderInitializer.initializeCompositorContent(layoutDriver,
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabObserver.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabObserver.java
index 8798bf7..58372e43 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabObserver.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabObserver.java
@@ -121,7 +121,7 @@
         } else if (mCurrentState == State.WAITING_LOAD_FINISH) {
             if (mCustomTabsConnection != null) {
                 mCustomTabsConnection.sendNavigationInfo(
-                        mSession, tab.getUrlString(), tab.getTitle(), (Uri) null);
+                        mSession, tab.getUrl().getSpec(), tab.getTitle(), (Uri) null);
             }
             mPageLoadStartedTimestamp = SystemClock.elapsedRealtime();
         }
@@ -206,7 +206,7 @@
         if (tab.getWebContents() == null) return;
         String title = tab.getTitle();
         if (TextUtils.isEmpty(title)) return;
-        String urlString = tab.getUrlString();
+        String urlString = tab.getUrl().getSpec();
 
         ShareImageFileUtils.captureScreenshotForContents(tab.getWebContents(), mContentBitmapWidth,
                 mContentBitmapHeight, (Uri snapshotPath) -> {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabSessionHandler.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabSessionHandler.java
index e33f933..fb4c6a0 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabSessionHandler.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabSessionHandler.java
@@ -122,7 +122,7 @@
     @Nullable
     public String getCurrentUrl() {
         Tab tab = mTabProvider.getTab();
-        return tab == null ? null : tab.getUrlString();
+        return tab == null ? null : tab.getUrl().getSpec();
     }
 
     @Override
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabTaskDescriptionHelper.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabTaskDescriptionHelper.java
index 2f75665..40825ac 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabTaskDescriptionHelper.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabTaskDescriptionHelper.java
@@ -33,6 +33,7 @@
 import org.chromium.components.security_state.SecurityStateModel;
 import org.chromium.content_public.browser.NavigationHandle;
 import org.chromium.ui.util.ColorUtils;
+import org.chromium.url.GURL;
 
 import javax.inject.Inject;
 
@@ -232,7 +233,7 @@
         if (currentTab == null) return null;
 
         String label = currentTab.getTitle();
-        String domain = UrlUtilities.getDomainAndRegistry(currentTab.getUrlString(), false);
+        String domain = UrlUtilities.getDomainAndRegistry(currentTab.getUrl().getSpec(), false);
         if (TextUtils.isEmpty(label)) {
             label = domain;
         }
@@ -270,13 +271,12 @@
         Tab currentTab = mTabProvider.getTab();
         if (currentTab == null) return;
 
-        final String currentUrl = currentTab.getUrlString();
+        final GURL currentUrl = currentTab.getUrl();
         mFaviconHelper.getLocalFaviconImageForURL(
                 Profile.fromWebContents(currentTab.getWebContents()), currentTab.getUrl(), 0,
                 (image, iconUrl) -> {
                     if (mTabProvider.getTab() == null
-                            || !TextUtils.equals(
-                                    currentUrl, mTabProvider.getTab().getUrlString())) {
+                            || !currentUrl.equals(mTabProvider.getTab().getUrl())) {
                         return;
                     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbar.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbar.java
index eb9cdb2..c77e6a44 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbar.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbar.java
@@ -17,7 +17,6 @@
 import android.graphics.Color;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
-import android.net.Uri;
 import android.text.SpannableString;
 import android.text.TextUtils;
 import android.text.style.ForegroundColorSpan;
@@ -80,8 +79,7 @@
 import org.chromium.ui.text.SpanApplier.SpanInfo;
 import org.chromium.ui.util.ColorUtils;
 import org.chromium.ui.widget.Toast;
-
-import java.util.List;
+import org.chromium.url.GURL;
 
 /**
  * The Toolbar layout to be used for a custom tab. This is used for both phone and tablet UIs.
@@ -149,7 +147,7 @@
 
     private CustomTabToolbarAnimationDelegate mAnimDelegate;
     private int mState = STATE_DOMAIN_ONLY;
-    private String mFirstUrl;
+    private GURL mFirstUrl;
 
     private CustomTabLocationBar mLocationBar;
     private LocationBarModel mLocationBarModel;
@@ -341,7 +339,7 @@
         }
 
         // TODO(bauerb): Remove this once trusted CDN publisher URLs have rolled out completely.
-        if (mState == STATE_TITLE_ONLY) return parsePublisherNameFromUrl(tab.getUrlString());
+        if (mState == STATE_TITLE_ONLY) return parsePublisherNameFromUrl(tab.getUrl());
 
         return null;
     }
@@ -351,10 +349,10 @@
         super.onNavigatedToDifferentPage();
         mLocationBarModel.notifyTitleChanged();
         if (mState == STATE_TITLE_ONLY) {
-            if (TextUtils.isEmpty(mFirstUrl)) {
-                mFirstUrl = getToolbarDataProvider().getTab().getUrlString();
+            if (mFirstUrl == null || mFirstUrl.isEmpty()) {
+                mFirstUrl = getToolbarDataProvider().getTab().getUrl();
             } else {
-                if (mFirstUrl.equals(getToolbarDataProvider().getTab().getUrlString())) return;
+                if (mFirstUrl.equals(getToolbarDataProvider().getTab().getUrl())) return;
                 setUrlBarHidden(false);
             }
         }
@@ -583,17 +581,17 @@
         return false;
     }
 
-    private static String parsePublisherNameFromUrl(String url) {
+    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+    static String parsePublisherNameFromUrl(GURL url) {
         // TODO(ianwen): Make it generic to parse url from URI path. http://crbug.com/599298
         // The url should look like: https://www.google.com/amp/s/www.nyt.com/ampthml/blogs.html
         // or https://www.google.com/amp/www.nyt.com/ampthml/blogs.html.
-        Uri uri = Uri.parse(url);
-        List<String> segments = uri.getPathSegments();
-        if (segments.size() >= 3) {
-            if (segments.get(1).length() > 1) return segments.get(1);
-            return segments.get(2);
+        String[] segments = url.getPath().split("/");
+        if (segments.length >= 4 && "amp".equals(segments[1])) {
+            if (segments[2].length() > 1) return segments[2];
+            return segments[3];
         }
-        return url;
+        return url.getSpec();
     }
 
     @Override
@@ -784,7 +782,7 @@
             }
 
             String publisherUrl = TrustedCdn.getPublisherUrl(tab);
-            String url = publisherUrl != null ? publisherUrl : tab.getUrlString().trim();
+            String url = publisherUrl != null ? publisherUrl : tab.getUrl().getSpec().trim();
             if (mState == STATE_TITLE_ONLY) {
                 if (!TextUtils.isEmpty(mLocationBarDataProvider.getTitle())) {
                     updateTitleBar();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbarCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbarCoordinator.java
index f8c9c12..fee56c1 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbarCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbarCoordinator.java
@@ -37,6 +37,7 @@
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.toolbar.ToolbarManager;
 import org.chromium.ui.util.TokenHolder;
+import org.chromium.url.GURL;
 
 import javax.inject.Inject;
 import javax.inject.Named;
@@ -142,7 +143,7 @@
         Tab tab = mTabProvider.getTab();
         if (tab == null) return;
 
-        sendButtonPendingIntentWithUrlAndTitle(params, tab.getUrlString(), tab.getTitle());
+        sendButtonPendingIntentWithUrlAndTitle(params, tab.getUrl(), tab.getTitle());
 
         RecordUserAction.record("CustomTabsCustomActionButtonClick");
         Resources resources = mActivity.getResources();
@@ -160,9 +161,9 @@
      * @param title The title to attach as additional data to the {@link PendingIntent}.
      */
     private void sendButtonPendingIntentWithUrlAndTitle(
-            CustomButtonParams params, String url, String title) {
+            CustomButtonParams params, GURL url, String title) {
         Intent addedIntent = new Intent();
-        addedIntent.setData(Uri.parse(url));
+        addedIntent.setData(Uri.parse(url.getSpec()));
         addedIntent.putExtra(Intent.EXTRA_SUBJECT, title);
         try {
             params.getPendingIntent().send(
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadUtils.java b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadUtils.java
index 748d246..1277eaa 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadUtils.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadUtils.java
@@ -259,7 +259,7 @@
             final OfflinePageBridge bridge =
                     OfflinePageBridge.getForProfile(Profile.fromWebContents(tab.getWebContents()));
             bridge.scheduleDownload(tab.getWebContents(), OfflinePageBridge.ASYNC_NAMESPACE,
-                    tab.getUrlString(), DownloadUiActionFlags.PROMPT_DUPLICATE, origin);
+                    tab.getUrl().getSpec(), DownloadUiActionFlags.PROMPT_DUPLICATE, origin);
         } else {
             // Otherwise, the download can be started immediately.
             OfflinePageDownloadBridge.startDownload(tab, origin);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/metrics/UmaSessionStats.java b/chrome/android/java/src/org/chromium/chrome/browser/metrics/UmaSessionStats.java
index ad82507c9..4da2540 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/metrics/UmaSessionStats.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/metrics/UmaSessionStats.java
@@ -8,8 +8,6 @@
 import android.content.ComponentCallbacks;
 import android.content.Context;
 import android.content.res.Configuration;
-import android.text.TextUtils;
-
 import org.chromium.base.ApplicationStatus;
 import org.chromium.base.Log;
 import org.chromium.base.annotations.CalledByNative;
@@ -65,11 +63,12 @@
             UmaSessionStatsJni.get().recordPageLoadedWithKeyboard();
         }
 
-        String url = tab.getUrlString();
-        if (!TextUtils.isEmpty(url) && UrlUtilities.isHttpOrHttps(url)) {
+        GURL url = tab.getUrl();
+        if (UrlUtilities.isHttpOrHttps(url)) {
             PostTask.postTask(TaskTraits.BEST_EFFORT_MAY_BLOCK, () -> {
                 boolean isEligible =
-                        InstantAppsHandler.getInstance().getInstantAppIntentForUrl(url) != null;
+                        InstantAppsHandler.getInstance().getInstantAppIntentForUrl(url.getSpec())
+                        != null;
                 RecordHistogram.recordBooleanHistogram(
                         "Android.InstantApps.EligiblePageLoaded", isEligible);
             });
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/GetPagesByNamespaceForLivePageSharingCallback.java b/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/GetPagesByNamespaceForLivePageSharingCallback.java
index 7a6b391a8..fc27357 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/GetPagesByNamespaceForLivePageSharingCallback.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/GetPagesByNamespaceForLivePageSharingCallback.java
@@ -33,7 +33,7 @@
         // If there is already a page in the Live Page Sharing namespace and matches the url, share
         // it directly.
         for (OfflinePageItem item : items) {
-            if (item.getUrl().equals(mTab.getUrlString())) {
+            if (item.getUrl().equals(mTab.getUrl().getSpec())) {
                 OfflinePageUtils.sharePublishedPage(item, mTab.getWindowAndroid(), mShareCallback);
                 return;
             }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/OfflinePageUtils.java b/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/OfflinePageUtils.java
index 539deae6..d0dd988 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/OfflinePageUtils.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/OfflinePageUtils.java
@@ -442,7 +442,7 @@
         boolean isPageTemporary =
                 offlinePageBridge.isTemporaryNamespace(offlinePage.getClientId().getNamespace());
         String tabTitle = tab.getTitle();
-        getOfflinePageUriForSharing(tab.getUrlString(), isPageTemporary, offlinePath,
+        getOfflinePageUriForSharing(tab.getUrl().getSpec(), isPageTemporary, offlinePath,
                 (Uri uri)
                         -> maybeShareOfflinePageWithUri(tabTitle, webContents, offlinePageBridge,
                                 offlinePage, isPageTemporary, shareCallback, uri));
@@ -923,7 +923,7 @@
                     assert false;
                     return;
             }
-            recordTabRestoreHistogram(tabRestoreType, tab.getUrlString());
+            recordTabRestoreHistogram(tabRestoreType, tab.getUrl().getSpec());
         }
 
         /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/indicator/OfflineIndicatorController.java b/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/indicator/OfflineIndicatorController.java
index 9c027bb..60eda81 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/indicator/OfflineIndicatorController.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/indicator/OfflineIndicatorController.java
@@ -187,7 +187,7 @@
         if (tab == null) return false;
         if (tab.isShowingErrorPage()) return false;
         if (OfflinePageUtils.isOfflinePage(tab)) return false;
-        if (TextUtils.equals(tab.getUrlString(), ContentUrlConstants.ABOUT_BLANK_DISPLAY_URL)) {
+        if (TextUtils.equals(tab.getUrl().getSpec(), ContentUrlConstants.ABOUT_BLANK_DISPLAY_URL)) {
             return false;
         }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/printing/TabPrinter.java b/chrome/android/java/src/org/chromium/chrome/browser/printing/TabPrinter.java
index 0bdd0e6a..1577608 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/printing/TabPrinter.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/printing/TabPrinter.java
@@ -60,7 +60,7 @@
         String title = tab.getTitle();
         if (!TextUtils.isEmpty(title)) return title;
 
-        String url = tab.getUrlString();
+        String url = tab.getUrl().getSpec();
         if (!TextUtils.isEmpty(url)) return url;
 
         return mDefaultTitle;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/share/ShareDelegateImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/share/ShareDelegateImpl.java
index 400450e..5e91f1fc 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/share/ShareDelegateImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/share/ShareDelegateImpl.java
@@ -6,7 +6,6 @@
 
 import android.app.Activity;
 import android.net.Uri;
-import android.text.TextUtils;
 
 import androidx.annotation.IntDef;
 import androidx.annotation.NonNull;
@@ -232,8 +231,7 @@
         WebContents webContents = currentTab.getWebContents();
         if (webContents == null) return false;
         if (webContents.getMainFrame() == null) return false;
-        String url = currentTab.getUrlString();
-        if (TextUtils.isEmpty(url)) return false;
+        if (currentTab.getUrl().isEmpty()) return false;
         if (currentTab.isShowingErrorPage() || SadTab.isShowing(currentTab)) {
             return false;
         }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/suggestions/NavigationRecorder.java b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/NavigationRecorder.java
index d5ecb6d..1a69ea4e6 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/suggestions/NavigationRecorder.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/NavigationRecorder.java
@@ -19,6 +19,7 @@
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.content_public.browser.WebContentsObserver;
 import org.chromium.ui.base.PageTransition;
+import org.chromium.url.GURL;
 
 /**
  * Records stats related to a page visit, such as the time spent on the website, or if the user
@@ -60,7 +61,7 @@
                 @Override
                 public void navigationEntryCommitted(LoadCommittedDetails details) {
                     if (startStackIndex != navController.getLastCommittedEntryIndex()) return;
-                    endRecording(tab, tab.getUrlString());
+                    endRecording(tab, tab.getUrl());
                 }
             };
             webContents.addObserver(mWebContentsObserver);
@@ -97,7 +98,7 @@
         if ((params.getTransitionType() & transitionTypeMask) != 0) endRecording(tab, null);
     }
 
-    private void endRecording(@Nullable Tab removeObserverFromTab, @Nullable String endUrl) {
+    private void endRecording(@Nullable Tab removeObserverFromTab, @Nullable GURL endUrl) {
         if (removeObserverFromTab != null) {
             removeObserverFromTab.removeObserver(this);
             if (removeObserverFromTab.getWebContents() != null && mWebContentsObserver != null) {
@@ -112,9 +113,9 @@
     /** Plain holder for the data of a recorded visit. */
     public static class VisitData {
         public final long duration;
-        public final String endUrl;
+        public final GURL endUrl;
 
-        public VisitData(long duration, String endUrl) {
+        public VisitData(long duration, GURL endUrl) {
             this.duration = duration;
             this.endUrl = endUrl;
         }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabFavicon.java b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabFavicon.java
index c561b4f..2586d85 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabFavicon.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabFavicon.java
@@ -12,6 +12,7 @@
 import org.chromium.base.annotations.NativeMethods;
 import org.chromium.chrome.R;
 import org.chromium.content_public.browser.WebContents;
+import org.chromium.url.GURL;
 
 /**
  * Fetches a favicon for active WebContents in a Tab.
@@ -31,7 +32,7 @@
     private Bitmap mFavicon;
     private int mFaviconWidth;
     private int mFaviconHeight;
-    private String mFaviconUrl;
+    private GURL mFaviconUrl;
 
     static TabFavicon from(Tab tab) {
         TabFavicon favicon = get(tab);
@@ -87,7 +88,7 @@
         if (mTab.isNativePage() || mTab.getWebContents() == null) return null;
 
         // Use the cached favicon only if the page wasn't changed.
-        if (mFavicon != null && mFaviconUrl != null && mFaviconUrl.equals(mTab.getUrlString())) {
+        if (mFavicon != null && mFaviconUrl != null && mFaviconUrl.equals(mTab.getUrl())) {
             return mFavicon;
         }
 
@@ -121,7 +122,7 @@
     @CalledByNative
     private void onFaviconAvailable(Bitmap icon) {
         if (icon == null) return;
-        String url = mTab.getUrlString();
+        GURL url = mTab.getUrl();
         boolean pageUrlChanged = !url.equals(mFaviconUrl);
         // This method will be called multiple times if the page has more than one favicon.
         // We are trying to use the |mIdealFaviconSize|x|mIdealFaviconSize| DP icon here, or the
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabImpl.java
index f7229ed..97ccb744 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabImpl.java
@@ -312,7 +312,7 @@
 
             // Reload the NativePage (if any), since the old NativePage has a reference to the old
             // activity.
-            if (isNativePage()) maybeShowNativePage(getUrlString(), true);
+            if (isNativePage()) maybeShowNativePage(getUrl().getSpec(), true);
         }
 
         // Notify the event to observers only when we do the reparenting task, not when we simply
@@ -359,14 +359,9 @@
         return mId;
     }
 
-    // TODO(crbug.com/1113249) move getUrl() and getUrlString() to CriticalPersistedTabData
-    @Override
-    public String getUrlString() {
-        return getUrl().getSpec();
-    }
-
     @CalledByNative
     @Override
+    // TODO(crbug.com/1113249) move getUrl() to CriticalPersistedTabData
     public GURL getUrl() {
         if (!isInitialized()) {
             return GURL.emptyGURL();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabWebContentsObserver.java b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabWebContentsObserver.java
index e44456d..3e10899a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabWebContentsObserver.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabWebContentsObserver.java
@@ -230,7 +230,7 @@
 
                             /* buttonAction= */ () -> {
                                 if (sadTab.showSendFeedbackView()) {
-                                    mTab.getActivity().startHelpAndFeedback(mTab.getUrlString(),
+                                    mTab.getActivity().startHelpAndFeedback(mTab.getUrl().getSpec(),
                                             "MobileSadTabFeedback",
                                             Profile.fromWebContents(mTab.getWebContents()));
                                 } else {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/LocationBarModel.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/LocationBarModel.java
index 301b3a5..99fe1ba 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/LocationBarModel.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/LocationBarModel.java
@@ -213,7 +213,7 @@
         if (!hasTab() || !getTab().isInitialized()) return "";
 
         // Tab.getUrl() returns empty string if it does not have a URL.
-        return getTab().getUrlString().trim();
+        return getTab().getUrl().getSpec().trim();
     }
 
     public void notifyUrlChanged() {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java
index 8490f18..7156ecc2 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java
@@ -625,7 +625,7 @@
 
             @Override
             public void onShown(Tab tab, @TabSelectionType int type) {
-                if (TextUtils.isEmpty(tab.getUrlString())) return;
+                if (tab.getUrl().isEmpty()) return;
                 mControlContainer.setReadyForBitmapCapture(true);
             }
 
@@ -812,7 +812,16 @@
         mLayoutStateObserver = new LayoutStateProvider.LayoutStateObserver() {
             @Override
             public void onStartedShowing(@LayoutType int layoutType, boolean showToolbar) {
-                updateForLayout(layoutType, showToolbar, false);
+                if (layoutType == LayoutType.TAB_SWITCHER) {
+                    mLocationBarModel.setIsShowingTabSwitcher(true);
+                    mToolbar.setTabSwitcherMode(true, showToolbar, false);
+                    updateButtonStatus();
+                    if (mLocationBarModel.shouldShowLocationBarInOverviewMode()) {
+                        assert mLocationBar instanceof LocationBarCoordinator;
+                        ((LocationBarCoordinator) mLocationBar).startAutocompletePrefetch();
+                    }
+                }
+                mToolbar.setContentAttached(layoutType == LayoutType.BROWSING);
             }
 
             @Override
@@ -886,28 +895,6 @@
     }
 
     /**
-     * Handle a layout change event.
-     * @param layoutType The layout being switched to.
-     * @param showToolbar Whether the toolbar should be shown.
-     * @param shouldFocusOmnibox Whether we should attempt to focus the omnibox.
-     */
-    private void updateForLayout(
-            @LayoutType int layoutType, boolean showToolbar, boolean shouldFocusOmnibox) {
-        if (layoutType == LayoutType.TAB_SWITCHER) {
-            mLocationBarModel.setIsShowingTabSwitcher(true);
-            mToolbar.setTabSwitcherMode(true, showToolbar, false);
-            updateButtonStatus();
-            if (mLocationBarModel.shouldShowLocationBarInOverviewMode()) {
-                assert mLocationBar instanceof LocationBarCoordinator;
-                ((LocationBarCoordinator) mLocationBar).startAutocompletePrefetch();
-            }
-        }
-        mToolbar.setContentAttached(layoutType == LayoutType.BROWSING);
-
-        if (shouldFocusOmnibox) maybeFocusOmnibox(layoutType, mActivityTabProvider.get());
-    }
-
-    /**
      * May set Omnibox focused if the Tab has the flag to require focusing the Omnibox.
      */
     private void maybeFocusOmnibox(@LayoutType int layout, Tab tab) {
@@ -1780,15 +1767,6 @@
 
         mLayoutStateProvider = layoutStateProvider;
         mLayoutStateProvider.addObserver(mLayoutStateObserver);
-
-        if (mLayoutStateProvider.isLayoutVisible(LayoutType.TAB_SWITCHER)) {
-            // TODO(1210431): We shouldn't need to post this. Instead we should wait until the
-            //                dependencies are ready. This logic was introduced to move asynchronous
-            //                observer events from the infra (LayoutManager) into the feature using
-            //                it.
-            mControlContainer.post(() -> updateForLayout(LayoutType.TAB_SWITCHER, true, true));
-        }
-
         mAppThemeColorProvider.setLayoutStateProvider(mLayoutStateProvider);
         mLocationBarModel.setLayoutStateProvider(mLayoutStateProvider);
         if (mBottomControlsCoordinatorSupplier.get() != null) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/StartSurfaceToolbarMediator.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/StartSurfaceToolbarMediator.java
index 1e16b7b..16779d6 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/StartSurfaceToolbarMediator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/StartSurfaceToolbarMediator.java
@@ -29,7 +29,6 @@
 import static org.chromium.chrome.browser.toolbar.top.StartSurfaceToolbarProperties.TAB_SWITCHER_BUTTON_IS_VISIBLE;
 import static org.chromium.chrome.browser.toolbar.top.StartSurfaceToolbarProperties.TRANSLATION_Y;
 
-import android.os.Handler;
 import android.view.View;
 import android.view.View.OnClickListener;
 
@@ -338,14 +337,6 @@
                 }
             }
         };
-
-        if (mLayoutStateProvider.isLayoutVisible(LayoutType.TAB_SWITCHER)) {
-            new Handler().post(() -> {
-                mLayoutStateObserver.onStartedShowing(LayoutType.TAB_SWITCHER, true);
-                mLayoutStateObserver.onFinishedShowing(LayoutType.TAB_SWITCHER);
-            });
-        }
-
         mLayoutStateProvider.addObserver(mLayoutStateObserver);
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/translate/TranslateIntentHandler.java b/chrome/android/java/src/org/chromium/chrome/browser/translate/TranslateIntentHandler.java
index 777bc41..adf36a8 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/translate/TranslateIntentHandler.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/translate/TranslateIntentHandler.java
@@ -100,7 +100,7 @@
             recordTranslateTabResultUMA(TranslateTabIntentResult.INCOGNITO_TAB);
             return;
         }
-        if (expectedUrl == null || !expectedUrl.equals(tab.getUrlString())) {
+        if (expectedUrl == null || !expectedUrl.equals(tab.getUrl().getSpec())) {
             recordTranslateTabResultUMA(TranslateTabIntentResult.EXPECTED_URL_MISMATCH);
             return;
         }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/compositor/layouts/LayoutManagerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/compositor/layouts/LayoutManagerTest.java
index 13996b17..c427b86e 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/compositor/layouts/LayoutManagerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/compositor/layouts/LayoutManagerTest.java
@@ -121,6 +121,8 @@
 
     private float mDpToPx;
 
+    private OneshotSupplierImpl<LayoutStateProvider> mLayoutStateProviderSupplier;
+
     class LayoutObserverCallbackHelper extends CallbackHelper {
         @LayoutType
         public int layoutType;
@@ -211,9 +213,13 @@
         OneshotSupplierImpl<OverviewModeBehavior> overviewModeBehaviorSupplier =
                 new OneshotSupplierImpl<>();
 
+        if (mLayoutStateProviderSupplier == null) {
+            mLayoutStateProviderSupplier = new OneshotSupplierImpl<>();
+        }
+
         mManagerPhone = new LayoutManagerChromePhone(layoutManagerHost, container, mStartSurface,
                 tabContentManagerSupplier, null, overviewModeBehaviorSupplier,
-                () -> mTopUiThemeColorProvider);
+                mLayoutStateProviderSupplier, () -> mTopUiThemeColorProvider);
         verify(mStartSurfaceController)
                 .addOverviewModeObserver(mStartSurfaceOverviewModeCaptor.capture());
 
@@ -466,10 +472,17 @@
             performToolbarSideSwipe(ScrollDirection.RIGHT);
             Assert.assertEquals(
                     LayoutType.TOOLBAR_SWIPE, mManager.getActiveLayout().getLayoutType());
-            Assert.assertTrue(mManager.isLayoutVisible(LayoutType.TOOLBAR_SWIPE));
+            Assert.assertTrue(
+                    mLayoutStateProviderSupplier.get().isLayoutVisible(LayoutType.TOOLBAR_SWIPE));
         });
 
-        startedShowingCallback.waitForCallback(0);
+        // The |startedShowingCallback| callCount 0 is reserved for the default layout during
+        // initialization. Because LayoutManager does not explicitly hide the old layout when a new
+        // layout is forced to show, the callCount for |finishedShowingCallback|,
+        // |startedHidingCallback|, and |finishedHidingCallback| are still 0.
+        // TODO(crbug.com/1108496): update the callCount when LayoutManager explicitly hide the old
+        // layout.
+        startedShowingCallback.waitForCallback(1);
         Assert.assertEquals(LayoutType.TOOLBAR_SWIPE, startedShowingCallback.layoutType);
 
         finishedShowingCallback.waitForCallback(0);
@@ -478,7 +491,8 @@
         TestThreadUtils.runOnUiThreadBlocking(() -> {
             finishToolbarSideSwipe();
             Assert.assertEquals(LayoutType.BROWSING, mManager.getActiveLayout().getLayoutType());
-            Assert.assertTrue(mManager.isLayoutVisible(LayoutType.BROWSING));
+            Assert.assertTrue(
+                    mLayoutStateProviderSupplier.get().isLayoutVisible(LayoutType.BROWSING));
         });
 
         startedHidingCallback.waitForCallback(0);
@@ -487,7 +501,7 @@
         finishedHidingCallback.waitForCallback(0);
         Assert.assertEquals(LayoutType.TOOLBAR_SWIPE, finishedHidingCallback.layoutType);
 
-        startedShowingCallback.waitForCallback(1);
+        startedShowingCallback.waitForCallback(2);
         Assert.assertEquals(LayoutType.BROWSING, startedShowingCallback.layoutType);
 
         finishedShowingCallback.waitForCallback(1);
@@ -515,7 +529,8 @@
                     "layoutManager is way too long to end motion", simulateTime(mManager, 1000));
             Assert.assertEquals(
                     LayoutType.TAB_SWITCHER, mManager.getActiveLayout().getLayoutType());
-            Assert.assertTrue(mManager.isLayoutVisible(LayoutType.TAB_SWITCHER));
+            Assert.assertTrue(
+                    mLayoutStateProviderSupplier.get().isLayoutVisible(LayoutType.TAB_SWITCHER));
         });
 
         // The |startedShowingCallback| callCount 0 is reserved for the default layout during
@@ -535,7 +550,8 @@
             Assert.assertTrue(
                     "layoutManager is way too long to end motion", simulateTime(mManager, 1000));
 
-            Assert.assertTrue(mManager.isLayoutVisible(LayoutType.BROWSING));
+            Assert.assertTrue(
+                    mLayoutStateProviderSupplier.get().isLayoutVisible(LayoutType.BROWSING));
         });
 
         startedHidingCallback.waitForCallback(0);
@@ -573,10 +589,17 @@
             Assert.assertThat("Incorrect active LayoutType",
                     mManager.getActiveLayout().getLayoutType(), is(LayoutType.SIMPLE_ANIMATION));
             Assert.assertThat("Incorrect active Layout",
-                    mManager.isLayoutVisible(LayoutType.SIMPLE_ANIMATION), is(true));
+                    mLayoutStateProviderSupplier.get().isLayoutVisible(LayoutType.SIMPLE_ANIMATION),
+                    is(true));
         });
 
-        startedShowingCallback.waitForCallback(0);
+        // The |startedShowingCallback| callCount 0 is reserved for the default layout during
+        // initialization. Because LayoutManager does not explicitly hide the old layout when a new
+        // layout is forced to show, the callCount for |finishedShowingCallback|,
+        // |startedHidingCallback|, and |finishedHidingCallback| are still 0.
+        // TODO(crbug.com/1108496): update the callCount when LayoutManager explicitly hide the old
+        // layout.
+        startedShowingCallback.waitForCallback(1);
         Assert.assertThat("startedShowingCallback with incorrect LayoutType",
                 startedShowingCallback.layoutType, is(LayoutType.SIMPLE_ANIMATION));
 
@@ -603,7 +626,7 @@
         Assert.assertThat("finishedHidingCallback with incorrectLayoutType",
                 finishedHidingCallback.layoutType, is(LayoutType.SIMPLE_ANIMATION));
 
-        startedShowingCallback.waitForCallback(1);
+        startedShowingCallback.waitForCallback(2);
         Assert.assertThat("startedShowingCallback with incorrectLayoutType",
                 startedShowingCallback.layoutType, is(LayoutType.BROWSING));
 
@@ -618,46 +641,46 @@
             LayoutObserverCallbackHelper startedHidingCallback,
             LayoutObserverCallbackHelper finishedHidingCallback) throws TimeoutException {
         TestThreadUtils.runOnUiThreadBlocking(() -> {
-            initializeLayoutManagerPhone(2, 0);
-            mManager.addObserver(new LayoutStateProvider.LayoutStateObserver() {
-                @Override
-                public void onStartedShowing(int layoutType, boolean showToolbar) {
-                    Log.d(TAG, "Started to show: " + layoutType);
-                    startedShowingCallback.layoutType = layoutType;
-                    startedShowingCallback.notifyCalled();
-                }
+            mLayoutStateProviderSupplier = new OneshotSupplierImpl<>();
 
-                @Override
-                public void onFinishedShowing(int layoutType) {
-                    Log.d(TAG, "finished to show: " + layoutType);
-                    finishedShowingCallback.layoutType = layoutType;
-                    finishedShowingCallback.notifyCalled();
-                }
+            mLayoutStateProviderSupplier.onAvailable((layoutStateProvider) -> {
+                layoutStateProvider.addObserver(new LayoutStateProvider.LayoutStateObserver() {
+                    @Override
+                    public void onStartedShowing(int layoutType, boolean showToolbar) {
+                        Log.d(TAG, "Started to show: " + layoutType);
+                        startedShowingCallback.layoutType = layoutType;
+                        startedShowingCallback.notifyCalled();
+                    }
 
-                @Override
-                public void onStartedHiding(
-                        int layoutType, boolean showToolbar, boolean delayAnimation) {
-                    Log.d(TAG, "Started to hide: " + layoutType);
-                    startedHidingCallback.layoutType = layoutType;
-                    startedHidingCallback.notifyCalled();
-                }
+                    @Override
+                    public void onFinishedShowing(int layoutType) {
+                        Log.d(TAG, "finished to show: " + layoutType);
+                        finishedShowingCallback.layoutType = layoutType;
+                        finishedShowingCallback.notifyCalled();
+                    }
 
-                @Override
-                public void onFinishedHiding(int layoutType) {
-                    Log.d(TAG, "finished to hide: " + layoutType);
-                    finishedHidingCallback.layoutType = layoutType;
-                    finishedHidingCallback.notifyCalled();
-                }
+                    @Override
+                    public void onStartedHiding(
+                            int layoutType, boolean showToolbar, boolean delayAnimation) {
+                        Log.d(TAG, "Started to hide: " + layoutType);
+                        startedHidingCallback.layoutType = layoutType;
+                        startedHidingCallback.notifyCalled();
+                    }
+
+                    @Override
+                    public void onFinishedHiding(int layoutType) {
+                        Log.d(TAG, "finished to hide: " + layoutType);
+                        finishedHidingCallback.layoutType = layoutType;
+                        finishedHidingCallback.notifyCalled();
+                    }
+                });
             });
 
+            initializeLayoutManagerPhone(2, 0);
             Assert.assertEquals(LayoutType.BROWSING, mManager.getActiveLayout().getLayoutType());
         });
 
-        if (mManager.isLayoutVisible(LayoutType.BROWSING)) {
-            startedShowingCallback.layoutType = LayoutType.BROWSING;
-        } else {
-            startedShowingCallback.waitForCallback(0);
-        }
+        startedShowingCallback.waitForCallback(0);
         Assert.assertEquals(LayoutType.BROWSING, startedShowingCallback.layoutType);
     }
 
@@ -669,15 +692,19 @@
         CallbackHelper tabSelectionHintedCallback = new CallbackHelper();
 
         TestThreadUtils.runOnUiThreadBlocking(() -> {
-            initializeLayoutManagerPhone(2, 0);
-            mManager.addObserver(new LayoutStateProvider.LayoutStateObserver() {
-                @Override
-                public void onTabSelectionHinted(int tabId) {
-                    Log.d(TAG, "onTabSelectionHinted");
-                    tabSelectionHintedCallback.notifyCalled();
-                }
+            mLayoutStateProviderSupplier = new OneshotSupplierImpl<>();
+
+            mLayoutStateProviderSupplier.onAvailable((layoutStateProvider) -> {
+                layoutStateProvider.addObserver(new LayoutStateProvider.LayoutStateObserver() {
+                    @Override
+                    public void onTabSelectionHinted(int tabId) {
+                        Log.d(TAG, "onTabSelectionHinted");
+                        tabSelectionHintedCallback.notifyCalled();
+                    }
+                });
             });
 
+            initializeLayoutManagerPhone(2, 0);
             mManager.showOverview(true);
 
             Assert.assertTrue(
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/gesturenav/NavigationHandlerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/gesturenav/NavigationHandlerTest.java
index 0654131..054ce6468 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/gesturenav/NavigationHandlerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/gesturenav/NavigationHandlerTest.java
@@ -297,7 +297,7 @@
                 mActivityTestRule.getActivity().getLayoutManager().hideOverview(false);
             }
         });
-        LayoutTestUtils.waitForLayout(mActivityTestRule.getActivity().getLayoutManager(),
-                inSwitcher ? LayoutType.TAB_SWITCHER : LayoutType.BROWSING);
+        LayoutTestUtils.waitForLayout(
+                mActivityTestRule.getActivity().getLayoutManager(), LayoutType.TAB_SWITCHER);
     }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/suggestions/NavigationRecorderTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/suggestions/NavigationRecorderTest.java
index e6a5553..cefdcfda 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/suggestions/NavigationRecorderTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/suggestions/NavigationRecorderTest.java
@@ -72,7 +72,7 @@
             @Override
             public void onResult(NavigationRecorder.VisitData visit) {
                 // When the tab is hidden we receive a notification with no end URL.
-                assertEquals(UrlConstants.NTP_URL, visit.endUrl);
+                assertEquals(UrlConstants.NTP_URL, visit.endUrl.getSpec());
                 callback.notifyCalled();
             }
         });
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/widget/bottomsheet/BottomSheetControllerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/widget/bottomsheet/BottomSheetControllerTest.java
index 596d6d8..d233903 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/widget/bottomsheet/BottomSheetControllerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/widget/bottomsheet/BottomSheetControllerTest.java
@@ -653,8 +653,7 @@
             }
         });
 
-        LayoutTestUtils.waitForLayout(mActivity.getLayoutManager(),
-                shown ? LayoutType.TAB_SWITCHER : LayoutType.BROWSING);
+        LayoutTestUtils.waitForLayout(mActivity.getLayoutManager(), LayoutType.TAB_SWITCHER);
         ThreadUtils.runOnUiThreadBlocking(mTestSupport::endAllAnimations);
     }
 
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/compositor/layouts/SceneOverlayTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/compositor/layouts/SceneOverlayTest.java
index 89b2127..95bd9bec 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/compositor/layouts/SceneOverlayTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/compositor/layouts/SceneOverlayTest.java
@@ -5,6 +5,8 @@
 package org.chromium.chrome.browser.compositor.layouts;
 
 import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
@@ -21,9 +23,11 @@
 import org.robolectric.annotation.Config;
 
 import org.chromium.base.supplier.ObservableSupplier;
+import org.chromium.base.supplier.OneshotSupplierImpl;
 import org.chromium.base.test.BaseRobolectricTestRunner;
 import org.chromium.chrome.browser.compositor.bottombar.contextualsearch.ContextualSearchPanel;
 import org.chromium.chrome.browser.compositor.layouts.content.TabContentManager;
+import org.chromium.chrome.browser.layouts.LayoutStateProvider;
 import org.chromium.chrome.browser.layouts.SceneOverlay;
 import org.chromium.chrome.browser.theme.TopUiThemeColorProvider;
 import org.chromium.chrome.browser.toolbar.bottom.ScrollingBottomViewSceneLayer;
@@ -55,6 +59,9 @@
     private ObservableSupplier<TabContentManager> mTabContentManagerSupplier;
 
     @Mock
+    private OneshotSupplierImpl<LayoutStateProvider> mLayoutStateProviderOneshotSupplier;
+
+    @Mock
     private TopUiThemeColorProvider mTopUiThemeColorProvider;
 
     private LayoutManagerImpl mLayoutManager;
@@ -66,9 +73,11 @@
         when(mLayoutManagerHost.getContext()).thenReturn(mContext);
         when(mContext.getResources()).thenReturn(mResources);
         when(mResources.getDisplayMetrics()).thenReturn(mDisplayMetrics);
+        doNothing().when(mLayoutStateProviderOneshotSupplier).set(any());
 
         mLayoutManager = new LayoutManagerImpl(mLayoutManagerHost, mContainerView,
-                mTabContentManagerSupplier, null, () -> mTopUiThemeColorProvider);
+                mTabContentManagerSupplier, null, mLayoutStateProviderOneshotSupplier,
+                () -> mTopUiThemeColorProvider);
     }
 
     @Test
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbarUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbarUnitTest.java
new file mode 100644
index 0000000..2dff6c1
--- /dev/null
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbarUnitTest.java
@@ -0,0 +1,34 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.customtabs.features.toolbar;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+import org.chromium.testing.local.LocalRobolectricTestRunner;
+import org.chromium.url.JUnitTestGURLs;
+
+/**
+ * Tests AMP url handling in the CustomTab Toolbar.
+ */
+@RunWith(LocalRobolectricTestRunner.class)
+@Config(manifest = Config.NONE)
+public class CustomTabToolbarUnitTest {
+    @Test
+    public void testParsesPublisherFromAmp() {
+        assertEquals("www.nyt.com",
+                CustomTabToolbar.parsePublisherNameFromUrl(
+                        JUnitTestGURLs.getGURL(JUnitTestGURLs.AMP_URL)));
+        assertEquals("www.nyt.com",
+                CustomTabToolbar.parsePublisherNameFromUrl(
+                        JUnitTestGURLs.getGURL(JUnitTestGURLs.AMP_CACHE_URL)));
+        assertEquals(JUnitTestGURLs.EXAMPLE_URL,
+                CustomTabToolbar.parsePublisherNameFromUrl(
+                        JUnitTestGURLs.getGURL(JUnitTestGURLs.EXAMPLE_URL)));
+    }
+}
diff --git a/chrome/browser/app_controller_mac.mm b/chrome/browser/app_controller_mac.mm
index 6622f6d..25ead0e2 100644
--- a/chrome/browser/app_controller_mac.mm
+++ b/chrome/browser/app_controller_mac.mm
@@ -145,7 +145,7 @@
 // there are only minimized windows), it will unminimize it.
 Browser* ActivateBrowser(Profile* profile) {
   Browser* browser = chrome::FindLastActiveWithProfile(
-      profile->IsGuestSession()
+      (profile->IsGuestSession() || profile->IsEphemeralGuestProfile())
           ? profile->GetPrimaryOTRProfile(/*create_if_needed=*/true)
           : profile);
   if (browser)
@@ -1201,8 +1201,10 @@
   // locked profile needs authentication and the system profile cannot have a
   // browser.
   const PrefService* prefService = g_browser_process->local_state();
+  bool is_last_profile_guest =
+      lastProfile->IsGuestSession() || lastProfile->IsEphemeralGuestProfile();
   if (IsProfileSignedOut(lastProfile) || lastProfile->IsSystemProfile() ||
-      (lastProfile->IsGuestSession() && prefService &&
+      (is_last_profile_guest && prefService &&
        !prefService->GetBoolean(prefs::kBrowserGuestModeEnabled))) {
     ProfilePicker::Show(ProfilePicker::EntryPoint::kProfileLocked);
     return;
@@ -1404,8 +1406,8 @@
     // to AppKit as there's nothing that it can do either.
     return NO;
   }
-  if (lastProfile->IsGuestSession() || IsProfileSignedOut(lastProfile) ||
-      lastProfile->IsSystemProfile()) {
+  if (lastProfile->IsGuestSession() || lastProfile->IsEphemeralGuestProfile() ||
+      IsProfileSignedOut(lastProfile) || lastProfile->IsSystemProfile()) {
     ProfilePicker::Show(ProfilePicker::EntryPoint::kProfileLocked);
   } else if (ProfilePicker::ShouldShowAtLaunch()) {
     ProfilePicker::Show(
@@ -1543,7 +1545,7 @@
   Profile* profile = [self safeLastProfileForNewWindows];
 
   const PrefService* prefs = g_browser_process->local_state();
-  return !profile->IsGuestSession() ||
+  return (!profile->IsGuestSession() && !profile->IsEphemeralGuestProfile()) ||
          prefs->GetBoolean(prefs::kBrowserGuestModeEnabled);
 }
 
diff --git a/chrome/browser/app_controller_mac_browsertest.mm b/chrome/browser/app_controller_mac_browsertest.mm
index 2efa2a6..d23dfe4e 100644
--- a/chrome/browser/app_controller_mac_browsertest.mm
+++ b/chrome/browser/app_controller_mac_browsertest.mm
@@ -58,6 +58,7 @@
 #include "chrome/browser/web_applications/test/web_app_install_test_utils.h"
 #include "chrome/browser/web_applications/web_app_provider.h"
 #include "chrome/common/chrome_constants.h"
+#include "chrome/common/chrome_features.h"
 #include "chrome/common/chrome_switches.h"
 #include "chrome/common/pref_names.h"
 #include "chrome/common/url_constants.h"
@@ -403,8 +404,10 @@
 IN_PROC_BROWSER_TEST_F(AppControllerProfilePickerBrowserTest,
                        OpenGuestProfileOnlyIfGuestModeIsEnabled) {
   CreateAndWaitForSystemProfile();
+  base::FilePath guest_profile_path = ProfileManager::GetGuestProfilePath();
   PrefService* local_state = g_browser_process->local_state();
-  local_state->SetString(prefs::kProfileLastUsed, chrome::kGuestProfileDir);
+  local_state->SetString(prefs::kProfileLastUsed,
+                         guest_profile_path.BaseName().value());
   local_state->SetBoolean(prefs::kBrowserGuestModeEnabled, false);
 
   AppController* ac = base::mac::ObjCCast<AppController>(
@@ -412,7 +415,14 @@
   ASSERT_TRUE(ac);
 
   Profile* profile = [ac lastProfile];
-  EXPECT_TRUE(profile->IsGuestSession());
+  ASSERT_TRUE(profile);
+  EXPECT_EQ(guest_profile_path, profile->GetPath());
+  if (base::FeatureList::IsEnabled(
+          features::kEnableEphemeralGuestProfilesOnDesktop)) {
+    EXPECT_TRUE(profile->IsEphemeralGuestProfile());
+  } else {
+    EXPECT_TRUE(profile->IsGuestSession());
+  }
 
   NSMenu* menu = [ac applicationDockMenu:NSApp];
   ASSERT_TRUE(menu);
@@ -444,15 +454,23 @@
   // Create the guest profile, and set it as the last used profile so the
   // app controller can use it on init.
   CreateAndWaitForSystemProfile();
+  base::FilePath guest_profile_path = ProfileManager::GetGuestProfilePath();
   PrefService* local_state = g_browser_process->local_state();
-  local_state->SetString(prefs::kProfileLastUsed, chrome::kGuestProfileDir);
+  local_state->SetString(prefs::kProfileLastUsed,
+                         guest_profile_path.BaseName().value());
 
   // Prohibiting guest mode forces the user manager flow for About Chrome.
   local_state->SetBoolean(prefs::kBrowserGuestModeEnabled, false);
 
-  Profile* guest_profile = [ac lastProfile];
-  EXPECT_EQ(ProfileManager::GetGuestProfilePath(), guest_profile->GetPath());
-  EXPECT_TRUE(guest_profile->IsGuestSession());
+  Profile* profile = [ac lastProfile];
+  ASSERT_TRUE(profile);
+  EXPECT_EQ(guest_profile_path, profile->GetPath());
+  if (base::FeatureList::IsEnabled(
+          features::kEnableEphemeralGuestProfilesOnDesktop)) {
+    EXPECT_TRUE(profile->IsEphemeralGuestProfile());
+  } else {
+    EXPECT_TRUE(profile->IsGuestSession());
+  }
 
   // Tell the browser to open About Chrome.
   EXPECT_EQ(1u, active_browser_list()->size());
@@ -518,15 +536,22 @@
   // Create the system profile. Set the guest as the last used profile so the
   // app controller can use it on init.
   CreateAndWaitForSystemProfile();
-  PrefService* local_state = g_browser_process->local_state();
-  local_state->SetString(prefs::kProfileLastUsed, chrome::kGuestProfileDir);
+  base::FilePath guest_profile_path = ProfileManager::GetGuestProfilePath();
+  g_browser_process->local_state()->SetString(
+      prefs::kProfileLastUsed, guest_profile_path.BaseName().value());
 
   AppController* ac = base::mac::ObjCCastStrict<AppController>(
       [[NSApplication sharedApplication] delegate]);
 
   Profile* profile = [ac lastProfile];
-  EXPECT_EQ(ProfileManager::GetGuestProfilePath(), profile->GetPath());
-  EXPECT_TRUE(profile->IsGuestSession());
+  ASSERT_TRUE(profile);
+  EXPECT_EQ(guest_profile_path, profile->GetPath());
+  if (base::FeatureList::IsEnabled(
+          features::kEnableEphemeralGuestProfilesOnDesktop)) {
+    EXPECT_TRUE(profile->IsEphemeralGuestProfile());
+  } else {
+    EXPECT_TRUE(profile->IsGuestSession());
+  }
 
   EXPECT_EQ(1u, active_browser_list()->size());
   BOOL result = [ac applicationShouldHandleReopen:NSApp hasVisibleWindows:NO];
diff --git a/chrome/browser/ash/accessibility/accessibility_manager.cc b/chrome/browser/ash/accessibility/accessibility_manager.cc
index 00e0eee..5018f38 100644
--- a/chrome/browser/ash/accessibility/accessibility_manager.cc
+++ b/chrome/browser/ash/accessibility/accessibility_manager.cc
@@ -1283,14 +1283,11 @@
   if (details.notification_type ==
       AccessibilityNotificationType::kToggleDictation) {
     AccessibilityController::Get()->SetDictationActive(details.enabled);
-    AccessibilityController::Get()->NotifyAccessibilityStatusChanged();
-    return;
   }
 
   // Update system tray menu visibility. Prefs tracked inside ash handle their
   // own updates to avoid race conditions (pref updates are asynchronous between
   // chrome and ash).
-  // TODO(hferreiro): repeated condition
   if (details.notification_type ==
           AccessibilityNotificationType::kToggleScreenMagnifier ||
       details.notification_type ==
diff --git a/chrome/browser/ash/accessibility/dictation.cc b/chrome/browser/ash/accessibility/dictation.cc
index 8995463b..54d76289 100644
--- a/chrome/browser/ash/accessibility/dictation.cc
+++ b/chrome/browser/ash/accessibility/dictation.cc
@@ -16,6 +16,8 @@
 #include "chrome/common/pref_names.h"
 #include "components/language/core/browser/pref_names.h"
 #include "components/prefs/pref_service.h"
+#include "content/public/browser/browser_task_traits.h"
+#include "content/public/browser/browser_thread.h"
 #include "content/public/browser/storage_partition.h"
 #include "services/audio/public/cpp/sounds/sounds_manager.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
@@ -221,7 +223,10 @@
   if (!speech_recognizer_)
     return;
 
-  CommitCurrentText();
+  // Post commit text delayed to avoid a dcheck.
+  content::GetUIThreadTaskRunner({})->PostTask(
+      FROM_HERE, base::BindOnce(&Dictation::CommitCurrentText,
+                                weak_ptr_factory_.GetWeakPtr()));
   if (!composition_->text.empty()) {
     audio::SoundsManager::Get()->Play(static_cast<int>(Sound::kDictationEnd));
   } else {
diff --git a/chrome/browser/ash/accessibility/dictation_browsertest.cc b/chrome/browser/ash/accessibility/dictation_browsertest.cc
index f83bfc0..f7b52a35 100644
--- a/chrome/browser/ash/accessibility/dictation_browsertest.cc
+++ b/chrome/browser/ash/accessibility/dictation_browsertest.cc
@@ -6,6 +6,8 @@
 
 #include <memory>
 
+#include "ash/accessibility/accessibility_controller_impl.h"
+#include "ash/shell.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/timer/timer.h"
 #include "chrome/browser/ash/accessibility/accessibility_manager.h"
@@ -25,6 +27,7 @@
 #include "ui/base/ime/chromeos/mock_ime_input_context_handler.h"
 #include "ui/base/ime/dummy_text_input_client.h"
 #include "ui/base/ime/input_method_base.h"
+#include "ui/events/test/event_generator.h"
 
 namespace ash {
 namespace {
@@ -102,6 +105,13 @@
 
   void SetUpOnMainThread() override {
     ui::IMEBridge::Get()->SetInputContextHandler(input_context_handler_.get());
+    generator_ = std::make_unique<ui::test::EventGenerator>(
+        ash::Shell::Get()->GetPrimaryRootWindow());
+    ash::Shell::Get()
+        ->accessibility_controller()
+        ->dictation()
+        .SetDialogAccepted();
+    ash::Shell::Get()->accessibility_controller()->dictation().SetEnabled(true);
     if (GetParam().second == kOnDeviceRecognition) {
       // Replaces normal CrosSpeechRecognitionService with a fake one.
       CrosSpeechRecognitionServiceFactory::GetInstance()
@@ -202,16 +212,17 @@
   void ToggleDictation() {
     // We are trying to toggle on if Dictation is currently off.
     bool will_toggle_on = IsDictationOff();
-    GetManager()->ToggleDictation();
+    generator_->PressKey(ui::VKEY_D, ui::EF_COMMAND_DOWN);
+    generator_->ReleaseKey(ui::VKEY_D, ui::EF_COMMAND_DOWN);
     if (will_toggle_on) {
       // SpeechRecognition may be turned on asynchronously. Wait for it to
       // complete before moving on to ensures that we are ready to receive
       // speech. In Dictation, a tone is played when recognition starts,
       // indicating to the user that they can begin speaking.
       WaitForRecognitionStarted();
-      // Now wait for the callbacks to propagate on the UI thread.
-      base::RunLoop().RunUntilIdle();
     }
+    // Now wait for the callbacks to propagate on the UI thread.
+    base::RunLoop().RunUntilIdle();
   }
 
   ui::CompositionText GetLastCompositionText() {
@@ -220,6 +231,7 @@
   }
 
   std::unique_ptr<ui::MockIMEInputContextHandler> input_context_handler_;
+  std::unique_ptr<ui::test::EventGenerator> generator_;
   ui::CompositionText empty_composition_text_;
 
   // For network recognition.
@@ -261,6 +273,8 @@
   EXPECT_EQ(kSecondSpeechResult16, GetLastCompositionText().text);
 
   SendSpeechResult(kFinalSpeechResult, true /* is_final */);
+  // Wait for interim results to be finalized.
+  base::RunLoop().RunUntilIdle();
   EXPECT_EQ(1, input_context_handler_->commit_text_call_count());
   EXPECT_EQ(kFinalSpeechResult16, input_context_handler_->last_commit_text());
 
@@ -279,7 +293,12 @@
   EnableChromeVox();
   EXPECT_TRUE(manager->IsSpokenFeedbackEnabled());
 
-  ToggleDictation();
+  // Toggle Dictation on directly.
+  GetManager()->ToggleDictation();
+  WaitForRecognitionStarted();
+  // Now wait for the callbacks to propagate on the UI thread.
+  base::RunLoop().RunUntilIdle();
+
   EXPECT_EQ(GetLastCompositionText().text, empty_composition_text_.text);
 
   SendSpeechResult(kFirstSpeechResult, false /* is_final */);
@@ -289,6 +308,8 @@
   EXPECT_EQ(GetLastCompositionText().text, empty_composition_text_.text);
 
   SendSpeechResult(kFinalSpeechResult, true /* is_final */);
+  // Wait for interim results to be finalized.
+  base::RunLoop().RunUntilIdle();
   EXPECT_EQ(1, input_context_handler_->commit_text_call_count());
   EXPECT_EQ(kFinalSpeechResult16, input_context_handler_->last_commit_text());
 
@@ -328,6 +349,8 @@
   // Firing the timer, which simluates waiting for some time without new speech,
   // should end dictation.
   timer->FireNow();
+  // Wait for interim results to be finalized.
+  base::RunLoop().RunUntilIdle();
   EXPECT_TRUE(IsDictationOff());
   EXPECT_EQ(1, input_context_handler_->commit_text_call_count());
   EXPECT_EQ(kFirstSpeechResult16, input_context_handler_->last_commit_text());
@@ -358,13 +381,23 @@
   EnableChromeVox();
   EXPECT_TRUE(manager->IsSpokenFeedbackEnabled());
 
-  ToggleDictation();
+  // Toggle Dictation on directly.
+  GetManager()->ToggleDictation();
+  WaitForRecognitionStarted();
+  // Now wait for the callbacks to propagate on the UI thread.
+  base::RunLoop().RunUntilIdle();
+
   EXPECT_EQ(GetLastCompositionText().text, empty_composition_text_.text);
 
   SendSpeechResult(kFinalSpeechResult, false /* is_final */);
   EXPECT_EQ(GetLastCompositionText().text, empty_composition_text_.text);
 
-  ToggleDictation();
+  // Toggle Dictation off.
+  GetManager()->ToggleDictation();
+  base::RunLoop().RunUntilIdle();
+
+  // Wait for interim results to be finalized.
+  base::RunLoop().RunUntilIdle();
   EXPECT_EQ(1, input_context_handler_->commit_text_call_count());
   EXPECT_EQ(kFinalSpeechResult16, input_context_handler_->last_commit_text());
 }
@@ -373,6 +406,8 @@
   // Turn on dictation and say something.
   ToggleDictation();
   SendSpeechResult(kFirstSpeechResult, true /* is final */);
+  // Wait for interim results to be finalized.
+  base::RunLoop().RunUntilIdle();
 
   // Speech goes to the default IMEInputContextHandler.
   EXPECT_EQ(kFirstSpeechResult16, input_context_handler_->last_commit_text());
@@ -392,6 +427,8 @@
   }
 
   SendSpeechResult(kSecondSpeechResult, true /* is final*/);
+  // Wait for interim results to be finalized.
+  base::RunLoop().RunUntilIdle();
 
   std::u16string expected = kSecondSpeechResult16;
   if (GetParam().first != kTestDefaultListening)
@@ -412,6 +449,8 @@
   std::unique_ptr<ui::TextInputClient> new_client =
       std::make_unique<ui::DummyTextInputClient>();
   NotifyTextInputStateChanged(new_client.get());
+  // Wait for interim results to be finalized.
+  base::RunLoop().RunUntilIdle();
 
   // Check that dictation has turned off.
   EXPECT_EQ(1, input_context_handler_->commit_text_call_count());
@@ -422,6 +461,8 @@
   // Turn on dictation and send a final result.
   ToggleDictation();
   SendSpeechResult("Purple", true /* is final */);
+  // Wait for interim results to be finalized.
+  base::RunLoop().RunUntilIdle();
 
   EXPECT_EQ(u"Purple", input_context_handler_->last_commit_text());
   if (GetParam().first == kTestDefaultListening) {
diff --git a/chrome/browser/ash/accessibility/switch_access_browsertest.cc b/chrome/browser/ash/accessibility/switch_access_browsertest.cc
index 34941b5..9438f648 100644
--- a/chrome/browser/ash/accessibility/switch_access_browsertest.cc
+++ b/chrome/browser/ash/accessibility/switch_access_browsertest.cc
@@ -391,8 +391,9 @@
   ASSERT_TRUE(IsMouseEventsEnabled(600, 600));
 }
 
+// Test is flaky: https://crbug.com/1216048.
 IN_PROC_BROWSER_TEST_F(SwitchAccessTest,
-                       PointScanClickWhenMouseEventsDisabled) {
+                       DISABLED_PointScanClickWhenMouseEventsDisabled) {
   EnableSwitchAccess({'1', 'A'} /* select */, {'2', 'B'} /* next */,
                      {'3', 'C'} /* previous */);
 
diff --git a/chrome/browser/ash/file_system_provider/registry.cc b/chrome/browser/ash/file_system_provider/registry.cc
index ab4d77de..4c19ed0 100644
--- a/chrome/browser/ash/file_system_provider/registry.cc
+++ b/chrome/browser/ash/file_system_provider/registry.cc
@@ -158,8 +158,9 @@
 
     std::string file_system_id;
     std::string display_name;
-    bool writable = false;
-    bool supports_notify_tag = false;
+    absl::optional<bool> writable = file_system->FindBoolKey(kPrefKeyWritable);
+    absl::optional<bool> supports_notify_tag =
+        file_system->FindBoolKey(kPrefKeySupportsNotifyTag);
     absl::optional<int> opened_files_limit =
         file_system->FindIntKey(kPrefKeyOpenedFilesLimit);
 
@@ -169,11 +170,8 @@
                                                      &file_system_id) ||
          !file_system->GetStringWithoutPathExpansion(kPrefKeyDisplayName,
                                                      &display_name) ||
-         !file_system->GetBooleanWithoutPathExpansion(kPrefKeyWritable,
-                                                      &writable) ||
-         !file_system->GetBooleanWithoutPathExpansion(kPrefKeySupportsNotifyTag,
-                                                      &supports_notify_tag) ||
-         file_system_id.empty() || display_name.empty()) ||
+         !writable || !supports_notify_tag || file_system_id.empty() ||
+         display_name.empty()) ||
         // Optional fields.
         (opened_files_limit.has_value() && opened_files_limit.value() < 0)) {
       LOG(ERROR)
@@ -184,8 +182,8 @@
     MountOptions options;
     options.file_system_id = file_system_id;
     options.display_name = display_name;
-    options.writable = writable;
-    options.supports_notify_tag = supports_notify_tag;
+    options.writable = writable.value();
+    options.supports_notify_tag = supports_notify_tag.value();
     options.opened_files_limit = opened_files_limit.value_or(0);
 
     RestoredFileSystem restored_file_system;
@@ -202,16 +200,20 @@
         const base::Value* watcher_value = watchers->FindKey(it.key());
         DCHECK(watcher_value);
 
+        if (!watcher_value->GetAsDictionary(&watcher)) {
+          LOG(ERROR) << "Malformed watcher information in preferences.";
+          continue;
+        }
+
         std::string entry_path;
-        bool recursive = false;
+        absl::optional<bool> recursive =
+            watcher->FindBoolKey(kPrefKeyWatcherRecursive);
         std::string last_tag;
         const base::ListValue* persistent_origins = NULL;
 
-        if (!watcher_value->GetAsDictionary(&watcher) ||
-            !watcher->GetStringWithoutPathExpansion(kPrefKeyWatcherEntryPath,
+        if (!watcher->GetStringWithoutPathExpansion(kPrefKeyWatcherEntryPath,
                                                     &entry_path) ||
-            !watcher->GetBooleanWithoutPathExpansion(kPrefKeyWatcherRecursive,
-                                                     &recursive) ||
+            !recursive ||
             !watcher->GetStringWithoutPathExpansion(kPrefKeyWatcherLastTag,
                                                     &last_tag) ||
             !watcher->GetListWithoutPathExpansion(
@@ -226,7 +228,7 @@
         Watcher restored_watcher;
         restored_watcher.entry_path =
             base::FilePath::FromUTF8Unsafe(entry_path);
-        restored_watcher.recursive = recursive;
+        restored_watcher.recursive = recursive.value();
         restored_watcher.last_tag = last_tag;
         for (const auto& persistent_origin : persistent_origins->GetList()) {
           std::string origin;
@@ -239,7 +241,7 @@
           restored_watcher.subscribers[origin_as_gurl].persistent = true;
         }
         restored_file_system.watchers[WatcherKey(
-            base::FilePath::FromUTF8Unsafe(entry_path), recursive)] =
+            base::FilePath::FromUTF8Unsafe(entry_path), recursive.value())] =
             restored_watcher;
       }
     }
diff --git a/chrome/browser/ash/file_system_provider/registry_unittest.cc b/chrome/browser/ash/file_system_provider/registry_unittest.cc
index 0ee70ec1..0e239b2e 100644
--- a/chrome/browser/ash/file_system_provider/registry_unittest.cc
+++ b/chrome/browser/ash/file_system_provider/registry_unittest.cc
@@ -203,15 +203,14 @@
                                                          &display_name));
   EXPECT_EQ(kDisplayName, display_name);
 
-  bool writable = false;
-  EXPECT_TRUE(
-      file_system->GetBooleanWithoutPathExpansion(kPrefKeyWritable, &writable));
-  EXPECT_TRUE(writable);
+  absl::optional<bool> writable = file_system->FindBoolKey(kPrefKeyWritable);
+  EXPECT_TRUE(writable.has_value());
+  EXPECT_TRUE(writable.value());
 
-  bool supports_notify_tag = false;
-  EXPECT_TRUE(file_system->GetBooleanWithoutPathExpansion(
-      kPrefKeySupportsNotifyTag, &supports_notify_tag));
-  EXPECT_TRUE(supports_notify_tag);
+  absl::optional<bool> supports_notify_tag =
+      file_system->FindBoolKey(kPrefKeySupportsNotifyTag);
+  EXPECT_TRUE(supports_notify_tag.has_value());
+  EXPECT_TRUE(supports_notify_tag.value());
 
   absl::optional<int> opened_files_limit =
       file_system->FindIntKey(kPrefKeyOpenedFilesLimit);
@@ -231,10 +230,10 @@
                                                      &entry_path));
   EXPECT_EQ(fake_watcher_.entry_path.value(), entry_path);
 
-  bool recursive = false;
-  EXPECT_TRUE(watcher->GetBooleanWithoutPathExpansion(kPrefKeyWatcherRecursive,
-                                                      &recursive));
-  EXPECT_EQ(fake_watcher_.recursive, recursive);
+  absl::optional<bool> recursive =
+      watcher->FindBoolKey(kPrefKeyWatcherRecursive);
+  EXPECT_TRUE(recursive.has_value());
+  EXPECT_EQ(fake_watcher_.recursive, recursive.value());
 
   std::string last_tag;
   EXPECT_TRUE(watcher->GetStringWithoutPathExpansion(kPrefKeyWatcherLastTag,
diff --git a/chrome/browser/ash/login/signin/offline_signin_limiter.cc b/chrome/browser/ash/login/signin/offline_signin_limiter.cc
index 324ea6d0..a19c996 100644
--- a/chrome/browser/ash/login/signin/offline_signin_limiter.cc
+++ b/chrome/browser/ash/login/signin/offline_signin_limiter.cc
@@ -75,21 +75,21 @@
   // Start listening for pref changes.
   pref_change_registrar_.Init(prefs);
   pref_change_registrar_.Add(
-      prefs::kSAMLOfflineSigninTimeLimit,
-      base::BindRepeating(&OfflineSigninLimiter::UpdateLimit,
-                          base::Unretained(this)));
-  pref_change_registrar_.Add(
       prefs::kGaiaOfflineSigninTimeLimitDays,
       base::BindRepeating(&OfflineSigninLimiter::UpdateLimit,
                           base::Unretained(this)));
   pref_change_registrar_.Add(
-      prefs::kSamlLockScreenOfflineSigninTimeLimitDays,
-      base::BindRepeating(&OfflineSigninLimiter::UpdateLockScreenLimit,
+      prefs::kSAMLOfflineSigninTimeLimit,
+      base::BindRepeating(&OfflineSigninLimiter::UpdateLimit,
                           base::Unretained(this)));
   pref_change_registrar_.Add(
       prefs::kGaiaLockScreenOfflineSigninTimeLimitDays,
       base::BindRepeating(&OfflineSigninLimiter::UpdateLockScreenLimit,
                           base::Unretained(this)));
+  pref_change_registrar_.Add(
+      prefs::kSamlLockScreenOfflineSigninTimeLimitDays,
+      base::BindRepeating(&OfflineSigninLimiter::UpdateLockScreenLimit,
+                          base::Unretained(this)));
   // Start listening to power state.
   base::PowerMonitor::AddPowerSuspendObserver(this);
 
diff --git a/chrome/browser/ash/login/signin/offline_signin_limiter.h b/chrome/browser/ash/login/signin/offline_signin_limiter.h
index b411556..80f72b06 100644
--- a/chrome/browser/ash/login/signin/offline_signin_limiter.h
+++ b/chrome/browser/ash/login/signin/offline_signin_limiter.h
@@ -68,8 +68,8 @@
   // Convenience method to get the time limit for SAML and no-SAML flows
   // taking into consideration a possible override from the command line.
   // Returns nullopt if it is an invalid time.
-  absl::optional<base::TimeDelta> GetGaiaSamlTimeLimit();
   absl::optional<base::TimeDelta> GetGaiaNoSamlTimeLimit();
+  absl::optional<base::TimeDelta> GetGaiaSamlTimeLimit();
   absl::optional<base::TimeDelta> GetGaiaNoSamlLockScreenTimeLimit();
   absl::optional<base::TimeDelta> GetGaiaSamlLockScreenTimeLimit();
   absl::optional<base::TimeDelta> GetTimeLimitOverrideForTesting();
diff --git a/chrome/browser/ash/login/signin/offline_signin_limiter_unittest.cc b/chrome/browser/ash/login/signin/offline_signin_limiter_unittest.cc
index 3baec2b6..890f23d 100644
--- a/chrome/browser/ash/login/signin/offline_signin_limiter_unittest.cc
+++ b/chrome/browser/ash/login/signin/offline_signin_limiter_unittest.cc
@@ -151,6 +151,586 @@
   return &testing_local_state_;
 }
 
+TEST_F(OfflineSigninLimiterTest, NoGaiaDefaultLimit) {
+  AddGaiaUser();
+  PrefService* prefs = profile_->GetPrefs();
+
+  // Authenticate offline. Verify that the flag enforcing online login is not
+  // changed and the time of last login with SAML is not set.
+  CreateLimiter();
+  SetUpUserManager();
+  EXPECT_CALL(*user_manager_, SaveForceOnlineSignin(test_gaia_account_id_, _))
+      .Times(0);
+  limiter_->SignedIn(UserContext::AUTH_FLOW_OFFLINE);
+
+  const PrefService::Preference* pref =
+      prefs->FindPreference(prefs::kGaiaLastOnlineSignInTime);
+  ASSERT_TRUE(pref);
+  EXPECT_FALSE(pref->HasUserSetting());
+
+  // Verify that no timer is running.
+  EXPECT_FALSE(timer_->IsRunning());
+}
+
+TEST_F(OfflineSigninLimiterTest, NoGaiaNoLimit) {
+  AddGaiaUser();
+  PrefService* prefs = profile_->GetPrefs();
+
+  // Remove the time limit.
+  prefs->SetInteger(prefs::kGaiaOfflineSigninTimeLimitDays, -1);
+
+  // Authenticate offline. Verify that the flag enforcing online login is not
+  // changed and the time of last login with SAML is not set.
+  CreateLimiter();
+  SetUpUserManager();
+  EXPECT_CALL(*user_manager_, SaveForceOnlineSignin(test_gaia_account_id_, _))
+      .Times(0);
+  limiter_->SignedIn(UserContext::AUTH_FLOW_OFFLINE);
+
+  const PrefService::Preference* pref =
+      prefs->FindPreference(prefs::kGaiaLastOnlineSignInTime);
+  ASSERT_TRUE(pref);
+  EXPECT_FALSE(pref->HasUserSetting());
+
+  // Verify that no timer is running.
+  EXPECT_FALSE(timer_->IsRunning());
+}
+
+TEST_F(OfflineSigninLimiterTest, NoGaiaZeroLimitWhenOffline) {
+  AddSAMLUser();
+  PrefService* prefs = profile_->GetPrefs();
+
+  // Set a zero time limit.
+  prefs->SetInteger(prefs::kGaiaOfflineSigninTimeLimitDays, 0);
+
+  // Set the time of last login with SAML.
+  prefs->SetTime(prefs::kGaiaLastOnlineSignInTime,
+                 task_environment_.GetMockClock()->Now());
+  // Remove time limit.
+  prefs->SetInteger(prefs::kSAMLOfflineSigninTimeLimit, -1);
+
+  // Authenticate against Gaia with SAML. Verify that the flag enforcing
+  // online login and the time of last login without SAML are cleared.
+  CreateLimiter();
+  EXPECT_CALL(*user_manager_,
+              SaveForceOnlineSignin(test_saml_account_id_, false))
+      .Times(1);
+  EXPECT_CALL(*user_manager_,
+              SaveForceOnlineSignin(test_saml_account_id_, true))
+      .Times(0);
+  limiter_->SignedIn(UserContext::AUTH_FLOW_GAIA_WITH_SAML);
+
+  const PrefService::Preference* pref =
+      prefs->FindPreference(prefs::kGaiaLastOnlineSignInTime);
+  ASSERT_TRUE(pref);
+  EXPECT_FALSE(pref->HasUserSetting());
+
+  // Verify that no timer is running.
+  EXPECT_FALSE(timer_->IsRunning());
+
+  // Log out.
+  DestroyLimiter();
+
+  // Advance clock by 1 hour.
+  task_environment_.FastForwardBy(base::TimeDelta::FromHours(1));
+
+  // Authenticate offline. Verify that the flag enforcing online login is not
+  // changed.
+  CreateLimiter();
+  SetUpUserManager();
+  EXPECT_CALL(*user_manager_, SaveForceOnlineSignin(test_saml_account_id_, _))
+      .Times(0);
+  limiter_->SignedIn(UserContext::AUTH_FLOW_OFFLINE);
+
+  // Verify that no timer is running.
+  EXPECT_FALSE(timer_->IsRunning());
+}
+
+TEST_F(OfflineSigninLimiterTest, NoGaiaSetLimitWhileLoggedIn) {
+  AddSAMLUser();
+  PrefService* prefs = profile_->GetPrefs();
+
+  // Remove the time limit.
+  prefs->SetInteger(prefs::kGaiaOfflineSigninTimeLimitDays, -1);
+
+  // Set the time of last login without SAML.
+  prefs->SetTime(prefs::kGaiaLastOnlineSignInTime,
+                 task_environment_.GetMockClock()->Now());
+
+  // Authenticate against Gaia with SAML. Verify that the flag enforcing
+  // online login and the time of last login without SAML are cleared.
+  CreateLimiter();
+  EXPECT_CALL(*user_manager_,
+              SaveForceOnlineSignin(test_saml_account_id_, false))
+      .Times(1);
+  EXPECT_CALL(*user_manager_,
+              SaveForceOnlineSignin(test_saml_account_id_, true))
+      .Times(0);
+  limiter_->SignedIn(UserContext::AUTH_FLOW_GAIA_WITH_SAML);
+
+  const PrefService::Preference* pref =
+      prefs->FindPreference(prefs::kGaiaLastOnlineSignInTime);
+  ASSERT_TRUE(pref);
+  EXPECT_FALSE(pref->HasUserSetting());
+
+  // Verify that timer is running due to Gaia log in with SAML.
+  EXPECT_TRUE(timer_->IsRunning());
+
+  // Remove the time limit from SAML.
+  prefs->SetInteger(prefs::kSAMLOfflineSigninTimeLimit, -1);
+
+  // Verify that no timer is running.
+  EXPECT_FALSE(timer_->IsRunning());
+
+  // Set a zero time limit.
+  prefs->SetInteger(prefs::kGaiaOfflineSigninTimeLimitDays, 0);
+
+  // Verify that no timer is running.
+  EXPECT_FALSE(timer_->IsRunning());
+}
+
+TEST_F(OfflineSigninLimiterTest, GaiaDefaultLimit) {
+  AddGaiaUser();
+  PrefService* prefs = profile_->GetPrefs();
+
+  // Authenticate against Gaia without SAML. Verify that the flag enforcing
+  // online login is cleared and the time of last login without SAML is set.
+  CreateLimiter();
+  EXPECT_CALL(*user_manager_,
+              SaveForceOnlineSignin(test_gaia_account_id_, false))
+      .Times(1);
+  EXPECT_CALL(*user_manager_,
+              SaveForceOnlineSignin(test_gaia_account_id_, true))
+      .Times(0);
+  limiter_->SignedIn(UserContext::AUTH_FLOW_GAIA_WITHOUT_SAML);
+
+  base::Time last_gaia_signin_time =
+      prefs->GetTime(prefs::kGaiaLastOnlineSignInTime);
+  EXPECT_EQ(task_environment_.GetMockClock()->Now(), last_gaia_signin_time);
+
+  // Verify that no timer is running.
+  EXPECT_FALSE(timer_->IsRunning());
+
+  // Log out. Verify that the flag enforcing online login is not set.
+  DestroyLimiter();
+
+  // Advance time by an hour.
+  task_environment_.FastForwardBy(base::TimeDelta::FromHours(1));
+
+  // Authenticate against Gaia without SAML. Verify that the flag enforcing
+  // online login is cleared and the time of last login without SAML is updated.
+  CreateLimiter();
+  Mock::VerifyAndClearExpectations(user_manager_);
+  SetUpUserManager();
+  EXPECT_CALL(*user_manager_,
+              SaveForceOnlineSignin(test_gaia_account_id_, false))
+      .Times(1);
+  EXPECT_CALL(*user_manager_,
+              SaveForceOnlineSignin(test_gaia_account_id_, true))
+      .Times(0);
+  limiter_->SignedIn(UserContext::AUTH_FLOW_GAIA_WITHOUT_SAML);
+
+  last_gaia_signin_time = prefs->GetTime(prefs::kGaiaLastOnlineSignInTime);
+  EXPECT_EQ(task_environment_.GetMockClock()->Now(), last_gaia_signin_time);
+
+  // Verify that no timer is running.
+  EXPECT_FALSE(timer_->IsRunning());
+
+  // Log out. Verify that the flag enforcing online login is not set.
+  DestroyLimiter();
+
+  // Advance time by an hour.
+  const base::Time gaia_signin_time = task_environment_.GetMockClock()->Now();
+  task_environment_.FastForwardBy(base::TimeDelta::FromHours(1));
+
+  // Authenticate offline. Verify that the flag enforcing online login and the
+  // time of last login without SAML are not changed.
+  CreateLimiter();
+  Mock::VerifyAndClearExpectations(user_manager_);
+  SetUpUserManager();
+  EXPECT_CALL(*user_manager_, SaveForceOnlineSignin(test_gaia_account_id_, _))
+      .Times(0);
+  limiter_->SignedIn(UserContext::AUTH_FLOW_OFFLINE);
+
+  last_gaia_signin_time = prefs->GetTime(prefs::kGaiaLastOnlineSignInTime);
+  EXPECT_EQ(gaia_signin_time, last_gaia_signin_time);
+
+  // Verify that no the timer is running.
+  EXPECT_FALSE(timer_->IsRunning());
+}
+
+TEST_F(OfflineSigninLimiterTest, GaiaNoLimit) {
+  AddGaiaUser();
+  PrefService* prefs = profile_->GetPrefs();
+
+  // Remove the time limit.
+  prefs->SetInteger(prefs::kGaiaOfflineSigninTimeLimitDays, -1);
+
+  // Authenticate against Gaia without SAML. Verify that the flag enforcing
+  // online login is cleared and the time of last login without SAML is set.
+  CreateLimiter();
+  EXPECT_CALL(*user_manager_,
+              SaveForceOnlineSignin(test_gaia_account_id_, false))
+      .Times(1);
+  EXPECT_CALL(*user_manager_,
+              SaveForceOnlineSignin(test_gaia_account_id_, true))
+      .Times(0);
+  limiter_->SignedIn(UserContext::AUTH_FLOW_GAIA_WITHOUT_SAML);
+
+  base::Time last_gaia_signin_time =
+      prefs->GetTime(prefs::kGaiaLastOnlineSignInTime);
+  EXPECT_EQ(task_environment_.GetMockClock()->Now(), last_gaia_signin_time);
+
+  // Verify that no timer is running.
+  EXPECT_FALSE(timer_->IsRunning());
+
+  // Log out. Verify that the flag enforcing online login is not set.
+  DestroyLimiter();
+
+  // Advance time by an hour.
+  task_environment_.FastForwardBy(base::TimeDelta::FromHours(1));
+
+  // Authenticate against Gaia without SAML. Verify that the flag enforcing
+  // online login is cleared and the time of last login without SAML is updated.
+  CreateLimiter();
+  Mock::VerifyAndClearExpectations(user_manager_);
+  SetUpUserManager();
+  EXPECT_CALL(*user_manager_,
+              SaveForceOnlineSignin(test_gaia_account_id_, false))
+      .Times(1);
+  EXPECT_CALL(*user_manager_,
+              SaveForceOnlineSignin(test_gaia_account_id_, true))
+      .Times(0);
+  limiter_->SignedIn(UserContext::AUTH_FLOW_GAIA_WITHOUT_SAML);
+
+  last_gaia_signin_time = prefs->GetTime(prefs::kGaiaLastOnlineSignInTime);
+  EXPECT_EQ(task_environment_.GetMockClock()->Now(), last_gaia_signin_time);
+
+  // Verify that no timer is running.
+  EXPECT_FALSE(timer_->IsRunning());
+
+  // Log out. Verify that the flag enforcing online login is not set.
+  DestroyLimiter();
+
+  // Advance time by an hour.
+  const base::Time gaia_signin_time = task_environment_.GetMockClock()->Now();
+  task_environment_.FastForwardBy(base::TimeDelta::FromHours(1));
+
+  // Authenticate offline. Verify that the flag enforcing online login and the
+  // time of last login without SAML are not changed.
+  CreateLimiter();
+  Mock::VerifyAndClearExpectations(user_manager_);
+  SetUpUserManager();
+  EXPECT_CALL(*user_manager_, SaveForceOnlineSignin(test_gaia_account_id_, _))
+      .Times(0);
+  limiter_->SignedIn(UserContext::AUTH_FLOW_OFFLINE);
+
+  last_gaia_signin_time = prefs->GetTime(prefs::kGaiaLastOnlineSignInTime);
+  EXPECT_EQ(gaia_signin_time, last_gaia_signin_time);
+
+  // Verify that no timer is running.
+  EXPECT_FALSE(timer_->IsRunning());
+}
+
+TEST_F(OfflineSigninLimiterTest, GaiaZeroLimit) {
+  AddGaiaUser();
+  PrefService* prefs = profile_->GetPrefs();
+
+  // Set a zero time limit.
+  prefs->SetInteger(prefs::kGaiaOfflineSigninTimeLimitDays, 0);
+
+  // Authenticate against Gaia without SAML. Verify that the flag enforcing
+  // online login is cleared and then set immediately. Also verify that the time
+  // of last login without SAML is set.
+  CreateLimiter();
+  Sequence sequence;
+  EXPECT_CALL(*user_manager_,
+              SaveForceOnlineSignin(test_gaia_account_id_, false))
+      .Times(1)
+      .InSequence(sequence);
+  EXPECT_CALL(*user_manager_,
+              SaveForceOnlineSignin(test_gaia_account_id_, true))
+      .Times(1)
+      .InSequence(sequence);
+  limiter_->SignedIn(UserContext::AUTH_FLOW_GAIA_WITHOUT_SAML);
+
+  const base::Time last_gaia_signin_time =
+      prefs->GetTime(prefs::kGaiaLastOnlineSignInTime);
+  EXPECT_EQ(task_environment_.GetMockClock()->Now(), last_gaia_signin_time);
+}
+
+TEST_F(OfflineSigninLimiterTest, GaiaSetLimitWhileLoggedIn) {
+  AddGaiaUser();
+  PrefService* prefs = profile_->GetPrefs();
+
+  // Remove the time limit.
+  prefs->SetInteger(prefs::kGaiaOfflineSigninTimeLimitDays, -1);
+
+  // Authenticate against Gaia without SAML. Verify that the flag enforcing
+  // online login is cleared and the time of last login without SAML is set.
+  CreateLimiter();
+  EXPECT_CALL(*user_manager_,
+              SaveForceOnlineSignin(test_gaia_account_id_, false))
+      .Times(1);
+  EXPECT_CALL(*user_manager_,
+              SaveForceOnlineSignin(test_gaia_account_id_, true))
+      .Times(0);
+  limiter_->SignedIn(UserContext::AUTH_FLOW_GAIA_WITHOUT_SAML);
+
+  const base::Time last_gaia_signin_time =
+      prefs->GetTime(prefs::kGaiaLastOnlineSignInTime);
+  EXPECT_EQ(task_environment_.GetMockClock()->Now(), last_gaia_signin_time);
+
+  // Verify that no timer is running.
+  EXPECT_FALSE(timer_->IsRunning());
+
+  // Set a zero time limit. Verify that the flag enforcing online login is set.
+  Mock::VerifyAndClearExpectations(user_manager_);
+  SetUpUserManager();
+  EXPECT_CALL(*user_manager_,
+              SaveForceOnlineSignin(test_gaia_account_id_, false))
+      .Times(0);
+  EXPECT_CALL(*user_manager_,
+              SaveForceOnlineSignin(test_gaia_account_id_, true))
+      .Times(1);
+  prefs->SetInteger(prefs::kGaiaOfflineSigninTimeLimitDays, 0);
+}
+
+TEST_F(OfflineSigninLimiterTest, GaiaRemoveLimit) {
+  AddGaiaUser();
+  PrefService* prefs = profile_->GetPrefs();
+
+  // Set the time of last Gaia login without SAML and set limit.
+  prefs->SetTime(prefs::kGaiaLastOnlineSignInTime,
+                 task_environment_.GetMockClock()->Now());
+  prefs->SetInteger(prefs::kGaiaOfflineSigninTimeLimitDays, 7);  // 1 week.
+
+  // Authenticate against Gaia without SAML. Verify that the flag enforcing
+  // online login is cleared and the time of last login without SAML is set.
+  CreateLimiter();
+  EXPECT_CALL(*user_manager_,
+              SaveForceOnlineSignin(test_gaia_account_id_, false))
+      .Times(1);
+  EXPECT_CALL(*user_manager_,
+              SaveForceOnlineSignin(test_gaia_account_id_, true))
+      .Times(0);
+  limiter_->SignedIn(UserContext::AUTH_FLOW_GAIA_WITHOUT_SAML);
+
+  const base::Time last_gaia_signin_time =
+      prefs->GetTime(prefs::kGaiaLastOnlineSignInTime);
+  EXPECT_EQ(task_environment_.GetMockClock()->Now(), last_gaia_signin_time);
+
+  // Verify that the timer is running.
+  EXPECT_TRUE(timer_->IsRunning());
+
+  // Remove the time limit.
+  prefs->SetInteger(prefs::kGaiaOfflineSigninTimeLimitDays, -1);
+
+  // Verify that the flag enforcing online login is not changed.
+  Mock::VerifyAndClearExpectations(user_manager_);
+  SetUpUserManager();
+  EXPECT_CALL(*user_manager_, SaveForceOnlineSignin(test_gaia_account_id_, _))
+      .Times(0);
+}
+
+TEST_F(OfflineSigninLimiterTest, GaiaLogInWithExpiredLimit) {
+  AddGaiaUser();
+  PrefService* prefs = profile_->GetPrefs();
+
+  // Set the time of last Gaia login without SAML and set limit.
+  prefs->SetTime(prefs::kGaiaLastOnlineSignInTime,
+                 task_environment_.GetMockClock()->Now());
+  prefs->SetInteger(prefs::kGaiaOfflineSigninTimeLimitDays, 7);  // 1 week.
+
+  // Advance time by four weeks.
+  task_environment_.FastForwardBy(base::TimeDelta::FromDays(28));  // 4 weeks.
+
+  // Authenticate against Gaia without SAML. Verify that the flag enforcing
+  // online login is cleared and the time of last login without SAML is updated.
+  CreateLimiter();
+  EXPECT_CALL(*user_manager_,
+              SaveForceOnlineSignin(test_gaia_account_id_, false))
+      .Times(1);
+  EXPECT_CALL(*user_manager_,
+              SaveForceOnlineSignin(test_gaia_account_id_, true))
+      .Times(0);
+  limiter_->SignedIn(UserContext::AUTH_FLOW_GAIA_WITHOUT_SAML);
+
+  const base::Time last_gaia_signin_time =
+      prefs->GetTime(prefs::kGaiaLastOnlineSignInTime);
+  EXPECT_EQ(task_environment_.GetMockClock()->Now(), last_gaia_signin_time);
+
+  // Verify that the timer is running.
+  EXPECT_TRUE(timer_->IsRunning());
+}
+
+TEST_F(OfflineSigninLimiterTest, GaiaLogInOfflineWithExpiredLimit) {
+  AddGaiaUser();
+  PrefService* prefs = profile_->GetPrefs();
+
+  // Set the time of last Gaia login without SAML and set limit.
+  prefs->SetTime(prefs::kGaiaLastOnlineSignInTime,
+                 task_environment_.GetMockClock()->Now());
+  prefs->SetInteger(prefs::kGaiaOfflineSigninTimeLimitDays, 7);  // 1 week.
+
+  // Advance time by four weeks.
+  const base::Time gaia_signin_time = task_environment_.GetMockClock()->Now();
+  task_environment_.FastForwardBy(base::TimeDelta::FromDays(28));  // 4 weeks.
+
+  // Authenticate offline. Verify that the flag enforcing online login is
+  // set and the time of last login without SAML is not changed.
+  CreateLimiter();
+  EXPECT_CALL(*user_manager_,
+              SaveForceOnlineSignin(test_gaia_account_id_, false))
+      .Times(0);
+  EXPECT_CALL(*user_manager_,
+              SaveForceOnlineSignin(test_gaia_account_id_, true))
+      .Times(1);
+  limiter_->SignedIn(UserContext::AUTH_FLOW_OFFLINE);
+  InSessionPasswordSyncManager* password_sync_manager =
+      InSessionPasswordSyncManagerFactory::GetForProfile(profile_.get());
+  EXPECT_FALSE(password_sync_manager->IsLockReauthEnabled());
+
+  const base::Time last_gaia_signin_time =
+      prefs->GetTime(prefs::kGaiaLastOnlineSignInTime);
+  EXPECT_EQ(gaia_signin_time, last_gaia_signin_time);
+
+  // Verify that no timer is running.
+  EXPECT_FALSE(timer_->IsRunning());
+}
+
+TEST_F(OfflineSigninLimiterTest, GaiaLimitExpiredWhileSuspended) {
+  AddGaiaUser();
+  PrefService* prefs = profile_->GetPrefs();
+
+  // Set the time of Gaia last login without SAML and set time limit.
+  prefs->SetTime(prefs::kGaiaLastOnlineSignInTime,
+                 task_environment_.GetMockClock()->Now());
+  prefs->SetInteger(prefs::kGaiaOfflineSigninTimeLimitDays, 7);  // 1 week.
+
+  // Authenticate against Gaia without SAML. Verify that the flag enforcing
+  // online login is cleared and the time of last login without SAML is set.
+  CreateLimiter();
+  EXPECT_CALL(*user_manager_,
+              SaveForceOnlineSignin(test_gaia_account_id_, false))
+      .Times(1);
+  EXPECT_CALL(*user_manager_,
+              SaveForceOnlineSignin(test_gaia_account_id_, true))
+      .Times(0);
+  limiter_->SignedIn(UserContext::AUTH_FLOW_GAIA_WITHOUT_SAML);
+
+  // Suspend for 4 weeks.
+  test_power_monitor_source_.Suspend();
+  task_environment_.AdvanceClock(base::TimeDelta::FromDays(28));  // 4 weeks.
+
+  // Resume power. Verify that the flag enforcing online login is set.
+  Mock::VerifyAndClearExpectations(user_manager_);
+  EXPECT_CALL(*user_manager_,
+              SaveForceOnlineSignin(test_gaia_account_id_, false))
+      .Times(0);
+  EXPECT_CALL(*user_manager_,
+              SaveForceOnlineSignin(test_gaia_account_id_, true))
+      .Times(1);
+  test_power_monitor_source_.Resume();
+  task_environment_.RunUntilIdle();
+}
+
+TEST_F(OfflineSigninLimiterTest, GaiaLogInOfflineWithOnLockReauth) {
+  AddGaiaUser();
+  PrefService* prefs = profile_->GetPrefs();
+
+  // Set the time of last Gaia login without SAML and time limit.
+  prefs->SetTime(prefs::kGaiaLastOnlineSignInTime,
+                 task_environment_.GetMockClock()->Now());
+  prefs->SetInteger(prefs::kGaiaOfflineSigninTimeLimitDays, 7);  // 1 week.
+
+  // Enable re-authentication on the lock screen.
+  prefs->SetBoolean(prefs::kLockScreenReauthenticationEnabled, true);
+
+  // Advance time by four weeks.
+  task_environment_.FastForwardBy(base::TimeDelta::FromDays(28));  // 4 weeks.
+
+  // Authenticate offline and check if InSessionPasswordSyncManager is created.
+  CreateLimiter();
+  limiter_->SignedIn(UserContext::AUTH_FLOW_OFFLINE);
+  InSessionPasswordSyncManager* password_sync_manager =
+      InSessionPasswordSyncManagerFactory::GetForProfile(profile_.get());
+  // Verify that we enter InSessionPasswordSyncManager::ForceReauthOnLockScreen.
+  EXPECT_TRUE(password_sync_manager->IsLockReauthEnabled());
+  // After changing the re-auth flag timer should be stopped.
+  EXPECT_FALSE(timer_->IsRunning());
+}
+
+TEST_F(OfflineSigninLimiterTest, GaiaNoLastOnlineSigninWithLimit) {
+  AddGaiaUser();
+  PrefService* prefs = profile_->GetPrefs();
+
+  // Set the time limit.
+  prefs->SetInteger(prefs::kGaiaOfflineSigninTimeLimitDays, 7);  // 1 week.
+
+  // Authenticate offline. Verify that the flag enforcing online is set due no
+  // `last_gaia_signin_time` value.
+  CreateLimiter();
+  EXPECT_CALL(*user_manager_,
+              SaveForceOnlineSignin(test_gaia_account_id_, false))
+      .Times(0);
+  EXPECT_CALL(*user_manager_,
+              SaveForceOnlineSignin(test_gaia_account_id_, true))
+      .Times(1);
+  limiter_->SignedIn(UserContext::AUTH_FLOW_OFFLINE);
+
+  base::Time last_gaia_signin_time =
+      prefs->GetTime(prefs::kGaiaLastOnlineSignInTime);
+  EXPECT_TRUE(last_gaia_signin_time.is_null());
+
+  // Verify that no timer is running.
+  EXPECT_FALSE(timer_->IsRunning());
+
+  // Log out.
+  DestroyLimiter();
+
+  // Authenticate against Gaia without SAML. Verify that the flag enforcing
+  // online login is cleared and the time of last login without SAML is set.
+  CreateLimiter();
+  Mock::VerifyAndClearExpectations(user_manager_);
+  SetUpUserManager();
+  EXPECT_CALL(*user_manager_,
+              SaveForceOnlineSignin(test_gaia_account_id_, false))
+      .Times(1);
+  EXPECT_CALL(*user_manager_,
+              SaveForceOnlineSignin(test_gaia_account_id_, true))
+      .Times(0);
+  limiter_->SignedIn(UserContext::AUTH_FLOW_GAIA_WITHOUT_SAML);
+
+  last_gaia_signin_time = prefs->GetTime(prefs::kGaiaLastOnlineSignInTime);
+  EXPECT_EQ(task_environment_.GetMockClock()->Now(), last_gaia_signin_time);
+
+  // Verify that the timer is running.
+  EXPECT_TRUE(timer_->IsRunning());
+
+  // Log out.
+  DestroyLimiter();
+
+  // Advance time by an hour.
+  const base::Time gaia_signin_time = task_environment_.GetMockClock()->Now();
+  task_environment_.FastForwardBy(base::TimeDelta::FromHours(1));
+
+  // Authenticate offline. Verify that the flag enforcing online login and the
+  // time of last login without SAML are not changed.
+  CreateLimiter();
+  Mock::VerifyAndClearExpectations(user_manager_);
+  SetUpUserManager();
+  EXPECT_CALL(*user_manager_, SaveForceOnlineSignin(test_gaia_account_id_, _))
+      .Times(0);
+  limiter_->SignedIn(UserContext::AUTH_FLOW_OFFLINE);
+
+  last_gaia_signin_time = prefs->GetTime(prefs::kGaiaLastOnlineSignInTime);
+  EXPECT_EQ(gaia_signin_time, last_gaia_signin_time);
+
+  // Verify that the timer is running.
+  EXPECT_TRUE(timer_->IsRunning());
+}
+
 TEST_F(OfflineSigninLimiterTest, NoSAMLDefaultLimit) {
   AddGaiaUser();
   PrefService* prefs = profile_->GetPrefs();
@@ -776,584 +1356,4 @@
   EXPECT_FALSE(timer_->IsRunning());
 }
 
-TEST_F(OfflineSigninLimiterTest, NoGaiaDefaultLimit) {
-  AddGaiaUser();
-  PrefService* prefs = profile_->GetPrefs();
-
-  // Authenticate offline. Verify that the flag enforcing online login is not
-  // changed and the time of last login with SAML is not set.
-  CreateLimiter();
-  SetUpUserManager();
-  EXPECT_CALL(*user_manager_, SaveForceOnlineSignin(test_gaia_account_id_, _))
-      .Times(0);
-  limiter_->SignedIn(UserContext::AUTH_FLOW_OFFLINE);
-
-  const PrefService::Preference* pref =
-      prefs->FindPreference(prefs::kGaiaLastOnlineSignInTime);
-  ASSERT_TRUE(pref);
-  EXPECT_FALSE(pref->HasUserSetting());
-
-  // Verify that no timer is running.
-  EXPECT_FALSE(timer_->IsRunning());
-}
-
-TEST_F(OfflineSigninLimiterTest, NoGaiaNoLimit) {
-  AddGaiaUser();
-  PrefService* prefs = profile_->GetPrefs();
-
-  // Remove the time limit.
-  prefs->SetInteger(prefs::kGaiaOfflineSigninTimeLimitDays, -1);
-
-  // Authenticate offline. Verify that the flag enforcing online login is not
-  // changed and the time of last login with SAML is not set.
-  CreateLimiter();
-  SetUpUserManager();
-  EXPECT_CALL(*user_manager_, SaveForceOnlineSignin(test_gaia_account_id_, _))
-      .Times(0);
-  limiter_->SignedIn(UserContext::AUTH_FLOW_OFFLINE);
-
-  const PrefService::Preference* pref =
-      prefs->FindPreference(prefs::kGaiaLastOnlineSignInTime);
-  ASSERT_TRUE(pref);
-  EXPECT_FALSE(pref->HasUserSetting());
-
-  // Verify that no timer is running.
-  EXPECT_FALSE(timer_->IsRunning());
-}
-
-TEST_F(OfflineSigninLimiterTest, NoGaiaZeroLimitWhenOffline) {
-  AddSAMLUser();
-  PrefService* prefs = profile_->GetPrefs();
-
-  // Set a zero time limit.
-  prefs->SetInteger(prefs::kGaiaOfflineSigninTimeLimitDays, 0);
-
-  // Set the time of last login with SAML.
-  prefs->SetTime(prefs::kGaiaLastOnlineSignInTime,
-                 task_environment_.GetMockClock()->Now());
-  // Remove time limit.
-  prefs->SetInteger(prefs::kSAMLOfflineSigninTimeLimit, -1);
-
-  // Authenticate against Gaia with SAML. Verify that the flag enforcing
-  // online login and the time of last login without SAML are cleared.
-  CreateLimiter();
-  EXPECT_CALL(*user_manager_,
-              SaveForceOnlineSignin(test_saml_account_id_, false))
-      .Times(1);
-  EXPECT_CALL(*user_manager_,
-              SaveForceOnlineSignin(test_saml_account_id_, true))
-      .Times(0);
-  limiter_->SignedIn(UserContext::AUTH_FLOW_GAIA_WITH_SAML);
-
-  const PrefService::Preference* pref =
-      prefs->FindPreference(prefs::kGaiaLastOnlineSignInTime);
-  ASSERT_TRUE(pref);
-  EXPECT_FALSE(pref->HasUserSetting());
-
-  // Verify that no timer is running.
-  EXPECT_FALSE(timer_->IsRunning());
-
-  // Log out.
-  DestroyLimiter();
-
-  // Advance clock by 1 hour.
-  task_environment_.FastForwardBy(base::TimeDelta::FromHours(1));
-
-  // Authenticate offline. Verify that the flag enforcing online login is not
-  // changed.
-  CreateLimiter();
-  SetUpUserManager();
-  EXPECT_CALL(*user_manager_, SaveForceOnlineSignin(test_saml_account_id_, _))
-      .Times(0);
-  limiter_->SignedIn(UserContext::AUTH_FLOW_OFFLINE);
-
-  // Verify that no timer is running.
-  EXPECT_FALSE(timer_->IsRunning());
-}
-
-TEST_F(OfflineSigninLimiterTest, NoGaiaSetLimitWhileLoggedIn) {
-  AddSAMLUser();
-  PrefService* prefs = profile_->GetPrefs();
-
-  // Remove the time limit.
-  prefs->SetInteger(prefs::kGaiaOfflineSigninTimeLimitDays, -1);
-
-  // Set the time of last login without SAML.
-  prefs->SetTime(prefs::kGaiaLastOnlineSignInTime,
-                 task_environment_.GetMockClock()->Now());
-
-  // Authenticate against Gaia with SAML. Verify that the flag enforcing
-  // online login and the time of last login without SAML are cleared.
-  CreateLimiter();
-  EXPECT_CALL(*user_manager_,
-              SaveForceOnlineSignin(test_saml_account_id_, false))
-      .Times(1);
-  EXPECT_CALL(*user_manager_,
-              SaveForceOnlineSignin(test_saml_account_id_, true))
-      .Times(0);
-  limiter_->SignedIn(UserContext::AUTH_FLOW_GAIA_WITH_SAML);
-
-  const PrefService::Preference* pref =
-      prefs->FindPreference(prefs::kGaiaLastOnlineSignInTime);
-  ASSERT_TRUE(pref);
-  EXPECT_FALSE(pref->HasUserSetting());
-
-  // Verify that timer is running due to Gaia log in with SAML.
-  EXPECT_TRUE(timer_->IsRunning());
-
-  // Remove the time limit from SAML.
-  prefs->SetInteger(prefs::kSAMLOfflineSigninTimeLimit, -1);
-
-  // Verify that no timer is running.
-  EXPECT_FALSE(timer_->IsRunning());
-
-  // Set a zero time limit.
-  prefs->SetInteger(prefs::kGaiaOfflineSigninTimeLimitDays, 0);
-
-  // Verify that no timer is running.
-  EXPECT_FALSE(timer_->IsRunning());
-}
-
-TEST_F(OfflineSigninLimiterTest, GaiaDefaultLimit) {
-  AddGaiaUser();
-  PrefService* prefs = profile_->GetPrefs();
-
-  // Authenticate against Gaia without SAML. Verify that the flag enforcing
-  // online login is cleared and the time of last login without SAML is set.
-  CreateLimiter();
-  EXPECT_CALL(*user_manager_,
-              SaveForceOnlineSignin(test_gaia_account_id_, false))
-      .Times(1);
-  EXPECT_CALL(*user_manager_,
-              SaveForceOnlineSignin(test_gaia_account_id_, true))
-      .Times(0);
-  limiter_->SignedIn(UserContext::AUTH_FLOW_GAIA_WITHOUT_SAML);
-
-  base::Time last_gaia_signin_time =
-      prefs->GetTime(prefs::kGaiaLastOnlineSignInTime);
-  EXPECT_EQ(task_environment_.GetMockClock()->Now(), last_gaia_signin_time);
-
-  // Verify that no timer is running.
-  EXPECT_FALSE(timer_->IsRunning());
-
-  // Log out. Verify that the flag enforcing online login is not set.
-  DestroyLimiter();
-
-  // Advance time by an hour.
-  task_environment_.FastForwardBy(base::TimeDelta::FromHours(1));
-
-  // Authenticate against Gaia without SAML. Verify that the flag enforcing
-  // online login is cleared and the time of last login without SAML is updated.
-  CreateLimiter();
-  Mock::VerifyAndClearExpectations(user_manager_);
-  SetUpUserManager();
-  EXPECT_CALL(*user_manager_,
-              SaveForceOnlineSignin(test_gaia_account_id_, false))
-      .Times(1);
-  EXPECT_CALL(*user_manager_,
-              SaveForceOnlineSignin(test_gaia_account_id_, true))
-      .Times(0);
-  limiter_->SignedIn(UserContext::AUTH_FLOW_GAIA_WITHOUT_SAML);
-
-  last_gaia_signin_time = prefs->GetTime(prefs::kGaiaLastOnlineSignInTime);
-  EXPECT_EQ(task_environment_.GetMockClock()->Now(), last_gaia_signin_time);
-
-  // Verify that no timer is running.
-  EXPECT_FALSE(timer_->IsRunning());
-
-  // Log out. Verify that the flag enforcing online login is not set.
-  DestroyLimiter();
-
-  // Advance time by an hour.
-  const base::Time gaia_signin_time = task_environment_.GetMockClock()->Now();
-  task_environment_.FastForwardBy(base::TimeDelta::FromHours(1));
-
-  // Authenticate offline. Verify that the flag enforcing online login and the
-  // time of last login without SAML are not changed.
-  CreateLimiter();
-  Mock::VerifyAndClearExpectations(user_manager_);
-  SetUpUserManager();
-  EXPECT_CALL(*user_manager_, SaveForceOnlineSignin(test_gaia_account_id_, _))
-      .Times(0);
-  limiter_->SignedIn(UserContext::AUTH_FLOW_OFFLINE);
-
-  last_gaia_signin_time = prefs->GetTime(prefs::kGaiaLastOnlineSignInTime);
-  EXPECT_EQ(gaia_signin_time, last_gaia_signin_time);
-
-  // Verify that no the timer is running.
-  EXPECT_FALSE(timer_->IsRunning());
-}
-
-TEST_F(OfflineSigninLimiterTest, GaiaNoLimit) {
-  AddGaiaUser();
-  PrefService* prefs = profile_->GetPrefs();
-
-  // Remove the time limit.
-  prefs->SetInteger(prefs::kGaiaOfflineSigninTimeLimitDays, -1);
-
-  // Authenticate against Gaia without SAML. Verify that the flag enforcing
-  // online login is cleared and the time of last login without SAML is set.
-  CreateLimiter();
-  EXPECT_CALL(*user_manager_,
-              SaveForceOnlineSignin(test_gaia_account_id_, false))
-      .Times(1);
-  EXPECT_CALL(*user_manager_,
-              SaveForceOnlineSignin(test_gaia_account_id_, true))
-      .Times(0);
-  limiter_->SignedIn(UserContext::AUTH_FLOW_GAIA_WITHOUT_SAML);
-
-  base::Time last_gaia_signin_time =
-      prefs->GetTime(prefs::kGaiaLastOnlineSignInTime);
-  EXPECT_EQ(task_environment_.GetMockClock()->Now(), last_gaia_signin_time);
-
-  // Verify that no timer is running.
-  EXPECT_FALSE(timer_->IsRunning());
-
-  // Log out. Verify that the flag enforcing online login is not set.
-  DestroyLimiter();
-
-  // Advance time by an hour.
-  task_environment_.FastForwardBy(base::TimeDelta::FromHours(1));
-
-  // Authenticate against Gaia without SAML. Verify that the flag enforcing
-  // online login is cleared and the time of last login without SAML is updated.
-  CreateLimiter();
-  Mock::VerifyAndClearExpectations(user_manager_);
-  SetUpUserManager();
-  EXPECT_CALL(*user_manager_,
-              SaveForceOnlineSignin(test_gaia_account_id_, false))
-      .Times(1);
-  EXPECT_CALL(*user_manager_,
-              SaveForceOnlineSignin(test_gaia_account_id_, true))
-      .Times(0);
-  limiter_->SignedIn(UserContext::AUTH_FLOW_GAIA_WITHOUT_SAML);
-
-  last_gaia_signin_time = prefs->GetTime(prefs::kGaiaLastOnlineSignInTime);
-  EXPECT_EQ(task_environment_.GetMockClock()->Now(), last_gaia_signin_time);
-
-  // Verify that no timer is running.
-  EXPECT_FALSE(timer_->IsRunning());
-
-  // Log out. Verify that the flag enforcing online login is not set.
-  DestroyLimiter();
-
-  // Advance time by an hour.
-  const base::Time gaia_signin_time = task_environment_.GetMockClock()->Now();
-  task_environment_.FastForwardBy(base::TimeDelta::FromHours(1));
-
-  // Authenticate offline. Verify that the flag enforcing online login and the
-  // time of last login without SAML are not changed.
-  CreateLimiter();
-  Mock::VerifyAndClearExpectations(user_manager_);
-  SetUpUserManager();
-  EXPECT_CALL(*user_manager_, SaveForceOnlineSignin(test_gaia_account_id_, _))
-      .Times(0);
-  limiter_->SignedIn(UserContext::AUTH_FLOW_OFFLINE);
-
-  last_gaia_signin_time = prefs->GetTime(prefs::kGaiaLastOnlineSignInTime);
-  EXPECT_EQ(gaia_signin_time, last_gaia_signin_time);
-
-  // Verify that no timer is running.
-  EXPECT_FALSE(timer_->IsRunning());
-}
-
-TEST_F(OfflineSigninLimiterTest, GaiaZeroLimit) {
-  AddGaiaUser();
-  PrefService* prefs = profile_->GetPrefs();
-
-  // Set a zero time limit.
-  prefs->SetInteger(prefs::kGaiaOfflineSigninTimeLimitDays, 0);
-
-  // Authenticate against Gaia without SAML. Verify that the flag enforcing
-  // online login is cleared and then set immediately. Also verify that the time
-  // of last login without SAML is set.
-  CreateLimiter();
-  Sequence sequence;
-  EXPECT_CALL(*user_manager_,
-              SaveForceOnlineSignin(test_gaia_account_id_, false))
-      .Times(1)
-      .InSequence(sequence);
-  EXPECT_CALL(*user_manager_,
-              SaveForceOnlineSignin(test_gaia_account_id_, true))
-      .Times(1)
-      .InSequence(sequence);
-  limiter_->SignedIn(UserContext::AUTH_FLOW_GAIA_WITHOUT_SAML);
-
-  const base::Time last_gaia_signin_time =
-      prefs->GetTime(prefs::kGaiaLastOnlineSignInTime);
-  EXPECT_EQ(task_environment_.GetMockClock()->Now(), last_gaia_signin_time);
-}
-
-TEST_F(OfflineSigninLimiterTest, GaiaSetLimitWhileLoggedIn) {
-  AddGaiaUser();
-  PrefService* prefs = profile_->GetPrefs();
-
-  // Remove the time limit.
-  prefs->SetInteger(prefs::kGaiaOfflineSigninTimeLimitDays, -1);
-
-  // Authenticate against Gaia without SAML. Verify that the flag enforcing
-  // online login is cleared and the time of last login without SAML is set.
-  CreateLimiter();
-  EXPECT_CALL(*user_manager_,
-              SaveForceOnlineSignin(test_gaia_account_id_, false))
-      .Times(1);
-  EXPECT_CALL(*user_manager_,
-              SaveForceOnlineSignin(test_gaia_account_id_, true))
-      .Times(0);
-  limiter_->SignedIn(UserContext::AUTH_FLOW_GAIA_WITHOUT_SAML);
-
-  const base::Time last_gaia_signin_time =
-      prefs->GetTime(prefs::kGaiaLastOnlineSignInTime);
-  EXPECT_EQ(task_environment_.GetMockClock()->Now(), last_gaia_signin_time);
-
-  // Verify that no timer is running.
-  EXPECT_FALSE(timer_->IsRunning());
-
-  // Set a zero time limit. Verify that the flag enforcing online login is set.
-  Mock::VerifyAndClearExpectations(user_manager_);
-  SetUpUserManager();
-  EXPECT_CALL(*user_manager_,
-              SaveForceOnlineSignin(test_gaia_account_id_, false))
-      .Times(0);
-  EXPECT_CALL(*user_manager_,
-              SaveForceOnlineSignin(test_gaia_account_id_, true))
-      .Times(1);
-  prefs->SetInteger(prefs::kGaiaOfflineSigninTimeLimitDays, 0);
-}
-
-TEST_F(OfflineSigninLimiterTest, GaiaRemoveLimit) {
-  AddGaiaUser();
-  PrefService* prefs = profile_->GetPrefs();
-
-  // Set the time of last Gaia login without SAML and set limit.
-  prefs->SetTime(prefs::kGaiaLastOnlineSignInTime,
-                 task_environment_.GetMockClock()->Now());
-  prefs->SetInteger(prefs::kGaiaOfflineSigninTimeLimitDays, 7);  // 1 week.
-
-  // Authenticate against Gaia without SAML. Verify that the flag enforcing
-  // online login is cleared and the time of last login without SAML is set.
-  CreateLimiter();
-  EXPECT_CALL(*user_manager_,
-              SaveForceOnlineSignin(test_gaia_account_id_, false))
-      .Times(1);
-  EXPECT_CALL(*user_manager_,
-              SaveForceOnlineSignin(test_gaia_account_id_, true))
-      .Times(0);
-  limiter_->SignedIn(UserContext::AUTH_FLOW_GAIA_WITHOUT_SAML);
-
-  const base::Time last_gaia_signin_time =
-      prefs->GetTime(prefs::kGaiaLastOnlineSignInTime);
-  EXPECT_EQ(task_environment_.GetMockClock()->Now(), last_gaia_signin_time);
-
-  // Verify that the timer is running.
-  EXPECT_TRUE(timer_->IsRunning());
-
-  // Remove the time limit.
-  prefs->SetInteger(prefs::kGaiaOfflineSigninTimeLimitDays, -1);
-
-  // Verify that the flag enforcing online login is not changed.
-  Mock::VerifyAndClearExpectations(user_manager_);
-  SetUpUserManager();
-  EXPECT_CALL(*user_manager_, SaveForceOnlineSignin(test_gaia_account_id_, _))
-      .Times(0);
-}
-
-TEST_F(OfflineSigninLimiterTest, GaiaLogInWithExpiredLimit) {
-  AddGaiaUser();
-  PrefService* prefs = profile_->GetPrefs();
-
-  // Set the time of last Gaia login without SAML and set limit.
-  prefs->SetTime(prefs::kGaiaLastOnlineSignInTime,
-                 task_environment_.GetMockClock()->Now());
-  prefs->SetInteger(prefs::kGaiaOfflineSigninTimeLimitDays, 7);  // 1 week.
-
-  // Advance time by four weeks.
-  task_environment_.FastForwardBy(base::TimeDelta::FromDays(28));  // 4 weeks.
-
-  // Authenticate against Gaia without SAML. Verify that the flag enforcing
-  // online login is cleared and the time of last login without SAML is updated.
-  CreateLimiter();
-  EXPECT_CALL(*user_manager_,
-              SaveForceOnlineSignin(test_gaia_account_id_, false))
-      .Times(1);
-  EXPECT_CALL(*user_manager_,
-              SaveForceOnlineSignin(test_gaia_account_id_, true))
-      .Times(0);
-  limiter_->SignedIn(UserContext::AUTH_FLOW_GAIA_WITHOUT_SAML);
-
-  const base::Time last_gaia_signin_time =
-      prefs->GetTime(prefs::kGaiaLastOnlineSignInTime);
-  EXPECT_EQ(task_environment_.GetMockClock()->Now(), last_gaia_signin_time);
-
-  // Verify that the timer is running.
-  EXPECT_TRUE(timer_->IsRunning());
-}
-
-TEST_F(OfflineSigninLimiterTest, GaiaLogInOfflineWithExpiredLimit) {
-  AddGaiaUser();
-  PrefService* prefs = profile_->GetPrefs();
-
-  // Set the time of last Gaia login without SAML and set limit.
-  prefs->SetTime(prefs::kGaiaLastOnlineSignInTime,
-                 task_environment_.GetMockClock()->Now());
-  prefs->SetInteger(prefs::kGaiaOfflineSigninTimeLimitDays, 7);  // 1 week.
-
-  // Advance time by four weeks.
-  const base::Time gaia_signin_time = task_environment_.GetMockClock()->Now();
-  task_environment_.FastForwardBy(base::TimeDelta::FromDays(28));  // 4 weeks.
-
-  // Authenticate offline. Verify that the flag enforcing online login is
-  // set and the time of last login without SAML is not changed.
-  CreateLimiter();
-  EXPECT_CALL(*user_manager_,
-              SaveForceOnlineSignin(test_gaia_account_id_, false))
-      .Times(0);
-  EXPECT_CALL(*user_manager_,
-              SaveForceOnlineSignin(test_gaia_account_id_, true))
-      .Times(1);
-  limiter_->SignedIn(UserContext::AUTH_FLOW_OFFLINE);
-  InSessionPasswordSyncManager* password_sync_manager =
-      InSessionPasswordSyncManagerFactory::GetForProfile(profile_.get());
-  EXPECT_FALSE(password_sync_manager->IsLockReauthEnabled());
-
-  const base::Time last_gaia_signin_time =
-      prefs->GetTime(prefs::kGaiaLastOnlineSignInTime);
-  EXPECT_EQ(gaia_signin_time, last_gaia_signin_time);
-
-  // Verify that no timer is running.
-  EXPECT_FALSE(timer_->IsRunning());
-}
-
-TEST_F(OfflineSigninLimiterTest, GaiaLimitExpiredWhileSuspended) {
-  AddGaiaUser();
-  PrefService* prefs = profile_->GetPrefs();
-
-  // Set the time of Gaia last login without SAML and set time limit.
-  prefs->SetTime(prefs::kGaiaLastOnlineSignInTime,
-                 task_environment_.GetMockClock()->Now());
-  prefs->SetInteger(prefs::kGaiaOfflineSigninTimeLimitDays, 7);  // 1 week.
-
-  // Authenticate against Gaia without SAML. Verify that the flag enforcing
-  // online login is cleared and the time of last login without SAML is set.
-  CreateLimiter();
-  EXPECT_CALL(*user_manager_,
-              SaveForceOnlineSignin(test_gaia_account_id_, false))
-      .Times(1);
-  EXPECT_CALL(*user_manager_,
-              SaveForceOnlineSignin(test_gaia_account_id_, true))
-      .Times(0);
-  limiter_->SignedIn(UserContext::AUTH_FLOW_GAIA_WITHOUT_SAML);
-
-  // Suspend for 4 weeks.
-  test_power_monitor_source_.Suspend();
-  task_environment_.AdvanceClock(base::TimeDelta::FromDays(28));  // 4 weeks.
-
-  // Resume power. Verify that the flag enforcing online login is set.
-  Mock::VerifyAndClearExpectations(user_manager_);
-  EXPECT_CALL(*user_manager_,
-              SaveForceOnlineSignin(test_gaia_account_id_, false))
-      .Times(0);
-  EXPECT_CALL(*user_manager_,
-              SaveForceOnlineSignin(test_gaia_account_id_, true))
-      .Times(1);
-  test_power_monitor_source_.Resume();
-  task_environment_.RunUntilIdle();
-}
-
-TEST_F(OfflineSigninLimiterTest, GaiaLogInOfflineWithOnLockReauth) {
-  AddGaiaUser();
-  PrefService* prefs = profile_->GetPrefs();
-
-  // Set the time of last Gaia login without SAML and time limit.
-  prefs->SetTime(prefs::kGaiaLastOnlineSignInTime,
-                 task_environment_.GetMockClock()->Now());
-  prefs->SetInteger(prefs::kGaiaOfflineSigninTimeLimitDays, 7);  // 1 week.
-
-  // Enable re-authentication on the lock screen.
-  prefs->SetBoolean(prefs::kLockScreenReauthenticationEnabled, true);
-
-  // Advance time by four weeks.
-  task_environment_.FastForwardBy(base::TimeDelta::FromDays(28));  // 4 weeks.
-
-  // Authenticate offline and check if InSessionPasswordSyncManager is created.
-  CreateLimiter();
-  limiter_->SignedIn(UserContext::AUTH_FLOW_OFFLINE);
-  InSessionPasswordSyncManager* password_sync_manager =
-      InSessionPasswordSyncManagerFactory::GetForProfile(profile_.get());
-  // Verify that we enter InSessionPasswordSyncManager::ForceReauthOnLockScreen.
-  EXPECT_TRUE(password_sync_manager->IsLockReauthEnabled());
-  // After changing the re-auth flag timer should be stopped.
-  EXPECT_FALSE(timer_->IsRunning());
-}
-
-TEST_F(OfflineSigninLimiterTest, GaiaNoLastOnlineSigninWithLimit) {
-  AddGaiaUser();
-  PrefService* prefs = profile_->GetPrefs();
-
-  // Set the time limit.
-  prefs->SetInteger(prefs::kGaiaOfflineSigninTimeLimitDays, 7);  // 1 week.
-
-  // Authenticate offline. Verify that the flag enforcing online is set due no
-  // `last_gaia_signin_time` value.
-  CreateLimiter();
-  EXPECT_CALL(*user_manager_,
-              SaveForceOnlineSignin(test_gaia_account_id_, false))
-      .Times(0);
-  EXPECT_CALL(*user_manager_,
-              SaveForceOnlineSignin(test_gaia_account_id_, true))
-      .Times(1);
-  limiter_->SignedIn(UserContext::AUTH_FLOW_OFFLINE);
-
-  base::Time last_gaia_signin_time =
-      prefs->GetTime(prefs::kGaiaLastOnlineSignInTime);
-  EXPECT_TRUE(last_gaia_signin_time.is_null());
-
-  // Verify that no timer is running.
-  EXPECT_FALSE(timer_->IsRunning());
-
-  // Log out.
-  DestroyLimiter();
-
-  // Authenticate against Gaia without SAML. Verify that the flag enforcing
-  // online login is cleared and the time of last login without SAML is set.
-  CreateLimiter();
-  Mock::VerifyAndClearExpectations(user_manager_);
-  SetUpUserManager();
-  EXPECT_CALL(*user_manager_,
-              SaveForceOnlineSignin(test_gaia_account_id_, false))
-      .Times(1);
-  EXPECT_CALL(*user_manager_,
-              SaveForceOnlineSignin(test_gaia_account_id_, true))
-      .Times(0);
-  limiter_->SignedIn(UserContext::AUTH_FLOW_GAIA_WITHOUT_SAML);
-
-  last_gaia_signin_time = prefs->GetTime(prefs::kGaiaLastOnlineSignInTime);
-  EXPECT_EQ(task_environment_.GetMockClock()->Now(), last_gaia_signin_time);
-
-  // Verify that the timer is running.
-  EXPECT_TRUE(timer_->IsRunning());
-
-  // Log out.
-  DestroyLimiter();
-
-  // Advance time by an hour.
-  const base::Time gaia_signin_time = task_environment_.GetMockClock()->Now();
-  task_environment_.FastForwardBy(base::TimeDelta::FromHours(1));
-
-  // Authenticate offline. Verify that the flag enforcing online login and the
-  // time of last login without SAML are not changed.
-  CreateLimiter();
-  Mock::VerifyAndClearExpectations(user_manager_);
-  SetUpUserManager();
-  EXPECT_CALL(*user_manager_, SaveForceOnlineSignin(test_gaia_account_id_, _))
-      .Times(0);
-  limiter_->SignedIn(UserContext::AUTH_FLOW_OFFLINE);
-
-  last_gaia_signin_time = prefs->GetTime(prefs::kGaiaLastOnlineSignInTime);
-  EXPECT_EQ(gaia_signin_time, last_gaia_signin_time);
-
-  // Verify that the timer is running.
-  EXPECT_TRUE(timer_->IsRunning());
-}
-
 }  //  namespace chromeos
diff --git a/chrome/browser/ash/login/users/supervised_user_manager_impl.cc b/chrome/browser/ash/login/users/supervised_user_manager_impl.cc
index c3429a9..a5c399e 100644
--- a/chrome/browser/ash/login/users/supervised_user_manager_impl.cc
+++ b/chrome/browser/ash/login/users/supervised_user_manager_impl.cc
@@ -172,13 +172,15 @@
     SetUserIntegerValue(user_id, kSupervisedUserPasswordRevision,
                         password_revision.value());
 
-  bool flag;
-  if (password_info->GetBooleanWithoutPathExpansion(kRequirePasswordUpdate,
-                                                    &flag)) {
-    SetUserBooleanValue(user_id, kSupervisedUserNeedPasswordUpdate, flag);
+  absl::optional<bool> flag =
+      password_info->FindBoolKey(kRequirePasswordUpdate);
+  if (flag.has_value()) {
+    SetUserBooleanValue(user_id, kSupervisedUserNeedPasswordUpdate,
+                        flag.value());
   }
-  if (password_info->GetBooleanWithoutPathExpansion(kHasIncompleteKey, &flag))
-    SetUserBooleanValue(user_id, kSupervisedUserIncompleteKey, flag);
+  flag = password_info->FindBoolKey(kHasIncompleteKey);
+  if (flag.has_value())
+    SetUserBooleanValue(user_id, kSupervisedUserIncompleteKey, flag.value());
 
   std::string salt;
   if (password_info->GetStringWithoutPathExpansion(kSalt, &salt))
@@ -213,7 +215,12 @@
                                                     bool* out_value) const {
   PrefService* local_state = g_browser_process->local_state();
   const base::DictionaryValue* dictionary = local_state->GetDictionary(key);
-  return dictionary->GetBooleanWithoutPathExpansion(user_id, out_value);
+  absl::optional<bool> flag = dictionary->FindBoolKey(user_id);
+  if (!flag)
+    return false;
+
+  *out_value = flag.value();
+  return true;
 }
 
 void SupervisedUserManagerImpl::SetUserStringValue(const std::string& user_id,
diff --git a/chrome/browser/browsing_data/cookies_tree_model_unittest.cc b/chrome/browser/browsing_data/cookies_tree_model_unittest.cc
index c7f3f6f..7fe4d62 100644
--- a/chrome/browser/browsing_data/cookies_tree_model_unittest.cc
+++ b/chrome/browser/browsing_data/cookies_tree_model_unittest.cc
@@ -1552,7 +1552,7 @@
       .Times(2);
   origin->CreateContentException(
       cookie_settings, CONTENT_SETTING_SESSION_ONLY);
-  EXPECT_TRUE(cookie_settings->IsCookieAccessAllowed(host, host));
+  EXPECT_TRUE(cookie_settings->IsFullCookieAccessAllowed(host, host));
   EXPECT_TRUE(cookie_settings->IsCookieSessionOnly(host));
 }
 
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index 7c17e55..23b50c4 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -5734,7 +5734,7 @@
   // Persistent MediaDevice IDs are allowed if cookies are allowed.
   return CookieSettingsFactory::GetForProfile(
              Profile::FromBrowserContext(browser_context))
-      ->IsCookieAccessAllowed(url, site_for_cookies, top_frame_origin);
+      ->IsFullCookieAccessAllowed(url, site_for_cookies, top_frame_origin);
 }
 
 #if !defined(OS_ANDROID)
diff --git a/chrome/browser/chromeos/platform_keys/key_permissions/extension_key_permissions_service.cc b/chrome/browser/chromeos/platform_keys/key_permissions/extension_key_permissions_service.cc
index 236a5e5..e77eb51 100644
--- a/chrome/browser/chromeos/platform_keys/key_permissions/extension_key_permissions_service.cc
+++ b/chrome/browser/chromeos/platform_keys/key_permissions/extension_key_permissions_service.cc
@@ -281,10 +281,11 @@
     } else if (entry.GetAsDictionary(&dict_entry)) {
       dict_entry->GetStringWithoutPathExpansion(kStateStoreSPKI, &spki_b64);
       KeyEntry new_entry(spki_b64);
-      dict_entry->GetBooleanWithoutPathExpansion(kStateStoreSignOnce,
-                                                 &new_entry.sign_once);
-      dict_entry->GetBooleanWithoutPathExpansion(kStateStoreSignUnlimited,
-                                                 &new_entry.sign_unlimited);
+      new_entry.sign_once = dict_entry->FindBoolKey(kStateStoreSignOnce)
+                                .value_or(new_entry.sign_once);
+      new_entry.sign_unlimited =
+          dict_entry->FindBoolKey(kStateStoreSignUnlimited)
+              .value_or(new_entry.sign_unlimited);
       state_store_entries_.push_back(new_entry);
     } else {
       LOG(ERROR) << "Found invalid entry of type " << entry.type()
diff --git a/chrome/browser/commerce/merchant_viewer/android/BUILD.gn b/chrome/browser/commerce/merchant_viewer/android/BUILD.gn
index 766df426..c87c05b1 100644
--- a/chrome/browser/commerce/merchant_viewer/android/BUILD.gn
+++ b/chrome/browser/commerce/merchant_viewer/android/BUILD.gn
@@ -116,6 +116,7 @@
     "//third_party/mockito:mockito_java",
     "//ui/android:ui_no_recycler_view_java",
     "//url:gurl_java",
+    "//url:gurl_junit_test_support",
   ]
   resources_package = "org.chromium.chrome.tab_ui"
 }
diff --git a/chrome/browser/commerce/merchant_viewer/android/javatests/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustSignalsMediatorTest.java b/chrome/browser/commerce/merchant_viewer/android/javatests/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustSignalsMediatorTest.java
index f68449e..cf9716bc 100644
--- a/chrome/browser/commerce/merchant_viewer/android/javatests/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustSignalsMediatorTest.java
+++ b/chrome/browser/commerce/merchant_viewer/android/javatests/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustSignalsMediatorTest.java
@@ -34,6 +34,7 @@
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.content_public.browser.WebContentsObserver;
 import org.chromium.url.GURL;
+import org.chromium.url.JUnitTestGURLs;
 
 /**
  * Tests for {@link MerchantTrustSignalsMediator}.
@@ -79,11 +80,11 @@
         MockitoAnnotations.initMocks(this);
         doReturn(mTabModelFilterProvider).when(mMockTabModelSelector).getTabModelFilterProvider();
         doReturn(100).when(mMockPrimaryTab).getId();
-        doReturn("fake://url/2").when(mMockPrimaryTab).getUrlString();
+        doReturn(JUnitTestGURLs.getGURL(JUnitTestGURLs.URL_1)).when(mMockPrimaryTab).getUrl();
         doReturn(mMockWebContents).when(mMockPrimaryTab).getWebContents();
 
         doReturn(200).when(mMockSecondaryTab).getId();
-        doReturn("fake://url/2").when(mMockSecondaryTab).getUrlString();
+        doReturn(JUnitTestGURLs.getGURL(JUnitTestGURLs.URL_2)).when(mMockSecondaryTab).getUrl();
         doReturn(mMockWebContents).when(mMockSecondaryTab).getWebContents();
     }
 
@@ -226,4 +227,4 @@
 
         return mediator;
     }
-}
\ No newline at end of file
+}
diff --git a/chrome/browser/content_settings/cookie_settings_factory_unittest.cc b/chrome/browser/content_settings/cookie_settings_factory_unittest.cc
index 8602330..fe9f342 100644
--- a/chrome/browser/content_settings/cookie_settings_factory_unittest.cc
+++ b/chrome/browser/content_settings/cookie_settings_factory_unittest.cc
@@ -44,16 +44,17 @@
 
   // The modification should apply to the regular profile and incognito profile.
   EXPECT_FALSE(
-      cookie_settings_->IsCookieAccessAllowed(kBlockedSite, kBlockedSite));
-  EXPECT_FALSE(
-      incognito_settings->IsCookieAccessAllowed(kBlockedSite, kBlockedSite));
+      cookie_settings_->IsFullCookieAccessAllowed(kBlockedSite, kBlockedSite));
+  EXPECT_FALSE(incognito_settings->IsFullCookieAccessAllowed(kBlockedSite,
+                                                             kBlockedSite));
 
   // Modify an incognito cookie setting and check that this does not propagate
   // into regular mode.
   incognito_settings->SetCookieSetting(kHttpsSite, CONTENT_SETTING_BLOCK);
-  EXPECT_TRUE(cookie_settings_->IsCookieAccessAllowed(kHttpsSite, kHttpsSite));
+  EXPECT_TRUE(
+      cookie_settings_->IsFullCookieAccessAllowed(kHttpsSite, kHttpsSite));
   EXPECT_FALSE(
-      incognito_settings->IsCookieAccessAllowed(kHttpsSite, kHttpsSite));
+      incognito_settings->IsFullCookieAccessAllowed(kHttpsSite, kHttpsSite));
 }
 
 TEST_F(CookieSettingsFactoryTest, IncognitoBehaviorOfBlockingEverything) {
@@ -65,25 +66,26 @@
   cookie_settings_->SetDefaultCookieSetting(CONTENT_SETTING_BLOCK);
 
   // It should be effective for regular and incognito session.
-  EXPECT_FALSE(cookie_settings_->IsCookieAccessAllowed(kFirstPartySite,
-                                                       kFirstPartySite));
-  EXPECT_FALSE(incognito_settings->IsCookieAccessAllowed(kFirstPartySite,
-                                                         kFirstPartySite));
+  EXPECT_FALSE(cookie_settings_->IsFullCookieAccessAllowed(kFirstPartySite,
+                                                           kFirstPartySite));
+  EXPECT_FALSE(incognito_settings->IsFullCookieAccessAllowed(kFirstPartySite,
+                                                             kFirstPartySite));
 
   // A whitelisted item set in incognito mode should only apply to incognito
   // mode.
   incognito_settings->SetCookieSetting(kAllowedSite, CONTENT_SETTING_ALLOW);
-  EXPECT_TRUE(
-      incognito_settings->IsCookieAccessAllowed(kAllowedSite, kAllowedSite));
+  EXPECT_TRUE(incognito_settings->IsFullCookieAccessAllowed(kAllowedSite,
+                                                            kAllowedSite));
   EXPECT_FALSE(
-      cookie_settings_->IsCookieAccessAllowed(kAllowedSite, kAllowedSite));
+      cookie_settings_->IsFullCookieAccessAllowed(kAllowedSite, kAllowedSite));
 
   // A whitelisted item set in regular mode should apply to regular and
   // incognito mode.
   cookie_settings_->SetCookieSetting(kHttpsSite, CONTENT_SETTING_ALLOW);
   EXPECT_TRUE(
-      incognito_settings->IsCookieAccessAllowed(kHttpsSite, kHttpsSite));
-  EXPECT_TRUE(cookie_settings_->IsCookieAccessAllowed(kHttpsSite, kHttpsSite));
+      incognito_settings->IsFullCookieAccessAllowed(kHttpsSite, kHttpsSite));
+  EXPECT_TRUE(
+      cookie_settings_->IsFullCookieAccessAllowed(kHttpsSite, kHttpsSite));
 }
 
 // Android does not have guest profiles.
diff --git a/chrome/browser/content_settings/host_content_settings_map_unittest.cc b/chrome/browser/content_settings/host_content_settings_map_unittest.cc
index f8994f09..d76c84b 100644
--- a/chrome/browser/content_settings/host_content_settings_map_unittest.cc
+++ b/chrome/browser/content_settings/host_content_settings_map_unittest.cc
@@ -533,18 +533,18 @@
 
   GURL host_ending_with_dot("http://example.com./");
 
-  EXPECT_TRUE(cookie_settings->IsCookieAccessAllowed(host_ending_with_dot,
-                                                     host_ending_with_dot));
+  EXPECT_TRUE(cookie_settings->IsFullCookieAccessAllowed(host_ending_with_dot,
+                                                         host_ending_with_dot));
   host_content_settings_map->SetContentSettingDefaultScope(
       host_ending_with_dot, GURL(), ContentSettingsType::COOKIES,
       CONTENT_SETTING_DEFAULT);
-  EXPECT_TRUE(cookie_settings->IsCookieAccessAllowed(host_ending_with_dot,
-                                                     host_ending_with_dot));
+  EXPECT_TRUE(cookie_settings->IsFullCookieAccessAllowed(host_ending_with_dot,
+                                                         host_ending_with_dot));
   host_content_settings_map->SetContentSettingDefaultScope(
       host_ending_with_dot, GURL(), ContentSettingsType::COOKIES,
       CONTENT_SETTING_BLOCK);
-  EXPECT_FALSE(cookie_settings->IsCookieAccessAllowed(host_ending_with_dot,
-                                                      host_ending_with_dot));
+  EXPECT_FALSE(cookie_settings->IsFullCookieAccessAllowed(
+      host_ending_with_dot, host_ending_with_dot));
 
   EXPECT_EQ(CONTENT_SETTING_ALLOW,
             host_content_settings_map->GetContentSetting(
diff --git a/chrome/browser/devtools/devtools_window.cc b/chrome/browser/devtools/devtools_window.cc
index 905e509..182d5ac 100644
--- a/chrome/browser/devtools/devtools_window.cc
+++ b/chrome/browser/devtools/devtools_window.cc
@@ -55,6 +55,7 @@
 #include "components/zoom/zoom_controller.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/child_process_security_policy.h"
+#include "content/public/browser/color_chooser.h"
 #include "content/public/browser/devtools_agent_host.h"
 #include "content/public/browser/file_select_listener.h"
 #include "content/public/browser/keyboard_event_processing_result.h"
@@ -1372,7 +1373,7 @@
   return javascript_dialogs::AppModalDialogManager::GetInstance();
 }
 
-content::ColorChooser* DevToolsWindow::OpenColorChooser(
+std::unique_ptr<content::ColorChooser> DevToolsWindow::OpenColorChooser(
     WebContents* web_contents,
     SkColor initial_color,
     const std::vector<blink::mojom::ColorSuggestionPtr>& suggestions) {
diff --git a/chrome/browser/devtools/devtools_window.h b/chrome/browser/devtools/devtools_window.h
index 163e82d..bdc143f 100644
--- a/chrome/browser/devtools/devtools_window.h
+++ b/chrome/browser/devtools/devtools_window.h
@@ -366,7 +366,7 @@
       const content::NativeWebKeyboardEvent& event) override;
   content::JavaScriptDialogManager* GetJavaScriptDialogManager(
       content::WebContents* source) override;
-  content::ColorChooser* OpenColorChooser(
+  std::unique_ptr<content::ColorChooser> OpenColorChooser(
       content::WebContents* web_contents,
       SkColor color,
       const std::vector<blink::mojom::ColorSuggestionPtr>& suggestions)
diff --git a/chrome/browser/extensions/activity_log/activity_actions.cc b/chrome/browser/extensions/activity_log/activity_actions.cc
index 17524ad8..df83aa93 100644
--- a/chrome/browser/extensions/activity_log/activity_actions.cc
+++ b/chrome/browser/extensions/activity_log/activity_actions.cc
@@ -189,10 +189,9 @@
   if (other()) {
     std::unique_ptr<ExtensionActivity::Other> other_field(
         new ExtensionActivity::Other);
-    bool prerender;
-    if (other()->GetBooleanWithoutPathExpansion(constants::kActionPrerender,
-                                                &prerender)) {
-      other_field->prerender = std::make_unique<bool>(prerender);
+    if (absl::optional<bool> prerender =
+            other()->FindBoolKey(constants::kActionPrerender)) {
+      other_field->prerender = std::make_unique<bool>(*prerender);
     }
     const base::DictionaryValue* web_request;
     if (other()->GetDictionaryWithoutPathExpansion(constants::kActionWebRequest,
diff --git a/chrome/browser/extensions/api/content_settings/content_settings_apitest.cc b/chrome/browser/extensions/api/content_settings/content_settings_apitest.cc
index fd56adf..c5918023 100644
--- a/chrome/browser/extensions/api/content_settings/content_settings_apitest.cc
+++ b/chrome/browser/extensions/api/content_settings/content_settings_apitest.cc
@@ -90,7 +90,7 @@
     // Check default content settings by using an unknown URL.
     GURL example_url("http://www.example.com");
     EXPECT_TRUE(
-        cookie_settings->IsCookieAccessAllowed(example_url, example_url));
+        cookie_settings->IsFullCookieAccessAllowed(example_url, example_url));
     EXPECT_TRUE(cookie_settings->IsCookieSessionOnly(example_url));
     EXPECT_EQ(CONTENT_SETTING_ALLOW,
               map->GetContentSetting(example_url, example_url,
@@ -122,7 +122,7 @@
 
     // Check content settings for www.google.com
     GURL url("http://www.google.com");
-    EXPECT_FALSE(cookie_settings->IsCookieAccessAllowed(url, url));
+    EXPECT_FALSE(cookie_settings->IsFullCookieAccessAllowed(url, url));
     EXPECT_EQ(CONTENT_SETTING_ALLOW,
               map->GetContentSetting(url, url, ContentSettingsType::IMAGES));
     EXPECT_EQ(
@@ -157,7 +157,7 @@
 
     // Check content settings for www.google.com
     GURL url("http://www.google.com");
-    EXPECT_TRUE(cookie_settings->IsCookieAccessAllowed(url, url));
+    EXPECT_TRUE(cookie_settings->IsFullCookieAccessAllowed(url, url));
     EXPECT_FALSE(cookie_settings->IsCookieSessionOnly(url));
     EXPECT_EQ(CONTENT_SETTING_ALLOW,
               map->GetContentSetting(url, url, ContentSettingsType::IMAGES));
@@ -195,7 +195,7 @@
         CookieSettingsFactory::GetForProfile(profile_).get();
 
     content_settings.push_back(
-        cookie_settings->IsCookieAccessAllowed(url, url));
+        cookie_settings->IsFullCookieAccessAllowed(url, url));
     content_settings.push_back(cookie_settings->IsCookieSessionOnly(url));
     content_settings.push_back(
         map->GetContentSetting(url, url, ContentSettingsType::IMAGES));
diff --git a/chrome/browser/extensions/api/tabs/tabs_api.cc b/chrome/browser/extensions/api/tabs/tabs_api.cc
index 24bc74e..373c0f7 100644
--- a/chrome/browser/extensions/api/tabs/tabs_api.cc
+++ b/chrome/browser/extensions/api/tabs/tabs_api.cc
@@ -1056,12 +1056,8 @@
       continue;
     }
 
-    // Bug fix for crbug.com/1197888. Disable query during any tab drag to
-    // ensure that the result matches the eventual state of the tab strip.
-    TabStripModel* tab_strip =
-        ExtensionTabUtil::GetEditableTabStripModel(browser);
-    if (!tab_strip)
-      return RespondNow(Error(tabs_constants::kTabStripNotEditableQueryError));
+    TabStripModel* tab_strip = browser->tab_strip_model();
+    DCHECK(tab_strip);
     for (int i = 0; i < tab_strip->count(); ++i) {
       WebContents* web_contents = tab_strip->GetWebContentsAt(i);
 
@@ -1248,9 +1244,6 @@
     return RespondNow(Error(std::move(error)));
   }
 
-  if (!ExtensionTabUtil::IsTabStripEditable())
-    return RespondNow(Error(tabs_constants::kTabStripNotEditableError));
-
   return RespondNow(ArgumentList(tabs::Get::Results::Create(
       *CreateTabObjectHelper(contents, extension(), source_context_type(),
                              tab_strip, tab_index))));
diff --git a/chrome/browser/extensions/api/tabs/tabs_api_unittest.cc b/chrome/browser/extensions/api/tabs/tabs_api_unittest.cc
index 0917707..7538daa 100644
--- a/chrome/browser/extensions/api/tabs/tabs_api_unittest.cc
+++ b/chrome/browser/extensions/api/tabs/tabs_api_unittest.cc
@@ -166,6 +166,7 @@
         function.get(), args, browser(), api_test_utils::NONE));
   }
 
+  // Start logical drag.
   browser_window()->SetIsTabStripEditable(false);
   ASSERT_FALSE(browser_window()->IsTabStripEditable());
 
@@ -184,6 +185,26 @@
     EXPECT_EQ(*value->FindStringKey("pendingUrl"), url);
   }
 
+  // Succeed while edit in progress and calling chrome.tabs.query.
+  {
+    const char* args = "[{}]";
+    scoped_refptr<TabsQueryFunction> function =
+        base::MakeRefCounted<TabsQueryFunction>();
+    function->set_extension(extension);
+    ASSERT_TRUE(extension_function_test_utils::RunFunction(
+        function.get(), args, browser(), api_test_utils::NONE));
+  }
+
+  // Succeed while edit in progress and calling chrome.tabs.get.
+  {
+    std::string args = base::StringPrintf("[%d]", tab_ids[0]);
+    scoped_refptr<TabsGetFunction> function =
+        base::MakeRefCounted<TabsGetFunction>();
+    function->set_extension(extension);
+    ASSERT_TRUE(extension_function_test_utils::RunFunction(
+        function.get(), args, browser(), api_test_utils::NONE));
+  }
+
   // Bug fix for crbug.com/1198717. Error updating tabs while drag in progress.
   {
     std::string args =
@@ -219,21 +240,9 @@
     EXPECT_EQ(tabs_constants::kTabStripNotEditableError, error);
   }
 
-  // Bug fix for crbug.com/1197888. Disable query during any tab drag to ensure
-  // that the result matches the eventual state of the tab strip.
-  {
-    const char* args = "[{}]";
-    scoped_refptr<TabsQueryFunction> function =
-        base::MakeRefCounted<TabsQueryFunction>();
-    function->set_extension(extension);
-    std::string error =
-        extension_function_test_utils::RunFunctionAndReturnError(
-            function.get(), args, browser());
-    EXPECT_EQ(tabs_constants::kTabStripNotEditableQueryError, error);
-  }
-
   // TODO(solomonkinard): Consider adding tests for drag cancellation.
 
+  // Clean up.
   while (!browser()->tab_strip_model()->empty())
     browser()->tab_strip_model()->DetachWebContentsAt(0);
 }
diff --git a/chrome/browser/extensions/extension_install_prompt.cc b/chrome/browser/extensions/extension_install_prompt.cc
index a58a264..e34474b 100644
--- a/chrome/browser/extensions/extension_install_prompt.cc
+++ b/chrome/browser/extensions/extension_install_prompt.cc
@@ -265,6 +265,7 @@
     case REMOTE_INSTALL_PROMPT:
     case REPAIR_PROMPT:
     case DELEGATED_PERMISSIONS_PROMPT:
+    case EXTENSION_REQUEST_PROMPT:
       id = IDS_CANCEL;
       break;
     case PERMISSIONS_PROMPT:
@@ -274,7 +275,6 @@
       id = IDS_EXTENSION_EXTERNAL_INSTALL_PROMPT_ABORT_BUTTON;
       break;
     case POST_INSTALL_PERMISSIONS_PROMPT:
-    case EXTENSION_REQUEST_PROMPT:
     case EXTENSION_PENDING_REQUEST_PROMPT:
       id = IDS_CLOSE;
       break;
diff --git a/chrome/browser/extensions/extension_view_host.cc b/chrome/browser/extensions/extension_view_host.cc
index 8952874b..70a877c 100644
--- a/chrome/browser/extensions/extension_view_host.cc
+++ b/chrome/browser/extensions/extension_view_host.cc
@@ -18,6 +18,7 @@
 #include "components/autofill/content/browser/content_autofill_driver_factory.h"
 #include "components/autofill/core/browser/browser_autofill_manager.h"
 #include "components/web_modal/web_contents_modal_dialog_manager.h"
+#include "content/public/browser/color_chooser.h"
 #include "content/public/browser/file_select_listener.h"
 #include "content/public/browser/keyboard_event_processing_result.h"
 #include "content/public/browser/notification_source.h"
@@ -215,7 +216,7 @@
   return blink::WebInputEvent::IsPinchGestureEventType(event.GetType());
 }
 
-content::ColorChooser* ExtensionViewHost::OpenColorChooser(
+std::unique_ptr<content::ColorChooser> ExtensionViewHost::OpenColorChooser(
     content::WebContents* web_contents,
     SkColor initial_color,
     const std::vector<blink::mojom::ColorSuggestionPtr>& suggestions) {
diff --git a/chrome/browser/extensions/extension_view_host.h b/chrome/browser/extensions/extension_view_host.h
index 6081419..6d17b8d1 100644
--- a/chrome/browser/extensions/extension_view_host.h
+++ b/chrome/browser/extensions/extension_view_host.h
@@ -77,7 +77,7 @@
       const content::NativeWebKeyboardEvent& event) override;
   bool PreHandleGestureEvent(content::WebContents* source,
                              const blink::WebGestureEvent& event) override;
-  content::ColorChooser* OpenColorChooser(
+  std::unique_ptr<content::ColorChooser> OpenColorChooser(
       content::WebContents* web_contents,
       SkColor color,
       const std::vector<blink::mojom::ColorSuggestionPtr>& suggestions)
diff --git a/chrome/browser/media/webrtc/capture_handle_browsertest.cc b/chrome/browser/media/webrtc/capture_handle_browsertest.cc
new file mode 100644
index 0000000..e92cc83
--- /dev/null
+++ b/chrome/browser/media/webrtc/capture_handle_browsertest.cc
@@ -0,0 +1,568 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <string>
+#include <vector>
+
+#include "base/files/file_path.h"
+#include "base/path_service.h"
+#include "base/strings/strcat.h"
+#include "base/strings/stringprintf.h"
+#include "build/buildflag.h"
+#include "chrome/browser/media/webrtc/webrtc_browsertest_base.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_tabstrip.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "components/permissions/permission_request_manager.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_base.h"
+#include "content/public/test/browser_test_utils.h"
+
+// TODO(crbug.com/1215089): Enable this test suite on Lacros.
+#if !BUILDFLAG(IS_CHROMEOS_LACROS)
+
+using content::WebContents;
+
+namespace {
+
+// The captured tab is identified by its title.
+const char kCapturedTabTitle[] = "totally-unique-captured-page-title";
+
+// Capturing page.
+const char kCapturingPageMain[] = "/webrtc/capturing_page_main.html";
+// Captured page.
+const char kCapturedPageMain[] = "/webrtc/captured_page_main.html";
+// Similar contents to kCapturedPageMain, but on a different page, which can
+// be served same-origin or cross-origin.
+const char kCapturedPageOther[] = "/webrtc/captured_page_other.html";
+
+const char* kArbitraryOrigin = "https://arbitrary-origin.com";
+const char* kNoCaptureHandle = "no-capture-handle";
+
+std::string StringifyPermittedOrigins(
+    const std::vector<std::string>& permitted_origins) {
+  if (permitted_origins.empty()) {
+    return "[]";
+  }
+  return base::StrCat(
+      {"[\"", base::JoinString(permitted_origins, "\", \""), "\"]"});
+}
+
+std::string StringifyCaptureHandle(WebContents* web_contents,
+                                   bool expose_origin,
+                                   const std::string& handle) {
+  if (!expose_origin && handle.empty()) {
+    return "";
+  }
+
+  std::string origin_str;
+  if (expose_origin) {
+    const auto origin =
+        url::Origin::Create(web_contents->GetLastCommittedURL());
+    origin_str =
+        base::StringPrintf(",\"origin\":\"%s\"", origin.Serialize().c_str());
+  }
+
+  return base::StringPrintf("{\"handle\":\"%s\"%s}", handle.c_str(),
+                            origin_str.c_str());
+}
+
+// Conveniently pack together a captured tab and the capture-handle that
+// is expected to be observed by capturers from a permitted origin.
+struct TabInfo {
+  void StartCapturing() {
+    // Bring the tab into focus. This avoids getDisplayMedia rejection.
+    browser->tab_strip_model()->ActivateTabAt(tab_strip_index);
+
+    std::string script_result;
+    EXPECT_TRUE(content::ExecuteScriptAndExtractString(
+        web_contents->GetMainFrame(), "captureOtherTab();", &script_result));
+    EXPECT_EQ(script_result, "capture-success");
+  }
+
+  url::Origin GetOrigin() const {
+    return url::Origin::Create(web_contents->GetLastCommittedURL());
+  }
+
+  std::string GetOriginAsString() const { return GetOrigin().Serialize(); }
+
+  void SetCaptureHandleConfig(
+      bool expose_origin,
+      const std::string& handle,
+      const std::vector<std::string>& permitted_origins) {
+    std::string script_result;
+    EXPECT_TRUE(content::ExecuteScriptAndExtractString(
+        web_contents->GetMainFrame(),
+        base::StringPrintf(
+            "callSetCaptureHandleConfig(%s, \"%s\", %s);",
+            expose_origin ? "true" : "false", handle.c_str(),
+            StringifyPermittedOrigins(permitted_origins).c_str()),
+        &script_result));
+    EXPECT_EQ(script_result, "capture-handle-set");
+
+    capture_handle =
+        StringifyCaptureHandle(web_contents, expose_origin, handle);
+  }
+
+  std::string ReadCaptureHandleFromSettings() {
+    std::string script_result;
+    EXPECT_TRUE(content::ExecuteScriptAndExtractString(
+        web_contents->GetMainFrame(), "readCaptureHandleFromSettings();",
+        &script_result));
+    return script_result;
+  }
+
+  void Navigate(Browser* browser, GURL url, bool expect_handle_reset = false) {
+    std::string script_result;
+    ASSERT_TRUE(content::ExecuteScriptAndExtractString(
+        web_contents->GetMainFrame(),
+        base::StringPrintf("clickLinkToUrl(\"%s\");", url.spec().c_str()),
+        &script_result));
+    ASSERT_EQ(script_result, "link-success");
+
+    if (expect_handle_reset) {
+      capture_handle = "";
+    }
+  }
+
+  std::string LastEvent() {
+    std::string script_result = "error-not-modified";
+    EXPECT_TRUE(content::ExecuteScriptAndExtractString(
+        web_contents->GetMainFrame(), "readLastEvent();", &script_result));
+    return script_result;
+  }
+
+  Browser* browser;
+  WebContents* web_contents;
+  int tab_strip_index;
+  std::string capture_handle;  // Expected value for those who may observe.
+};
+
+TabInfo MakeTabInfoFromActiveTab(Browser* browser,
+                                 bool expose_origin,
+                                 const std::string& handle) {
+  WebContents* const web_contents =
+      browser->tab_strip_model()->GetActiveWebContents();
+  return TabInfo{browser, web_contents,
+                 browser->tab_strip_model()->active_index(),
+                 StringifyCaptureHandle(web_contents, expose_origin, handle)};
+}
+
+}  // namespace
+
+// Essentially depends on InProcessBrowserTest, but WebRtcTestBase provides
+// detection of JS errors.
+class CaptureHandleBrowserTest : public WebRtcTestBase {
+ public:
+  void SetUpInProcessBrowserTestFixture() override {
+    WebRtcTestBase::SetUpInProcessBrowserTestFixture();
+
+    DetectErrorsInJavaScript();
+
+    base::FilePath test_dir;
+    ASSERT_TRUE(base::PathService::Get(chrome::DIR_TEST_DATA, &test_dir));
+
+    for (size_t i = 0; i < 3; ++i) {
+      servers_.emplace_back(std::make_unique<net::EmbeddedTestServer>());
+      servers_[i]->ServeFilesFromDirectory(test_dir);
+      ASSERT_TRUE(servers_[i]->Start());
+    }
+  }
+
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    command_line->AppendSwitch(
+        switches::kEnableExperimentalWebPlatformFeatures);
+    command_line->AppendSwitchASCII(
+        switches::kAutoSelectTabCaptureSourceByTitle, kCapturedTabTitle);
+  }
+
+  void TearDownOnMainThread() override {
+    for (auto& server : servers_) {
+      if (server) {
+        ASSERT_TRUE(server->ShutdownAndWaitUntilComplete());
+      }
+    }
+
+    WebRtcTestBase::TearDownOnMainThread();
+  }
+
+  // Same as WebRtcTestBase::OpenTestPageInNewTab, but does not assume
+  // a single embedded server is used for all pages.
+  WebContents* OpenTestPageInNewTab(const std::string& test_page,
+                                    net::EmbeddedTestServer* server) const {
+    chrome::AddTabAt(browser(), GURL(url::kAboutBlankURL), -1, true);
+    GURL url = server->GetURL(test_page);
+    ui_test_utils::NavigateToURL(browser(), url);
+    WebContents* new_tab = browser()->tab_strip_model()->GetActiveWebContents();
+    permissions::PermissionRequestManager::FromWebContents(new_tab)
+        ->set_auto_response_for_test(
+            permissions::PermissionRequestManager::ACCEPT_ALL);
+    return new_tab;
+  }
+
+  TabInfo SetUpCapturingPage(bool start_capturing) {
+    OpenTestPageInNewTab(kCapturingPageMain, servers_[kCapturingServer].get());
+
+    auto result = MakeTabInfoFromActiveTab(browser(), true, "capturing_page");
+    if (start_capturing) {
+      result.StartCapturing();
+    }
+
+    event_sinks_.push_back(result.web_contents);
+
+    return result;
+  }
+
+  TabInfo SetUpCapturedPage(bool expose_origin,
+                            const std::string& handle,
+                            const std::vector<std::string>& permitted_origins,
+                            bool self_capture = false) {
+    // Normally, the captured page has its own server (=origin) and own file.
+    // But if self-capture is tested, use the origin and page of the capturer.
+    const char* page = self_capture ? kCapturingPageMain : kCapturedPageMain;
+    const int server_index = self_capture ? kCapturingServer : kCapturedServer;
+
+    auto* const web_contents =
+        OpenTestPageInNewTab(page, servers_[server_index].get());
+
+    // The target for getDisplayMedia is determined via the title. If we want
+    // the capturing page to capture itself, then it has to change its title.
+    if (self_capture) {
+      std::string script_result;
+      EXPECT_TRUE(content::ExecuteScriptAndExtractString(
+          web_contents->GetMainFrame(),
+          base::StringPrintf("setTitle(\"%s\");", kCapturedTabTitle),
+          &script_result));
+      EXPECT_EQ(script_result, "title-changed");
+    }
+
+    auto tab_info = MakeTabInfoFromActiveTab(browser(), expose_origin, handle);
+
+    tab_info.SetCaptureHandleConfig(expose_origin, handle, permitted_origins);
+
+    return tab_info;
+  }
+
+  static constexpr size_t kCapturedServer = 0;
+  static constexpr size_t kCapturingServer = 1;
+  static constexpr size_t kOtherCapturedServer = 2;
+
+  // Checked for no unconsumed events.
+  std::vector<WebContents*> event_sinks_;
+
+  // Three servers to create three origins (different ports). One server for the
+  // captured page, one for the top-level capturer and one for the embedded
+  // capturer. Some tests will use one server for multiple pages so as to
+  // make them same-origin.
+  std::vector<std::unique_ptr<net::EmbeddedTestServer>> servers_;
+};
+
+IN_PROC_BROWSER_TEST_F(CaptureHandleBrowserTest,
+                       HandleAndOriginExposedIfAllPermitted) {
+  TabInfo captured_tab =
+      SetUpCapturedPage(/*expose_origin=*/true, "handle", {"*"});
+
+  TabInfo capturing_tab = SetUpCapturingPage(/*start_capturing=*/true);
+
+  // The capture handle set by the captured tab is observable by the capturer.
+  EXPECT_EQ(capturing_tab.ReadCaptureHandleFromSettings(),
+            captured_tab.capture_handle);
+}
+
+IN_PROC_BROWSER_TEST_F(CaptureHandleBrowserTest,
+                       HandleAndOriginExposedIfCapturerOriginPermitted) {
+  TabInfo capturing_tab = SetUpCapturingPage(/*start_capturing=*/false);
+
+  TabInfo captured_tab = SetUpCapturedPage(/*expose_origin=*/true, "handle",
+                                           {capturing_tab.GetOriginAsString()});
+
+  capturing_tab.StartCapturing();
+
+  // The capture handle set by the captured tab is observable by the capturer.
+  EXPECT_EQ(capturing_tab.ReadCaptureHandleFromSettings(),
+            captured_tab.capture_handle);
+}
+
+IN_PROC_BROWSER_TEST_F(CaptureHandleBrowserTest,
+                       HandleAndOriginNotExposedIfCapturerOriginNotPermitted) {
+  TabInfo capturing_tab = SetUpCapturingPage(/*start_capturing=*/false);
+
+  TabInfo captured_tab =
+      SetUpCapturedPage(/*expose_origin=*/true, "handle", {kArbitraryOrigin});
+
+  capturing_tab.StartCapturing();
+
+  // The capture handle isn't observable by the capturer.
+  EXPECT_EQ(capturing_tab.ReadCaptureHandleFromSettings(), kNoCaptureHandle);
+}
+
+IN_PROC_BROWSER_TEST_F(CaptureHandleBrowserTest, CanExposeOnlyHandle) {
+  TabInfo captured_tab =
+      SetUpCapturedPage(/*expose_origin=*/false, "handle", {"*"});
+  ASSERT_EQ(captured_tab.capture_handle.find("origin"), std::string::npos);
+
+  TabInfo capturing_tab = SetUpCapturingPage(/*start_capturing=*/true);
+
+  // The capture handle set by the captured tab is observable by the capturer.
+  EXPECT_EQ(capturing_tab.ReadCaptureHandleFromSettings(),
+            captured_tab.capture_handle);
+}
+
+IN_PROC_BROWSER_TEST_F(CaptureHandleBrowserTest,
+                       CanExposeEmptyHandleIfExposingOrigin) {
+  TabInfo captured_tab =
+      SetUpCapturedPage(/*expose_origin=*/true, /*handle=*/"", {"*"});
+  // Still expecting "handle: \"\"" in there.
+  ASSERT_NE(captured_tab.capture_handle.find("handle"), std::string::npos);
+
+  TabInfo capturing_tab = SetUpCapturingPage(/*start_capturing=*/true);
+
+  // The capture handle set by the captured tab is observable by the capturer.
+  EXPECT_EQ(capturing_tab.ReadCaptureHandleFromSettings(),
+            captured_tab.capture_handle);
+}
+
+IN_PROC_BROWSER_TEST_F(CaptureHandleBrowserTest,
+                       EmptyCaptureHandleConfigMeansCaptureHandleNotExposed) {
+  // Note - even if we set permitted origins, the empty config is empty.
+  TabInfo captured_tab =
+      SetUpCapturedPage(/*expose_origin=*/false, /*handle=*/"", {"*"});
+  // Not expecting "handle: \"\"" in there, nor "origin:..."
+  ASSERT_EQ(captured_tab.capture_handle, "");
+
+  TabInfo capturing_tab = SetUpCapturingPage(/*start_capturing=*/true);
+
+  // The capture handle isn't observable by the capturer.
+  EXPECT_EQ(capturing_tab.ReadCaptureHandleFromSettings(), kNoCaptureHandle);
+}
+
+IN_PROC_BROWSER_TEST_F(
+    CaptureHandleBrowserTest,
+    CallingSetCaptureHandleConfigWithEmptyConfigFiresEventAndClearsValue) {
+  TabInfo captured_tab =
+      SetUpCapturedPage(/*expose_origin=*/true, "handle", {"*"});
+
+  TabInfo capturing_tab = SetUpCapturingPage(/*start_capturing=*/true);
+
+  // The capture handle set by the captured tab is observable by the capturer.
+  EXPECT_EQ(capturing_tab.ReadCaptureHandleFromSettings(),
+            captured_tab.capture_handle);
+
+  // New CaptureHandleConfig set by captured tab triggers an event, and all
+  // subsequent calls to getSettings produce the new values.
+  captured_tab.SetCaptureHandleConfig(/*expose_origin=*/false, "", {});
+  EXPECT_EQ(capturing_tab.LastEvent(), "{}");
+  EXPECT_EQ(capturing_tab.ReadCaptureHandleFromSettings(), kNoCaptureHandle);
+}
+
+IN_PROC_BROWSER_TEST_F(
+    CaptureHandleBrowserTest,
+    CallingSetCaptureHandleConfigWithNewHandleChangesConfigAndFiresEvent) {
+  TabInfo captured_tab =
+      SetUpCapturedPage(/*expose_origin=*/true, "handle", {"*"});
+
+  TabInfo capturing_tab = SetUpCapturingPage(/*start_capturing=*/true);
+
+  // The capture handle set by the captured tab is observable by the capturer.
+  EXPECT_EQ(capturing_tab.ReadCaptureHandleFromSettings(),
+            captured_tab.capture_handle);
+
+  // New CaptureHandleConfig set by captured tab triggers an event, and all
+  // subsequent calls to getSettings produce the new values.
+  captured_tab.SetCaptureHandleConfig(/*expose_origin=*/true, "new_handle",
+                                      {"*"});
+  EXPECT_EQ(capturing_tab.LastEvent(), captured_tab.capture_handle);
+  EXPECT_EQ(capturing_tab.ReadCaptureHandleFromSettings(),
+            captured_tab.capture_handle);
+}
+
+IN_PROC_BROWSER_TEST_F(
+    CaptureHandleBrowserTest,
+    CallingSetCaptureHandleConfigWithNewOriginValueChangesConfigAndFiresEvent) {
+  TabInfo captured_tab =
+      SetUpCapturedPage(/*expose_origin=*/true, "handle", {"*"});
+
+  TabInfo capturing_tab = SetUpCapturingPage(/*start_capturing=*/true);
+
+  // The capture handle set by the captured tab is observable by the capturer.
+  EXPECT_EQ(capturing_tab.ReadCaptureHandleFromSettings(),
+            captured_tab.capture_handle);
+
+  // New CaptureHandleConfig set by captured tab triggers an event, and all
+  // subsequent calls to getSettings produce the new values.
+  captured_tab.SetCaptureHandleConfig(/*expose_origin=*/false, "handle", {"*"});
+  EXPECT_EQ(capturing_tab.LastEvent(), captured_tab.capture_handle);
+  EXPECT_EQ(capturing_tab.ReadCaptureHandleFromSettings(),
+            captured_tab.capture_handle);
+}
+
+IN_PROC_BROWSER_TEST_F(
+    CaptureHandleBrowserTest,
+    PermittedOriginsChangeThatRemovesCapturerCausesEventAndEmptyConfig) {
+  TabInfo captured_tab =
+      SetUpCapturedPage(/*expose_origin=*/true, "handle", {"*"});
+
+  TabInfo capturing_tab = SetUpCapturingPage(/*start_capturing=*/true);
+
+  // The capture handle set by the captured tab is observable by the capturer.
+  EXPECT_EQ(capturing_tab.ReadCaptureHandleFromSettings(),
+            captured_tab.capture_handle);
+
+  // New CaptureHandleConfig set by captured tab triggers an event, and all
+  // subsequent calls to getSettings produce the new values.
+  captured_tab.SetCaptureHandleConfig(/*expose_origin=*/true, "handle",
+                                      {kArbitraryOrigin});
+  EXPECT_EQ(capturing_tab.LastEvent(), "{}");
+  EXPECT_EQ(capturing_tab.ReadCaptureHandleFromSettings(), kNoCaptureHandle);
+}
+
+IN_PROC_BROWSER_TEST_F(
+    CaptureHandleBrowserTest,
+    PermittedOriginsChangeThatAddsCapturerCausesEventAndConfigExposure) {
+  TabInfo captured_tab =
+      SetUpCapturedPage(/*expose_origin=*/true, "handle", {kArbitraryOrigin});
+
+  TabInfo capturing_tab = SetUpCapturingPage(/*start_capturing=*/true);
+
+  // The capture handle set by the captured tab is observable by the capturer.
+  EXPECT_EQ(capturing_tab.ReadCaptureHandleFromSettings(), kNoCaptureHandle);
+
+  // New CaptureHandleConfig set by captured tab triggers an event, and all
+  // subsequent calls to getSettings produce the new values.
+  captured_tab.SetCaptureHandleConfig(/*expose_origin=*/true, "handle", {"*"});
+  EXPECT_EQ(capturing_tab.LastEvent(), captured_tab.capture_handle);
+  EXPECT_EQ(capturing_tab.ReadCaptureHandleFromSettings(),
+            captured_tab.capture_handle);
+}
+
+IN_PROC_BROWSER_TEST_F(
+    CaptureHandleBrowserTest,
+    PermittedOriginsChangeThatDoesNotAffectCapturerDoesNotCauseEventOrChange) {
+  TabInfo captured_tab =
+      SetUpCapturedPage(/*expose_origin=*/true, "handle", {"*"});
+
+  TabInfo capturing_tab = SetUpCapturingPage(/*start_capturing=*/true);
+
+  // The capture handle set by the captured tab is observable by the capturer.
+  EXPECT_EQ(capturing_tab.ReadCaptureHandleFromSettings(),
+            captured_tab.capture_handle);
+
+  // New CaptureHandleConfig set by captured tab triggers an event, and all
+  // subsequent calls to getSettings produce the new values.
+  captured_tab.SetCaptureHandleConfig(/*expose_origin=*/true, "handle",
+                                      {capturing_tab.GetOriginAsString()});
+  EXPECT_EQ(capturing_tab.ReadCaptureHandleFromSettings(),
+            captured_tab.capture_handle);
+}
+
+IN_PROC_BROWSER_TEST_F(CaptureHandleBrowserTest,
+                       SameDocumentNavigationDoesNotClearTheCaptureHandle) {
+  TabInfo captured_tab =
+      SetUpCapturedPage(/*expose_origin=*/true, "handle", {"*"});
+
+  TabInfo capturing_tab = SetUpCapturingPage(/*start_capturing=*/true);
+
+  // Sanity test - there was an initial handle.
+  EXPECT_EQ(capturing_tab.ReadCaptureHandleFromSettings(),
+            captured_tab.capture_handle);
+
+  // In-document navigation does not change the capture handle (config).
+  std::string navigation_result;
+  EXPECT_TRUE(content::ExecuteScriptAndExtractString(
+      captured_tab.web_contents->GetMainFrame(), "clickLinkToPageBottom();",
+      &navigation_result));
+  ASSERT_EQ(navigation_result, "navigated");
+
+  // No event was fired (verified in teardown) and getSettings returns the
+  // same configuration as previously.
+  EXPECT_EQ(capturing_tab.ReadCaptureHandleFromSettings(),
+            captured_tab.capture_handle);
+}
+
+IN_PROC_BROWSER_TEST_F(CaptureHandleBrowserTest,
+                       CrossDocumentNavigationClearsTheCaptureHandle) {
+  TabInfo captured_tab =
+      SetUpCapturedPage(/*expose_origin=*/true, "handle", {"*"});
+
+  TabInfo capturing_tab = SetUpCapturingPage(/*start_capturing=*/true);
+
+  // Sanity test - there was an initial handle.
+  EXPECT_EQ(capturing_tab.ReadCaptureHandleFromSettings(),
+            captured_tab.capture_handle);
+
+  // Cross-document navigation clears the capture handle (config).
+  captured_tab.Navigate(browser(),
+                        servers_[kCapturedServer]->GetURL(kCapturedPageOther),
+                        /*expect_handle_reset=*/true);
+
+  // Navigation cleared the the capture handle, and that fired an event
+  // with the empty CaptureHandle.
+  EXPECT_EQ(capturing_tab.LastEvent(), "{}");
+  EXPECT_EQ(capturing_tab.ReadCaptureHandleFromSettings(), kNoCaptureHandle);
+}
+
+IN_PROC_BROWSER_TEST_F(CaptureHandleBrowserTest,
+                       CrossOriginNavigationClearsTheCaptureHandle) {
+  TabInfo captured_tab =
+      SetUpCapturedPage(/*expose_origin=*/true, "handle", {"*"});
+
+  TabInfo capturing_tab = SetUpCapturingPage(/*start_capturing=*/true);
+
+  // Sanity test - there was an initial handle.
+  EXPECT_EQ(capturing_tab.ReadCaptureHandleFromSettings(),
+            captured_tab.capture_handle);
+
+  // Sanity over the test itself - the new server has a different origin.
+  ASSERT_FALSE(url::Origin::Create(servers_[kOtherCapturedServer]->base_url())
+                   .IsSameOriginWith(captured_tab.GetOrigin()));
+
+  // Cross-origin navigation clears the capture handle (config) and fires
+  // an event with the empty CaptureHandle.
+  captured_tab.Navigate(
+      browser(), servers_[kOtherCapturedServer]->GetURL(kCapturedPageOther),
+      /*expect_handle_reset=*/true);
+  EXPECT_EQ(capturing_tab.LastEvent(), "{}");
+  EXPECT_EQ(capturing_tab.ReadCaptureHandleFromSettings(), kNoCaptureHandle);
+}
+
+IN_PROC_BROWSER_TEST_F(CaptureHandleBrowserTest,
+                       SelfCaptureSanityWhenPermitted) {
+  TabInfo tab = SetUpCapturedPage(/*expose_origin=*/true, "handle", {"*"},
+                                  /*self_capture=*/true);
+  tab.StartCapturing();
+
+  // Correct initial value read.
+  EXPECT_EQ(tab.ReadCaptureHandleFromSettings(), tab.capture_handle);
+
+  // Events correctly fired when self-capturing.
+  tab.SetCaptureHandleConfig(/*expose_origin=*/true, "new_handle", {"*"});
+  EXPECT_EQ(tab.LastEvent(), tab.capture_handle);
+  EXPECT_EQ(tab.ReadCaptureHandleFromSettings(), tab.capture_handle);
+}
+
+IN_PROC_BROWSER_TEST_F(CaptureHandleBrowserTest,
+                       SelfCaptureSanityWhenNotPermitted) {
+  TabInfo tab =
+      SetUpCapturedPage(/*expose_origin=*/true, "handle", {kArbitraryOrigin},
+                        /*self_capture=*/true);
+
+  ASSERT_TRUE(tab.GetOrigin().IsSameOriginWith(tab.GetOrigin()));
+
+  tab.StartCapturing();
+
+  // Correct initial value read.
+  EXPECT_EQ(tab.ReadCaptureHandleFromSettings(), kNoCaptureHandle);
+
+  // No events fired when self-capturing but not allowed to observe..
+  tab.SetCaptureHandleConfig(/*expose_origin=*/true, "new_handle",
+                             {kArbitraryOrigin});
+  EXPECT_EQ(tab.ReadCaptureHandleFromSettings(), kNoCaptureHandle);
+}
+
+#endif  //  !BUILDFLAG(IS_CHROMEOS_LACROS)
diff --git a/chrome/browser/password_manager/multi_profile_credentials_filter_unittest.cc b/chrome/browser/password_manager/multi_profile_credentials_filter_unittest.cc
index 072c7c4..3a430ee 100644
--- a/chrome/browser/password_manager/multi_profile_credentials_filter_unittest.cc
+++ b/chrome/browser/password_manager/multi_profile_credentials_filter_unittest.cc
@@ -45,6 +45,10 @@
     return nullptr;
   }
   void ShowProfileCustomizationBubble(Browser* browser) override {}
+  void ShowEnterpriseProfileInterceptionDialog(
+      const std::string& email,
+      base::OnceCallback<void(bool)> callback,
+      Browser* browser) override {}
 };
 
 class TestPasswordManagerClient
diff --git a/chrome/browser/policy/cloud/user_policy_signin_service.cc b/chrome/browser/policy/cloud/user_policy_signin_service.cc
index 92f2902..bddcf11d 100644
--- a/chrome/browser/policy/cloud/user_policy_signin_service.cc
+++ b/chrome/browser/policy/cloud/user_policy_signin_service.cc
@@ -17,8 +17,10 @@
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/signin/account_id_from_account_info.h"
 #include "chrome/browser/signin/signin_util.h"
+#include "chrome/common/pref_names.h"
 #include "components/policy/core/common/cloud/cloud_policy_client_registration_helper.h"
 #include "components/policy/core/common/cloud/user_cloud_policy_manager.h"
+#include "components/prefs/pref_change_registrar.h"
 #include "components/signin/public/identity_manager/account_info.h"
 #include "components/signin/public/identity_manager/consent_level.h"
 #include "content/public/browser/notification_details.h"
@@ -49,6 +51,13 @@
   // happens in the background after PKS initialization - so this service
   // should always be created before the oauth token is available.
   DCHECK(!CanApplyPoliciesForSignedInUser(/*check_for_refresh_token=*/true));
+
+  profile_pref_change_registrar_.Init(profile->GetPrefs());
+  profile_pref_change_registrar_.Add(
+      prefs::kUserAcceptedAccountManagement,
+      base::BindRepeating(
+          &UserPolicySigninService::OnAccountManagementPrefChange,
+          base::Unretained(this)));
 }
 
 UserPolicySigninService::~UserPolicySigninService() {
@@ -146,6 +155,8 @@
     return;
   }
 
+  profile_pref_change_registrar_.RemoveAll();
+
   InitializeForSignedInUser(
       AccountIdFromAccountInfo(
           identity_manager()->GetPrimaryAccountInfo(consent_level())),
@@ -154,6 +165,11 @@
           ->GetURLLoaderFactoryForBrowserProcess());
 }
 
+void UserPolicySigninService::OnAccountManagementPrefChange() {
+  if (CanApplyPoliciesForSignedInUser(/*check_for_refresh_token=*/true))
+    TryInitializeForSignedInUser();
+}
+
 void UserPolicySigninService::InitializeUserCloudPolicyManager(
     const AccountId& account_id,
     std::unique_ptr<CloudPolicyClient> client) {
diff --git a/chrome/browser/policy/cloud/user_policy_signin_service.h b/chrome/browser/policy/cloud/user_policy_signin_service.h
index 6d9c042..8a8b83c 100644
--- a/chrome/browser/policy/cloud/user_policy_signin_service.h
+++ b/chrome/browser/policy/cloud/user_policy_signin_service.h
@@ -12,6 +12,7 @@
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
 #include "chrome/browser/policy/cloud/user_policy_signin_service_base.h"
+#include "components/prefs/pref_change_registrar.h"
 #include "components/signin/public/identity_manager/identity_manager.h"
 
 class AccountId;
@@ -86,6 +87,9 @@
   // cloud policy.
   void ProhibitSignoutIfNeeded();
 
+  // Called when the value of the `profile.account_management` pref changes.
+  void OnAccountManagementPrefChange();
+
   // Helper method that attempts calls |InitializeForSignedInUser| only if
   // |policy_manager| is not-nul. Expects that there is a refresh token for
   // the primary account.
@@ -96,6 +100,7 @@
                                       PolicyRegistrationCallback callback);
 
   std::unique_ptr<CloudPolicyClientRegistrationHelper> registration_helper_;
+  PrefChangeRegistrar profile_pref_change_registrar_;
 
   DISALLOW_COPY_AND_ASSIGN(UserPolicySigninService);
 };
diff --git a/chrome/browser/printing/print_backend_browsertest.cc b/chrome/browser/printing/print_backend_browsertest.cc
index de068b5..1fdc52b5 100644
--- a/chrome/browser/printing/print_backend_browsertest.cc
+++ b/chrome/browser/printing/print_backend_browsertest.cc
@@ -25,7 +25,6 @@
 #include "printing/mojom/print.mojom.h"
 #include "testing/gmock/include/gmock/gmock-matchers.h"
 #include "testing/gtest/include/gtest/gtest.h"
-#include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace printing {
 
@@ -103,9 +102,9 @@
   }
 
   void OnDidGetDefaultPrinterName(
-      absl::optional<std::string>& capture_printer_name,
-      const absl::optional<std::string>& printer_name) {
-    capture_printer_name = printer_name;
+      mojom::DefaultPrinterNameResultPtr& capture_printer_name,
+      mojom::DefaultPrinterNameResultPtr printer_name) {
+    capture_printer_name = std::move(printer_name);
     CheckForQuit();
   }
 
@@ -166,7 +165,7 @@
   // Launch the service, but without initializing to desired locale.
   LaunchUninitialized();
 
-  absl::optional<std::string> default_printer_name;
+  mojom::DefaultPrinterNameResultPtr default_printer_name;
   mojom::PrinterSemanticCapsAndDefaultsResultPtr printer_caps;
 
   // Safe to use base::Unretained(this) since waiting locally on the callback
@@ -175,7 +174,9 @@
       base::BindOnce(&PrintBackendBrowserTest::OnDidGetDefaultPrinterName,
                      base::Unretained(this), std::ref(default_printer_name)));
   WaitUntilCallbackReceived();
-  EXPECT_FALSE(default_printer_name.has_value());
+  ASSERT_TRUE(default_printer_name->is_result_code());
+  EXPECT_EQ(default_printer_name->get_result_code(),
+            mojom::ResultCode::kFailed);
 
   GetPrintBackendService()->GetPrinterSemanticCapsAndDefaults(
       kDefaultPrinterName,
@@ -211,7 +212,7 @@
   LaunchService();
   AddDefaultPrinter();
 
-  absl::optional<std::string> default_printer_name;
+  mojom::DefaultPrinterNameResultPtr default_printer_name;
 
   // Safe to use base::Unretained(this) since waiting locally on the callback
   // forces a shorter lifetime than `this`.
@@ -219,8 +220,9 @@
       base::BindOnce(&PrintBackendBrowserTest::OnDidGetDefaultPrinterName,
                      base::Unretained(this), std::ref(default_printer_name)));
   WaitUntilCallbackReceived();
-  ASSERT_TRUE(default_printer_name.has_value());
-  EXPECT_EQ(default_printer_name.value(), kDefaultPrinterName);
+  ASSERT_TRUE(default_printer_name->is_default_printer_name());
+  EXPECT_EQ(default_printer_name->get_default_printer_name(),
+            kDefaultPrinterName);
 }
 
 IN_PROC_BROWSER_TEST_F(PrintBackendBrowserTest,
diff --git a/chrome/browser/profiles/gaia_info_update_service.cc b/chrome/browser/profiles/gaia_info_update_service.cc
index b58c719..0567f7c 100644
--- a/chrome/browser/profiles/gaia_info_update_service.cc
+++ b/chrome/browser/profiles/gaia_info_update_service.cc
@@ -71,12 +71,8 @@
     ClearProfileEntry();
   }
 
-  auto maybe_account_info =
-      identity_manager_
-          ->FindExtendedAccountInfoForAccountWithRefreshTokenByAccountId(
-              unconsented_primary_account_info.account_id);
-  if (maybe_account_info.has_value())
-    UpdatePrimaryAccount(maybe_account_info.value());
+  UpdatePrimaryAccount(identity_manager_->FindExtendedAccountInfoByAccountId(
+      unconsented_primary_account_info.account_id));
 }
 
 void GAIAInfoUpdateService::UpdatePrimaryAccount(const AccountInfo& info) {
@@ -197,12 +193,8 @@
     // downloaded).
     for (gaia::ListedAccount account :
          accounts_in_cookie_jar_info.signed_in_accounts) {
-      auto maybe_account_info =
-          identity_manager_
-              ->FindExtendedAccountInfoForAccountWithRefreshTokenByAccountId(
-                  account.id);
-      if (maybe_account_info.has_value())
-        UpdateAnyAccount(*maybe_account_info);
+      UpdateAnyAccount(
+          identity_manager_->FindExtendedAccountInfoByAccountId(account.id));
     }
   }
 }
diff --git a/chrome/browser/profiles/profile_downloader.cc b/chrome/browser/profiles/profile_downloader.cc
index 744a4c8..bc7e6c3 100644
--- a/chrome/browser/profiles/profile_downloader.cc
+++ b/chrome/browser/profiles/profile_downloader.cc
@@ -70,10 +70,6 @@
   StartFetchingOAuth2AccessToken();
 }
 
-std::u16string ProfileDownloader::GetProfileHostedDomain() const {
-  return base::UTF8ToUTF16(account_info_.hosted_domain);
-}
-
 std::u16string ProfileDownloader::GetProfileFullName() const {
   return base::UTF8ToUTF16(account_info_.full_name);
 }
@@ -107,12 +103,10 @@
 
 void ProfileDownloader::StartFetchingImage() {
   VLOG(1) << "Fetching user entry with token: " << auth_token_;
-  auto maybe_account_info =
-      identity_manager_
-          ->FindExtendedAccountInfoForAccountWithRefreshTokenByAccountId(
-              account_id_);
-  if (maybe_account_info.has_value())
-    account_info_ = maybe_account_info.value();
+  AccountInfo maybe_account_info =
+      identity_manager_->FindExtendedAccountInfoByAccountId(account_id_);
+  if (!maybe_account_info.IsEmpty())
+    account_info_ = maybe_account_info;
 
   if (account_info_.IsValid()) {
     // FetchImageData might call the delegate's OnProfileDownloadSuccess
diff --git a/chrome/browser/profiles/profile_downloader.h b/chrome/browser/profiles/profile_downloader.h
index decb637..41280fd 100644
--- a/chrome/browser/profiles/profile_downloader.h
+++ b/chrome/browser/profiles/profile_downloader.h
@@ -51,9 +51,6 @@
   // token is available. Should not be called more than once.
   virtual void StartForAccount(const CoreAccountId& account_id);
 
-  // On successful download this returns the hosted domain of the user.
-  virtual std::u16string GetProfileHostedDomain() const;
-
   // On successful download this returns the full name of the user. For example
   // "Pat Smith".
   virtual std::u16string GetProfileFullName() const;
diff --git a/chrome/browser/resources/BUILD.gn b/chrome/browser/resources/BUILD.gn
index a226f4a7..9c1a726c 100644
--- a/chrome/browser/resources/BUILD.gn
+++ b/chrome/browser/resources/BUILD.gn
@@ -106,7 +106,6 @@
     if (is_linux || is_chromeos || is_win || is_mac) {
       deps += [
         "bluetooth_internals:closure_compile",
-        "bookmarks:closure_compile",
         "commander:closure_compile",
         "discards:closure_compile",
         "download_internals:closure_compile",
@@ -149,9 +148,6 @@
         "nearby_share/shared:closure_compile_module",
       ]
     }
-    if (enable_extensions) {
-      deps += [ "extensions:closure_compile" ]
-    }
     if (enable_nacl) {
       deps += [ "about_nacl:closure_compile" ]
     }
diff --git a/chrome/browser/resources/bookmarks/BUILD.gn b/chrome/browser/resources/bookmarks/BUILD.gn
index dd8bd076..85805f9d 100644
--- a/chrome/browser/resources/bookmarks/BUILD.gn
+++ b/chrome/browser/resources/bookmarks/BUILD.gn
@@ -3,14 +3,15 @@
 # found in the LICENSE file.
 
 import("//chrome/common/features.gni")
-import("//third_party/closure_compiler/compile_js.gni")
 import("//tools/grit/grit_rule.gni")
 import("//tools/grit/preprocess_if_expr.gni")
 import("//tools/polymer/html_to_js.gni")
+import("//tools/typescript/ts_library.gni")
 import("//ui/webui/resources/tools/generate_grd.gni")
 import("../tools/optimize_webui.gni")
 
 preprocess_folder = "preprocessed"
+tsc_folder = "tsc"
 preprocess_manifest = "preprocessed_manifest.json"
 preprocess_gen_manifest = "preprocessed_gen_manifest.json"
 
@@ -19,14 +20,13 @@
 
   optimize_webui("build") {
     host = "bookmarks"
-    input = rebase_path("$target_gen_dir/$preprocess_folder", root_build_dir)
+    input = rebase_path("$target_gen_dir/$tsc_folder", root_build_dir)
     js_out_files = [ "bookmarks.rollup.js" ]
     js_module_in_files = [ "bookmarks.js" ]
     out_manifest = "$target_gen_dir/$build_manifest"
 
     deps = [
-      ":preprocess",
-      ":preprocess_generated",
+      ":build_ts",
       "../../../../ui/webui/resources:preprocess",
     ]
     excludes = [ "chrome://resources/js/cr.m.js" ]
@@ -42,14 +42,8 @@
     manifest_files = [ "$target_gen_dir/$build_manifest" ]
     resource_path_rewrites = [ "bookmarks.rollup.js|bookmarks.js" ]
   } else {
-    deps = [
-      ":preprocess",
-      ":preprocess_generated",
-    ]
-    manifest_files = [
-      "$target_gen_dir/$preprocess_manifest",
-      "$target_gen_dir/$preprocess_gen_manifest",
-    ]
+    deps = [ ":build_ts" ]
+    manifest_files = [ "$target_gen_dir/tsconfig.manifest" ]
   }
   grd_prefix = "bookmarks"
   out_grd = "$target_gen_dir/${grd_prefix}_resources.grd"
@@ -60,20 +54,20 @@
   out_folder = "$target_gen_dir/$preprocess_folder"
   out_manifest = "$target_gen_dir/$preprocess_manifest"
   in_files = [
-    "actions.js",
-    "api_listener.js",
-    "bookmarks.js",
-    "browser_proxy.js",
-    "constants.js",
-    "debouncer.js",
-    "dialog_focus_manager.js",
-    "dnd_manager.js",
-    "mouse_focus_behavior.js",
-    "reducers.js",
-    "store_client.js",
-    "store.js",
-    "types.js",
-    "util.js",
+    "actions.ts",
+    "api_listener.ts",
+    "bookmarks.ts",
+    "browser_proxy.ts",
+    "constants.ts",
+    "debouncer.ts",
+    "dialog_focus_manager.ts",
+    "dnd_manager.ts",
+    "mouse_focus_behavior.ts",
+    "reducers.ts",
+    "store_client.ts",
+    "store.ts",
+    "types.ts",
+    "util.ts",
   ]
 }
 
@@ -83,31 +77,31 @@
   out_folder = "$target_gen_dir/$preprocess_folder"
   out_manifest = "$target_gen_dir/$preprocess_gen_manifest"
   in_files = [
-    "app.js",
-    "command_manager.js",
-    "edit_dialog.js",
-    "folder_node.js",
-    "item.js",
-    "list.js",
-    "router.js",
-    "shared_style.js",
-    "shared_vars.js",
-    "toolbar.js",
+    "app.ts",
+    "command_manager.ts",
+    "edit_dialog.ts",
+    "folder_node.ts",
+    "item.ts",
+    "list.ts",
+    "router.ts",
+    "shared_style.ts",
+    "shared_vars.ts",
+    "toolbar.ts",
   ]
 }
 
 html_to_js("web_components") {
   js_files = [
-    "app.js",
-    "command_manager.js",
-    "edit_dialog.js",
-    "folder_node.js",
-    "item.js",
-    "list.js",
-    "router.js",
-    "shared_style.js",
-    "shared_vars.js",
-    "toolbar.js",
+    "app.ts",
+    "command_manager.ts",
+    "edit_dialog.ts",
+    "folder_node.ts",
+    "item.ts",
+    "list.ts",
+    "router.ts",
+    "shared_style.ts",
+    "shared_vars.ts",
+    "toolbar.ts",
   ]
 }
 
@@ -128,261 +122,51 @@
   output_dir = "$root_gen_dir/chrome"
 }
 
-js_type_check("closure_compile") {
-  is_polymer3 = true
+ts_library("build_ts") {
+  root_dir = "$target_gen_dir/$preprocess_folder"
+  out_dir = "$target_gen_dir/$tsc_folder"
+  tsconfig_base = "tsconfig_base.json"
+  in_files = [
+    "actions.ts",
+    "api_listener.ts",
+    "app.ts",
+    "bookmarks.ts",
+    "browser_proxy.ts",
+    "command_manager.ts",
+    "constants.ts",
+    "debouncer.ts",
+    "dialog_focus_manager.ts",
+    "dnd_manager.ts",
+    "edit_dialog.ts",
+    "folder_node.ts",
+    "item.ts",
+    "list.ts",
+    "mouse_focus_behavior.ts",
+    "reducers.ts",
+    "router.ts",
+    "shared_vars.ts",
+    "shared_style.ts",
+    "store.ts",
+    "store_client.ts",
+    "toolbar.ts",
+    "types.ts",
+    "util.ts",
+  ]
+  definitions = [
+    "//tools/typescript/definitions/bookmarks.d.ts",
+    "//tools/typescript/definitions/bookmark_manager_private.d.ts",
+    "//tools/typescript/definitions/chrome_event.d.ts",
+    "//tools/typescript/definitions/chrome_send.d.ts",
+    "//tools/typescript/definitions/metrics_private.d.ts",
+    "//tools/typescript/definitions/tabs.d.ts",
+    "//tools/typescript/definitions/windows.d.ts",
+  ]
   deps = [
-    ":actions",
-    ":api_listener",
-    ":app",
-    ":browser_proxy",
-    ":command_manager",
-    ":constants",
-    ":debouncer",
-    ":dialog_focus_manager",
-    ":dnd_manager",
-    ":edit_dialog",
-    ":folder_node",
-    ":item",
-    ":list",
-    ":mouse_focus_behavior",
-    ":reducers",
-    ":router",
-    ":store",
-    ":store_client",
-    ":toolbar",
-    ":types",
-    ":util",
+    "//third_party/polymer/v3_0:library",
+    "//ui/webui/resources:library",
   ]
-}
-
-js_library("actions") {
-  deps = [
-    ":types",
-    ":util",
-    "//ui/webui/resources/js:cr.m",
-    "//ui/webui/resources/js/cr/ui:store.m",
+  extra_deps = [
+    ":preprocess",
+    ":preprocess_generated",
   ]
-
-  externs_list = [ "$externs_path/bookmarks.js" ]
-}
-
-js_library("api_listener") {
-  deps = [
-    ":actions",
-    ":browser_proxy",
-    ":constants",
-    ":debouncer",
-    ":store",
-    ":util",
-    "//ui/webui/resources/js:cr.m",
-    "//ui/webui/resources/js/cr/ui:store.m",
-  ]
-
-  externs_list = [ "$externs_path/bookmarks.js" ]
-}
-
-js_library("app") {
-  deps = [
-    ":api_listener",
-    ":dnd_manager",
-    ":mouse_focus_behavior",
-    ":router",
-    ":store",
-    ":store_client",
-    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
-    "//ui/webui/resources/cr_elements:find_shortcut_behavior",
-    "//ui/webui/resources/cr_elements/cr_splitter:cr_splitter",
-    "//ui/webui/resources/js:load_time_data.m",
-  ]
-  externs_list = [
-    "$externs_path/bookmarks.js",
-    "$externs_path/metrics_private.js",
-  ]
-}
-
-js_library("browser_proxy") {
-  deps = [
-    ":constants",
-    "//ui/webui/resources/js:cr.m",
-  ]
-}
-
-js_library("command_manager") {
-  deps = [
-    ":actions",
-    ":api_listener",
-    ":browser_proxy",
-    ":dialog_focus_manager",
-    ":edit_dialog",
-    ":store_client",
-    ":types",
-    ":util",
-    "//third_party/polymer/v3_0/components-chromium/iron-a11y-keys-behavior:iron-a11y-keys-behavior",
-    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
-    "//ui/webui/resources/cr_elements/cr_action_menu:cr_action_menu.m",
-    "//ui/webui/resources/cr_elements/cr_lazy_render:cr_lazy_render.m",
-    "//ui/webui/resources/cr_elements/cr_toast:cr_toast_manager.m",
-    "//ui/webui/resources/js:cr.m",
-    "//ui/webui/resources/js:load_time_data.m",
-    "//ui/webui/resources/js:plural_string_proxy",
-    "//ui/webui/resources/js/cr/ui:keyboard_shortcut_list.m",
-  ]
-  externs_list = chrome_extension_public_externs + [
-                   "$externs_path/bookmark_manager_private.js",
-                   "$externs_path/bookmarks.js",
-                 ]
-}
-
-js_library("constants") {
-}
-
-js_library("debouncer") {
-  deps = [
-    "//ui/webui/resources/js:assert.m",
-    "//ui/webui/resources/js:promise_resolver.m",
-  ]
-}
-
-js_library("dialog_focus_manager") {
-  deps = [ "//ui/webui/resources/js:cr.m" ]
-}
-
-js_library("dnd_manager") {
-  deps = [
-    ":api_listener",
-    ":debouncer",
-    ":folder_node",
-    ":store",
-    ":types",
-    ":util",
-  ]
-  externs_list = [
-    "$externs_path/bookmark_manager_private.js",
-    "$externs_path/metrics_private.js",
-  ]
-}
-
-js_library("edit_dialog") {
-  deps = [
-    ":api_listener",
-    ":dialog_focus_manager",
-    ":types",
-    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
-    "//ui/webui/resources/cr_elements/cr_dialog:cr_dialog.m",
-    "//ui/webui/resources/cr_elements/cr_input:cr_input.m",
-    "//ui/webui/resources/js:assert.m",
-    "//ui/webui/resources/js:load_time_data.m",
-  ]
-  externs_list = [ "$externs_path/bookmarks.js" ]
-}
-
-js_library("folder_node") {
-  deps = [
-    ":actions",
-    ":command_manager",
-    ":constants",
-    ":store_client",
-    ":types",
-    ":util",
-    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
-    "//ui/webui/resources/js:util.m",
-  ]
-}
-
-js_library("item") {
-  deps = [
-    ":actions",
-    ":browser_proxy",
-    ":command_manager",
-    ":constants",
-    ":store_client",
-    ":types",
-    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
-    "//ui/webui/resources/js:assert.m",
-    "//ui/webui/resources/js:cr.m",
-    "//ui/webui/resources/js:icon.m",
-    "//ui/webui/resources/js/cr/ui:focus_without_ink.m",
-  ]
-}
-
-js_library("list") {
-  deps = [
-    ":actions",
-    ":command_manager",
-    ":constants",
-    ":item",
-    ":store_client",
-    ":types",
-    ":util",
-    "//third_party/polymer/v3_0/components-chromium/iron-list:iron-list",
-    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
-    "//ui/webui/resources/js:list_property_update_behavior.m",
-    "//ui/webui/resources/js:load_time_data.m",
-    "//ui/webui/resources/js:plural_string_proxy",
-  ]
-}
-
-js_library("mouse_focus_behavior") {
-}
-
-js_library("reducers") {
-  deps = [
-    ":actions",
-    ":types",
-    "//ui/webui/resources/js:assert.m",
-  ]
-}
-
-js_library("router") {
-  deps = [
-    ":actions",
-    ":constants",
-    ":store_client",
-    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
-  ]
-}
-
-js_library("store") {
-  deps = [
-    ":reducers",
-    ":types",
-    ":util",
-    "//ui/webui/resources/js:cr.m",
-    "//ui/webui/resources/js/cr/ui:store.m",
-  ]
-}
-
-js_library("store_client") {
-  deps = [
-    ":store",
-    ":types",
-    "//ui/webui/resources/js:cr.m",
-    "//ui/webui/resources/js/cr/ui:store_client.m",
-  ]
-}
-
-js_library("toolbar") {
-  deps = [
-    ":command_manager",
-    ":constants",
-    ":edit_dialog",
-    ":store_client",
-    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
-    "//ui/webui/resources/cr_elements/cr_action_menu:cr_action_menu.m",
-    "//ui/webui/resources/cr_elements/cr_toolbar:cr_toolbar",
-    "//ui/webui/resources/cr_elements/cr_toolbar:cr_toolbar_search_field",
-    "//ui/webui/resources/js:assert.m",
-    "//ui/webui/resources/js:load_time_data.m",
-  ]
-}
-
-js_library("types") {
-  deps = [ ":constants" ]
-  externs_list = [ "$externs_path/bookmarks.js" ]
-}
-
-js_library("util") {
-  deps = [ ":types" ]
-
-  externs_list = [ "$externs_path/bookmarks.js" ]
 }
diff --git a/chrome/browser/resources/bookmarks/actions.js b/chrome/browser/resources/bookmarks/actions.js
deleted file mode 100644
index a45cd123..0000000
--- a/chrome/browser/resources/bookmarks/actions.js
+++ /dev/null
@@ -1,275 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import {assert} from 'chrome://resources/js/assert.m.js';
-import {Action} from 'chrome://resources/js/cr/ui/store.m.js';
-
-import {IncognitoAvailability, ROOT_NODE_ID} from './constants.js';
-import {BookmarksPageState, NodeMap} from './types.js';
-import {getDescendants, getDisplayedList, normalizeNode} from './util.js';
-
-/**
- * @fileoverview Module for functions which produce action objects. These are
- * listed in one place to document available actions and their parameters.
- */
-
-/**
- * @param {string} id
- * @param {chrome.bookmarks.BookmarkTreeNode} treeNode
- */
-export function createBookmark(id, treeNode) {
-  return {
-    name: 'create-bookmark',
-    id: id,
-    parentId: treeNode.parentId,
-    parentIndex: treeNode.index,
-    node: normalizeNode(treeNode),
-  };
-}
-
-/**
- * @param {string} id
- * @param {{title: string, url: (string|undefined)}} changeInfo
- * @return {!Action}
- */
-export function editBookmark(id, changeInfo) {
-  return {
-    name: 'edit-bookmark',
-    id: id,
-    changeInfo: changeInfo,
-  };
-}
-
-/**
- * @param {string} id
- * @param {string} parentId
- * @param {number} index
- * @param {string} oldParentId
- * @param {number} oldIndex
- * @return {!Action}
- */
-export function moveBookmark(id, parentId, index, oldParentId, oldIndex) {
-  return {
-    name: 'move-bookmark',
-    id: id,
-    parentId: parentId,
-    index: index,
-    oldParentId: oldParentId,
-    oldIndex: oldIndex,
-  };
-}
-
-/**
- * @param {string} id
- * @param {!Array<string>} newChildIds
- */
-export function reorderChildren(id, newChildIds) {
-  return {
-    name: 'reorder-children',
-    id: id,
-    children: newChildIds,
-  };
-}
-
-/**
- * @param {string} id
- * @param {string} parentId
- * @param {number} index
- * @param {NodeMap} nodes
- * @return {!Action}
- */
-export function removeBookmark(id, parentId, index, nodes) {
-  const descendants = getDescendants(nodes, id);
-  return {
-    name: 'remove-bookmark',
-    id: id,
-    descendants: descendants,
-    parentId: parentId,
-    index: index,
-  };
-}
-
-/**
- * @param {NodeMap} nodeMap
- * @return {!Action}
- */
-export function refreshNodes(nodeMap) {
-  return {
-    name: 'refresh-nodes',
-    nodes: nodeMap,
-  };
-}
-
-/**
- * @param {string} id
- * @param {NodeMap} nodes Current node state. Can be omitted in tests.
- * @return {?Action}
- */
-export function selectFolder(id, nodes) {
-  if (nodes && (id === ROOT_NODE_ID || !nodes[id] || nodes[id].url)) {
-    console.warn('Tried to select invalid folder: ' + id);
-    return null;
-  }
-
-  return {
-    name: 'select-folder',
-    id: id,
-  };
-}
-
-/**
- * @param {string} id
- * @param {boolean} open
- * @return {!Action}
- */
-export function changeFolderOpen(id, open) {
-  return {
-    name: 'change-folder-open',
-    id: id,
-    open: open,
-  };
-}
-
-/** @return {!Action} */
-export function clearSearch() {
-  return {
-    name: 'clear-search',
-  };
-}
-
-/** @return {!Action} */
-export function deselectItems() {
-  return {
-    name: 'deselect-items',
-  };
-}
-
-/**
- * @param {string} id
- * @param {BookmarksPageState} state
- * @param {{
- *     clear: boolean,
- *     range: boolean,
- *     toggle: boolean}} config Options for how the selection should change:
- *   - clear: If true, clears the previous selection before adding this one
- *   - range: If true, selects all items from the anchor to this item
- *   - toggle: If true, toggles the selection state of the item. Cannot be
- *     used with clear or range.
- * @return {!Action}
- */
-export function selectItem(id, state, config) {
-  assert(!config.toggle || !config.range);
-  assert(!config.toggle || !config.clear);
-
-  const anchor = state.selection.anchor;
-  const toSelect = [];
-  let newAnchor = id;
-
-  if (config.range && anchor) {
-    const displayedList = getDisplayedList(state);
-    const selectedIndex = displayedList.indexOf(id);
-    assert(selectedIndex !== -1);
-    let anchorIndex = displayedList.indexOf(anchor);
-    if (anchorIndex === -1) {
-      anchorIndex = selectedIndex;
-    }
-
-    // When performing a range selection, don't change the anchor from what
-    // was used in this selection.
-    newAnchor = displayedList[anchorIndex];
-
-    const startIndex = Math.min(anchorIndex, selectedIndex);
-    const endIndex = Math.max(anchorIndex, selectedIndex);
-
-    for (let i = startIndex; i <= endIndex; i++) {
-      toSelect.push(displayedList[i]);
-    }
-  } else {
-    toSelect.push(id);
-  }
-
-  return {
-    name: 'select-items',
-    clear: config.clear,
-    toggle: config.toggle,
-    anchor: newAnchor,
-    items: toSelect,
-  };
-}
-
-/**
- * @param {Array<string>} ids
- * @param {BookmarksPageState} state
- * @param {string=} anchor
- * @return {!Action}
- */
-export function selectAll(ids, state, anchor) {
-  return {
-    name: 'select-items',
-    clear: true,
-    toggle: false,
-    anchor: anchor ? anchor : state.selection.anchor,
-    items: ids,
-  };
-}
-
-/**
- * @param {string} id
- * @return {!Action}
- */
-export function updateAnchor(id) {
-  return {
-    name: 'update-anchor',
-    anchor: id,
-  };
-}
-
-/**
- * @param {string} term
- * @return {!Action}
- */
-export function setSearchTerm(term) {
-  if (!term) {
-    return clearSearch();
-  }
-
-  return {
-    name: 'start-search',
-    term: term,
-  };
-}
-
-/**
- * @param {!Array<string>} ids
- * @return {!Action}
- */
-export function setSearchResults(ids) {
-  return {
-    name: 'finish-search',
-    results: ids,
-  };
-}
-
-/**
- * @param {IncognitoAvailability} availability
- * @return {!Action}
- */
-export function setIncognitoAvailability(availability) {
-  assert(availability !== IncognitoAvailability.FORCED);
-  return {
-    name: 'set-incognito-availability',
-    value: availability,
-  };
-}
-
-/**
- * @param {boolean} canEdit
- * @return {!Action}
- */
-export function setCanEditBookmarks(canEdit) {
-  return {
-    name: 'set-can-edit',
-    value: canEdit,
-  };
-}
diff --git a/chrome/browser/resources/bookmarks/actions.ts b/chrome/browser/resources/bookmarks/actions.ts
new file mode 100644
index 0000000..c4ead8f
--- /dev/null
+++ b/chrome/browser/resources/bookmarks/actions.ts
@@ -0,0 +1,277 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import {assert} from 'chrome://resources/js/assert.m.js';
+import {Action} from 'chrome://resources/js/cr/ui/store.m.js';
+
+import {IncognitoAvailability, ROOT_NODE_ID} from './constants.js';
+import {BookmarkNode, BookmarksPageState, NodeMap} from './types.js';
+import {getDescendants, getDisplayedList, normalizeNode} from './util.js';
+
+/**
+ * @fileoverview Module for functions which produce action objects. These are
+ * listed in one place to document available actions and their parameters.
+ */
+
+export type CreateBookmarkAction = Action & {
+  id: string;
+  parentId: string;
+  parentIndex: number;
+  node: BookmarkNode;
+};
+
+export function createBookmark(
+    id: string,
+    treeNode: chrome.bookmarks.BookmarkTreeNode): CreateBookmarkAction {
+  return {
+    name: 'create-bookmark',
+    id: id,
+    parentId: treeNode.parentId!,
+    parentIndex: treeNode.index!,
+    node: normalizeNode(treeNode),
+  };
+}
+
+export type EditBookmarkAction = Action & {
+  id: string;
+  changeInfo: {title: string, url?: string};
+}
+
+export function editBookmark(
+    id: string, changeInfo: {title: string, url?: string}): EditBookmarkAction {
+  return {
+    name: 'edit-bookmark',
+    id: id,
+    changeInfo: changeInfo,
+  };
+}
+
+export type MoveBookmarkAction = Action & {
+  id: string;
+  parentId: string;
+  index: number;
+  oldParentId: string;
+  oldIndex: number;
+}
+
+export function moveBookmark(
+    id: string, parentId: string, index: number, oldParentId: string,
+    oldIndex: number): MoveBookmarkAction {
+  return {
+    name: 'move-bookmark',
+    id: id,
+    parentId: parentId,
+    index: index,
+    oldParentId: oldParentId,
+    oldIndex: oldIndex,
+  };
+}
+
+export type ReorderChildrenAction = Action & {
+  id: string;
+  children: string[];
+}
+
+export function reorderChildren(
+    id: string, newChildIds: string[]): ReorderChildrenAction {
+  return {
+    name: 'reorder-children',
+    id: id,
+    children: newChildIds,
+  };
+}
+
+export type RemoveBookmarkAction = Action & {
+  id: string;
+  parentId: string;
+  index: number;
+  descendants: Set<string>;
+}
+
+export function removeBookmark(
+    id: string, parentId: string, index: number,
+    nodes: NodeMap): RemoveBookmarkAction {
+  const descendants = getDescendants(nodes, id);
+  return {
+    name: 'remove-bookmark',
+    id: id,
+    descendants: descendants,
+    parentId: parentId,
+    index: index,
+  };
+}
+
+export type RefreshNodesAction = Action & {
+  nodes: NodeMap;
+}
+
+export function refreshNodes(nodeMap: NodeMap): RefreshNodesAction {
+  return {
+    name: 'refresh-nodes',
+    nodes: nodeMap,
+  };
+}
+
+export type SelectFolderAction = Action & {
+  id: string;
+}
+
+export function selectFolder(
+    id: string, nodes: NodeMap): SelectFolderAction|null {
+  if (nodes && (id === ROOT_NODE_ID || !nodes[id] || nodes[id]!.url)) {
+    console.warn('Tried to select invalid folder: ' + id);
+    return null;
+  }
+
+  return {
+    name: 'select-folder',
+    id: id,
+  };
+}
+
+export type ChangeFolderOpenAction = Action & {
+  id: string;
+  open: boolean;
+}
+
+export function changeFolderOpen(
+    id: string, open: boolean): ChangeFolderOpenAction {
+  return {
+    name: 'change-folder-open',
+    id: id,
+    open: open,
+  };
+}
+
+export function clearSearch(): Action {
+  return {
+    name: 'clear-search',
+  };
+}
+
+export function deselectItems(): Action {
+  return {
+    name: 'deselect-items',
+  };
+}
+
+export type SelectItemsAction = Action & {
+  clear: boolean;
+  toggle: boolean;
+  anchor: string;
+  items: string[];
+}
+
+export function selectItem(
+    id: string, state: BookmarksPageState,
+    config: {clear: boolean, range: boolean, toggle: boolean}):
+    SelectItemsAction {
+  assert(!config.toggle || !config.range);
+  assert(!config.toggle || !config.clear);
+
+  const anchor = state.selection.anchor;
+  const toSelect: string[] = [];
+  let newAnchor = id;
+
+  if (config.range && anchor) {
+    const displayedList = getDisplayedList(state);
+    const selectedIndex = displayedList.indexOf(id);
+    assert(selectedIndex !== -1);
+    let anchorIndex = displayedList.indexOf(anchor);
+    if (anchorIndex === -1) {
+      anchorIndex = selectedIndex;
+    }
+
+    // When performing a range selection, don't change the anchor from what
+    // was used in this selection.
+    newAnchor = displayedList[anchorIndex]!;
+
+    const startIndex = Math.min(anchorIndex, selectedIndex);
+    const endIndex = Math.max(anchorIndex, selectedIndex);
+
+    for (let i = startIndex; i <= endIndex; i++) {
+      toSelect.push(displayedList[i]!);
+    }
+  } else {
+    toSelect.push(id);
+  }
+
+  return {
+    name: 'select-items',
+    clear: config.clear,
+    toggle: config.toggle,
+    anchor: newAnchor,
+    items: toSelect,
+  };
+}
+
+export function selectAll(
+    ids: string[], state: BookmarksPageState,
+    anchor?: string): SelectItemsAction {
+  const finalAnchor: string = anchor ? anchor! : state.selection!.anchor!;
+  return {
+    name: 'select-items',
+    clear: true,
+    toggle: false,
+    anchor: finalAnchor,
+    items: ids,
+  };
+}
+
+export type UpdateAnchorAction = Action & {
+  anchor: string;
+}
+
+export function updateAnchor(id: string): UpdateAnchorAction {
+  return {
+    name: 'update-anchor',
+    anchor: id,
+  };
+}
+
+export type StartSearchAction = Action & {
+  term: string;
+}
+
+export function setSearchTerm(term: string): (Action|StartSearchAction) {
+  if (!term) {
+    return clearSearch();
+  }
+
+  return {
+    name: 'start-search',
+    term: term,
+  };
+}
+
+export type FinishSearchAction = Action & {
+  results: string[];
+}
+
+export function setSearchResults(ids: string[]): Action {
+  return {
+    name: 'finish-search',
+    results: ids,
+  } as Action;
+}
+
+export type SetPrefAction = Action & {
+  value: IncognitoAvailability|boolean;
+}
+
+export function setIncognitoAvailability(availability: IncognitoAvailability):
+    SetPrefAction {
+  assert(availability !== IncognitoAvailability.FORCED);
+  return {
+    name: 'set-incognito-availability',
+    value: availability,
+  };
+}
+
+export function setCanEditBookmarks(canEdit: boolean): SetPrefAction {
+  return {
+    name: 'set-can-edit',
+    value: canEdit,
+  };
+}
diff --git a/chrome/browser/resources/bookmarks/api_listener.js b/chrome/browser/resources/bookmarks/api_listener.ts
similarity index 67%
rename from chrome/browser/resources/bookmarks/api_listener.js
rename to chrome/browser/resources/bookmarks/api_listener.ts
index 1372390..8581e3b 100644
--- a/chrome/browser/resources/bookmarks/api_listener.js
+++ b/chrome/browser/resources/bookmarks/api_listener.ts
@@ -18,12 +18,10 @@
  * chrome.bookmarks API into actions to modify the local page state.
  */
 
-/** @type {boolean} */
-let trackUpdates = false;
-/** @type {!Array<string>} */
-let updatedItems = [];
+let trackUpdates: boolean = false;
+let updatedItems: string[] = [];
 
-let debouncer;
+let debouncer: Debouncer;
 
 /**
  * Batches UI updates so that no changes will be made to UI until the next
@@ -74,24 +72,17 @@
   debouncer.promise.then(highlightUpdatedItemsImpl);
 }
 
-/** @param {Action} action */
-function dispatch(action) {
+function dispatch(action: Action) {
   Store.getInstance().dispatch(action);
 }
 
-/**
- * @param {string} id
- * @param {{title: string, url: (string|undefined)}} changeInfo
- */
-function onBookmarkChanged(id, changeInfo) {
+function onBookmarkChanged(
+    id: string, changeInfo: chrome.bookmarks.ChangeInfo) {
   dispatch(editBookmark(id, changeInfo));
 }
 
-/**
- * @param {string} id
- * @param {chrome.bookmarks.BookmarkTreeNode} treeNode
- */
-function onBookmarkCreated(id, treeNode) {
+function onBookmarkCreated(
+    id: string, treeNode: chrome.bookmarks.BookmarkTreeNode) {
   batchUIUpdates();
   if (trackUpdates) {
     updatedItems.push(id);
@@ -99,26 +90,14 @@
   dispatch(createBookmark(id, treeNode));
 }
 
-/**
- * @param {string} id
- * @param {{parentId: string, index: number}} removeInfo
- */
-function onBookmarkRemoved(id, removeInfo) {
+function onBookmarkRemoved(
+    id: string, removeInfo: chrome.bookmarks.RemoveInfo) {
   batchUIUpdates();
   const nodes = Store.getInstance().data.nodes;
   dispatch(removeBookmark(id, removeInfo.parentId, removeInfo.index, nodes));
 }
 
-/**
- * @param {string} id
- * @param {{
- *    parentId: string,
- *    index: number,
- *    oldParentId: string,
- *    oldIndex: number
- * }} moveInfo
- */
-function onBookmarkMoved(id, moveInfo) {
+function onBookmarkMoved(id: string, moveInfo: chrome.bookmarks.MoveInfo) {
   batchUIUpdates();
   if (trackUpdates) {
     updatedItems.push(id);
@@ -128,11 +107,8 @@
       moveInfo.oldIndex));
 }
 
-/**
- * @param {string} id
- * @param {{childIds: !Array<string>}} reorderInfo
- */
-function onChildrenReordered(id, reorderInfo) {
+function onChildrenReordered(
+    id: string, reorderInfo: chrome.bookmarks.ReorderInfo) {
   dispatch(reorderChildren(id, reorderInfo.childIds));
 }
 
@@ -146,43 +122,31 @@
 
 function onImportEnded() {
   chrome.bookmarks.getTree(function(results) {
-    dispatch(refreshNodes(normalizeNodes(results[0])));
+    dispatch(refreshNodes(normalizeNodes(results[0]!)));
   });
   chrome.bookmarks.onCreated.addListener(onBookmarkCreated);
 }
 
-/**
- * @param {IncognitoAvailability} availability
- */
-function onIncognitoAvailabilityChanged(availability) {
+function onIncognitoAvailabilityChanged(availability: IncognitoAvailability) {
   dispatch(setIncognitoAvailability(availability));
 }
 
-/**
- * @param {boolean} canEdit
- */
-function onCanEditBookmarksChanged(canEdit) {
+function onCanEditBookmarksChanged(canEdit: boolean) {
   dispatch(setCanEditBookmarks(canEdit));
 }
 
-const listeners = [
-  {api: chrome.bookmarks.onChanged, fn: onBookmarkChanged},
-  {api: chrome.bookmarks.onChildrenReordered, fn: onChildrenReordered},
-  {api: chrome.bookmarks.onCreated, fn: onBookmarkCreated},
-  {api: chrome.bookmarks.onMoved, fn: onBookmarkMoved},
-  {api: chrome.bookmarks.onRemoved, fn: onBookmarkRemoved},
-  {api: chrome.bookmarks.onImportBegan, fn: onImportBegan},
-  {api: chrome.bookmarks.onImportEnded, fn: onImportEnded},
-];
+let incognitoAvailabilityListener: {eventName: string, uid: number}|null = null;
 
-/** @type {?{eventName: string, uid: number}} */
-let incognitoAvailabilityListener = null;
-
-/** @type {?{eventName: string, uid: number}} */
-let canEditBookmarksListener = null;
+let canEditBookmarksListener: {eventName: string, uid: number}|null = null;
 
 export function init() {
-  listeners.forEach((listener) => listener.api.addListener(listener.fn));
+  chrome.bookmarks.onChanged.addListener(onBookmarkChanged);
+  chrome.bookmarks.onChildrenReordered.addListener(onChildrenReordered);
+  chrome.bookmarks.onCreated.addListener(onBookmarkCreated);
+  chrome.bookmarks.onMoved.addListener(onBookmarkMoved);
+  chrome.bookmarks.onRemoved.addListener(onBookmarkRemoved);
+  chrome.bookmarks.onImportBegan.addListener(onImportBegan);
+  chrome.bookmarks.onImportEnded.addListener(onImportEnded);
 
   const browserProxy = BrowserProxy.getInstance();
   browserProxy.getIncognitoAvailability().then(onIncognitoAvailabilityChanged);
@@ -195,7 +159,13 @@
 }
 
 export function destroy() {
-  listeners.forEach((listener) => listener.api.removeListener(listener.fn));
+  chrome.bookmarks.onChanged.removeListener(onBookmarkChanged);
+  chrome.bookmarks.onChildrenReordered.removeListener(onChildrenReordered);
+  chrome.bookmarks.onCreated.removeListener(onBookmarkCreated);
+  chrome.bookmarks.onMoved.removeListener(onBookmarkMoved);
+  chrome.bookmarks.onRemoved.removeListener(onBookmarkRemoved);
+  chrome.bookmarks.onImportBegan.removeListener(onImportBegan);
+  chrome.bookmarks.onImportEnded.removeListener(onImportEnded);
   if (incognitoAvailabilityListener) {
     removeWebUIListener(/** @type {{eventName: string, uid: number}} */ (
         incognitoAvailabilityListener));
diff --git a/chrome/browser/resources/bookmarks/app.js b/chrome/browser/resources/bookmarks/app.ts
similarity index 67%
rename from chrome/browser/resources/bookmarks/app.js
rename to chrome/browser/resources/bookmarks/app.ts
index 9204c8c..a1b5c0b 100644
--- a/chrome/browser/resources/bookmarks/app.js
+++ b/chrome/browser/resources/bookmarks/app.ts
@@ -13,10 +13,12 @@
 import './shared_vars.js';
 import './strings.m.js';
 import './command_manager.js';
+import './toolbar.js';
 
-import {FindShortcutBehavior, FindShortcutBehaviorInterface} from 'chrome://resources/cr_elements/find_shortcut_behavior.js';
+import {CrSplitterElement} from 'chrome://resources/cr_elements/cr_splitter/cr_splitter.js';
+import {EventTracker} from 'chrome://resources/js/event_tracker.m.js';
+import {FindShortcutBehavior} from 'chrome://resources/cr_elements/find_shortcut_behavior.js';
 import {StoreObserver} from 'chrome://resources/js/cr/ui/store.m.js';
-import {StoreClientInterface as CrUiStoreClientInterface} from 'chrome://resources/js/cr/ui/store_client.m.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
 import {html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
@@ -24,62 +26,57 @@
 import {destroy as destroyApiListener, init as initApiListener} from './api_listener.js';
 import {LOCAL_STORAGE_FOLDER_STATE_KEY, LOCAL_STORAGE_TREE_WIDTH_KEY, ROOT_NODE_ID} from './constants.js';
 import {DNDManager} from './dnd_manager.js';
-import {MouseFocusBehavior} from './mouse_focus_behavior.js';
+import {MouseFocusMixin} from './mouse_focus_behavior.js';
 import {Store} from './store.js';
 import {BookmarksStoreClientInterface, StoreClient} from './store_client.js';
 import {BookmarksToolbarElement} from './toolbar.js';
 import {BookmarksPageState, FolderOpenState} from './types.js';
 import {createEmptyState, normalizeNodes} from './util.js';
 
-/**
- * @constructor
- * @extends {PolymerElement}
- * @implements {BookmarksStoreClientInterface}
- * @implements {CrUiStoreClientInterface}
- * @implements {StoreObserver<BookmarksPageState>}
- * @implements {FindShortcutBehaviorInterface}
- */
-const BookmarksAppElementBase = mixinBehaviors(
-    [StoreClient, MouseFocusBehavior, FindShortcutBehavior], PolymerElement);
+const BookmarksAppElementBase =
+    mixinBehaviors([StoreClient, FindShortcutBehavior],
+                   MouseFocusMixin(PolymerElement)) as {
+  new (): PolymerElement & BookmarksStoreClientInterface &
+      StoreObserver<BookmarksPageState> & FindShortcutBehavior
+}
 
-/** @polymer */
+export interface BookmarksAppElement {
+  $: {
+    splitter: CrSplitterElement,
+    sidebar: HTMLDivElement,
+  }
+}
+
 export class BookmarksAppElement extends BookmarksAppElementBase {
   static get is() {
     return 'bookmarks-app';
   }
 
-  static get template() {
-    return html`{__html_template__}`;
-  }
-
   static get properties() {
     return {
-      /** @private */
       searchTerm_: {
         type: String,
         observer: 'searchTermChanged_',
       },
 
-      /** @type {FolderOpenState} */
       folderOpenState_: {
         type: Object,
         observer: 'folderOpenStateChanged_',
       },
 
-      /** @private */
       sidebarWidth_: String,
     };
   }
 
+  private eventTracker_: EventTracker = new EventTracker();
+  private dndManager_: DNDManager|null = null;
+  private folderOpenState_: FolderOpenState;
+  private searchTerm_: string;
+  private sidebarWidth_: string;
+
   constructor() {
     super();
 
-    /** @private{?function(!Event)} */
-    this.boundUpdateSidebarWidth_ = null;
-
-    /** @private {DNDManager} */
-    this.dndManager_ = null;
-
     // Regular expression that captures the leading slash, the content and the
     // trailing slash in three different groups.
     const CANONICAL_PATH_REGEX = /(^\/)([\/-\w]+)(\/$)/;
@@ -89,31 +86,28 @@
     }
   }
 
-  /** @override */
   connectedCallback() {
     super.connectedCallback();
 
     document.documentElement.classList.remove('loading');
 
-    this.watch('searchTerm_', function(state) {
+    this.watch('searchTerm_', function(state: BookmarksPageState) {
       return state.search.term;
     });
 
-    this.watch('folderOpenState_', function(state) {
+    this.watch('folderOpenState_', function(state: BookmarksPageState) {
       return state.folderOpenState;
     });
 
     chrome.bookmarks.getTree((results) => {
-      const nodeMap = normalizeNodes(results[0]);
+      const nodeMap = normalizeNodes(results[0]!);
       const initialState = createEmptyState();
       initialState.nodes = nodeMap;
-      initialState.selectedFolder = nodeMap[ROOT_NODE_ID].children[0];
+      initialState.selectedFolder = nodeMap[ROOT_NODE_ID]!.children![0]!;
       const folderStateString =
           window.localStorage[LOCAL_STORAGE_FOLDER_STATE_KEY];
       initialState.folderOpenState = folderStateString ?
-          new Map(
-              /** @type Array<Array<boolean|string>> */ (
-                  JSON.parse(folderStateString))) :
+          new Map(JSON.parse(folderStateString)) :
           new Map();
 
       Store.getInstance().init(initialState);
@@ -126,8 +120,6 @@
       });
     });
 
-    this.boundUpdateSidebarWidth_ = this.updateSidebarWidth_.bind(this);
-
     this.initializeSplitter_();
 
     this.dndManager_ = new DNDManager();
@@ -137,16 +129,12 @@
   disconnectedCallback() {
     super.disconnectedCallback();
 
-    window.removeEventListener('resize', this.boundUpdateSidebarWidth_);
-    this.dndManager_.destroy();
+    this.eventTracker_.remove(window, 'resize');
+    this.dndManager_!.destroy();
     destroyApiListener();
   }
 
-  /**
-   * Set up the splitter and set the initial width from localStorage.
-   * @private
-   */
-  initializeSplitter_() {
+  private initializeSplitter_(): void {
     const splitter = this.$.splitter;
     const splitterTarget = this.$.sidebar;
 
@@ -155,8 +143,7 @@
       splitterTarget.style.width =
           window.localStorage[LOCAL_STORAGE_TREE_WIDTH_KEY];
     }
-    this.sidebarWidth_ =
-        /** @type {string} */ (getComputedStyle(splitterTarget).width);
+    this.sidebarWidth_ = getComputedStyle(splitterTarget).width;
 
     splitter.addEventListener('resize', (e) => {
       window.localStorage[LOCAL_STORAGE_TREE_WIDTH_KEY] =
@@ -164,18 +151,16 @@
       this.updateSidebarWidth_();
     });
 
-    splitter.addEventListener('dragmove', this.boundUpdateSidebarWidth_);
-    window.addEventListener('resize', this.boundUpdateSidebarWidth_);
+    this.eventTracker_.add(splitter, 'dragmove',
+                           () => this.updateSidebarWidth_());
+    this.eventTracker_.add(window, 'resize', () => this.updateSidebarWidth_());
   }
 
-  /** @private */
-  updateSidebarWidth_() {
-    this.sidebarWidth_ =
-        /** @type {string} */ (getComputedStyle(this.$.sidebar).width);
+  private updateSidebarWidth_(): void {
+    this.sidebarWidth_ = getComputedStyle(this.$.sidebar).width;
   }
 
-  /** @private */
-  searchTermChanged_(newValue, oldValue) {
+  private searchTermChanged_(newValue: string, oldValue?: string) {
     if (oldValue !== undefined && !newValue) {
       this.dispatchEvent(new CustomEvent('iron-announce', {
         bubbles: true,
@@ -205,37 +190,35 @@
     });
   }
 
-  /** @private */
-  folderOpenStateChanged_() {
+  private folderOpenStateChanged_(): void {
     window.localStorage[LOCAL_STORAGE_FOLDER_STATE_KEY] =
         JSON.stringify(Array.from(this.folderOpenState_));
   }
 
   // Override FindShortcutBehavior methods.
-  /** @override */
-  handleFindShortcut(modalContextOpen) {
+  handleFindShortcut(modalContextOpen: boolean): boolean {
     if (modalContextOpen) {
       return false;
     }
-    /** @type {!BookmarksToolbarElement} */ (
-        this.shadowRoot.querySelector('bookmarks-toolbar'))
-        .searchField.showAndFocus();
+    this.shadowRoot!.querySelector<BookmarksToolbarElement>(
+        'bookmarks-toolbar')!.searchField.showAndFocus();
     return true;
   }
 
   // Override FindShortcutBehavior methods.
-  /** @override */
-  searchInputHasFocus() {
-    return /** @type {!BookmarksToolbarElement} */ (
-               this.shadowRoot.querySelector('bookmarks-toolbar'))
-        .searchField.isSearchFocused();
+  searchInputHasFocus(): boolean {
+    return this.shadowRoot!.querySelector<BookmarksToolbarElement>(
+        'bookmarks-toolbar')!.searchField.isSearchFocused();
   }
 
-  /** @private */
-  onUndoClick_() {
+  private onUndoClick_(): void {
     this.dispatchEvent(
         new CustomEvent('command-undo', {bubbles: true, composed: true}));
   }
+
+  static get template() {
+    return html`{__html_template__}`;
+  }
 }
 
 customElements.define(BookmarksAppElement.is, BookmarksAppElement);
diff --git a/chrome/browser/resources/bookmarks/bookmarks.js b/chrome/browser/resources/bookmarks/bookmarks.ts
similarity index 100%
rename from chrome/browser/resources/bookmarks/bookmarks.js
rename to chrome/browser/resources/bookmarks/bookmarks.ts
diff --git a/chrome/browser/resources/bookmarks/browser_proxy.js b/chrome/browser/resources/bookmarks/browser_proxy.js
deleted file mode 100644
index 8d615c99..0000000
--- a/chrome/browser/resources/bookmarks/browser_proxy.js
+++ /dev/null
@@ -1,49 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import {sendWithPromise} from 'chrome://resources/js/cr.m.js';
-
-import {IncognitoAvailability} from './constants.js';
-
-export class BrowserProxy {
-  /**
-   * @return {!Promise<!IncognitoAvailability>} Promise resolved with the
-   *     current incognito mode preference.
-   */
-  getIncognitoAvailability() {
-    return sendWithPromise('getIncognitoAvailability');
-  }
-
-  /**
-   * @return {!Promise<boolean>} Promise resolved with whether the bookmarks
-   *     can be edited.
-   */
-  getCanEditBookmarks() {
-    return sendWithPromise('getCanEditBookmarks');
-  }
-
-  /**
-   * Notifies the metrics handler to record a histogram value.
-   * @param {string} histogram The name of the histogram to record
-   * @param {number} bucket The bucket to record
-   * @param {number} maxBucket The maximum bucket value in the histogram.
-   */
-  recordInHistogram(histogram, bucket, maxBucket) {
-    chrome.send(
-        'metricsHandler:recordInHistogram', [histogram, bucket, maxBucket]);
-  }
-
-  /** @return {!BrowserProxy} */
-  static getInstance() {
-    return instance || (instance = new BrowserProxy());
-  }
-
-  /** @param {!BrowserProxy} obj */
-  static setInstance(obj) {
-    instance = obj;
-  }
-}
-
-/** @type {?BrowserProxy} */
-let instance = null;
diff --git a/chrome/browser/resources/bookmarks/browser_proxy.ts b/chrome/browser/resources/bookmarks/browser_proxy.ts
new file mode 100644
index 0000000..2f794df
--- /dev/null
+++ b/chrome/browser/resources/bookmarks/browser_proxy.ts
@@ -0,0 +1,32 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import {sendWithPromise} from 'chrome://resources/js/cr.m.js';
+
+import {IncognitoAvailability} from './constants.js';
+
+export class BrowserProxy {
+  getIncognitoAvailability(): Promise<IncognitoAvailability> {
+    return sendWithPromise('getIncognitoAvailability');
+  }
+
+  getCanEditBookmarks(): Promise<boolean> {
+    return sendWithPromise('getCanEditBookmarks');
+  }
+
+  recordInHistogram(histogram: string, bucket: number, maxBucket: number) {
+    chrome.send(
+        'metricsHandler:recordInHistogram', [histogram, bucket, maxBucket]);
+  }
+
+  static getInstance(): BrowserProxy {
+    return instance || (instance = new BrowserProxy());
+  }
+
+  static setInstance(obj: BrowserProxy) {
+    instance = obj;
+  }
+}
+
+let instance: BrowserProxy|null = null;
diff --git a/chrome/browser/resources/bookmarks/command_manager.js b/chrome/browser/resources/bookmarks/command_manager.ts
similarity index 72%
rename from chrome/browser/resources/bookmarks/command_manager.js
rename to chrome/browser/resources/bookmarks/command_manager.ts
index f47e1c0..ae441676 100644
--- a/chrome/browser/resources/bookmarks/command_manager.js
+++ b/chrome/browser/resources/bookmarks/command_manager.ts
@@ -7,6 +7,7 @@
  * shortcuts.
  */
 import 'chrome://resources/cr_elements/cr_action_menu/cr_action_menu.m.js';
+import 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js';
 import 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
 import 'chrome://resources/cr_elements/cr_lazy_render/cr_lazy_render.m.js';
 import 'chrome://resources/cr_elements/shared_vars_css.m.js';
@@ -14,13 +15,17 @@
 import './edit_dialog.js';
 import './shared_style.js';
 import './strings.m.js';
+import './edit_dialog.js';
 
+import {CrActionMenuElement} from 'chrome://resources/cr_elements/cr_action_menu/cr_action_menu.m.js';
+import {CrDialogElement} from 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js';
+import {CrLazyRenderElement} from 'chrome://resources/cr_elements/cr_lazy_render/cr_lazy_render.m.js';
+import {EventTracker} from 'chrome://resources/js/event_tracker.m.js';
 import {getToastManager} from 'chrome://resources/cr_elements/cr_toast/cr_toast_manager.m.js';
 import {assert, assertNotReached} from 'chrome://resources/js/assert.m.js';
 import {isMac} from 'chrome://resources/js/cr.m.js';
 import {KeyboardShortcutList} from 'chrome://resources/js/cr/ui/keyboard_shortcut_list.m.js';
 import {StoreObserver} from 'chrome://resources/js/cr/ui/store.m.js';
-import {StoreClientInterface as CrUiStoreClientInterface} from 'chrome://resources/js/cr/ui/store_client.m.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
 import {PluralStringProxyImpl} from 'chrome://resources/js/plural_string_proxy.js';
 import {IronA11yAnnouncer} from 'chrome://resources/polymer/v3_0/iron-a11y-announcer/iron-a11y-announcer.js';
@@ -33,23 +38,25 @@
 import {DialogFocusManager} from './dialog_focus_manager.js';
 import {BookmarksEditDialogElement} from './edit_dialog.js';
 import {BookmarksStoreClientInterface, StoreClient} from './store_client.js';
-import {BookmarkNode, BookmarksPageState} from './types.js';
+import {BookmarkNode, BookmarksPageState, OpenCommandMenuDetail} from './types.js';
 import {canEditNode, canReorderChildren, getDisplayedList} from './util.js';
 
-/**
- * @constructor
- * @extends {PolymerElement}
- * @implements {BookmarksStoreClientInterface}
- * @implements {CrUiStoreClientInterface}
- * @implements {StoreObserver<BookmarksPageState>}
- */
 const BookmarksCommandManagerElementBase =
-    mixinBehaviors([StoreClient], PolymerElement);
+    mixinBehaviors([StoreClient], PolymerElement) as {
+  new (): PolymerElement & BookmarksStoreClientInterface &
+          StoreObserver<BookmarksPageState>
+}
 
-/** @type {?BookmarksCommandManagerElement} */
-let instance = null;
+export interface BookmarksCommandManagerElement {
+  $: {
+    dropdown: CrLazyRenderElement,
+    editDialog: CrLazyRenderElement,
+    openDialog: CrLazyRenderElement,
+  }
+}
 
-/** @polymer */
+let instance: BookmarksCommandManagerElement|null = null;
+
 export class BookmarksCommandManagerElement extends
     BookmarksCommandManagerElementBase {
   static get is() {
@@ -62,52 +69,47 @@
 
   static get properties() {
     return {
-      /** @private {!Array<Command>} */
       menuCommands_: {
         type: Array,
         computed: 'computeMenuCommands_(menuSource_)',
       },
 
-      /** @private {Set<string>} */
       menuIds_: Object,
 
-      /**
-       * Indicates where the context menu was opened from. Will be NONE if
-       * menu is not open, indicating that commands are from keyboard shortcuts
-       * or elsewhere in the UI.
-       * @private {MenuSource}
-       */
-      menuSource_: {
-        type: Number,
-        value: MenuSource.NONE,
-      },
+      menuSource_: Number,
 
-      /** @private */
       canPaste_: Boolean,
 
-      /** @private */
       globalCanEdit_: Boolean,
     };
   }
 
-  constructor() {
-    super();
-    /** @private {?Function} */
-    this.confirmOpenCallback_ = null;
-  }
+  /**
+   * Indicates where the context menu was opened from. Will be NONE if
+   * menu is not open, indicating that commands are from keyboard shortcuts
+   * or elsewhere in the UI.
+   */
+  private menuSource_: MenuSource = MenuSource.NONE;
+  private confirmOpenCallback_: (() => void)|null = null;
+  private canPaste_: boolean;
+  private globalCanEdit_: boolean;
+  private menuIds_: Set<string>;
+  private menuCommands_: Command[];
+  private browserProxy_: BrowserProxy;
+  private shortcuts_: Map<Command, KeyboardShortcutList>;
+  private eventTracker_: EventTracker = new EventTracker();
 
   connectedCallback() {
     super.connectedCallback();
     assert(instance === null);
     instance = this;
 
-    /** @private {!BrowserProxy} */
     this.browserProxy_ = BrowserProxy.getInstance();
 
-    this.watch('globalCanEdit_', state => state.prefs.canEdit);
+    this.watch(
+        'globalCanEdit_', state => (state as BookmarksPageState).prefs.canEdit);
     this.updateFromStore();
 
-    /** @private {!Map<Command, KeyboardShortcutList>} */
     this.shortcuts_ = new Map();
 
     this.addShortcut_(Command.EDIT, 'F2', 'Enter');
@@ -130,21 +132,16 @@
     this.addShortcut_(Command.COPY, 'Ctrl|c', 'Meta|c');
     this.addShortcut_(Command.PASTE, 'Ctrl|v', 'Meta|v');
 
-    /** @private {!Map<string, Function>} */
-    this.boundListeners_ = new Map();
+    this.eventTracker_.add(document, 'open-command-menu', e =>
+                           this.onOpenCommandMenu_(
+                               e as CustomEvent<OpenCommandMenuDetail>));
+    this.eventTracker_.add(document, 'keydown', e =>
+                           this.onKeydown_(e as KeyboardEvent));
 
-    const addDocumentListener = (eventName, handler) => {
-      assert(!this.boundListeners_.has(eventName));
-      const boundListener = handler.bind(this);
-      this.boundListeners_.set(eventName, boundListener);
-      document.addEventListener(eventName, boundListener);
-    };
-    addDocumentListener('open-command-menu', this.onOpenCommandMenu_);
-    addDocumentListener('keydown', this.onKeydown_);
-
-    const addDocumentListenerForCommand = (eventName, command) => {
-      addDocumentListener(eventName, (e) => {
-        if (e.path[0].tagName === 'INPUT') {
+    const addDocumentListenerForCommand = (eventName: string,
+                                           command: Command) => {
+      this.eventTracker_.add(document, eventName, e => {
+        if ((e.composedPath()[0] as HTMLElement).tagName === 'INPUT') {
           return;
         }
 
@@ -167,26 +164,20 @@
   disconnectedCallback() {
     super.disconnectedCallback();
     instance = null;
-    this.boundListeners_.forEach(
-        (handler, eventName) =>
-            document.removeEventListener(eventName, handler));
+    this.eventTracker_.removeAll();
   }
 
   /**
    * Display the command context menu at (|x|, |y|) in window coordinates.
    * Commands will execute on |items| if given, or on the currently selected
    * items.
-   * @param {number} x
-   * @param {number} y
-   * @param {MenuSource} source
-   * @param {Set<string>=} items
    */
-  openCommandMenuAtPosition(x, y, source, items) {
+  openCommandMenuAtPosition(
+      x: number, y: number, source: MenuSource, items?: Set<string>) {
     this.menuSource_ = source;
     this.menuIds_ = items || this.getState().selection.items;
 
-    const dropdown =
-        /** @type {!CrActionMenuElement} */ (this.$.dropdown.get());
+    const dropdown = (this.$.dropdown.get()) as CrActionMenuElement;
     // Ensure that the menu is fully rendered before trying to position it.
     flush();
     DialogFocusManager.getInstance().showDialog(
@@ -198,15 +189,12 @@
   /**
    * Display the command context menu positioned to cover the |target|
    * element. Commands will execute on the currently selected items.
-   * @param {!Element} target
-   * @param {MenuSource} source
    */
-  openCommandMenuAtElement(target, source) {
+  openCommandMenuAtElement(target: Element, source: MenuSource) {
     this.menuSource_ = source;
     this.menuIds_ = this.getState().selection.items;
 
-    const dropdown =
-        /** @type {!CrActionMenuElement} */ (this.$.dropdown.get());
+    const dropdown = this.$.dropdown.get() as CrActionMenuElement;
     // Ensure that the menu is fully rendered before trying to position it.
     flush();
     DialogFocusManager.getInstance().showDialog(
@@ -218,7 +206,7 @@
   closeCommandMenu() {
     this.menuIds_ = new Set();
     this.menuSource_ = MenuSource.NONE;
-    /** @type {!CrActionMenuElement} */ (this.$.dropdown.get()).close();
+    (this.$.dropdown.get() as CrActionMenuElement).close();
   }
 
   ////////////////////////////////////////////////////////////////////////////
@@ -228,11 +216,8 @@
    * Determine if the |command| can be executed with the given |itemIds|.
    * Commands which appear in the context menu should be implemented
    * separately using `isCommandVisible_` and `isCommandEnabled_`.
-   * @param {Command} command
-   * @param {!Set<string>} itemIds
-   * @return {boolean}
    */
-  canExecute(command, itemIds) {
+  canExecute(command: Command, itemIds: Set<string>): boolean {
     const state = this.getState();
     switch (command) {
       case Command.OPEN:
@@ -259,13 +244,7 @@
     }
   }
 
-  /**
-   * @param {Command} command
-   * @param {!Set<string>} itemIds
-   * @return {boolean} True if the command should be visible in the context
-   *     menu.
-   */
-  isCommandVisible_(command, itemIds) {
+  isCommandVisible_(command: Command, itemIds: Set<string>): boolean {
     switch (command) {
       case Command.EDIT:
         return itemIds.size === 1 && this.globalCanEdit_;
@@ -299,13 +278,7 @@
     return assert(false);
   }
 
-  /**
-   * @param {Command} command
-   * @param {!Set<string>} itemIds
-   * @return {boolean} True if the command should be clickable in the context
-   *     menu.
-   */
-  isCommandEnabled_(command, itemIds) {
+  private isCommandEnabled_(command: Command, itemIds: Set<string>): boolean {
     const state = this.getState();
     switch (command) {
       case Command.EDIT:
@@ -322,7 +295,7 @@
             IncognitoAvailability.DISABLED;
       case Command.SORT:
         return this.canChangeList_() &&
-            state.nodes[state.selectedFolder].children.length > 1;
+            state.nodes[state.selectedFolder]!.children!.length > 1;
       case Command.ADD_BOOKMARK:
       case Command.ADD_FOLDER:
         return this.canChangeList_();
@@ -337,33 +310,27 @@
 
   /**
    * Returns whether the currently displayed bookmarks list can be changed.
-   * @private
-   * @return {boolean}
    */
-  canChangeList_() {
+  private canChangeList_(): boolean {
     const state = this.getState();
     return state.search.term === '' &&
         canReorderChildren(state, state.selectedFolder);
   }
 
-  /**
-   * @param {Command} command
-   * @param {!Set<string>} itemIds
-   */
-  handle(command, itemIds) {
+  handle(command: Command, itemIds: Set<string>) {
     const state = this.getState();
     switch (command) {
       case Command.EDIT: {
-        const id = Array.from(itemIds)[0];
-        /** @type {!BookmarksEditDialogElement} */ (this.$.editDialog.get())
-            .showEditDialog(state.nodes[id]);
+        const id = Array.from(itemIds)[0]!;
+        (this.$.editDialog.get() as BookmarksEditDialogElement)
+            .showEditDialog(state.nodes[id]!);
         break;
       }
       case Command.COPY_URL:
       case Command.COPY: {
         const idList = Array.from(itemIds);
         chrome.bookmarkManagerPrivate.copy(idList, () => {
-          let labelPromise;
+          let labelPromise: Promise<string>;
           if (command === Command.COPY_URL) {
             labelPromise =
                 Promise.resolve(loadTimeData.getString('toastUrlCopied'));
@@ -376,14 +343,14 @@
           }
 
           this.showTitleToast_(
-              labelPromise, state.nodes[idList[0]].title, false);
+              labelPromise, state.nodes[idList[0]!]!.title, false);
         });
         break;
       }
       case Command.SHOW_IN_FOLDER: {
         const id = Array.from(itemIds)[0];
         this.dispatch(
-            selectFolder(assert(state.nodes[id].parentId), state.nodes));
+            selectFolder(assert(state.nodes[id!]!.parentId!), state.nodes));
         DialogFocusManager.getInstance().clearFocus();
         this.dispatchEvent(new CustomEvent(
             'highlight-items', {bubbles: true, composed: true, detail: [id]}));
@@ -391,8 +358,8 @@
       }
       case Command.DELETE: {
         const idList = Array.from(this.minimizeDeletionSet_(itemIds));
-        const title = state.nodes[idList[0]].title;
-        let labelPromise;
+        const title = state.nodes[idList[0]!]!.title;
+        let labelPromise: Promise<string>;
 
         if (idList.length === 1) {
           labelPromise =
@@ -421,7 +388,7 @@
         break;
       case Command.OPEN:
         if (this.isFolder_(itemIds)) {
-          const folderId = Array.from(itemIds)[0];
+          const folderId = Array.from(itemIds)[0]!;
           this.dispatch(selectFolder(folderId, state.nodes));
         } else {
           this.openUrls_(this.expandUrls_(itemIds), command);
@@ -453,15 +420,15 @@
       case Command.SORT:
         chrome.bookmarkManagerPrivate.sortChildren(
             assert(state.selectedFolder));
-        getToastManager().querySelector('dom-if').if = true;
+        getToastManager().querySelector('dom-if')!.if = true;
         getToastManager().show(loadTimeData.getString('toastFolderSorted'));
         break;
       case Command.ADD_BOOKMARK:
-        /** @type {!BookmarksEditDialogElement} */ (this.$.editDialog.get())
+        (this.$.editDialog.get() as BookmarksEditDialogElement)
             .showAddDialog(false, assert(state.selectedFolder));
         break;
       case Command.ADD_FOLDER:
-        /** @type {!BookmarksEditDialogElement} */ (this.$.editDialog.get())
+        (this.$.editDialog.get() as BookmarksEditDialogElement)
             .showAddDialog(true, assert(state.selectedFolder));
         break;
       case Command.IMPORT:
@@ -480,17 +447,10 @@
         itemIds, 'BookmarkManager.CommandExecuted', command);
   }
 
-  /**
-   * @param {!Event} e
-   * @param {!Set<string>} itemIds
-   * @return {boolean} True if the event was handled, triggering a keyboard
-   *     shortcut.
-   */
-  handleKeyEvent(e, itemIds) {
+  handleKeyEvent(e: Event, itemIds: Set<string>): boolean {
     for (const commandTuple of this.shortcuts_) {
-      const command = /** @type {Command} */ (commandTuple[0]);
-      const shortcut =
-          /** @type {KeyboardShortcutList} */ (commandTuple[1]);
+      const command = commandTuple[0] as Command;
+      const shortcut = commandTuple[1] as KeyboardShortcutList;
       if (shortcut.matchesEvent(e) && this.canExecute(command, itemIds)) {
         this.handle(command, itemIds);
 
@@ -508,13 +468,9 @@
 
   /**
    * Register a keyboard shortcut for a command.
-   * @param {Command} command Command that the shortcut will trigger.
-   * @param {string} shortcut Keyboard shortcut, using the syntax of
-   *     cr/ui/command.js.
-   * @param {string=} macShortcut If set, enables a replacement shortcut for
-   *     Mac.
    */
-  addShortcut_(command, shortcut, macShortcut) {
+  private addShortcut_(
+      command: Command, shortcut: string, macShortcut?: string) {
     shortcut = (isMac && macShortcut) ? macShortcut : shortcut;
     this.shortcuts_.set(command, new KeyboardShortcutList(shortcut));
   }
@@ -525,16 +481,14 @@
    * both a node and its descendant, we will only try to delete the topmost
    * node, preventing an error in the bookmarkManagerPrivate.removeTrees API
    * call.
-   * @param {!Set<string>} itemIds
-   * @return {!Set<string>}
    */
-  minimizeDeletionSet_(itemIds) {
-    const minimizedSet = new Set();
+  private minimizeDeletionSet_(itemIds: Set<string>): Set<string> {
+    const minimizedSet = new Set() as Set<string>;
     const nodes = this.getState().nodes;
     itemIds.forEach(function(itemId) {
-      let currentId = itemId;
+      let currentId = itemId!;
       while (currentId !== ROOT_NODE_ID) {
-        currentId = assert(nodes[currentId].parentId);
+        currentId = assert(nodes[currentId]!.parentId!);
         if (itemIds.has(currentId)) {
           return;
         }
@@ -547,11 +501,8 @@
   /**
    * Open the given |urls| in response to a |command|. May show a confirmation
    * dialog before opening large numbers of URLs.
-   * @param {!Array<string>} urls
-   * @param {Command} command
-   * @private
    */
-  openUrls_(urls, command) {
+  private openUrls_(urls: Array<string>, command: Command) {
     assert(
         command === Command.OPEN || command === Command.OPEN_NEW_TAB ||
         command === Command.OPEN_NEW_WINDOW ||
@@ -582,10 +533,11 @@
 
     this.confirmOpenCallback_ = openUrlsCallback;
     const dialog = this.$.openDialog.get();
-    dialog.querySelector('[slot=body]').textContent =
+    dialog.querySelector('[slot=body]')!.textContent =
         loadTimeData.getStringF('openDialogBody', urls.length);
 
-    DialogFocusManager.getInstance().showDialog(this.$.openDialog.get());
+    DialogFocusManager.getInstance().showDialog(
+        this.$.openDialog.get() as CrDialogElement);
   }
 
   /**
@@ -593,21 +545,18 @@
    * Note that these will be ordered by insertion order into the |itemIds|
    * set, and that it is possible to duplicate a URL by passing in both the
    * parent ID and child ID.
-   * @param {!Set<string>} itemIds
-   * @return {!Array<string>}
-   * @private
    */
-  expandUrls_(itemIds) {
-    const urls = [];
+  private expandUrls_(itemIds: Set<string>): string[] {
+    const urls: string[] = [];
     const nodes = this.getState().nodes;
 
     itemIds.forEach(function(id) {
-      const node = nodes[id];
+      const node = nodes[id]!;
       if (node.url) {
         urls.push(node.url);
       } else {
-        node.children.forEach(function(childId) {
-          const childNode = nodes[childId];
+        node.children!.forEach(function(childId) {
+          const childNode = nodes[childId]!;
           if (childNode.url) {
             urls.push(childNode.url);
           }
@@ -618,49 +567,28 @@
     return urls;
   }
 
-  /**
-   * @param {!Set<string>} itemIds
-   * @param {function(BookmarkNode):boolean} predicate
-   * @return {boolean} True if any node in |itemIds| returns true for
-   *     |predicate|.
-   */
-  containsMatchingNode_(itemIds, predicate) {
+  private containsMatchingNode_(
+      itemIds: Set<string>, predicate: (p1: BookmarkNode) => boolean): boolean {
     const nodes = this.getState().nodes;
 
     return Array.from(itemIds).some(function(id) {
-      return predicate(nodes[id]);
+      return predicate(nodes[id]!);
     });
   }
 
-  /**
-   * @param {!Set<string>} itemIds
-   * @return {boolean} True if |itemIds| is a single bookmark (non-folder)
-   *     node.
-   * @private
-   */
-  isSingleBookmark_(itemIds) {
+  private isSingleBookmark_(itemIds: Set<string>): boolean {
     return itemIds.size === 1 &&
         this.containsMatchingNode_(itemIds, function(node) {
           return !!node.url;
         });
   }
 
-  /**
-   * @param {!Set<string>} itemIds
-   * @return {boolean}
-   * @private
-   */
-  isFolder_(itemIds) {
+  private isFolder_(itemIds: Set<string>): boolean {
     return itemIds.size === 1 &&
         this.containsMatchingNode_(itemIds, node => !node.url);
   }
 
-  /**
-   * @param {Command} command
-   * @return {string}
-   * @private
-   */
-  getCommandLabel_(command) {
+  private getCommandLabel_(command: Command): string {
     // Handle non-pluralized strings first.
     let label = null;
     switch (command) {
@@ -669,8 +597,8 @@
           return '';
         }
 
-        const id = Array.from(this.menuIds_)[0];
-        const itemUrl = this.getState().nodes[id].url;
+        const id = Array.from(this.menuIds_)[0]!;
+        const itemUrl = this.getState().nodes[id]!.url;
         label = itemUrl ? 'menuEdit' : 'menuRename';
         break;
       case Command.CUT:
@@ -734,14 +662,8 @@
     return '';
   }
 
-  /**
-   * @param {string} case0 String ID for the case of zero URLs.
-   * @param {string} case1 String ID for the case of 1 URL.
-   * @param {string} caseOther String ID for string that includes the URL count.
-   * @return {string}
-   * @private
-   */
-  getPluralizedOpenAllString_(case0, case1, caseOther) {
+  private getPluralizedOpenAllString_(
+      case0: string, case1: string, caseOther: string): string {
     const multipleNodes = this.menuIds_.size > 1 ||
         this.containsMatchingNode_(this.menuIds_, node => !node.url);
 
@@ -757,12 +679,7 @@
     return loadTimeData.getStringF(caseOther, urls.length);
   }
 
-  /**
-   * @param {Command} command
-   * @return {string}
-   * @private
-   */
-  getCommandSublabel_(command) {
+  private getCommandSublabel_(command: Command): string {
     const multipleNodes = this.menuIds_.size > 1 ||
         this.containsMatchingNode_(this.menuIds_, function(node) {
           return !node.url;
@@ -776,8 +693,7 @@
     }
   }
 
-  /** @private */
-  computeMenuCommands_() {
+  private computeMenuCommands_(): Command[] {
     switch (this.menuSource_) {
       case MenuSource.ITEM:
       case MenuSource.TREE:
@@ -816,15 +732,10 @@
         return [];
     }
     assert(false);
+    return [];
   }
 
-  /**
-   * @param {Command} command
-   * @param {!Set<string>} itemIds
-   * @return {boolean}
-   * @private
-   */
-  showDividerAfter_(command, itemIds) {
+  private showDividerAfter_(command: Command, itemIds: Set<string>): boolean {
     switch (command) {
       case Command.SORT:
       case Command.ADD_FOLDER:
@@ -838,13 +749,8 @@
     return false;
   }
 
-  /**
-   * @param {!Set<string>} itemIds
-   * @param {string} histogram
-   * @param {number} command
-   * @private
-   */
-  recordCommandHistogram_(itemIds, histogram, command) {
+  private recordCommandHistogram_(
+      itemIds: Set<string>, histogram: string, command: number) {
     if (command === Command.OPEN) {
       command =
           this.isFolder_(itemIds) ? Command.OPEN_FOLDER : Command.OPEN_BOOKMARK;
@@ -856,29 +762,24 @@
   /**
    * Show a toast with a bookmark |title| inserted into a label, with the
    * title ellipsised if necessary.
-   * @param {!Promise<string>} labelPromise Promise which resolves with the
-   *    label for the toast.
-   * @param {string} title Bookmark title to insert.
-   * @param {boolean} canUndo If true, shows an undo button in the toast.
-   * @private
    */
-  async showTitleToast_(labelPromise, title, canUndo) {
+  private async showTitleToast_(
+      labelPromise: Promise<string>, title: string,
+      canUndo: boolean): Promise<void> {
     const label = await labelPromise;
     const pieces =
         loadTimeData.getSubstitutedStringPieces(label, title).map(function(p) {
           // Make the bookmark name collapsible.
-          p.collapsible = !!p.arg;
-          return p;
+          const result =
+              p as {value: string, arg: string, collapsible: boolean};
+          result.collapsible = !!p.arg;
+          return result;
         });
-    getToastManager().querySelector('dom-if').if = canUndo;
+    getToastManager().querySelector('dom-if')!.if = canUndo;
     getToastManager().showForStringPieces(pieces);
   }
 
-  /**
-   * @param {number} targetId
-   * @private
-   */
-  updateCanPaste_(targetId) {
+  private updateCanPaste_(targetId: string): Promise<void> {
     return new Promise(resolve => {
       chrome.bookmarkManagerPrivate.canPaste(`${targetId}`, result => {
         this.canPaste_ = result;
@@ -890,47 +791,37 @@
   ////////////////////////////////////////////////////////////////////////////
   // Event handlers:
 
-  /**
-   * @param {Event} e
-   * @private
-   */
-  async onOpenCommandMenu_(e) {
+  private async onOpenCommandMenu_(
+      e: CustomEvent<OpenCommandMenuDetail>): Promise<void> {
     if (e.detail.targetId) {
       await this.updateCanPaste_(e.detail.targetId);
     }
     if (e.detail.targetElement) {
-      this.openCommandMenuAtElement(e.detail.targetElement, e.detail.source);
+      this.openCommandMenuAtElement(e.detail.targetElement!, e.detail.source);
     } else {
-      this.openCommandMenuAtPosition(e.detail.x, e.detail.y, e.detail.source);
+      this.openCommandMenuAtPosition(e.detail.x!, e.detail.y!, e.detail.source);
     }
     this.browserProxy_.recordInHistogram(
         'BookmarkManager.CommandMenuOpened', e.detail.source,
         MenuSource.NUM_VALUES);
   }
 
-  /**
-   * @param {Event} e
-   * @private
-   */
-  onCommandClick_(e) {
+  private onCommandClick_(e: Event) {
     this.handle(
-        /** @type {Command} */ (
-            Number(e.currentTarget.getAttribute('command'))),
+        Number((e.currentTarget as HTMLElement).getAttribute('command')) as
+            Command,
         assert(this.menuIds_));
     this.closeCommandMenu();
   }
 
-  /**
-   * @param {!Event} e
-   * @private
-   */
-  onKeydown_(e) {
+  private onKeydown_(e: Event) {
     const path = e.composedPath();
-    if (path[0].tagName === 'INPUT') {
+    if ((path[0] as HTMLElement).tagName === 'INPUT') {
       return;
     }
     if ((e.target === document.body ||
-         path.some(el => el.tagName === 'BOOKMARKS-TOOLBAR')) &&
+         path.some(
+             el => (el as HTMLElement).tagName === 'BOOKMARKS-TOOLBAR')) &&
         !DialogFocusManager.getInstance().hasOpenDialog()) {
       this.handleKeyEvent(e, this.getState().selection.items);
     }
@@ -940,31 +831,27 @@
    * Close the menu on mousedown so clicks can propagate to the underlying UI.
    * This allows the user to right click the list while a context menu is
    * showing and get another context menu.
-   * @param {Event} e
-   * @private
    */
-  onMenuMousedown_(e) {
-    if (e.path[0].tagName !== 'DIALOG') {
+  private onMenuMousedown_(e: Event): void {
+    if ((e.composedPath()[0] as HTMLElement).tagName !== 'DIALOG') {
       return;
     }
 
     this.closeCommandMenu();
   }
 
-  /** @private */
-  onOpenCancelTap_() {
-    this.$.openDialog.get().cancel();
+  private onOpenCancelTap_() {
+    (this.$.openDialog.get() as CrDialogElement).cancel();
   }
 
-  /** @private */
-  onOpenConfirmTap_() {
-    this.confirmOpenCallback_();
-    this.$.openDialog.get().close();
+  private onOpenConfirmTap_() {
+    const confirmOpenCallback = assert(this.confirmOpenCallback_!);
+    confirmOpenCallback();
+    (this.$.openDialog.get() as CrDialogElement).close();
   }
 
-  /** @return {!BookmarksCommandManagerElement} */
-  static getInstance() {
-    return assert(instance);
+  static getInstance(): BookmarksCommandManagerElement {
+    return assert(instance)!;
   }
 }
 
diff --git a/chrome/browser/resources/bookmarks/constants.js b/chrome/browser/resources/bookmarks/constants.js
deleted file mode 100644
index 4d27fc7..0000000
--- a/chrome/browser/resources/bookmarks/constants.js
+++ /dev/null
@@ -1,106 +0,0 @@
-// Copyright 2017 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.
-
-/**
- * Enumeration of valid drop locations relative to an element. These are
- * bit masks to allow combining multiple locations in a single value.
- * @enum {number}
- * @const
- */
-export const DropPosition = {
-  NONE: 0,
-  ABOVE: 1,
-  ON: 2,
-  BELOW: 4,
-};
-
-/**
- * Commands which can be handled by the CommandManager. This enum is also used
- * for metrics and should be kept in sync with BookmarkManagerCommand in
- * enums.xml. Values must never be renumbered or reused.
- * @enum {number}
- * @const
- */
-export const Command = {
-  EDIT: 0,
-  COPY_URL: 1,
-  SHOW_IN_FOLDER: 2,
-  DELETE: 3,
-  OPEN_NEW_TAB: 4,
-  OPEN_NEW_WINDOW: 5,
-  OPEN_INCOGNITO: 6,
-  UNDO: 7,
-  REDO: 8,
-  // OPEN triggers when you double-click an item. NOT USED FOR METRICS.
-  OPEN: 9,
-  SELECT_ALL: 10,
-  DESELECT_ALL: 11,
-  COPY: 12,
-  CUT: 13,
-  PASTE: 14,
-  SORT: 15,
-  ADD_BOOKMARK: 16,
-  ADD_FOLDER: 17,
-  IMPORT: 18,
-  EXPORT: 19,
-  HELP_CENTER: 20,
-
-  // Added for more precise metrics purposes. OPEN is re-mapped to one of these.
-  OPEN_BOOKMARK: 21,
-  OPEN_FOLDER: 22,
-
-  // Append new values to the end of the enum.
-  MAX_VALUE: 23,
-};
-
-/**
- * Where the menu was opened from. This enum is also used for metrics and should
- * be kept in sync with BookmarkManagerMenuSource in enums.xml. Values must
- * never be renumbered or reused.
- * @enum {number}
- * @const
- */
-export const MenuSource = {
-  NONE: 0,
-  ITEM: 1,
-  TREE: 2,
-  TOOLBAR: 3,
-  LIST: 4,
-
-  // Append new values to the end of the enum.
-  NUM_VALUES: 5,
-};
-
-/**
- * Mirrors the C++ enum from IncognitoModePrefs.
- * @enum {number}
- * @const
- */
-export const IncognitoAvailability = {
-  ENABLED: 0,
-  DISABLED: 1,
-  FORCED: 2,
-};
-
-/** @const */
-export const LOCAL_STORAGE_FOLDER_STATE_KEY = 'folderOpenState';
-
-/** @const */
-export const LOCAL_STORAGE_TREE_WIDTH_KEY = 'treeWidth';
-
-/** @const */
-export const ROOT_NODE_ID = '0';
-
-/** @const */
-export const BOOKMARKS_BAR_ID = '1';
-
-/** @const {number} */
-export const OPEN_CONFIRMATION_LIMIT = 15;
-
-/**
- * Folders that are beneath this depth will be closed by default in the folder
- * tree (where the Bookmarks Bar folder is at depth 0).
- * @const {number}
- */
-export const FOLDER_OPEN_BY_DEFAULT_DEPTH = 1;
diff --git a/chrome/browser/resources/bookmarks/constants.ts b/chrome/browser/resources/bookmarks/constants.ts
new file mode 100644
index 0000000..a5ff3ffb
--- /dev/null
+++ b/chrome/browser/resources/bookmarks/constants.ts
@@ -0,0 +1,92 @@
+// Copyright 2017 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.
+
+/**
+ * Enumeration of valid drop locations relative to an element. These are
+ * bit masks to allow combining multiple locations in a single value.
+ */
+export enum DropPosition {
+  NONE = 0,
+  ABOVE = 1,
+  ON = 2,
+  BELOW = 4,
+}
+
+/**
+ * Commands which can be handled by the CommandManager. This enum is also used
+ * for metrics and should be kept in sync with BookmarkManagerCommand in
+ * enums.xml. Values must never be renumbered or reused.
+ */
+export enum Command {
+  EDIT = 0,
+  COPY_URL = 1,
+  SHOW_IN_FOLDER = 2,
+  DELETE = 3,
+  OPEN_NEW_TAB = 4,
+  OPEN_NEW_WINDOW = 5,
+  OPEN_INCOGNITO = 6,
+  UNDO = 7,
+  REDO = 8,
+  // OPEN triggers when you double-click an item. NOT USED FOR METRICS.
+  OPEN = 9,
+  SELECT_ALL = 10,
+  DESELECT_ALL = 11,
+  COPY = 12,
+  CUT = 13,
+  PASTE = 14,
+  SORT = 15,
+  ADD_BOOKMARK = 16,
+  ADD_FOLDER = 17,
+  IMPORT = 18,
+  EXPORT = 19,
+  HELP_CENTER = 20,
+
+  // Added for more precise metrics purposes. OPEN is re-mapped to one of these.
+  OPEN_BOOKMARK = 21,
+  OPEN_FOLDER = 22,
+
+  // Append new values to the end of the enum.
+  MAX_VALUE = 23,
+}
+
+/**
+ * Where the menu was opened from. This enum is also used for metrics and should
+ * be kept in sync with BookmarkManagerMenuSource in enums.xml. Values must
+ * never be renumbered or reused.
+ */
+export enum MenuSource {
+  NONE = 0,
+  ITEM = 1,
+  TREE = 2,
+  TOOLBAR = 3,
+  LIST = 4,
+
+  // Append new values to the end of the enum.
+  NUM_VALUES = 5,
+}
+
+/**
+ * Mirrors the C++ enum from IncognitoModePrefs.
+ */
+export enum IncognitoAvailability {
+  ENABLED = 0,
+  DISABLED = 1,
+  FORCED = 2,
+}
+
+export const LOCAL_STORAGE_FOLDER_STATE_KEY: string = 'folderOpenState';
+
+export const LOCAL_STORAGE_TREE_WIDTH_KEY: string = 'treeWidth';
+
+export const ROOT_NODE_ID: string = '0';
+
+export const BOOKMARKS_BAR_ID: string = '1';
+
+export const OPEN_CONFIRMATION_LIMIT: number = 15;
+
+/**
+ * Folders that are beneath this depth will be closed by default in the folder
+ * tree (where the Bookmarks Bar folder is at depth 0).
+ */
+export const FOLDER_OPEN_BY_DEFAULT_DEPTH: number = 1;
diff --git a/chrome/browser/resources/bookmarks/debouncer.js b/chrome/browser/resources/bookmarks/debouncer.ts
similarity index 69%
rename from chrome/browser/resources/bookmarks/debouncer.js
rename to chrome/browser/resources/bookmarks/debouncer.ts
index feefd5b..fc33438 100644
--- a/chrome/browser/resources/bookmarks/debouncer.js
+++ b/chrome/browser/resources/bookmarks/debouncer.ts
@@ -12,28 +12,25 @@
  */
 
 export class Debouncer {
-  /** @param {!function()} callback */
-  constructor(callback) {
-    /** @private {!function()} */
+  private callback_: () => void;
+  private timer_: number|null = null;
+  private timerProxy_: Window;
+  private boundTimerCallback_: () => void;
+  private isDone_: boolean = false;
+  private promiseResolver_: PromiseResolver<void>;
+
+  constructor(callback: () => void) {
     this.callback_ = callback;
-    /** @private {!Object} */
     this.timerProxy_ = window;
-    /** @private {?number} */
-    this.timer_ = null;
-    /** @private {!function()} */
     this.boundTimerCallback_ = this.timerCallback_.bind(this);
-    /** @private {boolean} */
-    this.isDone_ = false;
-    /** @private {!PromiseResolver} */
     this.promiseResolver_ = new PromiseResolver();
   }
 
   /**
    * Starts the timer for the callback, cancelling the old timer if there is
    * one.
-   * @param {number=} delay
    */
-  restartTimeout(delay) {
+  restartTimeout(delay?: number) {
     assert(!this.isDone_);
 
     this.cancelTimeout_();
@@ -41,17 +38,11 @@
         this.timerProxy_.setTimeout(this.boundTimerCallback_, delay || 0);
   }
 
-  /**
-   * @return {boolean} True if the Debouncer has finished processing.
-   */
-  done() {
+  done(): boolean {
     return this.isDone_;
   }
 
-  /**
-   * @return {!Promise} Promise which resolves immediately after the callback.
-   */
-  get promise() {
+  get promise(): Promise<void> {
     return this.promiseResolver_.promise;
   }
 
@@ -67,18 +58,16 @@
   /**
    * Cancel the timer callback, which can be restarted by calling
    * restartTimeout().
-   * @private
    */
-  cancelTimeout_() {
+  private cancelTimeout_() {
     if (this.timer_) {
       this.timerProxy_.clearTimeout(this.timer_);
     }
   }
 
-  /** @private */
-  timerCallback_() {
+  private timerCallback_() {
     this.isDone_ = true;
-    this.callback_.call();
+    this.callback_.call(this);
     this.promiseResolver_.resolve();
   }
 }
diff --git a/chrome/browser/resources/bookmarks/dialog_focus_manager.js b/chrome/browser/resources/bookmarks/dialog_focus_manager.ts
similarity index 65%
rename from chrome/browser/resources/bookmarks/dialog_focus_manager.js
rename to chrome/browser/resources/bookmarks/dialog_focus_manager.ts
index 7fcc53dc..499ef3b3 100644
--- a/chrome/browser/resources/bookmarks/dialog_focus_manager.js
+++ b/chrome/browser/resources/bookmarks/dialog_focus_manager.ts
@@ -2,6 +2,9 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js';
+
+import {CrDialogElement} from 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js';
 import {assert} from 'chrome://resources/js/assert.m.js';
 
 /**
@@ -10,19 +13,10 @@
  * first dialog was opened.
  */
 export class DialogFocusManager {
-  constructor() {
-    /** @private {HTMLElement} */
-    this.previousFocusElement_ = null;
+  private previousFocusElement_: HTMLElement|null = null;
+  private dialogs_: Set<HTMLDialogElement|CrDialogElement> = new Set();
 
-    /** @private {Set<HTMLDialogElement>} */
-    this.dialogs_ = new Set();
-  }
-
-  /**
-   * @param {HTMLDialogElement} dialog
-   * @param {function()=} showFn
-   */
-  showDialog(dialog, showFn) {
+  showDialog(dialog: (HTMLDialogElement|CrDialogElement), showFn?: () => void) {
     if (!showFn) {
       showFn = function() {
         dialog.showModal();
@@ -45,9 +39,9 @@
   }
 
   /**
-   * @return {boolean} True if the document currently has an open dialog.
+   * @return True if the document currently has an open dialog.
    */
-  hasOpenDialog() {
+  hasOpenDialog(): boolean {
     return this.dialogs_.size > 0;
   }
 
@@ -59,31 +53,22 @@
     this.previousFocusElement_ = null;
   }
 
-  /** @private */
-  updatePreviousFocus_() {
+  private updatePreviousFocus_() {
     this.previousFocusElement_ = this.getFocusedElement_();
   }
 
-  /**
-   * @return {HTMLElement}
-   * @private
-   */
-  getFocusedElement_() {
-    let focus = document.activeElement;
-    while (focus.root && focus.root.activeElement) {
-      focus = focus.root.activeElement;
+  private getFocusedElement_(): HTMLElement {
+    let focus = document.activeElement as HTMLElement;
+    while (focus.shadowRoot && focus.shadowRoot!.activeElement) {
+      focus = focus.shadowRoot!.activeElement as HTMLElement;
     }
 
     return focus;
   }
 
-  /**
-   * @param {HTMLDialogElement} dialog
-   * @return {function(Event)}
-   * @private
-   */
-  getCloseListener_(dialog) {
-    const closeListener = (e) => {
+  private getCloseListener_(dialog: (HTMLDialogElement|CrDialogElement)):
+      ((p1: Event) => void) {
+    const closeListener = (e: Event) => {
       // If the dialog is open, then it got reshown immediately and we
       // shouldn't clear it until it is closed again.
       if (dialog.open) {
@@ -102,16 +87,13 @@
     return closeListener;
   }
 
-  /** @return {!DialogFocusManager} */
-  static getInstance() {
+  static getInstance(): DialogFocusManager {
     return instance || (instance = new DialogFocusManager());
   }
 
-  /** @param {?DialogFocusManager} obj */
-  static setInstance(obj) {
+  static setInstance(obj: DialogFocusManager|null) {
     instance = obj;
   }
 }
 
-/** @type {?DialogFocusManager} */
-let instance = null;
+let instance: DialogFocusManager|null = null;
diff --git a/chrome/browser/resources/bookmarks/dnd_manager.js b/chrome/browser/resources/bookmarks/dnd_manager.ts
similarity index 64%
rename from chrome/browser/resources/bookmarks/dnd_manager.js
rename to chrome/browser/resources/bookmarks/dnd_manager.ts
index 336b933..5dd64f33 100644
--- a/chrome/browser/resources/bookmarks/dnd_manager.js
+++ b/chrome/browser/resources/bookmarks/dnd_manager.ts
@@ -2,7 +2,11 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import './folder_node.js';
+import './item.js';
+
 import {assert} from 'chrome://resources/js/assert.m.js';
+import {EventTracker} from 'chrome://resources/js/event_tracker.m.js';
 import {isTextInputElement} from 'chrome://resources/js/util.m.js';
 
 import {changeFolderOpen, deselectItems, selectItem} from './actions.js';
@@ -10,75 +14,54 @@
 import {DropPosition, ROOT_NODE_ID} from './constants.js';
 import {Debouncer} from './debouncer.js';
 import {BookmarksFolderNodeElement} from './folder_node.js';
+import {BookmarksItemElement} from './item.js';
 import {Store} from './store.js';
-import {BookmarkElement, BookmarkNode, DragData, DropDestination} from './types.js';
+import {BookmarkElement, BookmarkNode, DragData, DropDestination, NodeMap, ObjectMap} from './types.js';
 import {canEditNode, canReorderChildren, getDisplayedList, hasChildFolders, isShowingSearch, normalizeNode} from './util.js';
 
-/** @typedef {?{elements: !Array<BookmarkNode>, sameProfile: boolean}} */
-let NormalizedDragData;
+type NormalizedDragData = {
+  elements: BookmarkNode[],
+  sameProfile: boolean,
+};
 
-/** @const {number} */
-const DRAG_THRESHOLD = 15;
+const DRAG_THRESHOLD: number = 15;
 
-/**
- * @param {Element} element
- * @return {boolean}
- */
-function isBookmarkItem(element) {
+function isBookmarkItem(element: Element): boolean {
   return element.tagName === 'BOOKMARKS-ITEM';
 }
 
-/**
- * @param {Element} element
- * @return {boolean}
- */
-function isBookmarkFolderNode(element) {
+function isBookmarkFolderNode(element: Element): boolean {
   return element.tagName === 'BOOKMARKS-FOLDER-NODE';
 }
 
-/**
- * @param {Element} element
- * @return {boolean}
- */
-function isBookmarkList(element) {
+function isBookmarkList(element: Element): boolean {
   return element.tagName === 'BOOKMARKS-LIST';
 }
 
-/**
- * @param {Element} element
- * @return {boolean}
- */
-function isClosedBookmarkFolderNode(element) {
-  return isBookmarkFolderNode(/** @type {BookmarkElement} */ (element)) &&
-      !(/** @type {BookmarksFolderNodeElement} */ (element).isOpen);
+function isClosedBookmarkFolderNode(element: Element): boolean {
+  return isBookmarkFolderNode(element) &&
+      !((element as BookmarksFolderNodeElement).isOpen);
 }
 
-/**
- * @param {Array<!Element>|undefined} path
- * @return {BookmarkElement}
- */
-function getBookmarkElement(path) {
+function getBookmarkElement(path?: EventTarget[]): BookmarkElement|null {
   if (!path) {
     return null;
   }
 
-  for (let i = 0; i < path.length; i++) {
-    if (isBookmarkItem(path[i]) || isBookmarkFolderNode(path[i]) ||
-        isBookmarkList(path[i])) {
-      return /** @type {BookmarkElement} */ (path[i]);
+  for (let i = 0; i < path!.length; i++) {
+    const element = path![i] as Element;
+    if (isBookmarkItem(element) || isBookmarkFolderNode(element) ||
+        isBookmarkList(element)) {
+      return path![i] as BookmarkElement;
     }
   }
   return null;
 }
 
-/**
- * @param {Array<!Element>|undefined} path
- * @return {BookmarkElement}
- */
-function getDragElement(path) {
+function getDragElement(path: EventTarget[]): BookmarkElement|null {
   const dragElement = getBookmarkElement(path);
   for (let i = 0; i < path.length; i++) {
-    if (path[i].tagName === 'BUTTON') {
+    if ((path![i] as Element).tagName === 'BUTTON') {
       return null;
     }
   }
@@ -86,12 +69,8 @@
                                                                 null;
 }
 
-/**
- * @param {BookmarkElement} bookmarkElement
- * @return {BookmarkNode}
- */
-function getBookmarkNode(bookmarkElement) {
-  return Store.getInstance().data.nodes[bookmarkElement.itemId];
+function getBookmarkNode(bookmarkElement: BookmarkElement): BookmarkNode {
+  return Store.getInstance().data.nodes[bookmarkElement.itemId]!;
 }
 
 /**
@@ -99,16 +78,12 @@
  * bookmarkManagerPrivate API.
  */
 export class DragInfo {
-  constructor() {
-    /** @type {NormalizedDragData} */
-    this.dragData = null;
-  }
+  dragData: NormalizedDragData|null = null;
 
-  /** @param {DragData} newDragData */
-  setNativeDragData(newDragData) {
+  setNativeDragData(newDragData: DragData) {
     this.dragData = {
       sameProfile: newDragData.sameProfile,
-      elements: newDragData.elements.map((x) => normalizeNode(x))
+      elements: newDragData.elements!.map((x) => normalizeNode(x))
     };
   }
 
@@ -116,50 +91,44 @@
     this.dragData = null;
   }
 
-  /** @return {boolean} */
-  isDragValid() {
+  isDragValid(): boolean {
     return !!this.dragData;
   }
 
-  /** @return {boolean} */
-  isSameProfile() {
+  isSameProfile(): boolean {
     return !!this.dragData && this.dragData.sameProfile;
   }
 
-  /** @return {boolean} */
-  isDraggingFolders() {
+  isDraggingFolders(): boolean {
     return !!this.dragData && this.dragData.elements.some(function(node) {
       return !node.url;
     });
   }
 
-  /** @return {boolean} */
-  isDraggingBookmark(bookmarkId) {
+  isDraggingBookmark(bookmarkId: string): boolean {
     return !!this.dragData && this.isSameProfile() &&
         this.dragData.elements.some(function(node) {
           return node.id === bookmarkId;
         });
   }
 
-  /** @return {boolean} */
-  isDraggingChildBookmark(folderId) {
+  isDraggingChildBookmark(folderId: string): boolean {
     return !!this.dragData && this.isSameProfile() &&
         this.dragData.elements.some(function(node) {
           return node.parentId === folderId;
         });
   }
 
-  /** @return {boolean} */
-  isDraggingFolderToDescendant(itemId, nodes) {
+  isDraggingFolderToDescendant(itemId: string, nodes: NodeMap): boolean {
     if (!this.isSameProfile()) {
       return false;
     }
 
-    let parentId = nodes[itemId].parentId;
-    const parents = {};
+    let parentId = nodes[itemId]!.parentId;
+    const parents: ObjectMap<boolean> = {};
     while (parentId) {
       parents[parentId] = true;
-      parentId = nodes[parentId].parentId;
+      parentId = nodes[parentId]!.parentId;
     }
 
     return !!this.dragData && this.dragData.elements.some(function(node) {
@@ -172,26 +141,19 @@
  * Manages auto expanding of sidebar folders on hover while dragging.
  */
 class AutoExpander {
+  EXPAND_FOLDER_DELAY: number = 400;
+  private lastElement_: BookmarkElement|null = null;
+  private debouncer_: Debouncer;
+
   constructor() {
-    /** @const {number} */
-    this.EXPAND_FOLDER_DELAY = 400;
-
-    /** @private {?BookmarkElement} */
-    this.lastElement_ = null;
-
-    /** @type {!Debouncer} */
     this.debouncer_ = new Debouncer(() => {
       const store = Store.getInstance();
-      store.dispatch(changeFolderOpen(this.lastElement_.itemId, true));
+      store.dispatch(changeFolderOpen(this.lastElement_!.itemId, true));
       this.reset();
     });
   }
 
-  /**
-   * @param {Event} e
-   * @param {?BookmarkElement} overElement
-   */
-  update(e, overElement) {
+  update(e: Event, overElement: BookmarkElement|null) {
     const itemId = overElement ? overElement.itemId : null;
     const store = Store.getInstance();
 
@@ -199,7 +161,7 @@
     // expander. Falls through to reset the expander delay.
     if (overElement && overElement !== this.lastElement_ &&
         isClosedBookmarkFolderNode(overElement) &&
-        hasChildFolders(/** @type {string} */ (itemId), store.data.nodes)) {
+        hasChildFolders(itemId as string, store.data.nodes)) {
       this.reset();
       this.lastElement_ = overElement;
     }
@@ -225,45 +187,28 @@
  * between items or highlights folders which are valid drop targets.
  */
 class DropIndicator {
+  private removeDropIndicatorTimeoutId_: number|null;
+  private lastIndicatorElement_: BookmarkElement|null;
+  private lastIndicatorClassName_: string|null;
+  timerProxy: Window;
+
   constructor() {
-    /**
-     * @private {number|null} Timer id used to help minimize flicker.
-     */
     this.removeDropIndicatorTimeoutId_ = null;
-
-    /**
-     * The element that had a style applied it to indicate the drop location.
-     * This is used to easily remove the style when necessary.
-     * @private {BookmarkElement|null}
-     */
     this.lastIndicatorElement_ = null;
-
-    /**
-     * The style that was applied to indicate the drop location.
-     * @private {?string|null}
-     */
     this.lastIndicatorClassName_ = null;
-
-    /**
-     * Used to instantly remove the indicator style in tests.
-     * @private {!Object}
-     */
     this.timerProxy = window;
   }
 
   /**
    * Applies the drop indicator style on the target element and stores that
    * information to easily remove the style in the future.
-   * @param {HTMLElement} indicatorElement
-   * @param {DropPosition} position
    */
-  addDropIndicatorStyle(indicatorElement, position) {
+  addDropIndicatorStyle(indicatorElement: HTMLElement, position: DropPosition) {
     const indicatorStyleName = position === DropPosition.ABOVE ?
         'drag-above' :
         position === DropPosition.BELOW ? 'drag-below' : 'drag-on';
 
-    this.lastIndicatorElement_ =
-        /** @type {BookmarkElement} */ (indicatorElement);
+    this.lastIndicatorElement_ = indicatorElement as BookmarkElement;
     this.lastIndicatorClassName_ = indicatorStyleName;
 
     indicatorElement.classList.add(indicatorStyleName);
@@ -285,13 +230,12 @@
   /**
    * Displays the drop indicator on the current drop target to give the
    * user feedback on where the drop will occur.
-   * @param {DropDestination} dropDest
    */
-  update(dropDest) {
-    this.timerProxy.clearTimeout(this.removeDropIndicatorTimeoutId_);
+  update(dropDest: DropDestination) {
+    this.timerProxy.clearTimeout(this.removeDropIndicatorTimeoutId_!);
     this.removeDropIndicatorTimeoutId_ = null;
 
-    const indicatorElement = dropDest.element.getDropTarget();
+    const indicatorElement = dropDest.element.getDropTarget()!;
     const position = dropDest.position;
 
     this.removeDropIndicatorStyle();
@@ -318,29 +262,20 @@
  * Manages drag and drop events for the bookmarks-app.
  */
 export class DNDManager {
+  private dragInfo_: DragInfo|null;
+  private dropDestination_: DropDestination|null;
+  private dropIndicator_: DropIndicator|null;
+  private eventTracker_: EventTracker = new EventTracker();
+  private autoExpander_: AutoExpander|null;
+  private timerProxy_: any;
+  private lastPointerWasTouch_: boolean;
+
   constructor() {
-    /** @private {DragInfo} */
     this.dragInfo_ = null;
-
-    /** @private {?DropDestination} */
     this.dropDestination_ = null;
-
-    /** @private {DropIndicator} */
     this.dropIndicator_ = null;
-
-    /** @private {Object<string, function(!Event)>} */
-    this.documentListeners_ = null;
-
-    /** @private {?AutoExpander} */
     this.autoExpander_ = null;
-
-    /**
-     * Used to instantly clearDragData in tests.
-     * @private {!Object}
-     */
     this.timerProxy_ = window;
-
-    /** @private {boolean} */
     this.lastPointerWasTouch_ = false;
   }
 
@@ -349,19 +284,14 @@
     this.dropIndicator_ = new DropIndicator();
     this.autoExpander_ = new AutoExpander();
 
-    this.documentListeners_ = {
-      'dragstart': this.onDragStart_.bind(this),
-      'dragenter': this.onDragEnter_.bind(this),
-      'dragover': this.onDragOver_.bind(this),
-      'dragleave': this.onDragLeave_.bind(this),
-      'drop': this.onDrop_.bind(this),
-      'dragend': this.clearDragData_.bind(this),
-      'mousedown': this.onMouseDown_.bind(this),
-      'touchstart': this.onTouchStart_.bind(this),
-    };
-    for (const event in this.documentListeners_) {
-      document.addEventListener(event, this.documentListeners_[event]);
-    }
+    this.eventTracker_.add(document, 'dragstart', e => this.onDragStart_(e));
+    this.eventTracker_.add(document, 'dragenter', e => this.onDragEnter_(e));
+    this.eventTracker_.add(document, 'dragover', e => this.onDragOver_(e));
+    this.eventTracker_.add(document, 'dragleave', () => this.onDragLeave_());
+    this.eventTracker_.add(document, 'drop', e => this.onDrop_(e));
+    this.eventTracker_.add(document, 'dragend', () => this.clearDragData_());
+    this.eventTracker_.add(document, 'mousedown', () => this.onMouseDown_());
+    this.eventTracker_.add(document, 'touchstart', () => this.onTouchStart_());
 
     chrome.bookmarkManagerPrivate.onDragEnter.addListener(
         this.handleChromeDragEnter_.bind(this));
@@ -370,20 +300,14 @@
   }
 
   destroy() {
-    for (const event in this.documentListeners_) {
-      document.removeEventListener(event, this.documentListeners_[event]);
-    }
+    this.eventTracker_.removeAll();
   }
 
   ////////////////////////////////////////////////////////////////////////////
   // DragEvent handlers:
 
-  /**
-   * @private
-   * @param {Event} e
-   */
-  onDragStart_(e) {
-    const dragElement = getDragElement(e.path);
+  private onDragStart_(e: Event) {
+    const dragElement = getDragElement(e.composedPath());
     if (!dragElement) {
       return;
     }
@@ -406,14 +330,14 @@
       // delay on large amount of bookmark dragging.
       for (const itemId of displayingItems) {
         for (const element of dragData.elements) {
-          if (element.id === itemId) {
-            draggedNodes.push(element.id);
+          if (element!.id === itemId) {
+            draggedNodes.push(element!.id);
             break;
           }
         }
       }
     } else {
-      draggedNodes = dragData.elements.map((item) => item.id);
+      draggedNodes = dragData.elements.map((item) => item!.id);
     }
 
     assert(draggedNodes.length === dragData.elements.length);
@@ -422,22 +346,17 @@
     assert(dragNodeIndex !== -1);
 
     chrome.bookmarkManagerPrivate.startDrag(
-        draggedNodes, dragNodeIndex, this.lastPointerWasTouch_, e.clientX,
-        e.clientY);
+        draggedNodes, dragNodeIndex, this.lastPointerWasTouch_,
+        (e as DragEvent).clientX, (e as DragEvent).clientY);
   }
 
-  /** @private */
-  onDragLeave_() {
-    this.dropIndicator_.finish();
+  private onDragLeave_() {
+    this.dropIndicator_!.finish();
   }
 
-  /**
-   * @private
-   * @param {!Event} e
-   */
-  onDrop_(e) {
+  private onDrop_(e: Event) {
     // Allow normal DND on text inputs.
-    if (isTextInputElement(e.path[0])) {
+    if (isTextInputElement((e.composedPath()[0] as HTMLElement))) {
       return;
     }
 
@@ -459,23 +378,15 @@
     this.clearDragData_();
   }
 
-  /**
-   * @private
-   * @param {Event} e
-   */
-  onDragEnter_(e) {
+  private onDragEnter_(e: Event) {
     e.preventDefault();
   }
 
-  /**
-   * @private
-   * @param {Event} e
-   */
-  onDragOver_(e) {
+  private onDragOver_(e: Event) {
     this.dropDestination_ = null;
 
     // Allow normal DND on text inputs.
-    if (isTextInputElement(e.path[0])) {
+    if (isTextInputElement(e.composedPath()[0] as HTMLElement)) {
       return;
     }
 
@@ -483,72 +394,62 @@
     // navigation. We never want to do that for the bookmark manager.
     e.preventDefault();
 
-    if (!this.dragInfo_.isDragValid()) {
+    if (!this.dragInfo_!.isDragValid()) {
       return;
     }
 
     const state = Store.getInstance().data;
-    const items = this.dragInfo_.dragData.elements;
+    const items = this.dragInfo_!.dragData!.elements;
 
-    const overElement = getBookmarkElement(e.path);
-    this.autoExpander_.update(e, overElement);
+    const overElement = getBookmarkElement(e.composedPath());
+    this.autoExpander_!.update(e, overElement);
     if (!overElement) {
-      this.dropIndicator_.finish();
+      this.dropIndicator_!.finish();
       return;
     }
 
     // Now we know that we can drop. Determine if we will drop above, on or
     // below based on mouse position etc.
     this.dropDestination_ =
-        this.calculateDropDestination_(e.clientY, overElement);
+        this.calculateDropDestination_((e as DragEvent).clientY, overElement);
     if (!this.dropDestination_) {
-      this.dropIndicator_.finish();
+      this.dropIndicator_!.finish();
       return;
     }
 
-    this.dropIndicator_.update(this.dropDestination_);
+    this.dropIndicator_!.update(this.dropDestination_);
   }
 
-  /** @private */
-  onMouseDown_() {
+  private onMouseDown_() {
     this.lastPointerWasTouch_ = false;
   }
 
-  /** @private */
-  onTouchStart_() {
+  private onTouchStart_() {
     this.lastPointerWasTouch_ = true;
   }
 
-  /**
-   * @private
-   * @param {DragData} dragData
-   */
-  handleChromeDragEnter_(dragData) {
-    this.dragInfo_.setNativeDragData(dragData);
+  private handleChromeDragEnter_(dragData: DragData) {
+    this.dragInfo_!.setNativeDragData(dragData);
   }
 
   ////////////////////////////////////////////////////////////////////////////
   // Helper methods:
 
-  /** @private */
-  clearDragData_() {
-    this.autoExpander_.reset();
+  private clearDragData_() {
+    this.autoExpander_!.reset();
 
     // Defer the clearing of the data so that the bookmark manager API's drop
     // event doesn't clear the drop data before the web drop event has a
     // chance to execute (on Mac).
     this.timerProxy_.setTimeout(() => {
-      this.dragInfo_.clearDragData();
+      this.dragInfo_!.clearDragData();
       this.dropDestination_ = null;
-      this.dropIndicator_.finish();
+      this.dropIndicator_!.finish();
     }, 0);
   }
 
-  /**
-   * @param {DropDestination} dropDestination
-   * @return {{parentId: string, index: number}}
-   */
-  calculateDropInfo_(dropDestination) {
+  private calculateDropInfo_(dropDestination: DropDestination):
+      {parentId: string, index: number} {
     if (isBookmarkList(dropDestination.element)) {
       return {
         index: 0,
@@ -566,8 +467,8 @@
 
       // Drops between items in the normal list and the sidebar use the drop
       // destination node's parent.
-      parentId = assert(node.parentId);
-      index = state.nodes[parentId].children.indexOf(node.id);
+      parentId = assert(node.parentId!);
+      index = state.nodes[parentId]!.children!.indexOf(node.id);
 
       if (position === DropPosition.BELOW) {
         index++;
@@ -583,10 +484,8 @@
   /**
    * Calculates which items should be dragged based on the initial drag item
    * and the current selection. Dragged items will end up selected.
-   * @param {!BookmarkElement} dragElement
-   * @private
    */
-  calculateDragData_(dragElement) {
+  private calculateDragData_(dragElement: BookmarkElement) {
     const dragId = dragElement.itemId;
     const store = Store.getInstance();
     const state = store.data;
@@ -625,15 +524,10 @@
 
   /**
    * This function determines where the drop will occur.
-   * @private
-   * @param {number} elementClientY
-   * @param {!BookmarkElement} overElement
-   * @return {?DropDestination} If no valid drop position is found, null,
-   *   otherwise:
-   *       element - The target element that will receive the drop.
-   *       position - A |DropPosition| relative to the |element|.
    */
-  calculateDropDestination_(elementClientY, overElement) {
+  private calculateDropDestination_(
+      elementClientY: number,
+      overElement: BookmarkElement): DropDestination|null {
     const validDropPositions = this.calculateValidDropPositions_(overElement);
     if (validDropPositions === DropPosition.NONE) {
       return null;
@@ -642,7 +536,7 @@
     const above = validDropPositions & DropPosition.ABOVE;
     const below = validDropPositions & DropPosition.BELOW;
     const on = validDropPositions & DropPosition.ON;
-    const rect = overElement.getDropTarget().getBoundingClientRect();
+    const rect = overElement.getDropTarget()!.getBoundingClientRect();
     const yRatio = (elementClientY - rect.top) / rect.height;
 
     if (above && (yRatio <= .25 || yRatio <= .5 && (!below || !on))) {
@@ -662,13 +556,9 @@
 
   /**
    * Determines the valid drop positions for the given target element.
-   * @private
-   * @param {!BookmarkElement} overElement The element that we are currently
-   *     dragging over.
-   * @return {number} An bit field enumeration of valid drop locations.
    */
-  calculateValidDropPositions_(overElement) {
-    const dragInfo = this.dragInfo_;
+  private calculateValidDropPositions_(overElement: BookmarkElement): number {
+    const dragInfo = this.dragInfo_!;
     const state = Store.getInstance().data;
     let itemId = overElement.itemId;
 
@@ -701,13 +591,8 @@
     return validDropPositions;
   }
 
-  /**
-   * @private
-   * @param {BookmarkElement} overElement
-   * @return {number}
-   */
-  calculateDropAboveBelow_(overElement) {
-    const dragInfo = this.dragInfo_;
+  private calculateDropAboveBelow_(overElement: BookmarkElement): number {
+    const dragInfo = this.dragInfo_!;
     const state = Store.getInstance().data;
 
     if (isBookmarkList(overElement)) {
@@ -729,7 +614,8 @@
     let validDropPositions = DropPosition.NONE;
 
     // Cannot drop above if the item above is already in the drag source.
-    const previousElem = overElement.previousElementSibling;
+    const previousElem =
+        overElement.previousElementSibling as BookmarksFolderNodeElement;
     if (!previousElem || !dragInfo.isDraggingBookmark(previousElem.itemId)) {
       validDropPositions |= DropPosition.ABOVE;
     }
@@ -741,7 +627,8 @@
       return validDropPositions;
     }
 
-    const nextElement = overElement.nextElementSibling;
+    const nextElement =
+        overElement.nextElementSibling as BookmarksFolderNodeElement;
     // Cannot drop below if the item below is already in the drag source.
     if (!nextElement || !dragInfo.isDraggingBookmark(nextElement.itemId)) {
       validDropPositions |= DropPosition.BELOW;
@@ -752,18 +639,13 @@
 
   /**
    * Determine whether we can drop the dragged items on the drop target.
-   * @private
-   * @param {!BookmarkElement} overElement The element that we are currently
-   *     dragging over.
-   * @return {boolean} Whether we can drop the dragged items on the drop
-   *     target.
    */
-  canDropOn_(overElement) {
+  private canDropOn_(overElement: BookmarkElement): boolean {
     // Allow dragging onto empty bookmark lists.
     if (isBookmarkList(overElement)) {
       const state = Store.getInstance().data;
       return !!state.selectedFolder &&
-          state.nodes[state.selectedFolder].children.length === 0;
+          state.nodes[state.selectedFolder]!.children!.length === 0;
     }
 
     // We can only drop on a folder.
@@ -771,21 +653,16 @@
       return false;
     }
 
-    return !this.dragInfo_.isDraggingChildBookmark(overElement.itemId);
+    return !this.dragInfo_!.isDraggingChildBookmark(overElement.itemId);
   }
 
-  /**
-   * @param {DropDestination} dropDestination
-   * @private
-   */
-  shouldHighlight_(dropDestination) {
+  private shouldHighlight_(dropDestination: DropDestination): boolean {
     return isBookmarkItem(dropDestination.element) ||
         isBookmarkList(dropDestination.element);
   }
 
-  /** @param {!Object} timerProxy */
-  setTimerProxyForTesting(timerProxy) {
+  setTimerProxyForTesting(timerProxy: any) {
     this.timerProxy_ = timerProxy;
-    this.dropIndicator_.timerProxy = timerProxy;
+    this.dropIndicator_!.timerProxy = timerProxy;
   }
 }
diff --git a/chrome/browser/resources/bookmarks/edit_dialog.js b/chrome/browser/resources/bookmarks/edit_dialog.ts
similarity index 72%
rename from chrome/browser/resources/bookmarks/edit_dialog.js
rename to chrome/browser/resources/bookmarks/edit_dialog.ts
index 70c3c2b8..87546f0 100644
--- a/chrome/browser/resources/bookmarks/edit_dialog.js
+++ b/chrome/browser/resources/bookmarks/edit_dialog.ts
@@ -8,6 +8,8 @@
 import 'chrome://resources/cr_elements/shared_style_css.m.js';
 import './strings.m.js';
 
+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 {assert} from 'chrome://resources/js/assert.m.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';
@@ -16,7 +18,13 @@
 import {DialogFocusManager} from './dialog_focus_manager.js';
 import {BookmarkNode} from './types.js';
 
-/** @polymer */
+export interface BookmarksEditDialogElement {
+  $: {
+    dialog: CrDialogElement,
+    url: CrInputElement,
+  }
+}
+
 export class BookmarksEditDialogElement extends PolymerElement {
   static get is() {
     return 'bookmarks-edit-dialog';
@@ -28,53 +36,45 @@
 
   static get properties() {
     return {
-      /** @private */
       isFolder_: Boolean,
-
-      /** @private */
       isEdit_: Boolean,
 
       /**
        * Item that is being edited, or null when adding.
-       * @private {?BookmarkNode}
        */
       editItem_: Object,
 
       /**
        * Parent node for the item being added, or null when editing.
-       * @private {?string}
        */
       parentId_: String,
-
-      /** @private */
       titleValue_: String,
-
-      /** @private */
       urlValue_: String,
     };
   }
 
+  private isFolder_: boolean;
+  private isEdit_: boolean;
+  private editItem_: BookmarkNode|null;
+  private parentId_: string|null;
+  private titleValue_: string;
+  private urlValue_: string;
+
   /**
    * Show the dialog to add a new folder (if |isFolder|) or item, which will be
    * inserted into the tree as a child of |parentId|.
-   * @param {boolean} isFolder
-   * @param {string} parentId
    */
-  showAddDialog(isFolder, parentId) {
+  showAddDialog(isFolder: boolean, parentId: string) {
     this.reset_();
     this.isEdit_ = false;
     this.isFolder_ = isFolder;
     this.parentId_ = parentId;
 
-    DialogFocusManager.getInstance().showDialog(
-        /** @type {!HTMLDialogElement} */ (this.$.dialog));
+    DialogFocusManager.getInstance().showDialog(this.$.dialog);
   }
 
-  /**
-   * Show the edit dialog for |editItem|.
-   * @param {BookmarkNode} editItem
-   */
-  showEditDialog(editItem) {
+  /** Show the edit dialog for |editItem|. */
+  showEditDialog(editItem: BookmarkNode) {
     this.reset_();
     this.isEdit_ = true;
     this.isFolder_ = !editItem.url;
@@ -82,18 +82,16 @@
 
     this.titleValue_ = editItem.title;
     if (!this.isFolder_) {
-      this.urlValue_ = assert(editItem.url);
+      this.urlValue_ = assert(editItem.url!);
     }
 
-    DialogFocusManager.getInstance().showDialog(
-        /** @type {!HTMLDialogElement} */ (this.$.dialog));
+    DialogFocusManager.getInstance().showDialog(this.$.dialog);
   }
 
   /**
    * Clear out existing values from the dialog, allowing it to be reused.
-   * @private
    */
-  reset_() {
+  private reset_() {
     this.editItem_ = null;
     this.parentId_ = null;
     this.$.url.invalid = false;
@@ -101,13 +99,7 @@
     this.urlValue_ = '';
   }
 
-  /**
-   * @param {boolean} isFolder
-   * @param {boolean} isEdit
-   * @return {string}
-   * @private
-   */
-  getDialogTitle_(isFolder, isEdit) {
+  private getDialogTitle_(isFolder: boolean, isEdit: boolean): string {
     let title;
     if (isEdit) {
       title = isFolder ? 'renameFolderTitle' : 'editBookmarkTitle';
@@ -121,11 +113,9 @@
   /**
    * Validates the value of the URL field, returning true if it is a valid URL.
    * May modify the value by prepending 'http://' in order to make it valid.
-   * @return {boolean}
-   * @private
    */
-  validateUrl_() {
-    const urlInput = /** @type {CrInputElement} */ (this.$.url);
+  private validateUrl_(): boolean {
+    const urlInput = this.$.url;
     const originalValue = this.urlValue_;
 
     if (urlInput.validate()) {
@@ -142,9 +132,9 @@
     return false;
   }
 
-  /** @private */
-  onSaveButtonTap_() {
-    const edit = {'title': this.titleValue_};
+  private onSaveButtonTap_() {
+    const edit: { title: string, url?: string, parentId?: string|null } =
+        { 'title': this.titleValue_ };
     if (!this.isFolder_) {
       if (!this.validateUrl_()) {
         return;
@@ -154,7 +144,7 @@
     }
 
     if (this.isEdit_) {
-      chrome.bookmarks.update(this.editItem_.id, edit);
+      chrome.bookmarks.update(this.editItem_!.id, edit);
     } else {
       edit['parentId'] = this.parentId_;
       trackUpdatedItems();
@@ -163,8 +153,7 @@
     this.$.dialog.close();
   }
 
-  /** @private */
-  onCancelButtonTap_() {
+  private onCancelButtonTap_() {
     this.$.dialog.cancel();
   }
 }
diff --git a/chrome/browser/resources/bookmarks/folder_node.js b/chrome/browser/resources/bookmarks/folder_node.ts
similarity index 66%
rename from chrome/browser/resources/bookmarks/folder_node.js
rename to chrome/browser/resources/bookmarks/folder_node.ts
index d8dbec9a..27a7a1d 100644
--- a/chrome/browser/resources/bookmarks/folder_node.js
+++ b/chrome/browser/resources/bookmarks/folder_node.ts
@@ -10,7 +10,6 @@
 
 import {assert} from 'chrome://resources/js/assert.m.js';
 import {StoreObserver} from 'chrome://resources/js/cr/ui/store.m.js';
-import {StoreClientInterface as CrUiStoreClientInterface} from 'chrome://resources/js/cr/ui/store_client.m.js';
 import {isRTL} from 'chrome://resources/js/util.m.js';
 import {html, microTask, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
@@ -21,17 +20,19 @@
 import {BookmarkNode, BookmarksPageState} from './types.js';
 import {hasChildFolders, isShowingSearch} from './util.js';
 
-/**
- * @constructor
- * @extends {PolymerElement}
- * @implements {BookmarksStoreClientInterface}
- * @implements {CrUiStoreClientInterface}
- * @implements {StoreObserver<BookmarksPageState>}
- */
 const BookmarksFolderNodeElementBase =
-    mixinBehaviors(StoreClient, PolymerElement);
+    mixinBehaviors(StoreClient, PolymerElement) as {
+  new (): PolymerElement & BookmarksStoreClientInterface &
+      StoreObserver<BookmarksPageState>;
+}
 
-/** @polymer */
+export interface BookmarksFolderNodeElement {
+  $: {
+    container: HTMLDivElement,
+    descendants: HTMLDivElement,
+  }
+}
+
 export class BookmarksFolderNodeElement extends BookmarksFolderNodeElementBase {
   static get is() {
     return 'bookmarks-folder-node';
@@ -58,27 +59,20 @@
         computed: 'computeIsOpen_(openState_, depth)',
       },
 
-      /** @type {BookmarkNode} */
       item_: Object,
 
-      /** @private {?boolean} */
       openState_: Boolean,
 
-      /** @private */
       selectedFolder_: String,
 
-      /** @private */
       searchActive_: Boolean,
 
-      /** @private */
       isSelectedFolder_: {
         type: Boolean,
-        value: false,
         reflectToAttribute: true,
         computed: 'computeIsSelected_(itemId, selectedFolder_, searchActive_)'
       },
 
-      /** @private */
       hasChildFolder_: {
         type: Boolean,
         computed: 'computeHasChildFolder_(item_.children)',
@@ -86,6 +80,16 @@
     };
   }
 
+  depth: number;
+  isOpen: boolean;
+  itemId: string;
+  private item_: BookmarkNode;
+  private openState_: boolean;
+  private selectedFolder_: string;
+  private searchActive_: boolean;
+  private isSelectedFolder_: boolean = false;
+  private hasChildFolder_: boolean;
+
   static get observers() {
     return [
       'updateAriaExpanded_(hasChildFolder_, isOpen)',
@@ -103,48 +107,37 @@
   connectedCallback() {
     super.connectedCallback();
     this.watch('item_', state => {
-      return /** @type {!BookmarksPageState} */ (state).nodes[this.itemId];
+      return (state as BookmarksPageState).nodes[this.itemId];
     });
     this.watch('openState_', state => {
-      const bookmarksState = /** @type {!BookmarksPageState} */ (state);
+      const bookmarksState = state as BookmarksPageState;
       return bookmarksState.folderOpenState.has(this.itemId) ?
           bookmarksState.folderOpenState.get(this.itemId) :
           null;
     });
     this.watch('selectedFolder_', state => {
-      return /** @type {!BookmarksPageState} */ (state).selectedFolder;
+      return (state as BookmarksPageState).selectedFolder;
     });
     this.watch('searchActive_', state => {
-      return isShowingSearch(/** @type {!BookmarksPageState} */ (state));
+      return isShowingSearch(state as BookmarksPageState);
     });
 
     this.updateFromStore();
   }
 
-  /**
-   * @param {boolean} isSelectedFolder
-   * @return {string}
-   * @private
-   */
-  getContainerClass_(isSelectedFolder) {
+  private getContainerClass_(isSelectedFolder: boolean): string {
     return isSelectedFolder ? 'selected' : '';
   }
 
-  /** @return {!HTMLElement} */
-  getFocusTarget() {
-    return /** @type {!HTMLDivElement} */ (this.$.container);
+  getFocusTarget(): HTMLElement {
+    return this.$.container;
   }
 
-  /** @return {HTMLElement} */
-  getDropTarget() {
-    return /** @type {!HTMLDivElement} */ (this.$.container);
+  getDropTarget(): HTMLElement {
+    return this.$.container;
   }
 
-  /**
-   * @private
-   * @param {!Event} e
-   */
-  onKeydown_(e) {
+  private onKeydown_(e: KeyboardEvent) {
     let yDirection = 0;
     let xDirection = 0;
     let handled = true;
@@ -167,7 +160,7 @@
     }
 
     this.changeKeyboardSelection_(
-        xDirection, yDirection, this.root.activeElement);
+        xDirection, yDirection, this.shadowRoot!.activeElement);
 
     if (!handled) {
       handled = BookmarksCommandManagerElement.getInstance().handleKeyEvent(
@@ -182,16 +175,11 @@
     e.stopPropagation();
   }
 
-  /**
-   * @private
-   * @param {number} xDirection
-   * @param {number} yDirection
-   * @param {!HTMLElement} currentFocus
-   */
-  changeKeyboardSelection_(xDirection, yDirection, currentFocus) {
+  private changeKeyboardSelection_(
+      xDirection: number, yDirection: number, currentFocus: Element|null) {
     let newFocusFolderNode = null;
-    const isChildFolderNodeFocused =
-        currentFocus && currentFocus.tagName === 'BOOKMARKS-FOLDER-NODE';
+    const isChildFolderNodeFocused = currentFocus &&
+        (currentFocus as HTMLElement)!.tagName === 'BOOKMARKS-FOLDER-NODE';
 
     if (xDirection === 1) {
       // The right arrow opens a folder if closed and goes to the first child
@@ -210,8 +198,8 @@
         this.dispatch(changeFolderOpen(this.item_.id, false));
       } else {
         const parentFolderNode = this.getParentFolderNode_();
-        if (parentFolderNode.itemId !== ROOT_NODE_ID) {
-          parentFolderNode.getFocusTarget().focus();
+        if (parentFolderNode!.itemId !== ROOT_NODE_ID) {
+          parentFolderNode!.getFocusTarget().focus();
         }
       }
     }
@@ -232,8 +220,7 @@
       // Get the next child folder node if a child is focused.
       if (!newFocusFolderNode) {
         newFocusFolderNode = this.getNextChild_(
-            yDirection === -1,
-            /** @type {!BookmarksFolderNodeElement} */ (currentFocus));
+            yDirection === -1, (currentFocus! as BookmarksFolderNodeElement));
       }
 
       // The first child's predecessor is this node.
@@ -245,7 +232,7 @@
     // If there is no newly focused node, allow the parent to handle the change.
     if (!newFocusFolderNode) {
       if (this.itemId !== ROOT_NODE_ID) {
-        this.getParentFolderNode_().changeKeyboardSelection_(
+        this.getParentFolderNode_()!.changeKeyboardSelection_(
             0, yDirection, this);
       }
 
@@ -260,13 +247,9 @@
 
   /**
    * Returns the next or previous visible bookmark node relative to |child|.
-   * @private
-   * @param {boolean} reverse
-   * @param {!BookmarksFolderNodeElement} child
-   * @return {BookmarksFolderNodeElement|null} Returns null if there is no child
-   *     before/after |child|.
    */
-  getNextChild_(reverse, child) {
+  private getNextChild_(reverse: boolean, child: BookmarksFolderNodeElement):
+      BookmarksFolderNodeElement|null {
     let newFocus = null;
     const children = this.getChildFolderNodes_();
 
@@ -276,10 +259,10 @@
       // A child node's predecessor is either the previous child's last visible
       // descendant, or this node, which is its immediate parent.
       newFocus =
-          index === 0 ? null : children[index - 1].getLastVisibleDescendant_();
+          index === 0 ? null : children[index - 1]!.getLastVisibleDescendant_();
     } else if (index < children.length - 1) {
       // A successor to a child is the next child.
-      newFocus = children[index + 1];
+      newFocus = children[index + 1]!;
     }
 
     return newFocus;
@@ -287,131 +270,86 @@
 
   /**
    * Returns the immediate parent folder node, or null if there is none.
-   * @private
-   * @return {BookmarksFolderNodeElement|null}
    */
-  getParentFolderNode_() {
+  private getParentFolderNode_(): BookmarksFolderNodeElement|null {
     let parentFolderNode = this.parentNode;
     while (parentFolderNode &&
-           parentFolderNode.tagName !== 'BOOKMARKS-FOLDER-NODE') {
-      parentFolderNode = parentFolderNode.parentNode || parentFolderNode.host;
+           (parentFolderNode as HTMLElement).tagName !==
+               'BOOKMARKS-FOLDER-NODE') {
+      parentFolderNode =
+          parentFolderNode.parentNode || (parentFolderNode as ShadowRoot).host;
     }
-    return parentFolderNode || null;
+    return (parentFolderNode as BookmarksFolderNodeElement) || null;
   }
 
-  /**
-   * @private
-   * @return {BookmarksFolderNodeElement}
-   */
-  getLastVisibleDescendant_() {
+  private getLastVisibleDescendant_(): BookmarksFolderNodeElement {
     const children = this.getChildFolderNodes_();
     if (!this.isOpen || children.length === 0) {
       return this;
     }
 
-    return children.pop().getLastVisibleDescendant_();
+    return children.pop()!.getLastVisibleDescendant_();
   }
 
-  /** @private */
-  selectFolder_() {
+  private selectFolder_() {
     if (!this.isSelectedFolder_) {
       this.dispatch(selectFolder(this.itemId, this.getState().nodes));
     }
   }
 
-  /**
-   * @param {!Event} e
-   * @private
-   */
-  onContextMenu_(e) {
+  private onContextMenu_(e: MouseEvent) {
     e.preventDefault();
     this.selectFolder_();
     BookmarksCommandManagerElement.getInstance().openCommandMenuAtPosition(
         e.clientX, e.clientY, MenuSource.TREE, new Set([this.itemId]));
   }
 
-  /**
-   * @private
-   * @return {!Array<!BookmarksFolderNodeElement>}
-   */
-  getChildFolderNodes_() {
-    return Array.from(this.root.querySelectorAll('bookmarks-folder-node'));
+  private getChildFolderNodes_(): BookmarksFolderNodeElement[] {
+    return Array.from(this.shadowRoot!.querySelectorAll(
+        'bookmarks-folder-node'));
   }
 
   /**
    * Toggles whether the folder is open.
-   * @private
-   * @param {!Event} e
    */
-  toggleFolder_(e) {
+  private toggleFolder_(e: Event) {
     this.dispatch(changeFolderOpen(this.itemId, !this.isOpen));
     e.stopPropagation();
   }
 
-  /**
-   * @private
-   * @param {!Event} e
-   */
-  preventDefault_(e) {
+  private preventDefault_(e: Event) {
     e.preventDefault();
   }
 
-  /**
-   * @private
-   * @param {string} itemId
-   * @param {string} selectedFolder
-   * @return {boolean}
-   */
-  computeIsSelected_(itemId, selectedFolder, searchActive) {
+  private computeIsSelected_(
+      itemId: string, selectedFolder: string, searchActive: boolean): boolean {
     return itemId === selectedFolder && !searchActive;
   }
 
-  /**
-   * @private
-   * @return {boolean}
-   */
-  computeHasChildFolder_() {
+  private computeHasChildFolder_(): boolean {
     return hasChildFolders(this.itemId, this.getState().nodes);
   }
 
-  /** @private */
-  depthChanged_() {
+  private depthChanged_() {
     this.style.setProperty('--node-depth', String(this.depth));
     if (this.depth === -1) {
       this.$.descendants.removeAttribute('role');
     }
   }
 
-  /**
-   * @private
-   * @return {number}
-   */
-  getChildDepth_() {
+  private getChildDepth_(): number {
     return this.depth + 1;
   }
 
-  /**
-   * @param {string} itemId
-   * @private
-   * @return {boolean}
-   */
-  isFolder_(itemId) {
-    return !this.getState().nodes[itemId].url;
+  private isFolder_(itemId: string): boolean {
+    return !this.getState().nodes[itemId]!.url;
   }
 
-  /**
-   * @private
-   * @return {boolean}
-   */
-  isRootFolder_() {
+  private isRootFolder_(): boolean {
     return this.itemId === ROOT_NODE_ID;
   }
 
-  /**
-   * @private
-   * @return {string}
-   */
-  getTabIndex_() {
+  private getTabIndex_(): string {
     // This returns a tab index of 0 for the cached selected folder when the
     // search is active, even though this node is not technically selected. This
     // allows the sidebar to be focusable during a search.
@@ -421,11 +359,8 @@
   /**
    * Sets the 'aria-expanded' accessibility on nodes which need it. Note that
    * aria-expanded="false" is different to having the attribute be undefined.
-   * @param {boolean} hasChildFolder
-   * @param {boolean} isOpen
-   * @private
    */
-  updateAriaExpanded_(hasChildFolder, isOpen) {
+  private updateAriaExpanded_(hasChildFolder: boolean, isOpen: boolean) {
     if (hasChildFolder) {
       this.getFocusTarget().setAttribute('aria-expanded', String(isOpen));
     } else {
@@ -435,22 +370,16 @@
 
   /**
    * Scrolls the folder node into view when the folder is selected.
-   * @private
    */
-  scrollIntoViewIfNeeded_() {
+  private scrollIntoViewIfNeeded_() {
     if (!this.isSelectedFolder_) {
       return;
     }
 
-    microTask.run(() => this.$.container.scrollIntoViewIfNeeded());
+    microTask.run(() => this.$.container.scrollIntoView());
   }
 
-  /**
-   * @param {?boolean} openState
-   * @param {number} depth
-   * @return {boolean}
-   */
-  computeIsOpen_(openState, depth) {
+  private computeIsOpen_(openState: boolean|null, depth: number): boolean {
     return openState != null ? openState :
                                depth <= FOLDER_OPEN_BY_DEFAULT_DEPTH;
   }
diff --git a/chrome/browser/resources/bookmarks/item.js b/chrome/browser/resources/bookmarks/item.ts
similarity index 70%
rename from chrome/browser/resources/bookmarks/item.js
rename to chrome/browser/resources/bookmarks/item.ts
index 2a0ee7e..1d79302 100644
--- a/chrome/browser/resources/bookmarks/item.js
+++ b/chrome/browser/resources/bookmarks/item.ts
@@ -8,11 +8,11 @@
 import './shared_style.js';
 import './strings.m.js';
 
+import {CrIconButtonElement} from 'chrome://resources/cr_elements/cr_icon_button/cr_icon_button.m.js';
 import {assert} from 'chrome://resources/js/assert.m.js';
 import {isMac} from 'chrome://resources/js/cr.m.js';
 import {focusWithoutInk} from 'chrome://resources/js/cr/ui/focus_without_ink.m.js';
 import {StoreObserver} from 'chrome://resources/js/cr/ui/store.m.js';
-import {StoreClientInterface as CrUiStoreClientInterface} from 'chrome://resources/js/cr/ui/store_client.m.js';
 import {getFaviconForPageURL} from 'chrome://resources/js/icon.m.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
 import {html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
@@ -23,16 +23,19 @@
 import {BookmarksStoreClientInterface, StoreClient} from './store_client.js';
 import {BookmarkNode, BookmarksPageState} from './types.js';
 
-/**
- * @constructor
- * @extends {PolymerElement}
- * @implements {BookmarksStoreClientInterface}
- * @implements {CrUiStoreClientInterface}
- * @implements {StoreObserver<BookmarksPageState>}
- */
-const BookmarksItemElementBase = mixinBehaviors(StoreClient, PolymerElement);
+const BookmarksItemElementBase =
+    mixinBehaviors(StoreClient, PolymerElement) as {
+  new (): PolymerElement & BookmarksStoreClientInterface &
+      StoreObserver<BookmarksPageState>
+}
 
-/** @polymer */
+export interface BookmarksItemElement {
+  $: {
+    icon: HTMLDivElement,
+    menuButton: CrIconButtonElement,
+  }
+}
+
 export class BookmarksItemElement extends BookmarksItemElementBase {
   static get is() {
     return 'bookmarks-item';
@@ -48,32 +51,28 @@
         type: String,
         observer: 'onItemIdChanged_',
       },
-
       ironListTabIndex: Number,
-
-      /** @private {BookmarkNode} */
       item_: {
         type: Object,
         observer: 'onItemChanged_',
       },
-
-      /** @private */
       isSelectedItem_: {
         type: Boolean,
         reflectToAttribute: true,
       },
-
-      /** @private */
       isMultiSelect_: Boolean,
-
-      /** @private */
       isFolder_: Boolean,
-
-      /** @private */
       lastTouchPoints_: Number,
     };
   }
 
+  itemId: string;
+  private item_: BookmarkNode;
+  private isSelectedItem_: boolean;
+  private isMultiSelect_: boolean;
+  private isFolder_: boolean;
+  private lastTouchPoints_: number;
+
   static get observers() {
     return [
       'updateFavicon_(item_.url)',
@@ -83,43 +82,31 @@
   ready() {
     super.ready();
 
-    this.addEventListener(
-        'click',
-        e => this.onClick_(
-            /** @type {!MouseEvent} */ (e)));
-    this.addEventListener(
-        'dblclick',
-        e => this.onDblClick_(
-            /** @type {!MouseEvent} */ (e)));
+    this.addEventListener('click', e => this.onClick_(e as MouseEvent));
+    this.addEventListener('dblclick', e => this.onDblClick_(e as MouseEvent));
     this.addEventListener('contextmenu', e => this.onContextMenu_(e));
+    this.addEventListener('keydown', e => this.onKeydown_(e as KeyboardEvent));
     this.addEventListener(
-        'keydown',
-        e => this.onKeydown_(
-            /** @type {!KeyboardEvent} */ (e)));
+        'auxclick', e => this.onMiddleClick_(e as MouseEvent));
     this.addEventListener(
-        'auxclick',
-        e => this.onMiddleClick_(
-            /** @type {!MouseEvent} */ (e)));
+        'mousedown', e => this.cancelMiddleMouseBehavior_(e as MouseEvent));
     this.addEventListener(
-        'mousedown',
-        e => this.cancelMiddleMouseBehavior_(
-            /** @type {!MouseEvent} */ (e)));
+        'mouseup', e => this.cancelMiddleMouseBehavior_(e as MouseEvent));
     this.addEventListener(
-        'mouseup',
-        e => this.cancelMiddleMouseBehavior_(
-            /** @type {!MouseEvent} */ (e)));
-    this.addEventListener(
-        'touchstart',
-        e => this.onTouchStart_(
-            /** @type {!TouchEvent} */ (e)));
+        'touchstart', e => this.onTouchStart_(e as TouchEvent));
   }
 
   connectedCallback() {
     super.connectedCallback();
-    this.watch('item_', store => store.nodes[this.itemId]);
-    this.watch(
-        'isSelectedItem_', store => store.selection.items.has(this.itemId));
-    this.watch('isMultiSelect_', store => store.selection.items.size > 1);
+    this.watch('item_', state => {
+      return (state as BookmarksPageState).nodes[this.itemId];
+    });
+    this.watch('isSelectedItem_', state => {
+      return (state as BookmarksPageState).selection.items.has(this.itemId);
+    });
+    this.watch('isMultiSelect_', state => {
+      return (state as BookmarksPageState).selection.items.size > 1;
+    });
 
     this.updateFromStore();
   }
@@ -128,22 +115,20 @@
     focusWithoutInk(this.$.menuButton);
   }
 
-  /** @return {BookmarksItemElement} */
-  getDropTarget() {
+  getDropTarget(): BookmarksItemElement {
     return this;
   }
 
-  /**
-   * @param {Event} e
-   * @private
-   */
-  onContextMenu_(e) {
+  private onContextMenu_(e: MouseEvent) {
     e.preventDefault();
     e.stopPropagation();
 
     // Prevent context menu from appearing after a drag, but allow opening the
     // context menu through 2 taps
-    if (e.sourceCapabilities && e.sourceCapabilities.firesTouchEvents &&
+    const capabilities = (e as unknown as {
+                           sourceCapabilities: {firesTouchEvents?: boolean}
+                         }).sourceCapabilities;
+    if (capabilities && capabilities.firesTouchEvents &&
         this.lastTouchPoints_ !== 2) {
       return;
     }
@@ -165,11 +150,7 @@
     }));
   }
 
-  /**
-   * @param {Event} e
-   * @private
-   */
-  onMenuButtonClick_(e) {
+  private onMenuButtonClick_(e: Event) {
     e.stopPropagation();
     e.preventDefault();
 
@@ -189,8 +170,7 @@
     }));
   }
 
-  /** @private */
-  selectThisItem_() {
+  private selectThisItem_() {
     this.dispatch(selectItem(this.itemId, this.getState(), {
       clear: true,
       range: false,
@@ -198,16 +178,14 @@
     }));
   }
 
-  /** @private */
-  onItemIdChanged_() {
+  private onItemIdChanged_() {
     // TODO(tsergeant): Add a histogram to measure whether this assertion fails
     // for real users.
     assert(this.getState().nodes[this.itemId]);
     this.updateFromStore();
   }
 
-  /** @private */
-  onItemChanged_() {
+  private onItemChanged_() {
     this.isFolder_ = !this.item_.url;
     this.setAttribute(
         'aria-label',
@@ -215,11 +193,7 @@
             loadTimeData.getString('folderLabel'));
   }
 
-  /**
-   * @param {MouseEvent} e
-   * @private
-   */
-  onClick_(e) {
+  private onClick_(e: MouseEvent) {
     // Ignore double clicks so that Ctrl double-clicking an item won't deselect
     // the item before opening.
     if (e.detail !== 2) {
@@ -234,11 +208,7 @@
     e.preventDefault();
   }
 
-  /**
-   * @private
-   * @param {KeyboardEvent} e
-   */
-  onKeydown_(e) {
+  private onKeydown_(e: KeyboardEvent) {
     if (e.key === 'ArrowLeft') {
       this.focus();
     } else if (e.key === 'ArrowRight') {
@@ -252,11 +222,7 @@
     }
   }
 
-  /**
-   * @param {MouseEvent} e
-   * @private
-   */
-  onDblClick_(e) {
+  private onDblClick_(e: MouseEvent) {
     if (!this.isSelectedItem_) {
       this.selectThisItem_();
     }
@@ -268,11 +234,7 @@
     }
   }
 
-  /**
-   * @param {MouseEvent} e
-   * @private
-   */
-  onMiddleClick_(e) {
+  private onMiddleClick_(e: MouseEvent) {
     if (e.button !== 1) {
       return;
     }
@@ -290,41 +252,27 @@
     }
   }
 
-  /**
-   * @param {TouchEvent} e
-   * @private
-   */
-  onTouchStart_(e) {
+  private onTouchStart_(e: TouchEvent) {
     this.lastTouchPoints_ = e.touches.length;
   }
 
   /**
    * Prevent default middle-mouse behavior. On Windows, this prevents autoscroll
    * (during mousedown), and on Linux this prevents paste (during mouseup).
-   * @param {MouseEvent} e
-   * @private
    */
-  cancelMiddleMouseBehavior_(e) {
+  private cancelMiddleMouseBehavior_(e: MouseEvent) {
     if (e.button === 1) {
       e.preventDefault();
     }
   }
 
-  /**
-   * @param {string} url
-   * @private
-   */
-  updateFavicon_(url) {
+  private updateFavicon_(url: string) {
     this.$.icon.className = url ? 'website-icon' : 'folder-icon';
     this.$.icon.style.backgroundImage =
         url ? getFaviconForPageURL(url, false) : '';
   }
 
-  /**
-   * @return {string}
-   * @private
-   */
-  getButtonAriaLabel_() {
+  private getButtonAriaLabel_(): string {
     if (!this.item_) {
       return '';  // Item hasn't loaded, skip for now.
     }
@@ -339,10 +287,8 @@
 
   /**
    * This item is part of a group selection.
-   * @return {boolean}
-   * @private
    */
-  isMultiSelectMenu_() {
+  private isMultiSelectMenu_(): boolean {
     return this.isSelectedItem_ && this.isMultiSelect_;
   }
 }
diff --git a/chrome/browser/resources/bookmarks/list.js b/chrome/browser/resources/bookmarks/list.ts
similarity index 72%
rename from chrome/browser/resources/bookmarks/list.js
rename to chrome/browser/resources/bookmarks/list.ts
index 8f3603e..576cebf5 100644
--- a/chrome/browser/resources/bookmarks/list.js
+++ b/chrome/browser/resources/bookmarks/list.ts
@@ -6,16 +6,18 @@
 import 'chrome://resources/polymer/v3_0/iron-list/iron-list.js';
 import './shared_style.js';
 import './strings.m.js';
+import './item.js';
 
 import {assert} from 'chrome://resources/js/assert.m.js';
+import {EventTracker} from 'chrome://resources/js/event_tracker.m.js';
 import {isMac} from 'chrome://resources/js/cr.m.js';
 import {StoreObserver} from 'chrome://resources/js/cr/ui/store.m.js';
-import {StoreClientInterface as CrUiStoreClientInterface} from 'chrome://resources/js/cr/ui/store_client.m.js';
-import {ListPropertyUpdateBehavior, ListPropertyUpdateBehaviorInterface} from 'chrome://resources/js/list_property_update_behavior.m.js';
+import {ListPropertyUpdateBehavior} from 'chrome://resources/js/list_property_update_behavior.m.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
 import {PluralStringProxyImpl} from 'chrome://resources/js/plural_string_proxy.js';
 import {getDeepActiveElement} from 'chrome://resources/js/util.m.js';
 import {IronA11yAnnouncer} from 'chrome://resources/polymer/v3_0/iron-a11y-announcer/iron-a11y-announcer.js';
+import {IronListElement} from 'chrome://resources/polymer/v3_0/iron-list/iron-list.js';
 import {afterNextRender, html, microTask, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {deselectItems, selectAll, selectItem, updateAnchor} from './actions.js';
@@ -23,21 +25,23 @@
 import {MenuSource} from './constants.js';
 import {BookmarksItemElement} from './item.js';
 import {BookmarksStoreClientInterface, StoreClient} from './store_client.js';
-import {BookmarksPageState} from './types.js';
+import {BookmarksPageState, OpenCommandMenuDetail} from './types.js';
 import {canReorderChildren, getDisplayedList} from './util.js';
 
-/**
- * @constructor
- * @extends {PolymerElement}
- * @implements {BookmarksStoreClientInterface}
- * @implements {CrUiStoreClientInterface}
- * @implements {StoreObserver<BookmarksPageState>}
- * @implements {ListPropertyUpdateBehaviorInterface}
- */
 const BookmarksListElementBase =
-    mixinBehaviors([StoreClient, ListPropertyUpdateBehavior], PolymerElement);
+    mixinBehaviors([StoreClient, ListPropertyUpdateBehavior], PolymerElement) as
+{
+  new (): PolymerElement &BookmarksStoreClientInterface &
+      StoreObserver<BookmarksPageState> & ListPropertyUpdateBehavior
+}
 
-/** @polymer */
+export interface BookmarksListElement {
+  $: {
+    list: IronListElement,
+    message: HTMLDivElement,
+  }
+}
+
 export class BookmarksListElement extends BookmarksListElementBase {
   static get is() {
     return 'bookmarks-list';
@@ -53,7 +57,6 @@
        * A list of item ids wrapped in an Object. This is necessary because
        * iron-list is unable to distinguish focusing index 6 from focusing id
        * '6' so the item we supply to iron-list needs to be non-index-like.
-       * @private {Array<{id: string}>}
        */
       displayedList_: {
         type: Array,
@@ -64,63 +67,68 @@
         },
       },
 
-      /** @private {Array<string>} */
       displayedIds_: {
         type: Array,
         observer: 'onDisplayedIdsChanged_',
       },
 
-      /** @private */
       searchTerm_: {
         type: String,
         observer: 'onDisplayedListSourceChange_',
       },
 
-      /** @private */
       selectedFolder_: {
         type: String,
         observer: 'onDisplayedListSourceChange_',
       },
 
-      /** @private {Set<string>} */
       selectedItems_: Object,
     };
   }
 
+  private displayedList_: {id: string}[];
+  private displayedIds_: string[];
+  private eventTracker_: EventTracker = new EventTracker();
+  private searchTerm_: string;
+  private selectedFolder_: string;
+  private selectedItems_: Set<string>;
+  private boundOnHighlightItems_: (p1: CustomEvent) => void;
+
   ready() {
     super.ready();
     this.addEventListener('click', () => this.deselectItems_());
     this.addEventListener('contextmenu', e => this.onContextMenu_(e));
     this.addEventListener(
         'open-command-menu',
-        e => this.onOpenCommandMenu_(
-            /** @type {!CustomEvent<{source: !MenuSource}>} */ (e)));
+        e => this.onOpenCommandMenu_(e as CustomEvent<OpenCommandMenuDetail>));
   }
 
   connectedCallback() {
     super.connectedCallback();
 
-    const list = /** @type {IronListElement} */ (this.$.list);
+    const list = this.$.list;
     list.scrollTarget = this;
 
     this.watch('displayedIds_', function(state) {
-      return getDisplayedList(/** @type {!BookmarksPageState} */ (state));
+      return getDisplayedList(state as BookmarksPageState);
     });
     this.watch('searchTerm_', function(state) {
-      return state.search.term;
+      return (state as BookmarksPageState).search.term;
     });
     this.watch('selectedFolder_', function(state) {
-      return state.selectedFolder;
+      return (state as BookmarksPageState).selectedFolder;
     });
-    this.watch('selectedItems_', ({selection: {items}}) => items);
+    this.watch('selectedItems_', function(state) {
+      return (state as BookmarksPageState).selection.items;
+    });
     this.updateFromStore();
 
     this.$.list.addEventListener(
         'keydown', this.onItemKeydown_.bind(this), true);
 
-    /** @private {function(!Event)} */
-    this.boundOnHighlightItems_ = this.onHighlightItems_.bind(this);
-    document.addEventListener('highlight-items', this.boundOnHighlightItems_);
+    this.eventTracker_.add(
+        document, 'highlight-items',
+        e => this.onHighlightItems_(e as CustomEvent<string[]>));
 
     afterNextRender(this, function() {
       IronA11yAnnouncer.requestAvailability();
@@ -130,23 +138,19 @@
   disconnectedCallback() {
     super.disconnectedCallback();
 
-    document.removeEventListener(
-        'highlight-items', this.boundOnHighlightItems_);
+    this.eventTracker_.remove(document, 'highlight-items');
   }
 
-  /** @return {HTMLElement} */
-  getDropTarget() {
-    return /** @type {!HTMLDivElement} */ (this.$.message);
+  getDropTarget(): HTMLElement {
+    return this.$.message;
   }
 
   /**
    * Updates `displayedList_` using splices to be equivalent to `newValue`. This
    * allows the iron-list to delete sublists of items which preserves scroll and
    * focus on incremental update.
-   * @param {Array<string>} newValue
-   * @param {Array<string>} oldValue
    */
-  async onDisplayedIdsChanged_(newValue, oldValue) {
+  private async onDisplayedIdsChanged_(newValue: string[], oldValue: string[]) {
     const updatedList = newValue.map(id => ({id: id}));
     let skipFocus = false;
     let selectIndex = -1;
@@ -163,7 +167,8 @@
         selectIndex = Math.min(selectIndex, updatedList.length - 1);
       }
     }
-    this.updateList('displayedList_', item => item.id, updatedList);
+    this.updateList(
+        'displayedList_', item => (item as {id: string}).id, updatedList);
     // Trigger a layout of the iron list. Otherwise some elements may render
     // as blank entries. See https://crbug.com/848683
     this.$.list.dispatchEvent(
@@ -180,23 +185,20 @@
         // Focus menu button so 'Undo' is only one tab stop away on delete.
         const item = getDeepActiveElement();
         if (item) {
-          item.focusMenuButton();
+          (item as BookmarksItemElement).focusMenuButton();
         }
       });
     }
   }
 
-  /** @private */
-  onDisplayedListSourceChange_() {
+  private onDisplayedListSourceChange_() {
     this.scrollTop = 0;
   }
 
   /**
    * Scroll the list so that |itemId| is visible, if it is not already.
-   * @param {string} itemId
-   * @private
    */
-  scrollToId_(itemId) {
+  private scrollToId_(itemId: string) {
     const index = this.displayedIds_.indexOf(itemId);
     const list = this.$.list;
     if (index >= 0 && index < list.firstVisibleIndex ||
@@ -205,8 +207,7 @@
     }
   }
 
-  /** @private */
-  emptyListMessage_() {
+  private emptyListMessage_(): string {
     let emptyListMessage = 'noSearchResults';
     if (!this.searchTerm_) {
       emptyListMessage =
@@ -217,54 +218,42 @@
     return loadTimeData.getString(emptyListMessage);
   }
 
-  /** @private */
-  isEmptyList_() {
+  private isEmptyList_(): boolean {
     return this.displayedList_.length === 0;
   }
 
-  /** @private */
-  deselectItems_() {
+  private deselectItems_() {
     this.dispatch(deselectItems());
   }
 
-  /**
-   * @param{HTMLElement} el
-   * @private
-   */
-  getIndexForItemElement_(el) {
-    return this.$.list.modelForElement(el).index;
+  private getIndexForItemElement_(el: HTMLElement): number {
+    return (this.$.list.modelForElement(el) as unknown as {index: number})
+        .index;
   }
 
-  /**
-   * @param {!CustomEvent<{source: !MenuSource}>} e
-   * @private
-   */
-  onOpenCommandMenu_(e) {
+  private onOpenCommandMenu_(e: CustomEvent<{source: MenuSource}>) {
     // If the item is not visible, scroll to it before rendering the menu.
     if (e.detail.source === MenuSource.ITEM) {
-      this.scrollToId_(
-          /** @type {BookmarksItemElement} */ (e.composedPath()[0]).itemId);
+      this.scrollToId_((e.composedPath()[0] as BookmarksItemElement).itemId);
     }
   }
 
   /**
    * Highlight a list of items by selecting them, scrolling them into view and
    * focusing the first item.
-   * @param {Event} e
-   * @private
    */
-  onHighlightItems_(e) {
+  private onHighlightItems_(e: CustomEvent<string[]>) {
     // Ensure that we only select items which are actually being displayed.
     // This should only matter if an unrelated update to the bookmark model
     // happens with the perfect timing to end up in a tracked batch update.
-    const toHighlight = /** @type {!Array<string>} */
-        (e.detail.filter((item) => this.displayedIds_.indexOf(item) !== -1));
+    const toHighlight =
+        e.detail.filter((item) => this.displayedIds_.indexOf(item) !== -1);
 
     if (toHighlight.length <= 0) {
       return;
     }
 
-    const leadId = toHighlight[0];
+    const leadId = toHighlight[0]!;
     this.dispatch(selectAll(toHighlight, this.getState(), leadId));
 
     // Allow iron-list time to render additions to the list.
@@ -276,16 +265,11 @@
     });
   }
 
-  /**
-   * @param {Event} e
-   * @private
-   */
-  onItemKeydown_(e) {
+  private onItemKeydown_(e: KeyboardEvent) {
     let handled = true;
     const list = this.$.list;
     let focusMoved = false;
-    let focusedIndex =
-        this.getIndexForItemElement_(/** @type {HTMLElement} */ (e.target));
+    let focusedIndex = this.getIndexForItemElement_(e.target as HTMLElement);
     const oldFocusedIndex = focusedIndex;
     const cursorModifier = isMac ? e.metaKey : e.ctrlKey;
     if (e.key === 'ArrowUp') {
@@ -299,11 +283,11 @@
       focusedIndex = 0;
       focusMoved = true;
     } else if (e.key === 'End') {
-      focusedIndex = list.items.length - 1;
+      focusedIndex = list.items!.length - 1;
       focusMoved = true;
     } else if (e.key === ' ' && cursorModifier) {
       this.dispatch(
-          selectItem(this.displayedIds_[focusedIndex], this.getState(), {
+          selectItem(this.displayedIds_[focusedIndex]!, this.getState(), {
             clear: false,
             range: false,
             toggle: true,
@@ -313,15 +297,16 @@
     }
 
     if (focusMoved) {
-      focusedIndex = Math.min(list.items.length - 1, Math.max(0, focusedIndex));
+      focusedIndex = Math.min(list.items!.length - 1,
+                              Math.max(0, focusedIndex));
       list.focusItem(focusedIndex);
 
       if (cursorModifier && !e.shiftKey) {
-        this.dispatch(updateAnchor(this.displayedIds_[focusedIndex]));
+        this.dispatch(updateAnchor(this.displayedIds_[focusedIndex]!));
       } else {
         // If shift-selecting with no anchor, use the old focus index.
         if (e.shiftKey && this.getState().selection.anchor === null) {
-          this.dispatch(updateAnchor(this.displayedIds_[oldFocusedIndex]));
+          this.dispatch(updateAnchor(this.displayedIds_[oldFocusedIndex]!));
         }
 
         // If the focus moved from something other than a Ctrl + move event,
@@ -333,13 +318,13 @@
         };
 
         this.dispatch(selectItem(
-            this.displayedIds_[focusedIndex], this.getState(), config));
+            this.displayedIds_[focusedIndex]!, this.getState(), config));
       }
     }
 
     // Prevent the iron-list from changing focus on enter.
     if (e.key === 'Enter') {
-      if (e.composedPath()[0].tagName === 'CR-ICON-BUTTON') {
+      if ((e.composedPath()[0] as HTMLElement).tagName === 'CR-ICON-BUTTON') {
         return;
       }
       if (e.composedPath()[0] instanceof HTMLButtonElement) {
@@ -357,11 +342,7 @@
     }
   }
 
-  /**
-   * @param {Event} e
-   * @private
-   */
-  onContextMenu_(e) {
+  private onContextMenu_(e: MouseEvent) {
     e.preventDefault();
     this.deselectItems_();
 
@@ -376,21 +357,11 @@
     }));
   }
 
-  /**
-   * Returns a 1-based index for aria-rowindex.
-   * @param {number} index
-   * @return {number}
-   * @private
-   */
-  getAriaRowindex_(index) {
+  private getAriaRowindex_(index: number): number {
     return index + 1;
   }
 
-  /**
-   * @param {string} id
-   * @return {boolean}
-   */
-  getAriaSelected_(id) {
+  private getAriaSelected_(id: string): boolean {
     return this.selectedItems_.has(id);
   }
 }
diff --git a/chrome/browser/resources/bookmarks/mouse_focus_behavior.js b/chrome/browser/resources/bookmarks/mouse_focus_behavior.js
deleted file mode 100644
index 6740996..0000000
--- a/chrome/browser/resources/bookmarks/mouse_focus_behavior.js
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright 2017 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.
-
-/** @const */
-export const HIDE_FOCUS_RING_ATTRIBUTE = 'hide-focus-ring';
-
-/**
- * Behavior which adds the 'hide-focus-ring' attribute to a target element
- * when the user interacts with it using the mouse, allowing the focus outline
- * to be hidden without affecting keyboard users.
- * @polymerBehavior
- */
-export const MouseFocusBehavior = {
-  attached() {
-    this.boundOnMousedown_ = this.onMousedown_.bind(this);
-    this.boundOnKeydown = this.onKeydown_.bind(this);
-
-    // These events are added to the document because capture doesn't work
-    // properly when listeners are added to a Polymer element, because the
-    // event is considered AT_TARGET for the element, and is evaluated after
-    // inner captures.
-    document.addEventListener('mousedown', this.boundOnMousedown_, true);
-    document.addEventListener('keydown', this.boundOnKeydown, true);
-  },
-
-  detached() {
-    document.removeEventListener('mousedown', this.boundOnMousedown_, true);
-    document.removeEventListener('keydown', this.boundOnKeydown, true);
-  },
-
-  /** @private */
-  onMousedown_() {
-    this.setAttribute(HIDE_FOCUS_RING_ATTRIBUTE, '');
-  },
-
-  /**
-   * @param {KeyboardEvent} e
-   * @private
-   */
-  onKeydown_(e) {
-    if (!['Shift', 'Alt', 'Control', 'Meta'].includes(e.key)) {
-      this.removeAttribute(HIDE_FOCUS_RING_ATTRIBUTE);
-    }
-  },
-};
diff --git a/chrome/browser/resources/bookmarks/mouse_focus_behavior.ts b/chrome/browser/resources/bookmarks/mouse_focus_behavior.ts
new file mode 100644
index 0000000..5d266336
--- /dev/null
+++ b/chrome/browser/resources/bookmarks/mouse_focus_behavior.ts
@@ -0,0 +1,60 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import {dedupingMixin, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+export const HIDE_FOCUS_RING_ATTRIBUTE = 'hide-focus-ring';
+
+type Constructor<T> = new ( ...args: any[]) => T;
+
+/**
+ * Behavior which adds the 'hide-focus-ring' attribute to a target element
+ * when the user interacts with it using the mouse, allowing the focus outline
+ * to be hidden without affecting keyboard users.
+ */
+export const MouseFocusMixin = dedupingMixin(
+    <T extends Constructor<PolymerElement>>(superClass: T): T&
+    Constructor<MouseFocusMixinInterface> => {
+      class MouseFocusMixin extends superClass {
+        private boundOnMousedown_: (e: Event) => void;
+        boundOnKeydown: (e: KeyboardEvent) => void;
+
+        connectedCallback() {
+          super.connectedCallback();
+          this.boundOnMousedown_ = this.onMousedown_.bind(this);
+          this.boundOnKeydown = this.onKeydown_.bind(this);
+
+          // These events are added to the document because capture doesn't work
+          // properly when listeners are added to a Polymer element, because the
+          // event is considered AT_TARGET for the element, and is evaluated
+          // after inner captures.
+          document.addEventListener('mousedown', this.boundOnMousedown_, true);
+          document.addEventListener('keydown', this.boundOnKeydown, true);
+        }
+
+        disconnectedCallback() {
+          super.disconnectedCallback();
+          document.removeEventListener(
+              'mousedown', this.boundOnMousedown_, true);
+          document.removeEventListener(
+              'keydown', this.boundOnKeydown, true);
+        }
+
+        private onMousedown_() {
+          this.setAttribute(HIDE_FOCUS_RING_ATTRIBUTE, '');
+        }
+
+        private onKeydown_(e: KeyboardEvent) {
+          if (!['Shift', 'Alt', 'Control', 'Meta'].includes(e.key)) {
+            this.removeAttribute(HIDE_FOCUS_RING_ATTRIBUTE);
+          }
+        }
+      }
+
+      return MouseFocusMixin;
+    });
+
+export interface MouseFocusMixinInterface {
+  boundOnKeydown: (e: KeyboardEvent) => void;
+}
diff --git a/chrome/browser/resources/bookmarks/reducers.js b/chrome/browser/resources/bookmarks/reducers.js
deleted file mode 100644
index ea24531..0000000
--- a/chrome/browser/resources/bookmarks/reducers.js
+++ /dev/null
@@ -1,468 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import {assert} from 'chrome://resources/js/assert.m.js';
-
-import {BookmarkNode, BookmarksPageState, FolderOpenState, NodeMap, PreferencesState, SearchState, SelectionState} from './types.js';
-import {removeIdsFromMap, removeIdsFromObject, removeIdsFromSet} from './util.js';
-
-/**
- * @fileoverview Module of functions which produce a new page state in response
- * to an action. Reducers (in the same sense as Array.prototype.reduce) must be
- * pure functions: they must not modify existing state objects, or make any API
- * calls.
- */
-
-/**
- * @param {SelectionState} selectionState
- * @param {Object} action
- * @return {SelectionState}
- */
-function selectItems(selectionState, action) {
-  let newItems = new Set();
-  if (!action.clear) {
-    newItems = new Set(selectionState.items);
-  }
-
-  action.items.forEach(function(id) {
-    let add = true;
-    if (action.toggle) {
-      add = !newItems.has(id);
-    }
-
-    if (add) {
-      newItems.add(id);
-    } else {
-      newItems.delete(id);
-    }
-  });
-
-  return /** @type {SelectionState} */ (Object.assign({}, selectionState, {
-    items: newItems,
-    anchor: action.anchor,
-  }));
-}
-
-/**
- * @param {SelectionState} selectionState
- * @return {SelectionState}
- */
-function deselectAll(selectionState) {
-  return {
-    items: new Set(),
-    anchor: null,
-  };
-}
-
-/**
- * @param {SelectionState} selectionState
- * @param {!Set<string>} deleted
- * @return SelectionState
- */
-function deselectItems(selectionState, deleted) {
-  return /** @type {SelectionState} */ (Object.assign({}, selectionState, {
-    items: removeIdsFromSet(selectionState.items, deleted),
-    anchor: !selectionState.anchor || deleted.has(selectionState.anchor) ?
-        null :
-        selectionState.anchor,
-  }));
-}
-
-/**
- * @param {SelectionState} selectionState
- * @param {Object} action
- * @return {SelectionState}
- */
-function updateAnchor(selectionState, action) {
-  return /** @type {SelectionState} */ (Object.assign({}, selectionState, {
-    anchor: action.anchor,
-  }));
-}
-
-/**
- * Exported for tests.
- * @param {SelectionState} selection
- * @param {Object} action
- * @return {SelectionState}
- */
-export function updateSelection(selection, action) {
-  switch (action.name) {
-    case 'clear-search':
-    case 'finish-search':
-    case 'select-folder':
-    case 'deselect-items':
-      return deselectAll(selection);
-    case 'select-items':
-      return selectItems(selection, action);
-    case 'remove-bookmark':
-      return deselectItems(selection, action.descendants);
-    case 'move-bookmark':
-      // Deselect items when they are moved to another folder, since they will
-      // no longer be visible on screen (for simplicity, ignores items visible
-      // in search results).
-      if (action.parentId !== action.oldParentId &&
-          selection.items.has(action.id)) {
-        return deselectItems(selection, new Set([action.id]));
-      }
-      return selection;
-    case 'update-anchor':
-      return updateAnchor(selection, action);
-    default:
-      return selection;
-  }
-}
-
-/**
- * @param {SearchState} search
- * @param {Object} action
- * @return {SearchState}
- */
-function startSearch(search, action) {
-  return {
-    term: action.term,
-    inProgress: true,
-    results: search.results,
-  };
-}
-
-/**
- * @param {SearchState} search
- * @param {Object} action
- * @return {SearchState}
- */
-function finishSearch(search, action) {
-  return /** @type {SearchState} */ (Object.assign({}, search, {
-    inProgress: false,
-    results: action.results,
-  }));
-}
-
-/** @return {SearchState} */
-function clearSearch() {
-  return {
-    term: '',
-    inProgress: false,
-    results: null,
-  };
-}
-
-/**
- * @param {SearchState} search
- * @param {!Set<string>} deletedIds
- * @return {SearchState}
- */
-function removeDeletedResults(search, deletedIds) {
-  if (!search.results) {
-    return search;
-  }
-
-  const newResults = [];
-  search.results.forEach(function(id) {
-    if (!deletedIds.has(id)) {
-      newResults.push(id);
-    }
-  });
-  return /** @type {SearchState} */ (Object.assign({}, search, {
-    results: newResults,
-  }));
-}
-
-/**
- * @param {SearchState} search
- * @param {Object} action
- * @return {SearchState}
- */
-function updateSearch(search, action) {
-  switch (action.name) {
-    case 'start-search':
-      return startSearch(search, action);
-    case 'select-folder':
-    case 'clear-search':
-      return clearSearch();
-    case 'finish-search':
-      return finishSearch(search, action);
-    case 'remove-bookmark':
-      return removeDeletedResults(search, action.descendants);
-    default:
-      return search;
-  }
-}
-
-/**
- * @param {NodeMap} nodes
- * @param {string} id
- * @param {function(BookmarkNode):BookmarkNode} callback
- * @return {NodeMap}
- */
-function modifyNode(nodes, id, callback) {
-  const nodeModification = {};
-  nodeModification[id] = callback(nodes[id]);
-  return Object.assign({}, nodes, nodeModification);
-}
-
-/**
- * @param {NodeMap} nodes
- * @param {Object} action
- * @return {NodeMap}
- */
-function createBookmark(nodes, action) {
-  const nodeModifications = {};
-  nodeModifications[action.id] = action.node;
-
-  const parentNode = nodes[action.parentId];
-  const newChildren = parentNode.children.slice();
-  newChildren.splice(action.parentIndex, 0, action.id);
-  nodeModifications[action.parentId] = Object.assign({}, parentNode, {
-    children: newChildren,
-  });
-
-  return Object.assign({}, nodes, nodeModifications);
-}
-
-/**
- * @param {NodeMap} nodes
- * @param {Object} action
- * @return {NodeMap}
- */
-function editBookmark(nodes, action) {
-  // Do not allow folders to change URL (making them no longer folders).
-  if (!nodes[action.id].url && action.changeInfo.url) {
-    delete action.changeInfo.url;
-  }
-
-  return modifyNode(nodes, action.id, function(node) {
-    return /** @type {BookmarkNode} */ (
-        Object.assign({}, node, action.changeInfo));
-  });
-}
-
-/**
- * @param {NodeMap} nodes
- * @param {Object} action
- * @return {NodeMap}
- */
-function moveBookmark(nodes, action) {
-  const nodeModifications = {};
-  const id = action.id;
-
-  // Change node's parent.
-  nodeModifications[id] =
-      Object.assign({}, nodes[id], {parentId: action.parentId});
-
-  // Remove from old parent.
-  const oldParentId = action.oldParentId;
-  const oldParentChildren = nodes[oldParentId].children.slice();
-  oldParentChildren.splice(action.oldIndex, 1);
-  nodeModifications[oldParentId] =
-      Object.assign({}, nodes[oldParentId], {children: oldParentChildren});
-
-  // Add to new parent.
-  const parentId = action.parentId;
-  const parentChildren = oldParentId === parentId ?
-      oldParentChildren :
-      nodes[parentId].children.slice();
-  parentChildren.splice(action.index, 0, action.id);
-  nodeModifications[parentId] =
-      Object.assign({}, nodes[parentId], {children: parentChildren});
-
-  return Object.assign({}, nodes, nodeModifications);
-}
-
-/**
- * @param {NodeMap} nodes
- * @param {Object} action
- * @return {NodeMap}
- */
-function removeBookmark(nodes, action) {
-  const newState = modifyNode(nodes, action.parentId, function(node) {
-    const newChildren = node.children.slice();
-    newChildren.splice(action.index, 1);
-    return /** @type {BookmarkNode} */ (
-        Object.assign({}, node, {children: newChildren}));
-  });
-
-  return removeIdsFromObject(newState, action.descendants);
-}
-
-/**
- * @param {NodeMap} nodes
- * @param {Object} action
- * @return {NodeMap}
- */
-function reorderChildren(nodes, action) {
-  return modifyNode(nodes, action.id, function(node) {
-    return /** @type {BookmarkNode} */ (
-        Object.assign({}, node, {children: action.children}));
-  });
-}
-
-/**
- * Exported for tests.
- * @param {NodeMap} nodes
- * @param {Object} action
- * @return {NodeMap}
- */
-export function updateNodes(nodes, action) {
-  switch (action.name) {
-    case 'create-bookmark':
-      return createBookmark(nodes, action);
-    case 'edit-bookmark':
-      return editBookmark(nodes, action);
-    case 'move-bookmark':
-      return moveBookmark(nodes, action);
-    case 'remove-bookmark':
-      return removeBookmark(nodes, action);
-    case 'reorder-children':
-      return reorderChildren(nodes, action);
-    case 'refresh-nodes':
-      return action.nodes;
-    default:
-      return nodes;
-  }
-}
-
-/**
- * @param {NodeMap} nodes
- * @param {string} ancestorId
- * @param {string} childId
- * @return {boolean}
- */
-function isAncestorOf(nodes, ancestorId, childId) {
-  let currentId = childId;
-  // Work upwards through the tree from child.
-  while (currentId) {
-    if (currentId === ancestorId) {
-      return true;
-    }
-    currentId = nodes[currentId].parentId;
-  }
-  return false;
-}
-
-/**
- * Exported for tests.
- * @param {string} selectedFolder
- * @param {Object} action
- * @param {NodeMap} nodes
- * @return {string}
- */
-export function updateSelectedFolder(selectedFolder, action, nodes) {
-  switch (action.name) {
-    case 'select-folder':
-      return action.id;
-    case 'change-folder-open':
-      // When hiding the selected folder by closing its ancestor, select
-      // that ancestor instead.
-      if (!action.open && selectedFolder &&
-          isAncestorOf(nodes, action.id, selectedFolder)) {
-        return action.id;
-      }
-      return selectedFolder;
-    case 'remove-bookmark':
-      // When deleting the selected folder (or its ancestor), select the
-      // parent of the deleted node.
-      if (selectedFolder && isAncestorOf(nodes, action.id, selectedFolder)) {
-        return assert(nodes[action.id].parentId);
-      }
-      return selectedFolder;
-    default:
-      return selectedFolder;
-  }
-}
-
-/**
- * @param {FolderOpenState} folderOpenState
- * @param {string|undefined} id
- * @param {NodeMap} nodes
- * @return {FolderOpenState}
- */
-function openFolderAndAncestors(folderOpenState, id, nodes) {
-  const newFolderOpenState =
-      /** @type {FolderOpenState} */ (new Map(folderOpenState));
-  for (let currentId = id; currentId; currentId = nodes[currentId].parentId) {
-    newFolderOpenState.set(currentId, true);
-  }
-
-  return newFolderOpenState;
-}
-
-/**
- * @param {FolderOpenState} folderOpenState
- * @param {Object} action
- * @return {FolderOpenState}
- */
-function changeFolderOpen(folderOpenState, action) {
-  const newFolderOpenState =
-      /** @type {FolderOpenState} */ (new Map(folderOpenState));
-  newFolderOpenState.set(action.id, action.open);
-
-  return newFolderOpenState;
-}
-
-/**
- * Exported for tests.
- * @param {FolderOpenState} folderOpenState
- * @param {Object} action
- * @param {NodeMap} nodes
- * @return {FolderOpenState}
- */
-export function updateFolderOpenState(folderOpenState, action, nodes) {
-  switch (action.name) {
-    case 'change-folder-open':
-      return changeFolderOpen(folderOpenState, action);
-    case 'select-folder':
-      return openFolderAndAncestors(
-          folderOpenState, nodes[action.id].parentId, nodes);
-    case 'move-bookmark':
-      if (!nodes[action.id].children) {
-        return folderOpenState;
-      }
-
-      return openFolderAndAncestors(folderOpenState, action.parentId, nodes);
-    case 'remove-bookmark':
-      return removeIdsFromMap(folderOpenState, action.descendants);
-    default:
-      return folderOpenState;
-  }
-}
-
-/**
- * @param {PreferencesState} prefs
- * @param {Object} action
- * @return {PreferencesState}
- */
-function updatePrefs(prefs, action) {
-  switch (action.name) {
-    case 'set-incognito-availability':
-      return /** @type {PreferencesState} */ (Object.assign({}, prefs, {
-        incognitoAvailability: action.value,
-      }));
-    case 'set-can-edit':
-      return /** @type {PreferencesState} */ (Object.assign({}, prefs, {
-        canEdit: action.value,
-      }));
-    default:
-      return prefs;
-  }
-}
-
-/**
- * Root reducer for the Bookmarks page. This is called by the store in
- * response to an action, and the return value is used to update the UI.
- * @param {!BookmarksPageState} state
- * @param {Object} action
- * @return {!BookmarksPageState}
- */
-export function reduceAction(state, action) {
-  return {
-    nodes: updateNodes(state.nodes, action),
-    selectedFolder:
-        updateSelectedFolder(state.selectedFolder, action, state.nodes),
-    folderOpenState:
-        updateFolderOpenState(state.folderOpenState, action, state.nodes),
-    prefs: updatePrefs(state.prefs, action),
-    search: updateSearch(state.search, action),
-    selection: updateSelection(state.selection, action),
-  };
-}
diff --git a/chrome/browser/resources/bookmarks/reducers.ts b/chrome/browser/resources/bookmarks/reducers.ts
new file mode 100644
index 0000000..5e22447
--- /dev/null
+++ b/chrome/browser/resources/bookmarks/reducers.ts
@@ -0,0 +1,373 @@
+// Copyright 2017 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 Module of functions which produce a new page state in response
+ * to an action. Reducers (in the same sense as Array.prototype.reduce) must be
+ * pure functions: they must not modify existing state objects, or make any API
+ * calls.
+ */
+
+import {assert} from 'chrome://resources/js/assert.m.js';
+import {Action} from 'chrome://resources/js/cr/ui/store.m.js';
+
+import {ChangeFolderOpenAction, CreateBookmarkAction, EditBookmarkAction, FinishSearchAction, MoveBookmarkAction, RefreshNodesAction, RemoveBookmarkAction, ReorderChildrenAction, SelectFolderAction, SelectItemsAction, SetPrefAction, StartSearchAction, UpdateAnchorAction} from './actions.js';
+import {BookmarkNode, BookmarksPageState, FolderOpenState, NodeMap, PreferencesState, SearchState, SelectionState} from './types.js';
+import {removeIdsFromMap, removeIdsFromObject, removeIdsFromSet} from './util.js';
+
+function selectItems(
+    selectionState: SelectionState, action: SelectItemsAction): SelectionState {
+  let newItems = new Set();
+  if (!action.clear) {
+    newItems = new Set(selectionState.items);
+  }
+
+  action.items.forEach(function(id) {
+    let add = true;
+    if (action.toggle) {
+      add = !newItems.has(id);
+    }
+
+    if (add) {
+      newItems.add(id);
+    } else {
+      newItems.delete(id);
+    }
+  });
+
+  return (Object.assign({}, selectionState, {
+    items: newItems,
+    anchor: action.anchor,
+  }) as SelectionState);
+}
+
+function deselectAll(selectionState: SelectionState): SelectionState {
+  return {
+    items: new Set(),
+    anchor: null,
+  };
+}
+
+function deselectItems(
+    selectionState: SelectionState, deleted: Set<string>): SelectionState {
+  return /** @type {SelectionState} */ (Object.assign({}, selectionState, {
+    items: removeIdsFromSet(selectionState.items, deleted),
+    anchor: !selectionState.anchor || deleted.has(selectionState.anchor) ?
+        null :
+        selectionState.anchor,
+  }));
+}
+
+function updateAnchor(
+    selectionState: SelectionState,
+    action: UpdateAnchorAction): SelectionState {
+  return (Object.assign({}, selectionState, {
+    anchor: action.anchor,
+  }) as SelectionState);
+}
+
+// Exported for tests.
+export function updateSelection(
+    selection: SelectionState, action: Action): SelectionState {
+  switch (action.name) {
+    case 'clear-search':
+    case 'finish-search':
+    case 'select-folder':
+    case 'deselect-items':
+      return deselectAll(selection);
+    case 'select-items':
+      return selectItems(selection, action as SelectItemsAction);
+    case 'remove-bookmark':
+      return deselectItems(
+          selection, (action as RemoveBookmarkAction).descendants);
+    case 'move-bookmark':
+      // Deselect items when they are moved to another folder, since they will
+      // no longer be visible on screen (for simplicity, ignores items visible
+      // in search results).
+      const moveAction = action as MoveBookmarkAction;
+      if (moveAction.parentId !== moveAction.oldParentId &&
+          selection.items.has(moveAction.id)) {
+        return deselectItems(selection, new Set([moveAction.id]));
+      }
+      return selection;
+    case 'update-anchor':
+      return updateAnchor(selection, action as UpdateAnchorAction);
+    default:
+      return selection;
+  }
+}
+
+function startSearch(
+    search: SearchState, action: StartSearchAction): SearchState {
+  return {
+    term: action.term,
+    inProgress: true,
+    results: search.results,
+  };
+}
+
+function finishSearch(
+    search: SearchState, action: FinishSearchAction): SearchState {
+  return /** @type {SearchState} */ (Object.assign({}, search, {
+    inProgress: false,
+    results: action.results,
+  }));
+}
+
+function clearSearch(): SearchState {
+  return {
+    term: '',
+    inProgress: false,
+    results: null,
+  };
+}
+
+function removeDeletedResults(
+    search: SearchState, deletedIds: Set<string>): SearchState {
+  if (!search.results) {
+    return search;
+  }
+
+  const newResults: string[] = [];
+  search.results.forEach(function(id) {
+    if (!deletedIds.has(id)) {
+      newResults.push(id);
+    }
+  });
+  return (Object.assign({}, search, {
+    results: newResults,
+  }) as SearchState);
+}
+
+function updateSearch(search: SearchState, action: Action): SearchState {
+  switch (action.name) {
+    case 'start-search':
+      return startSearch(search, action as StartSearchAction);
+    case 'select-folder':
+    case 'clear-search':
+      return clearSearch();
+    case 'finish-search':
+      return finishSearch(search, action as FinishSearchAction);
+    case 'remove-bookmark':
+      return removeDeletedResults(
+          search, (action as RemoveBookmarkAction).descendants);
+    default:
+      return search;
+  }
+}
+
+function modifyNode(
+    nodes: NodeMap, id: string,
+    callback: (p1: BookmarkNode) => BookmarkNode): NodeMap {
+  const nodeModification: NodeMap = {};
+  nodeModification[id] = callback(nodes[id]!);
+  return Object.assign({}, nodes, nodeModification);
+}
+
+function createBookmark(nodes: NodeMap, action: CreateBookmarkAction): NodeMap {
+  const nodeModifications: NodeMap = {};
+  nodeModifications[action.id] = action.node;
+
+  const parentNode = nodes[action.parentId]!;
+  const newChildren = parentNode.children!.slice();
+  newChildren.splice(action.parentIndex, 0, action.id);
+  nodeModifications[action.parentId] = Object.assign({}, parentNode, {
+    children: newChildren,
+  });
+
+  return Object.assign({}, nodes, nodeModifications);
+}
+
+function editBookmark(nodes: NodeMap, action: EditBookmarkAction): NodeMap {
+  // Do not allow folders to change URL (making them no longer folders).
+  if (!nodes[action.id]!.url && action.changeInfo.url) {
+    delete action.changeInfo.url;
+  }
+
+  return modifyNode(nodes, action.id, function(node) {
+    return Object.assign({}, node, action.changeInfo);
+  });
+}
+
+function moveBookmark(nodes: NodeMap, action: MoveBookmarkAction): NodeMap {
+  const nodeModifications: NodeMap = {};
+  const id = action.id;
+
+  // Change node's parent.
+  nodeModifications[id] =
+      Object.assign({}, nodes[id], {parentId: action.parentId});
+
+  // Remove from old parent.
+  const oldParentId = action.oldParentId;
+  const oldParentChildren = nodes[oldParentId]!.children!.slice();
+  oldParentChildren.splice(action.oldIndex, 1);
+  nodeModifications[oldParentId] =
+      Object.assign({}, nodes[oldParentId], {children: oldParentChildren});
+
+  // Add to new parent.
+  const parentId = action.parentId;
+  const parentChildren = oldParentId === parentId ?
+      oldParentChildren :
+      nodes[parentId]!.children!.slice();
+  parentChildren.splice(action.index, 0, action.id);
+  nodeModifications[parentId] =
+      Object.assign({}, nodes[parentId], {children: parentChildren});
+
+  return Object.assign({}, nodes, nodeModifications);
+}
+
+function removeBookmark(nodes: NodeMap, action: RemoveBookmarkAction): NodeMap {
+  const newState = modifyNode(nodes, action.parentId, function(node) {
+    const newChildren = node.children!.slice();
+    newChildren.splice(action.index, 1);
+    return /** @type {BookmarkNode} */ (
+        Object.assign({}, node, {children: newChildren}));
+  });
+
+  return removeIdsFromObject(newState, action.descendants);
+}
+
+function reorderChildren(
+    nodes: NodeMap, action: ReorderChildrenAction): NodeMap {
+  return modifyNode(nodes, action.id, function(node) {
+    return /** @type {BookmarkNode} */ (
+        Object.assign({}, node, {children: action.children}));
+  });
+}
+
+export function updateNodes(nodes: NodeMap, action: Action): NodeMap {
+  switch (action.name) {
+    case 'create-bookmark':
+      return createBookmark(nodes, action as CreateBookmarkAction);
+    case 'edit-bookmark':
+      return editBookmark(nodes, action as EditBookmarkAction);
+    case 'move-bookmark':
+      return moveBookmark(nodes, action as MoveBookmarkAction);
+    case 'remove-bookmark':
+      return removeBookmark(nodes, action as RemoveBookmarkAction);
+    case 'reorder-children':
+      return reorderChildren(nodes, action as ReorderChildrenAction);
+    case 'refresh-nodes':
+      return (action as RefreshNodesAction).nodes;
+    default:
+      return nodes;
+  }
+}
+
+function isAncestorOf(
+    nodes: NodeMap, ancestorId: string, childId: string): boolean {
+  let currentId: string|undefined = childId;
+  // Work upwards through the tree from child.
+  while (currentId) {
+    if (currentId === ancestorId) {
+      return true;
+    }
+    currentId = nodes[currentId!]!.parentId;
+  }
+  return false;
+}
+
+// Exported for tests.
+export function updateSelectedFolder(
+    selectedFolder: string, action: Action, nodes: NodeMap): string {
+  switch (action.name) {
+    case 'select-folder':
+      return (action as SelectFolderAction).id;
+    case 'change-folder-open':
+      // When hiding the selected folder by closing its ancestor, select
+      // that ancestor instead.
+      const changeFolderAction = action as ChangeFolderOpenAction;
+      if (!changeFolderAction.open && selectedFolder &&
+          isAncestorOf(nodes, changeFolderAction.id, selectedFolder)) {
+        return changeFolderAction.id;
+      }
+      return selectedFolder;
+    case 'remove-bookmark':
+      // When deleting the selected folder (or its ancestor), select the
+      // parent of the deleted node.
+      const id = (action as RemoveBookmarkAction).id;
+      if (selectedFolder && isAncestorOf(nodes, id, selectedFolder)) {
+        return assert(nodes[id]!.parentId!);
+      }
+      return selectedFolder;
+    default:
+      return selectedFolder;
+  }
+}
+
+function openFolderAndAncestors(
+    folderOpenState: FolderOpenState, id: string, nodes: NodeMap):
+        FolderOpenState {
+  const newFolderOpenState = (new Map(folderOpenState) as FolderOpenState);
+  for (let currentId = id; currentId; currentId = nodes[currentId]!.parentId!) {
+    newFolderOpenState.set(currentId, true);
+  }
+
+  return newFolderOpenState;
+}
+
+function changeFolderOpen(
+    folderOpenState: FolderOpenState,
+    action: ChangeFolderOpenAction): FolderOpenState {
+  const newFolderOpenState = new Map(folderOpenState) as FolderOpenState;
+  newFolderOpenState.set(action.id, action.open);
+
+  return newFolderOpenState;
+}
+
+export function updateFolderOpenState(
+    folderOpenState: FolderOpenState, action: Action,
+    nodes: NodeMap): FolderOpenState {
+  switch (action.name) {
+    case 'change-folder-open':
+      return changeFolderOpen(
+          folderOpenState, action as ChangeFolderOpenAction);
+    case 'select-folder':
+      return openFolderAndAncestors(
+          folderOpenState, nodes[(action as SelectFolderAction).id]!.parentId!,
+          nodes);
+    case 'move-bookmark':
+      if (!nodes[(action as MoveBookmarkAction).id]!.children) {
+        return folderOpenState;
+      }
+      return openFolderAndAncestors(
+          folderOpenState, (action as MoveBookmarkAction).parentId, nodes);
+    case 'remove-bookmark':
+      return removeIdsFromMap(
+          folderOpenState, (action as RemoveBookmarkAction).descendants);
+    default:
+      return folderOpenState;
+  }
+}
+
+function updatePrefs(
+    prefs: PreferencesState, action: Action): PreferencesState {
+  const prefAction = action as SetPrefAction;
+  switch (prefAction.name) {
+    case 'set-incognito-availability':
+      return /** @type {PreferencesState} */ (Object.assign({}, prefs, {
+        incognitoAvailability: prefAction.value,
+      }));
+    case 'set-can-edit':
+      return /** @type {PreferencesState} */ (Object.assign({}, prefs, {
+        canEdit: prefAction.value,
+      }));
+    default:
+      return prefs;
+  }
+}
+
+export function reduceAction(
+    state: BookmarksPageState, action: Action): BookmarksPageState {
+  return {
+    nodes: updateNodes(state.nodes, action),
+    selectedFolder:
+        updateSelectedFolder(state.selectedFolder, action, state.nodes),
+    folderOpenState:
+        updateFolderOpenState(state.folderOpenState, action, state.nodes),
+    prefs: updatePrefs(state.prefs, action),
+    search: updateSearch(state.search, action),
+    selection: updateSelection(state.selection, action),
+  };
+}
diff --git a/chrome/browser/resources/bookmarks/router.js b/chrome/browser/resources/bookmarks/router.ts
similarity index 70%
rename from chrome/browser/resources/bookmarks/router.js
rename to chrome/browser/resources/bookmarks/router.ts
index 6f90ca2e0..2bac7c4a 100644
--- a/chrome/browser/resources/bookmarks/router.js
+++ b/chrome/browser/resources/bookmarks/router.ts
@@ -6,7 +6,6 @@
 import 'chrome://resources/polymer/v3_0/iron-location/iron-query-params.js';
 
 import {StoreObserver} from 'chrome://resources/js/cr/ui/store.m.js';
-import {StoreClientInterface as CrUiStoreClientInterface} from 'chrome://resources/js/cr/ui/store_client.m.js';
 import {Debouncer, html, microTask, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {selectFolder, setSearchTerm} from './actions.js';
@@ -14,20 +13,16 @@
 import {BookmarksStoreClientInterface, StoreClient} from './store_client.js';
 import {BookmarksPageState} from './types.js';
 
-/**
- * @constructor
- * @extends {PolymerElement}
- * @implements {BookmarksStoreClientInterface}
- * @implements {CrUiStoreClientInterface}
- * @implements {StoreObserver<BookmarksPageState>}
- */
-const BookmarksRouterElementBase = mixinBehaviors(StoreClient, PolymerElement);
+const BookmarksRouterElementBase =
+    mixinBehaviors(StoreClient, PolymerElement) as {
+  new (): PolymerElement & BookmarksStoreClientInterface &
+      StoreObserver<BookmarksPageState>
+}
 
 /**
  * This element is a one way bound interface that routes the page URL to
  * the searchTerm and selectedId. Clients must initialize themselves by
  * reading the router's fields after attach.
- * @polymer
  */
 export class BookmarksRouterElement extends BookmarksRouterElementBase {
   static get is() {
@@ -40,36 +35,34 @@
 
   static get properties() {
     return {
-      /**
-       * Parameter q is routed to the searchTerm.
-       * Parameter id is routed to the selectedId.
-       * @private
-       */
       queryParams_: Object,
 
-      /** @private {string} */
       query_: {
         type: String,
         observer: 'onQueryChanged_',
       },
 
-      /** @private {string} */
       urlQuery_: {
         type: String,
         observer: 'onUrlQueryChanged_',
       },
 
-      /** @private */
       searchTerm_: {
         type: String,
         value: '',
       },
 
-      /** @private {?string} */
       selectedId_: String,
     };
   }
 
+  private query_: string;
+  private queryParams_: {q?: string, id?: string};
+  private searchTerm_: string = '';
+  private selectedId_: string;
+  private urlQuery_: string;
+  private debounceJob_: Debouncer;
+
   static get observers() {
     return [
       'onQueryParamsChanged_(queryParams_)',
@@ -77,25 +70,18 @@
     ];
   }
 
-  constructor() {
-    super();
-    /** @private {Debouncer} */
-    this.debounceJob_;
-  }
-
   connectedCallback() {
     super.connectedCallback();
-    this.watch('selectedId_', function(state) {
+    this.watch('selectedId_', function(state: BookmarksPageState) {
       return state.selectedFolder;
     });
-    this.watch('searchTerm_', function(state) {
+    this.watch('searchTerm_', function(state: BookmarksPageState) {
       return state.search.term;
     });
     this.updateFromStore();
   }
 
-  /** @private */
-  onQueryParamsChanged_() {
+  private onQueryParamsChanged_() {
     const searchTerm = this.queryParams_.q || '';
     let selectedId = this.queryParams_.id;
     if (!selectedId && !searchTerm) {
@@ -112,35 +98,27 @@
       // Need to dispatch a deferred action so that during page load
       // `this.getState()` will only evaluate after the Store is initialized.
       this.dispatchAsync((dispatch) => {
-        dispatch(selectFolder(selectedId, this.getState().nodes));
+        dispatch(selectFolder(selectedId!, this.getState().nodes));
       });
     }
   }
 
-  /**
-   * @param {?string} current Current value of the query.
-   * @param {?string} previous Previous value of the query.
-   * @private
-   */
-  onQueryChanged_(current, previous) {
+  private onQueryChanged_(current: (string|null), previous: (string|null)) {
     if (previous !== undefined) {
       this.urlQuery_ = this.query_;
     }
   }
 
-  /** @private */
-  onUrlQueryChanged_() {
+  private onUrlQueryChanged_() {
     this.query_ = this.urlQuery_;
   }
 
-  /** @private */
-  onStateChanged_() {
+  private onStateChanged_() {
     this.debounceJob_ = Debouncer.debounce(
         this.debounceJob_, microTask, () => this.updateQueryParams_());
   }
 
-  /** @private */
-  updateQueryParams_() {
+  private updateQueryParams_() {
     if (this.searchTerm_) {
       this.queryParams_ = {q: this.searchTerm_};
     } else if (this.selectedId_ !== BOOKMARKS_BAR_ID) {
diff --git a/chrome/browser/resources/bookmarks/shared_style.js b/chrome/browser/resources/bookmarks/shared_style.ts
similarity index 100%
rename from chrome/browser/resources/bookmarks/shared_style.js
rename to chrome/browser/resources/bookmarks/shared_style.ts
diff --git a/chrome/browser/resources/bookmarks/shared_vars.js b/chrome/browser/resources/bookmarks/shared_vars.ts
similarity index 100%
rename from chrome/browser/resources/bookmarks/shared_vars.js
rename to chrome/browser/resources/bookmarks/shared_vars.ts
diff --git a/chrome/browser/resources/bookmarks/store.js b/chrome/browser/resources/bookmarks/store.ts
similarity index 74%
rename from chrome/browser/resources/bookmarks/store.js
rename to chrome/browser/resources/bookmarks/store.ts
index d13064d..3f23978 100644
--- a/chrome/browser/resources/bookmarks/store.js
+++ b/chrome/browser/resources/bookmarks/store.ts
@@ -13,22 +13,18 @@
  * the store.
  */
 
-/** @extends {CrUiStore<BookmarksPageState>} */
-export class Store extends CrUiStore {
+export class Store extends CrUiStore<BookmarksPageState> {
   constructor() {
     super(createEmptyState(), reduceAction);
   }
 
-  /** @return {!Store} */
-  static getInstance() {
+  static getInstance(): Store {
     return instance || (instance = new Store());
   }
 
-  /** @param {Store} obj */
-  static setInstance(obj) {
+  static setInstance(obj: Store) {
     instance = obj;
   }
 }
 
-/** @type {?Store} */
-let instance = null;
+let instance: (Store|null) = null;
diff --git a/chrome/browser/resources/bookmarks/store_client.js b/chrome/browser/resources/bookmarks/store_client.js
deleted file mode 100644
index cf0e3b3..0000000
--- a/chrome/browser/resources/bookmarks/store_client.js
+++ /dev/null
@@ -1,63 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import {StoreObserver} from 'chrome://resources/js/cr/ui/store.m.js';
-import {StoreClient as CrUiStoreClient, StoreClientInterface as CrUiStoreClientInterface} from 'chrome://resources/js/cr/ui/store_client.m.js';
-
-import {Store} from './store.js';
-import {BookmarksPageState} from './types.js';
-
-/**
- * @fileoverview Defines StoreClient, a Polymer behavior to tie a front-end
- * element to back-end data from the store.
- */
-
-/**
- * @polymerBehavior
- */
-const BookmarksStoreClientImpl = {
-  /**
-   * @param {string} localProperty
-   * @param {function(Object)} valueGetter
-   */
-  watch(localProperty, valueGetter) {
-    this.watch_(localProperty, valueGetter);
-  },
-
-  /**
-   * @return {BookmarksPageState}
-   */
-  getState() {
-    return this.getStore().data;
-  },
-
-  /**
-   * @return {Store}
-   */
-  getStore() {
-    return Store.getInstance();
-  },
-};
-
-export class BookmarksStoreClientInterface {
-  /**
-   * @param {string} localProperty
-   * @param {function(Object)} valueGetter
-   */
-  watch(localProperty, valueGetter) {}
-
-  /** @return {BookmarksPageState} */
-  getState() {}
-
-  /** @return {Store} */
-  getStore() {}
-}
-
-/**
- * @polymerBehavior
- * @implements {BookmarksStoreClientInterface}
- * @implements {CrUiStoreClientInterface}
- * @implements {StoreObserver<BookmarksPageState>}
- */
-export const StoreClient = [CrUiStoreClient, BookmarksStoreClientImpl];
diff --git a/chrome/browser/resources/bookmarks/store_client.ts b/chrome/browser/resources/bookmarks/store_client.ts
new file mode 100644
index 0000000..b7ed7c09
--- /dev/null
+++ b/chrome/browser/resources/bookmarks/store_client.ts
@@ -0,0 +1,41 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import {StoreObserver} from 'chrome://resources/js/cr/ui/store.m.js';
+import {StoreClient as CrUiStoreClient, StoreClientInterface as CrUiStoreClientInterface} from 'chrome://resources/js/cr/ui/store_client.m.js';
+import {mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {Store} from './store.js';
+import {BookmarksPageState} from './types.js';
+
+/**
+ * @fileoverview Defines StoreClient, a Polymer behavior to tie a front-end
+ * element to back-end data from the store.
+ */
+
+const BookmarksStoreClientImpl = {
+  watch(localProperty: string, valueGetter: (p: BookmarksPageState) => any) {
+    (this as any).watch_(localProperty, valueGetter);
+  },
+
+  getState(): BookmarksPageState {
+    return this.getStore().data;
+  },
+
+  getStore(): Store {
+    return Store.getInstance();
+  },
+};
+
+export interface BookmarksStoreClientInterface extends
+    CrUiStoreClientInterface {
+  watch(localProperty: string,
+        valueGetter: (p: BookmarksPageState) => any): void;
+
+  getState(): BookmarksPageState;
+
+  getStore(): Store;
+}
+
+export const StoreClient = [CrUiStoreClient, BookmarksStoreClientImpl];
diff --git a/chrome/browser/resources/bookmarks/toolbar.js b/chrome/browser/resources/bookmarks/toolbar.ts
similarity index 71%
rename from chrome/browser/resources/bookmarks/toolbar.js
rename to chrome/browser/resources/bookmarks/toolbar.ts
index 0e5cdde..86e6f729 100644
--- a/chrome/browser/resources/bookmarks/toolbar.js
+++ b/chrome/browser/resources/bookmarks/toolbar.ts
@@ -7,12 +7,13 @@
 import 'chrome://resources/cr_elements/icons.m.js';
 import './shared_style.js';
 import './strings.m.js';
+import 'chrome://resources/cr_elements/cr_toolbar/cr_toolbar.js';
+import 'chrome://resources/cr_elements/cr_toolbar/cr_toolbar_search_field.js';
 
 import {CrToolbarElement} from 'chrome://resources/cr_elements/cr_toolbar/cr_toolbar.js';
 import {CrToolbarSearchFieldElement} from 'chrome://resources/cr_elements/cr_toolbar/cr_toolbar_search_field.js';
 import {assert} from 'chrome://resources/js/assert.m.js';
 import {StoreObserver} from 'chrome://resources/js/cr/ui/store.m.js';
-import {StoreClientInterface as CrUiStoreClientInterface} from 'chrome://resources/js/cr/ui/store_client.m.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
 import {html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
@@ -22,16 +23,12 @@
 import {BookmarksStoreClientInterface, StoreClient} from './store_client.js';
 import {BookmarksPageState} from './types.js';
 
-/**
- * @constructor
- * @extends {PolymerElement}
- * @implements {BookmarksStoreClientInterface}
- * @implements {CrUiStoreClientInterface}
- * @implements {StoreObserver<BookmarksPageState>}
- */
-const BookmarksToolbarElementBase = mixinBehaviors(StoreClient, PolymerElement);
+const BookmarksToolbarElementBase =
+    mixinBehaviors([StoreClient], PolymerElement) as {
+  new (): PolymerElement & BookmarksStoreClientInterface &
+      StoreObserver<BookmarksPageState>
+}
 
-/** @polymer */
 export class BookmarksToolbarElement extends BookmarksToolbarElementBase {
   static get is() {
     return 'bookmarks-toolbar';
@@ -54,52 +51,49 @@
         readOnly: true,
       },
 
-      /** @private */
       narrow_: {
         type: Boolean,
         reflectToAttribute: true,
       },
 
-      /** @private */
       searchTerm_: {
         type: String,
         observer: 'onSearchTermChanged_',
       },
 
-      /** @private {!Set<string>} */
       selectedItems_: Object,
 
-      /** @private */
       globalCanEdit_: Boolean,
     };
   }
 
+  sidebarWidth: string;
+  showSelectionOverlay: boolean;
+  private narrow_: boolean;
+  private searchTerm_: string;
+  private selectedItems_: Set<string>;
+  private globalCanEdit_: boolean;
+
   connectedCallback() {
     super.connectedCallback();
-    this.watch('searchTerm_', function(state) {
+    this.watch('searchTerm_', function(state: BookmarksPageState) {
       return state.search.term;
     });
-    this.watch('selectedItems_', function(state) {
+    this.watch('selectedItems_', function(state: BookmarksPageState) {
       return state.selection.items;
     });
-    this.watch('globalCanEdit_', function(state) {
+    this.watch('globalCanEdit_', function(state: BookmarksPageState) {
       return state.prefs.canEdit;
     });
     this.updateFromStore();
   }
 
-  /** @return {CrToolbarSearchFieldElement} */
-  get searchField() {
-    return /** @type {CrToolbarElement} */ (
-               this.shadowRoot.querySelector('cr-toolbar'))
+  get searchField(): CrToolbarSearchFieldElement {
+    return this.shadowRoot!.querySelector<CrToolbarElement>('cr-toolbar')!
         .getSearchField();
   }
 
-  /**
-   * @param {Event} e
-   * @private
-   */
-  onMenuButtonOpenTap_(e) {
+  private onMenuButtonOpenTap_(e: Event) {
     this.dispatchEvent(new CustomEvent('open-command-menu', {
       bubbles: true,
       composed: true,
@@ -110,65 +104,45 @@
     }));
   }
 
-  /** @private */
-  onDeleteSelectionTap_() {
+  private onDeleteSelectionTap_() {
     const selection = this.selectedItems_;
     const commandManager = BookmarksCommandManagerElement.getInstance();
     assert(commandManager.canExecute(Command.DELETE, selection));
     commandManager.handle(Command.DELETE, selection);
   }
 
-  /** @private */
-  onClearSelectionTap_() {
+  private onClearSelectionTap_() {
     const commandManager = BookmarksCommandManagerElement.getInstance();
     assert(
         commandManager.canExecute(Command.DESELECT_ALL, this.selectedItems_));
     commandManager.handle(Command.DESELECT_ALL, this.selectedItems_);
   }
 
-  /**
-   * @param {!CustomEvent<string>} e
-   * @private
-   */
-  onSearchChanged_(e) {
+  private onSearchChanged_(e: CustomEvent<string>) {
     if (e.detail !== this.searchTerm_) {
       this.dispatch(setSearchTerm(e.detail));
     }
   }
 
-  /** @private */
-  onSidebarWidthChanged_() {
+  private onSidebarWidthChanged_() {
     this.style.setProperty('--sidebar-width', this.sidebarWidth);
   }
 
-  /** @private */
-  onSearchTermChanged_() {
+  private onSearchTermChanged_() {
     this.searchField.setValue(this.searchTerm_ || '');
   }
 
-  /**
-   * @return {boolean}
-   * @private
-   */
-  shouldShowSelectionOverlay_() {
+  private shouldShowSelectionOverlay_(): boolean {
     return this.selectedItems_.size > 1 && this.globalCanEdit_;
   }
 
-  /**
-   * @return {boolean}
-   * @private
-   */
-  canDeleteSelection_() {
+  private canDeleteSelection_(): boolean {
     return this.showSelectionOverlay &&
         BookmarksCommandManagerElement.getInstance().canExecute(
             Command.DELETE, this.selectedItems_);
   }
 
-  /**
-   * @return {string}
-   * @private
-   */
-  getItemsSelectedString_() {
+  private getItemsSelectedString_(): string {
     return loadTimeData.getStringF('itemsSelected', this.selectedItems_.size);
   }
 }
diff --git a/chrome/browser/resources/bookmarks/tsconfig_base.json b/chrome/browser/resources/bookmarks/tsconfig_base.json
new file mode 100644
index 0000000..3d55197
--- /dev/null
+++ b/chrome/browser/resources/bookmarks/tsconfig_base.json
@@ -0,0 +1,9 @@
+{
+  "extends": "../../../../tools/typescript/tsconfig_base.json",
+  "compilerOptions": {
+    "noPropertyAccessFromIndexSignature": false,
+    "noUnusedLocals": false,
+    "noUnusedParameters": false,
+    "strictPropertyInitialization": false
+  }
+}
diff --git a/chrome/browser/resources/bookmarks/types.js b/chrome/browser/resources/bookmarks/types.js
deleted file mode 100644
index 0031ae7c..0000000
--- a/chrome/browser/resources/bookmarks/types.js
+++ /dev/null
@@ -1,106 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import {DropPosition, IncognitoAvailability} from './constants.js';
-
-/**
- * @fileoverview Closure typedefs for Bookmarks.
- */
-
-/**
- * A normalized version of chrome.bookmarks.BookmarkTreeNode.
- * @typedef {{
- *   id: string,
- *   parentId: (string|undefined),
- *   url: (string|undefined),
- *   title: string,
- *   dateAdded: (number|undefined),
- *   dateGroupModified: (number|undefined),
- *   unmodifiable: (string|undefined),
- *   children: (!Array<string>|undefined),
- * }}
- */
-export let BookmarkNode;
-
-/**
- * @typedef {!Object<string, BookmarkNode>}
- */
-export let NodeMap;
-
-/**
- * @typedef {{
- *   items: !Set<string>,
- *   anchor: ?string,
- * }}
- *
- * |items| is used as a set and all values in the map are true.
- */
-export let SelectionState;
-
-/**
- * Note:
- * - If |results| is null, it means no search results have been returned. This
- *   is different to |results| being [], which means the last search returned 0
- *   results.
- * - |term| is the last search that was performed by the user, and |results| are
- *   the last results that were returned from the backend. We don't clear
- *   |results| on incremental searches, meaning that |results| can be 'stale'
- *   data from a previous search term (while |inProgress| is true). If you need
- *   to know the exact search term used to generate |results|, you'll need to
- *   add a new field to the state to track it (eg, SearchState.resultsTerm).
- * @typedef {{
- *   term: string,
- *   inProgress: boolean,
- *   results: ?Array<string>,
- * }}
- */
-export let SearchState;
-
-/** @typedef {!Map<string, boolean>} */
-export let FolderOpenState;
-
-/**
- * @typedef {{
- *   canEdit: boolean,
- *   incognitoAvailability: IncognitoAvailability,
- * }}
- */
-export let PreferencesState;
-
-/**
- * @typedef {{
- *   nodes: NodeMap,
- *   selectedFolder: string,
- *   folderOpenState: FolderOpenState,
- *   prefs: PreferencesState,
- *   search: SearchState,
- *   selection: SelectionState,
- * }}
- */
-export let BookmarksPageState;
-
-/** @typedef {{element: BookmarkElement, position: DropPosition}} */
-export let DropDestination;
-
-export class BookmarkElement extends HTMLElement {
-  constructor() {
-    super();
-
-    /** @type {string} */
-    this.itemId = '';
-  }
-
-  /** @return {HTMLElement} */
-  getDropTarget() {}
-}
-
-export class DragData {
-  constructor() {
-    /** @type {Array<chrome.bookmarks.BookmarkTreeNode>} */
-    this.elements = null;
-
-    /** @type {boolean} */
-    this.sameProfile = false;
-  }
-}
diff --git a/chrome/browser/resources/bookmarks/types.ts b/chrome/browser/resources/bookmarks/types.ts
new file mode 100644
index 0000000..d5f451d
--- /dev/null
+++ b/chrome/browser/resources/bookmarks/types.ts
@@ -0,0 +1,92 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import {DropPosition, IncognitoAvailability, MenuSource} from './constants.js';
+
+/**
+ * @fileoverview Closure typedefs for Bookmarks.
+ */
+
+// A normalized version of chrome.bookmarks.BookmarkTreeNode.
+export type BookmarkNode = {
+  id: string,
+  parentId?: string,
+  url?: string,
+  title: string,
+  dateAdded?: number,
+  dateGroupModified?: number,
+  unmodifiable?: string,
+  children?: string[],
+};
+
+export interface ObjectMap<Type> {
+  [index: string]: Type;
+}
+
+export type NodeMap = ObjectMap<BookmarkNode>;
+
+// |items| is used as a set and all values in the map are true.
+export type SelectionState = {
+  items: Set<string>,
+  anchor?: string|null,
+};
+
+export type OpenCommandMenuDetail = {
+  x?: number,
+  y?: number,
+  source: MenuSource,
+  targetId?: string,
+  targetElement?: HTMLElement,
+};
+
+/**
+ * Note:
+ * - If |results| is null, it means no search results have been returned. This
+ *   is different to |results| being [], which means the last search returned 0
+ *   results.
+ * - |term| is the last search that was performed by the user, and |results| are
+ *   the last results that were returned from the backend. We don't clear
+ *   |results| on incremental searches, meaning that |results| can be 'stale'
+ *   data from a previous search term (while |inProgress| is true). If you need
+ *   to know the exact search term used to generate |results|, you'll need to
+ *   add a new field to the state to track it (eg, SearchState.resultsTerm).
+ */
+export type SearchState = {
+  term: string,
+  inProgress: boolean,
+  results: string[]|null,
+};
+
+export type FolderOpenState = Map<string, boolean>;
+
+export type PreferencesState = {
+  canEdit: boolean, incognitoAvailability: IncognitoAvailability;
+};
+
+export type BookmarksPageState = {
+  nodes: NodeMap,
+  selectedFolder: string,
+  folderOpenState: FolderOpenState,
+  prefs: PreferencesState,
+  search: SearchState,
+  selection: SelectionState,
+};
+
+export type DropDestination = {
+  element: BookmarkElement,
+  position: DropPosition,
+};
+
+export class BookmarkElement extends HTMLElement {
+  itemId: string = '';
+
+  getDropTarget(): HTMLElement|null {
+    return null;
+  }
+}
+
+export class DragData {
+  elements: chrome.bookmarks.BookmarkTreeNode[]|null = null;
+  sameProfile: boolean = false;
+}
diff --git a/chrome/browser/resources/bookmarks/util.js b/chrome/browser/resources/bookmarks/util.js
deleted file mode 100644
index b0e1184..0000000
--- a/chrome/browser/resources/bookmarks/util.js
+++ /dev/null
@@ -1,218 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import {assert} from 'chrome://resources/js/assert.m.js';
-
-import {BOOKMARKS_BAR_ID, IncognitoAvailability, ROOT_NODE_ID} from './constants.js';
-import {BookmarkNode, BookmarksPageState, NodeMap} from './types.js';
-
-/**
- * @fileoverview Utility functions for the Bookmarks page.
- */
-
-/**
- * Returns the list of bookmark IDs to be displayed in the UI, taking into
- * account search and the currently selected folder.
- * @param {!BookmarksPageState} state
- * @return {!Array<string>}
- */
-export function getDisplayedList(state) {
-  if (isShowingSearch(state)) {
-    return assert(state.search.results);
-  }
-
-  return assert(state.nodes[state.selectedFolder].children);
-}
-
-/**
- * @param {chrome.bookmarks.BookmarkTreeNode} treeNode
- * @return {!BookmarkNode}
- */
-export function normalizeNode(treeNode) {
-  const node = Object.assign({}, treeNode);
-  // Node index is not necessary and not kept up-to-date. Remove it from the
-  // data structure so we don't accidentally depend on the incorrect
-  // information.
-  delete node.index;
-
-  if (!('url' in node)) {
-    // The onCreated API listener returns folders without |children| defined.
-    node.children = (node.children || []).map(function(child) {
-      return child.id;
-    });
-  }
-  return /** @type {BookmarkNode} */ (node);
-}
-
-/**
- * @param {chrome.bookmarks.BookmarkTreeNode} rootNode
- * @return {NodeMap}
- */
-export function normalizeNodes(rootNode) {
-  /** @type {NodeMap} */
-  const nodeMap = {};
-  const stack = [];
-  stack.push(rootNode);
-
-  while (stack.length > 0) {
-    const node = stack.pop();
-    nodeMap[node.id] = normalizeNode(node);
-    if (!node.children) {
-      continue;
-    }
-
-    node.children.forEach(function(child) {
-      stack.push(child);
-    });
-  }
-
-  return nodeMap;
-}
-
-/** @return {!BookmarksPageState} */
-export function createEmptyState() {
-  return {
-    nodes: {},
-    selectedFolder: BOOKMARKS_BAR_ID,
-    folderOpenState: new Map(),
-    prefs: {
-      canEdit: true,
-      incognitoAvailability: IncognitoAvailability.ENABLED,
-    },
-    search: {
-      term: '',
-      inProgress: false,
-      results: null,
-    },
-    selection: {
-      items: new Set(),
-      anchor: null,
-    },
-  };
-}
-
-/**
- * @param {BookmarksPageState} state
- * @return {boolean}
- */
-export function isShowingSearch(state) {
-  return state.search.results != null;
-}
-
-/**
- * Returns true if the node with ID |itemId| is modifiable, allowing
- * the node to be renamed, moved or deleted. Note that if a node is
- * uneditable, it may still have editable children (for example, the top-level
- * folders).
- * @param {BookmarksPageState} state
- * @param {string} itemId
- * @return {boolean}
- */
-export function canEditNode(state, itemId) {
-  return itemId !== ROOT_NODE_ID &&
-      state.nodes[itemId].parentId !== ROOT_NODE_ID &&
-      !state.nodes[itemId].unmodifiable && state.prefs.canEdit;
-}
-
-/**
- * Returns true if it is possible to modify the children list of the node with
- * ID |itemId|. This includes rearranging the children or adding new ones.
- * @param {BookmarksPageState} state
- * @param {string} itemId
- * @return {boolean}
- */
-export function canReorderChildren(state, itemId) {
-  return itemId !== ROOT_NODE_ID && !state.nodes[itemId].unmodifiable &&
-      state.prefs.canEdit;
-}
-
-/**
- * @param {string} id
- * @param {NodeMap} nodes
- * @return {boolean}
- */
-export function hasChildFolders(id, nodes) {
-  const children = nodes[id].children;
-  for (let i = 0; i < children.length; i++) {
-    if (nodes[children[i]].children) {
-      return true;
-    }
-  }
-  return false;
-}
-
-/**
- * Get all descendants of a node, including the node itself.
- * @param {NodeMap} nodes
- * @param {string} baseId
- * @return {!Set<string>}
- */
-export function getDescendants(nodes, baseId) {
-  const descendants = new Set();
-  const stack = [];
-  stack.push(baseId);
-
-  while (stack.length > 0) {
-    const id = stack.pop();
-    const node = nodes[id];
-
-    if (!node) {
-      continue;
-    }
-
-    descendants.add(id);
-
-    if (!node.children) {
-      continue;
-    }
-
-    node.children.forEach(function(childId) {
-      stack.push(childId);
-    });
-  }
-
-  return descendants;
-}
-
-/**
- * @param {!Object<string, T>} map
- * @param {!Set<string>} ids
- * @return {!Object<string, T>}
- * @template T
- */
-export function removeIdsFromObject(map, ids) {
-  const newObject = Object.assign({}, map);
-  ids.forEach(function(id) {
-    delete newObject[id];
-  });
-  return newObject;
-}
-
-
-/**
- * @param {!Map<string, T>} map
- * @param {!Set<string>} ids
- * @return {!Map<string, T>}
- * @template T
- */
-export function removeIdsFromMap(map, ids) {
-  const newMap = new Map(map);
-  ids.forEach(function(id) {
-    newMap.delete(id);
-  });
-  return newMap;
-}
-
-/**
- * @param {!Set<string>} set
- * @param {!Set<string>} ids
- * @return {!Set<string>}
- */
-export function removeIdsFromSet(set, ids) {
-  const difference = new Set(set);
-  ids.forEach(function(id) {
-    difference.delete(id);
-  });
-  return difference;
-}
diff --git a/chrome/browser/resources/bookmarks/util.ts b/chrome/browser/resources/bookmarks/util.ts
new file mode 100644
index 0000000..26be15c
--- /dev/null
+++ b/chrome/browser/resources/bookmarks/util.ts
@@ -0,0 +1,173 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import {assert} from 'chrome://resources/js/assert.m.js';
+
+import {BOOKMARKS_BAR_ID, IncognitoAvailability, ROOT_NODE_ID} from './constants.js';
+import {BookmarkNode, BookmarksPageState, NodeMap, ObjectMap} from './types.js';
+
+/**
+ * @fileoverview Utility functions for the Bookmarks page.
+ */
+
+export function getDisplayedList(state: BookmarksPageState): string[] {
+  if (isShowingSearch(state)) {
+    return assert(state.search.results!);
+  }
+
+  return assert(state.nodes[state.selectedFolder]!.children!);
+}
+
+export function normalizeNode(treeNode: chrome.bookmarks.BookmarkTreeNode):
+    BookmarkNode {
+  const node = Object.assign({}, treeNode);
+  // Node index is not necessary and not kept up-to-date. Remove it from the
+  // data structure so we don't accidentally depend on the incorrect
+  // information.
+  delete node.index;
+  delete node.children;
+  const bookmarkNode = node as unknown as BookmarkNode;
+
+  if (!('url' in node)) {
+    // The onCreated API listener returns folders without |children| defined.
+    bookmarkNode.children = (treeNode.children || []).map(function(child) {
+      return child.id;
+    });
+  }
+  return bookmarkNode;
+}
+
+export function normalizeNodes(rootNode: chrome.bookmarks.BookmarkTreeNode):
+    NodeMap {
+  const nodeMap: NodeMap = {};
+  const stack = [];
+  stack.push(rootNode);
+
+  while (stack.length > 0) {
+    const node = stack.pop()!;
+    nodeMap[node.id] = normalizeNode(node);
+    if (!node.children) {
+      continue;
+    }
+
+    node.children.forEach(function(child) {
+      stack.push(child);
+    });
+  }
+
+  return nodeMap;
+}
+
+export function createEmptyState(): BookmarksPageState {
+  return {
+    nodes: {},
+    selectedFolder: BOOKMARKS_BAR_ID,
+    folderOpenState: new Map(),
+    prefs: {
+      canEdit: true,
+      incognitoAvailability: IncognitoAvailability.ENABLED,
+    },
+    search: {
+      term: '',
+      inProgress: false,
+      results: null,
+    },
+    selection: {
+      items: new Set(),
+      anchor: null,
+    },
+  };
+}
+
+export function isShowingSearch(state: BookmarksPageState): boolean {
+  return state.search.results != null;
+}
+
+/**
+ * Returns true if the node with ID |itemId| is modifiable, allowing
+ * the node to be renamed, moved or deleted. Note that if a node is
+ * uneditable, it may still have editable children (for example, the top-level
+ * folders).
+ */
+export function canEditNode(
+    state: BookmarksPageState, itemId: string): boolean {
+  return itemId !== ROOT_NODE_ID &&
+      state.nodes![itemId]!.parentId !== ROOT_NODE_ID &&
+      !state.nodes![itemId]!.unmodifiable && state.prefs.canEdit;
+}
+
+/**
+ * Returns true if it is possible to modify the children list of the node with
+ * ID |itemId|. This includes rearranging the children or adding new ones.
+ */
+export function canReorderChildren(
+    state: BookmarksPageState, itemId: string): boolean {
+  return itemId !== ROOT_NODE_ID && !state.nodes[itemId]!.unmodifiable &&
+      state.prefs.canEdit;
+}
+
+export function hasChildFolders(id: string, nodes: NodeMap): boolean {
+  const children = nodes[id]!.children!;
+  for (let i = 0; i < children.length; i++) {
+    if (nodes[children[i]!]!.children) {
+      return true;
+    }
+  }
+  return false;
+}
+
+export function getDescendants(nodes: NodeMap, baseId: string): Set<string> {
+  const descendants = new Set() as Set<string>;
+  const stack: string[] = [];
+  stack.push(baseId);
+
+  while (stack.length > 0) {
+    const id = stack.pop()!;
+    const node = nodes[id];
+
+    if (!node) {
+      continue;
+    }
+
+    descendants.add(id);
+
+    if (!node!.children) {
+      continue;
+    }
+
+    node!.children.forEach(function(childId) {
+      stack.push(childId);
+    });
+  }
+
+  return descendants;
+}
+
+export function removeIdsFromObject<Type>(
+    map: ObjectMap<Type>, ids: Set<string>): ObjectMap<Type> {
+  const newObject = Object.assign({}, map);
+  ids.forEach(function(id) {
+    delete newObject[id];
+  });
+  return newObject;
+}
+
+
+export function removeIdsFromMap<Type>(
+    map: Map<string, Type>, ids: Set<string>): Map<string, Type> {
+  const newMap = new Map(map);
+  ids.forEach(function(id) {
+    newMap.delete(id);
+  });
+  return newMap;
+}
+
+export function removeIdsFromSet(
+    set: Set<string>, ids: Set<string>): Set<string> {
+  const difference = new Set(set);
+  ids.forEach(function(id) {
+    difference.delete(id);
+  });
+  return difference;
+}
diff --git a/chrome/browser/resources/extensions/BUILD.gn b/chrome/browser/resources/extensions/BUILD.gn
index d4cbf4e..397a1b6 100644
--- a/chrome/browser/resources/extensions/BUILD.gn
+++ b/chrome/browser/resources/extensions/BUILD.gn
@@ -5,10 +5,10 @@
 import("//build/config/chromeos/ui_mode.gni")
 import("//chrome/common/features.gni")
 import("//extensions/buildflags/buildflags.gni")
-import("//third_party/closure_compiler/compile_js.gni")
 import("//tools/grit/grit_rule.gni")
 import("//tools/grit/preprocess_if_expr.gni")
 import("//tools/polymer/html_to_js.gni")
+import("//tools/typescript/ts_library.gni")
 import("//ui/webui/resources/tools/generate_grd.gni")
 import("../tools/optimize_webui.gni")
 
@@ -23,14 +23,13 @@
 
   optimize_webui("build") {
     host = "extensions"
-    input = rebase_path("$target_gen_dir/$preprocess_folder", root_build_dir)
+    input = rebase_path("$target_gen_dir/tsc", root_build_dir)
     js_out_files = [ "extensions.rollup.js" ]
     js_module_in_files = [ "extensions.js" ]
     out_manifest = "$target_gen_dir/$build_manifest"
 
     deps = [
-      ":preprocess",
-      ":preprocess_generated",
+      ":build_ts",
       "../../../../ui/webui/resources:preprocess",
     ]
     excludes = [ "chrome://resources/js/cr.m.js" ]
@@ -52,14 +51,8 @@
     resource_path_rewrites = [ "extensions.rollup.js|extensions.js" ]
     manifest_files = [ "$target_gen_dir/$build_manifest" ]
   } else {
-    deps = [
-      ":preprocess",
-      ":preprocess_generated",
-    ]
-    manifest_files = [
-      "$target_gen_dir/$preprocess_manifest",
-      "$target_gen_dir/$preprocess_gen_manifest",
-    ]
+    deps = [ ":build_ts" ]
+    manifest_files = [ "$target_gen_dir/tsconfig.manifest" ]
   }
 }
 
@@ -125,13 +118,6 @@
   }
 }
 
-group("closure_compile") {
-  deps = [
-    ":extensions_module_resources",
-    "activity_log:closure_compile_module",
-  ]
-}
-
 group("web_components") {
   public_deps = [
     ":web_components_local",
@@ -189,319 +175,69 @@
   output_dir = "$root_gen_dir/chrome"
 }
 
-js_type_check("extensions_module_resources") {
-  is_polymer3 = true
-  deps = [
-    ":checkup",
-    ":code_section",
-    ":detail_view",
-    ":drag_and_drop_handler",
-    ":drop_overlay",
-    ":error_page",
-    ":host_permissions_toggle_list",
-    ":install_warnings_dialog",
-    ":item",
-    ":item_behavior",
-    ":item_list",
-    ":item_util",
-    ":keyboard_shortcut_delegate",
-    ":keyboard_shortcuts",
-    ":kiosk_browser_proxy",
-    ":load_error",
-    ":manager",
-    ":navigation_helper",
-    ":options_dialog",
-    ":pack_dialog",
-    ":pack_dialog_alert",
-    ":runtime_host_permissions",
-    ":runtime_hosts_dialog",
-    ":service",
-    ":shortcut_input",
-    ":shortcut_util",
-    ":sidebar",
-    ":toggle_row",
-    ":toolbar",
+ts_library("build_ts") {
+  root_dir = "$target_gen_dir/$preprocess_folder"
+  out_dir = "$target_gen_dir/tsc"
+  tsconfig_base = "tsconfig_base.json"
+  in_files = [
+    "activity_log/activity_log_history_item.js",
+    "activity_log/activity_log_history.js",
+    "activity_log/activity_log.js",
+    "activity_log/activity_log_stream_item.js",
+    "activity_log/activity_log_stream.js",
+    "checkup.js",
+    "code_section.js",
+    "detail_view.js",
+    "drag_and_drop_handler.js",
+    "drop_overlay.js",
+    "error_page.js",
+    "extensions.js",
+    "host_permissions_toggle_list.js",
+    "icons.js",
+    "install_warnings_dialog.js",
+    "item_behavior.js",
+    "item.js",
+    "item_list.js",
+    "item_util.js",
+    "keyboard_shortcut_delegate.js",
+    "keyboard_shortcuts.js",
+    "load_error.js",
+    "manager.js",
+    "navigation_helper.js",
+    "options_dialog.js",
+    "pack_dialog_alert.js",
+    "pack_dialog.js",
+    "runtime_host_permissions.js",
+    "runtime_hosts_dialog.js",
+    "service.js",
+    "shared_style.js",
+    "shared_vars.js",
+    "shortcut_input.js",
+    "shortcut_util.js",
+    "sidebar.js",
+    "toggle_row.js",
+    "toolbar.js",
   ]
+  definitions = [
+    "//tools/typescript/definitions/activity_log_private.d.ts",
+    "//tools/typescript/definitions/developer_private.d.ts",
+    "//tools/typescript/definitions/metrics_private.d.ts",
+  ]
+
   if (is_chromeos_ash) {
-    deps += [ ":kiosk_dialog" ]
-  }
-}
-
-js_library("checkup") {
-  deps = [
-    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
-  ]
-}
-
-js_library("code_section") {
-  deps = [
-    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
-    "//ui/webui/resources/js:i18n_behavior.m",
-    "//ui/webui/resources/js:load_time_data.m",
-  ]
-  externs_list = [ "$externs_path/developer_private.js" ]
-}
-
-js_library("detail_view") {
-  deps = [
-    ":item",
-    ":item_behavior",
-    ":item_util",
-    ":navigation_helper",
-    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
-    "//ui/webui/resources/cr_elements:cr_container_shadow_behavior.m",
-    "//ui/webui/resources/cr_elements/cr_action_menu:cr_action_menu.m",
-    "//ui/webui/resources/js:load_time_data.m",
-    "//ui/webui/resources/js/cr/ui:focus_without_ink.m",
-  ]
-  externs_list = [ "$externs_path/developer_private.js" ]
-}
-
-js_library("drop_overlay") {
-  deps = [
-    ":drag_and_drop_handler",
-    "//ui/webui/resources/js/cr/ui:drag_wrapper",
-  ]
-}
-
-js_library("drag_and_drop_handler") {
-  deps = [
-    ":service",
-    "//ui/webui/resources/js/cr/ui:drag_wrapper",
-  ]
-}
-
-js_library("error_page") {
-  deps = [
-    ":code_section",
-    ":item_util",
-    ":navigation_helper",
-    "//ui/webui/resources/cr_elements:cr_container_shadow_behavior.m",
-    "//ui/webui/resources/js/cr/ui:focus_outline_manager.m",
-    "//ui/webui/resources/js/cr/ui:focus_without_ink.m",
-  ]
-  externs_list = [
-    "$externs_path/developer_private.js",
-    "$externs_path/metrics_private.js",
-  ]
-}
-
-js_library("host_permissions_toggle_list") {
-  deps = [ ":item" ]
-  externs_list = [ "$externs_path/developer_private.js" ]
-}
-
-js_library("install_warnings_dialog") {
-  deps = [ "//ui/webui/resources/cr_elements/cr_dialog:cr_dialog.m" ]
-  externs_list = [ "$externs_path/developer_private.js" ]
-}
-
-js_library("item") {
-  deps = [
-    ":item_behavior",
-    ":item_util",
-    ":navigation_helper",
-    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
-    "//ui/webui/resources/cr_elements/cr_toast:cr_toast_manager.m",
-    "//ui/webui/resources/js:assert.m",
-    "//ui/webui/resources/js:i18n_behavior.m",
-  ]
-  externs_list = [ "$externs_path/developer_private.js" ]
-}
-
-js_library("item_behavior") {
-  deps = [ "//ui/webui/resources/js:load_time_data.m" ]
-  externs_list = [ "$externs_path/developer_private.js" ]
-}
-
-js_library("item_list") {
-  deps = [
-    ":checkup",
-    ":item",
-    "//third_party/polymer/v3_0/components-chromium/iron-a11y-announcer:iron-a11y-announcer",
-    "//ui/webui/resources/cr_elements:cr_container_shadow_behavior.m",
-    "//ui/webui/resources/js:i18n_behavior.m",
-    "//ui/webui/resources/js:load_time_data.m",
-  ]
-  externs_list = [
-    "$externs_path/developer_private.js",
-    "$externs_path/metrics_private.js",
-  ]
-}
-
-js_library("item_util") {
-  deps = [
-    ":navigation_helper",
-    "//ui/webui/resources/js:assert.m",
-    "//ui/webui/resources/js:load_time_data.m",
-  ]
-  externs_list = [ "$externs_path/developer_private.js" ]
-}
-
-js_library("keyboard_shortcut_delegate") {
-  deps = []
-  externs_list = [ "$externs_path/developer_private.js" ]
-}
-
-js_library("keyboard_shortcuts") {
-  deps = [
-    ":keyboard_shortcut_delegate",
-    "//ui/webui/resources/cr_elements:cr_container_shadow_behavior.m",
-  ]
-  externs_list = [
-    "$externs_path/developer_private.js",
-    "$externs_path/metrics_private.js",
-  ]
-}
-
-js_library("kiosk_browser_proxy") {
-  deps = [ "//ui/webui/resources/js:cr.m" ]
-}
-
-if (is_chromeos_ash) {
-  js_library("kiosk_dialog") {
-    deps = [
-      ":kiosk_browser_proxy",
-      "//ui/webui/resources/js:web_ui_listener_behavior.m",
+    in_files += [
+      "kiosk_browser_proxy.js",
+      "kiosk_dialog.js",
     ]
+    definitions += [ "//tools/typescript/definitions/chrome_send.d.ts" ]
   }
-}
 
-js_library("load_error") {
   deps = [
-    "//ui/webui/resources/cr_elements/cr_dialog:cr_dialog.m",
-    "//ui/webui/resources/js:assert.m",
+    "//third_party/polymer/v3_0:library",
+    "//ui/webui/resources:library",
   ]
-  externs_list = [ "$externs_path/developer_private.js" ]
-}
-
-js_library("manager") {
-  deps = [
-    ":detail_view",
-    ":item",
-    ":item_list",
-    ":item_util",
-    ":keyboard_shortcuts",
-    ":kiosk_browser_proxy",
-    ":load_error",
-    ":navigation_helper",
-    ":service",
-    ":sidebar",
-    ":toolbar",
-    "activity_log:activity_log",
-    "//ui/webui/resources/cr_elements/cr_drawer:cr_drawer",
-    "//ui/webui/resources/cr_elements/cr_view_manager:cr_view_manager",
-    "//ui/webui/resources/js:assert.m",
-    "//ui/webui/resources/js:load_time_data.m",
+  extra_deps = [
+    ":preprocess",
+    ":preprocess_generated",
   ]
-  externs_list = [ "$externs_path/developer_private.js" ]
-}
-
-js_library("navigation_helper") {
-  deps = [
-    "//ui/webui/resources/js:assert.m",
-    "//ui/webui/resources/js:load_time_data.m",
-  ]
-  externs_list = [ "$externs_path/developer_private.js" ]
-}
-
-js_library("options_dialog") {
-  deps = [
-    ":navigation_helper",
-    "//ui/webui/resources/cr_elements/cr_dialog:cr_dialog.m",
-  ]
-  externs_list = [ "$externs_path/developer_private.js" ]
-}
-
-js_library("pack_dialog") {
-  externs_list = [ "$externs_path/developer_private.js" ]
-}
-
-js_library("pack_dialog_alert") {
-  deps = [
-    "//ui/webui/resources/cr_elements/cr_dialog:cr_dialog.m",
-    "//ui/webui/resources/js:load_time_data.m",
-  ]
-  externs_list = [ "$externs_path/developer_private.js" ]
-}
-
-js_library("runtime_host_permissions") {
-  deps = [
-    ":item",
-    ":runtime_hosts_dialog",
-    "//ui/webui/resources/cr_elements/cr_action_menu:cr_action_menu.m",
-    "//ui/webui/resources/cr_elements/cr_radio_button:cr_radio_button.m",
-    "//ui/webui/resources/cr_elements/cr_radio_group:cr_radio_group.m",
-    "//ui/webui/resources/js/cr/ui:focus_without_ink.m",
-  ]
-  externs_list = [ "$externs_path/developer_private.js" ]
-}
-
-js_library("runtime_hosts_dialog") {
-  deps = [
-    "//ui/webui/resources/cr_elements/cr_dialog:cr_dialog.m",
-    "//ui/webui/resources/cr_elements/cr_input:cr_input.m",
-    "//ui/webui/resources/js:load_time_data.m",
-  ]
-}
-
-js_library("service") {
-  deps = [
-    ":error_page",
-    ":item",
-    ":keyboard_shortcut_delegate",
-    ":load_error",
-    ":navigation_helper",
-    ":pack_dialog",
-    ":toolbar",
-    "activity_log:activity_log",
-    "activity_log:activity_log_history",
-    "activity_log:activity_log_stream",
-    "//ui/webui/resources/js:assert.m",
-  ]
-  externs_list = chrome_extension_public_externs + [
-                   "$externs_path/activity_log_private.js",
-                   "$externs_path/developer_private.js",
-                   "$externs_path/management.js",
-                   "$externs_path/metrics_private.js",
-                 ]
-}
-
-js_library("shortcut_input") {
-  deps = [
-    ":keyboard_shortcut_delegate",
-    ":shortcut_util",
-    "//third_party/polymer/v3_0/components-chromium/iron-a11y-announcer:iron-a11y-announcer",
-    "//ui/webui/resources/js:assert.m",
-    "//ui/webui/resources/js:i18n_behavior.m",
-  ]
-  externs_list = [ "$externs_path/developer_private.js" ]
-}
-
-js_library("shortcut_util") {
-  deps = [
-    "//ui/webui/resources/js:assert.m",
-    "//ui/webui/resources/js:cr.m",
-  ]
-}
-
-js_library("sidebar") {
-  deps = [
-    ":navigation_helper",
-    "//ui/webui/resources/js:assert.m",
-  ]
-  externs_list = [ "$externs_path/metrics_private.js" ]
-}
-
-js_library("toggle_row") {
-}
-
-js_library("toolbar") {
-  deps = [
-    "//third_party/polymer/v3_0/components-chromium/iron-a11y-announcer:iron-a11y-announcer",
-    "//ui/webui/resources/cr_elements/cr_toast:cr_toast_manager.m",
-    "//ui/webui/resources/js:i18n_behavior.m",
-    "//ui/webui/resources/js:util.m",
-  ]
-  externs_list = [ "$externs_path/metrics_private.js" ]
 }
diff --git a/chrome/browser/resources/extensions/activity_log/BUILD.gn b/chrome/browser/resources/extensions/activity_log/BUILD.gn
index 4acaafb..b5ae50b 100644
--- a/chrome/browser/resources/extensions/activity_log/BUILD.gn
+++ b/chrome/browser/resources/extensions/activity_log/BUILD.gn
@@ -2,7 +2,6 @@
 #Use of this source code is governed by a BSD - style license that can be
 #found in the LICENSE file.
 
-import("//third_party/closure_compiler/compile_js.gni")
 import("//tools/polymer/html_to_js.gni")
 
 html_to_js("web_components") {
@@ -14,57 +13,3 @@
     "activity_log_stream_item.js",
   ]
 }
-
-js_type_check("closure_compile_module") {
-  is_polymer3 = true
-  deps = [
-    ":activity_log",
-    ":activity_log_history",
-    ":activity_log_history_item",
-    ":activity_log_stream",
-    ":activity_log_stream_item",
-  ]
-}
-
-js_library("activity_log") {
-  deps = [
-    ":activity_log_history",
-    ":activity_log_stream",
-    "..:navigation_helper",
-    "//ui/webui/resources/cr_elements:cr_container_shadow_behavior.m",
-    "//ui/webui/resources/js:i18n_behavior.m",
-    "//ui/webui/resources/js/cr/ui:focus_without_ink.m",
-  ]
-  externs_list = [
-    "$externs_path/activity_log_private.js",
-    "$externs_path/developer_private.js",
-  ]
-}
-
-js_library("activity_log_history") {
-  deps = [
-    ":activity_log_history_item",
-    "//ui/webui/resources/cr_elements/cr_action_menu:cr_action_menu.m",
-    "//ui/webui/resources/cr_elements/cr_search_field:cr_search_field",
-  ]
-  externs_list = [ "$externs_path/activity_log_private.js" ]
-}
-
-js_library("activity_log_history_item") {
-  deps = []
-  externs_list = [ "$externs_path/activity_log_private.js" ]
-}
-
-js_library("activity_log_stream") {
-  deps = [
-    ":activity_log_stream_item",
-    "//third_party/polymer/v3_0/components-chromium/iron-list:iron-list",
-    "//ui/webui/resources/cr_elements/cr_search_field:cr_search_field",
-  ]
-  externs_list = [ "$externs_path/activity_log_private.js" ]
-}
-
-js_library("activity_log_stream_item") {
-  deps = []
-  externs_list = [ "$externs_path/activity_log_private.js" ]
-}
diff --git a/chrome/browser/resources/extensions/activity_log/activity_log.js b/chrome/browser/resources/extensions/activity_log/activity_log.js
index 95c002b5..c2f2187 100644
--- a/chrome/browser/resources/extensions/activity_log/activity_log.js
+++ b/chrome/browser/resources/extensions/activity_log/activity_log.js
@@ -9,6 +9,7 @@
 import 'chrome://resources/cr_elements/shared_vars_css.m.js';
 import 'chrome://resources/polymer/v3_0/iron-pages/iron-pages.js';
 import './activity_log_stream.js';
+import './activity_log_history.js';
 import '../strings.m.js';
 import '../shared_style.js';
 import '../shared_vars.js';
diff --git a/chrome/browser/resources/extensions/activity_log/activity_log_history.js b/chrome/browser/resources/extensions/activity_log/activity_log_history.js
index a4c1469..c4e11fe 100644
--- a/chrome/browser/resources/extensions/activity_log/activity_log_history.js
+++ b/chrome/browser/resources/extensions/activity_log/activity_log_history.js
@@ -7,6 +7,7 @@
 import 'chrome://resources/cr_elements/cr_icon_button/cr_icon_button.m.js';
 import 'chrome://resources/cr_elements/cr_search_field/cr_search_field.js';
 import '../shared_style.js';
+import './activity_log_history_item.js';
 
 import {assert} from 'chrome://resources/js/assert.m.js';
 import {PromiseResolver} from 'chrome://resources/js/promise_resolver.m.js';
diff --git a/chrome/browser/resources/extensions/activity_log/activity_log_stream.js b/chrome/browser/resources/extensions/activity_log/activity_log_stream.js
index d0e7dba..0acbff2 100644
--- a/chrome/browser/resources/extensions/activity_log/activity_log_stream.js
+++ b/chrome/browser/resources/extensions/activity_log/activity_log_stream.js
@@ -6,6 +6,7 @@
 import 'chrome://resources/cr_elements/cr_search_field/cr_search_field.js';
 import 'chrome://resources/polymer/v3_0/iron-list/iron-list.js';
 import '../shared_style.js';
+import './activity_log_stream_item.js';
 
 import {assert} from 'chrome://resources/js/assert.m.js';
 import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
diff --git a/chrome/browser/resources/extensions/item_list.js b/chrome/browser/resources/extensions/item_list.js
index 7e990d4..5d6a4644 100644
--- a/chrome/browser/resources/extensions/item_list.js
+++ b/chrome/browser/resources/extensions/item_list.js
@@ -3,8 +3,9 @@
 // found in the LICENSE file.
 
 import 'chrome://resources/cr_components/managed_footnote/managed_footnote.js';
-import './shared_style.js';
 import './checkup.js';
+import './item.js';
+import './shared_style.js';
 
 import {CrContainerShadowBehavior} from 'chrome://resources/cr_elements/cr_container_shadow_behavior.m.js';
 import {I18nBehavior, I18nBehaviorInterface} from 'chrome://resources/js/i18n_behavior.m.js';
diff --git a/chrome/browser/resources/extensions/manager.js b/chrome/browser/resources/extensions/manager.js
index 3ef910e4..969b3e93 100644
--- a/chrome/browser/resources/extensions/manager.js
+++ b/chrome/browser/resources/extensions/manager.js
@@ -9,6 +9,7 @@
 import 'chrome://resources/cr_elements/cr_view_manager/cr_view_manager.js';
 import 'chrome://resources/cr_elements/hidden_style_css.m.js';
 import 'chrome://resources/cr_elements/shared_vars_css.m.js';
+import './activity_log/activity_log.js';
 import './detail_view.js';
 import './drop_overlay.js';
 import './error_page.js';
diff --git a/chrome/browser/resources/extensions/tsconfig_base.json b/chrome/browser/resources/extensions/tsconfig_base.json
new file mode 100644
index 0000000..3e71f763
--- /dev/null
+++ b/chrome/browser/resources/extensions/tsconfig_base.json
@@ -0,0 +1,9 @@
+{
+  "extends": "../../../../tools/typescript/tsconfig_base.json",
+  "compilerOptions": {
+    "allowJs": true,
+    "noUncheckedIndexedAccess": false,
+    "noUnusedLocals": false,
+    "strictPropertyInitialization": false
+  }
+}
diff --git a/chrome/browser/resources/settings/chromeos/BUILD.gn b/chrome/browser/resources/settings/chromeos/BUILD.gn
index 499d5d8c9..415f02bd 100644
--- a/chrome/browser/resources/settings/chromeos/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/BUILD.gn
@@ -228,6 +228,8 @@
     "page_visibility.js",
     "people_page/account_manager_browser_proxy.js",
     "people_page/profile_info_browser_proxy.js",
+    "chromeos/ambient_mode_page/ambient_mode_browser_proxy.js",
+    "chromeos/ambient_mode_page/constants.js",
     "people_page/sync_browser_proxy.js",
     "prefs/prefs_behavior.js",
     "prefs/prefs.js",
@@ -250,16 +252,14 @@
   in_files = [
     "a11y_page/captions_subpage.js",
     "a11y_page/live_caption_section.js",
-    "chromeos/ambient_mode_page/album_item.m.js",
-    "chromeos/ambient_mode_page/album_list.m.js",
-    "chromeos/ambient_mode_page/ambient_mode_browser_proxy.m.js",
-    "chromeos/ambient_mode_page/ambient_mode_page.m.js",
-    "chromeos/ambient_mode_page/ambient_mode_photos_page.m.js",
-    "chromeos/ambient_mode_page/text_with_tooltip.m.js",
-    "chromeos/ambient_mode_page/art_album_dialog.m.js",
-    "chromeos/ambient_mode_page/constants.m.js",
-    "chromeos/ambient_mode_page/topic_source_item.m.js",
-    "chromeos/ambient_mode_page/topic_source_list.m.js",
+    "chromeos/ambient_mode_page/album_item.js",
+    "chromeos/ambient_mode_page/album_list.js",
+    "chromeos/ambient_mode_page/ambient_mode_page.js",
+    "chromeos/ambient_mode_page/ambient_mode_photos_page.js",
+    "chromeos/ambient_mode_page/text_with_tooltip.js",
+    "chromeos/ambient_mode_page/art_album_dialog.js",
+    "chromeos/ambient_mode_page/topic_source_item.js",
+    "chromeos/ambient_mode_page/topic_source_list.js",
     "chromeos/bluetooth_page/bluetooth_device_list_item.js",
     "chromeos/bluetooth_page/bluetooth_page.js",
     "chromeos/bluetooth_page/bluetooth_subpage.js",
@@ -654,7 +654,7 @@
 group("polymer3_elements") {
   public_deps = [
     # Sub-folder targets
-    "ambient_mode_page:polymer3_elements",
+    "ambient_mode_page:web_components",
     "bluetooth_page:web_components",
     "crostini_page:polymer3_elements",
     "date_time_page:polymer3_elements",
diff --git a/chrome/browser/resources/settings/chromeos/ambient_mode_page/BUILD.gn b/chrome/browser/resources/settings/chromeos/ambient_mode_page/BUILD.gn
index c62e2e99..0d5a1467 100644
--- a/chrome/browser/resources/settings/chromeos/ambient_mode_page/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/ambient_mode_page/BUILD.gn
@@ -3,43 +3,37 @@
 # found in the LICENSE file.
 
 import("//third_party/closure_compiler/compile_js.gni")
-import("//tools/polymer/polymer.gni")
-import("//ui/webui/resources/tools/js_modulizer.gni")
+import("//tools/polymer/html_to_js.gni")
 import("../os_settings.gni")
 
 js_type_check("closure_compile_module") {
   closure_flags = os_settings_closure_flags
   is_polymer3 = true
   deps = [
-    ":album_item.m",
-    ":album_list.m",
-    ":ambient_mode_browser_proxy.m",
-    ":ambient_mode_page.m",
-    ":ambient_mode_photos_page.m",
-    ":art_album_dialog.m",
-    ":text_with_tooltip.m",
-    ":topic_source_item.m",
-    ":topic_source_list.m",
+    ":album_item",
+    ":album_list",
+    ":ambient_mode_browser_proxy",
+    ":ambient_mode_page",
+    ":ambient_mode_photos_page",
+    ":art_album_dialog",
+    ":text_with_tooltip",
+    ":topic_source_item",
+    ":topic_source_list",
   ]
 }
 
-js_library("constants.m") {
-  sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/ambient_mode_page/constants.m.js" ]
-  extra_deps = [ ":modulize" ]
+js_library("constants") {
 }
 
-js_library("ambient_mode_browser_proxy.m") {
-  sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/ambient_mode_page/ambient_mode_browser_proxy.m.js" ]
-  deps = [ ":constants.m" ]
+js_library("ambient_mode_browser_proxy") {
+  deps = [ ":constants" ]
   externs_list = [ "$externs_path/chrome_send.js" ]
-  extra_deps = [ ":modulize" ]
 }
 
-js_library("ambient_mode_page.m") {
-  sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/ambient_mode_page/ambient_mode_page.m.js" ]
+js_library("ambient_mode_page") {
   deps = [
-    ":ambient_mode_browser_proxy.m",
-    ":constants.m",
+    ":ambient_mode_browser_proxy",
+    ":constants",
     "..:deep_linking_behavior.m",
     "..:os_route.m",
     "../..:router",
@@ -48,14 +42,12 @@
     "//ui/webui/resources/js:i18n_behavior.m",
     "//ui/webui/resources/js:web_ui_listener_behavior.m",
   ]
-  extra_deps = [ ":ambient_mode_page_module" ]
 }
 
-js_library("ambient_mode_photos_page.m") {
-  sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/ambient_mode_page/ambient_mode_photos_page.m.js" ]
+js_library("ambient_mode_photos_page") {
   deps = [
-    ":ambient_mode_browser_proxy.m",
-    ":constants.m",
+    ":ambient_mode_browser_proxy",
+    ":constants",
     "..:os_route.m",
     "../..:router",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
@@ -64,160 +56,62 @@
     "//ui/webui/resources/js:load_time_data.m",
     "//ui/webui/resources/js:web_ui_listener_behavior.m",
   ]
-  extra_deps = [ ":ambient_mode_photos_page_module" ]
 }
 
-js_library("text_with_tooltip.m") {
-  sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/ambient_mode_page/text_with_tooltip.m.js" ]
+js_library("text_with_tooltip") {
   deps = [
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
   ]
-  extra_deps = [ ":text_with_tooltip_module" ]
 }
 
-js_library("topic_source_item.m") {
-  sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/ambient_mode_page/topic_source_item.m.js" ]
+js_library("topic_source_item") {
   deps = [
-    ":constants.m",
+    ":constants",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
     "//ui/webui/resources/js:i18n_behavior.m",
   ]
-  extra_deps = [ ":topic_source_item_module" ]
 }
 
-js_library("topic_source_list.m") {
-  sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/ambient_mode_page/topic_source_list.m.js" ]
+js_library("topic_source_list") {
   deps = [
-    ":constants.m",
+    ":constants",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
   ]
-  extra_deps = [ ":topic_source_list_module" ]
 }
 
-js_library("album_item.m") {
-  sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/ambient_mode_page/album_item.m.js" ]
+js_library("album_item") {
   deps = [
-    ":constants.m",
+    ":constants",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
     "//ui/webui/resources/js:i18n_behavior.m",
   ]
-  extra_deps = [ ":album_item_module" ]
 }
 
-js_library("album_list.m") {
-  sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/ambient_mode_page/album_list.m.js" ]
+js_library("album_list") {
   deps = [
-    ":constants.m",
+    ":constants",
     "..:os_route.m",
     "../..:global_scroll_target_behavior",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
   ]
-  extra_deps = [ ":album_list_module" ]
 }
 
-js_library("art_album_dialog.m") {
-  sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/ambient_mode_page/art_album_dialog.m.js" ]
+js_library("art_album_dialog") {
   deps = [
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
     "//ui/webui/resources/js:i18n_behavior.m",
   ]
-  extra_deps = [ ":art_album_dialog_module" ]
 }
 
-group("polymer3_elements") {
-  public_deps = [
-    ":album_item_module",
-    ":album_list_module",
-    ":ambient_mode_page_module",
-    ":ambient_mode_photos_page_module",
-    ":art_album_dialog_module",
-    ":modulize",
-    ":text_with_tooltip_module",
-    ":topic_source_item_module",
-    ":topic_source_list_module",
+html_to_js("web_components") {
+  js_files = [
+    "album_item.js",
+    "album_list.js",
+    "ambient_mode_page.js",
+    "ambient_mode_photos_page.js",
+    "art_album_dialog.js",
+    "text_with_tooltip.js",
+    "topic_source_item.js",
+    "topic_source_list.js",
   ]
 }
-
-polymer_modulizer("ambient_mode_page") {
-  js_file = "ambient_mode_page.js"
-  html_file = "ambient_mode_page.html"
-  html_type = "dom-module"
-  migrated_imports = os_settings_migrated_imports
-  namespace_rewrites = os_settings_namespace_rewrites
-  auto_imports = os_settings_auto_imports
-}
-
-polymer_modulizer("ambient_mode_photos_page") {
-  js_file = "ambient_mode_photos_page.js"
-  html_file = "ambient_mode_photos_page.html"
-  html_type = "dom-module"
-  migrated_imports = os_settings_migrated_imports
-  namespace_rewrites = os_settings_namespace_rewrites
-  auto_imports =
-      os_settings_auto_imports +
-      [ "ui/webui/resources/html/assert.html|assert,assertNotReached" ]
-}
-
-polymer_modulizer("text_with_tooltip") {
-  js_file = "text_with_tooltip.js"
-  html_file = "text_with_tooltip.html"
-  html_type = "dom-module"
-  migrated_imports = os_settings_migrated_imports
-  namespace_rewrites = os_settings_namespace_rewrites
-  auto_imports =
-      os_settings_auto_imports +
-      [ "ui/webui/resources/html/assert.html|assert,assertNotReached" ]
-}
-
-polymer_modulizer("topic_source_item") {
-  js_file = "topic_source_item.js"
-  html_file = "topic_source_item.html"
-  html_type = "dom-module"
-  migrated_imports = os_settings_migrated_imports
-  namespace_rewrites = os_settings_namespace_rewrites
-  auto_imports = os_settings_auto_imports
-}
-
-polymer_modulizer("topic_source_list") {
-  js_file = "topic_source_list.js"
-  html_file = "topic_source_list.html"
-  html_type = "dom-module"
-  migrated_imports = os_settings_migrated_imports
-  namespace_rewrites = os_settings_namespace_rewrites
-  auto_imports = os_settings_auto_imports
-}
-
-polymer_modulizer("album_item") {
-  js_file = "album_item.js"
-  html_file = "album_item.html"
-  html_type = "dom-module"
-  migrated_imports = os_settings_migrated_imports
-  namespace_rewrites = os_settings_namespace_rewrites
-  auto_imports = os_settings_auto_imports
-}
-
-polymer_modulizer("album_list") {
-  js_file = "album_list.js"
-  html_file = "album_list.html"
-  html_type = "dom-module"
-  migrated_imports = os_settings_migrated_imports
-  namespace_rewrites = os_settings_namespace_rewrites
-  auto_imports = os_settings_auto_imports
-}
-
-polymer_modulizer("art_album_dialog") {
-  js_file = "art_album_dialog.js"
-  html_file = "art_album_dialog.html"
-  html_type = "dom-module"
-  migrated_imports = os_settings_migrated_imports
-  namespace_rewrites = os_settings_namespace_rewrites
-  auto_imports = os_settings_auto_imports
-}
-
-js_modulizer("modulize") {
-  input_files = [
-    "ambient_mode_browser_proxy.js",
-    "constants.js",
-  ]
-  namespace_rewrites = os_settings_namespace_rewrites
-}
diff --git a/chrome/browser/resources/settings/chromeos/ambient_mode_page/album_item.html b/chrome/browser/resources/settings/chromeos/ambient_mode_page/album_item.html
index 5f231686..75a9bcc 100644
--- a/chrome/browser/resources/settings/chromeos/ambient_mode_page/album_item.html
+++ b/chrome/browser/resources/settings/chromeos/ambient_mode_page/album_item.html
@@ -1,200 +1,185 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
+<style include="settings-shared cr-shared-style iron-flex">
+  :host(:not([disabled])) {
+    cursor: default;
+    display: block;
+    outline: none;
+  }
 
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/iron-flex-layout-classes.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_style_css.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="constants.html">
-<link rel="import" href="text_with_tooltip.html">
-<link rel="import" href="../../settings_shared_css.html">
+  #albumContainer {
+    /* For a grid iron-list the list template item must have both a fixed
+       width and height. */
+    height: 236px;
+    line-height: 20px;
+  }
 
-<dom-module id="album-item">
-  <template>
-    <style include="settings-shared cr-shared-style iron-flex">
-      :host(:not([disabled])) {
-        cursor: default;
-        display: block;
-        outline: none;
-      }
+  #albumContainer.personal-album {
+    margin: 8px 0;
+    width: 184px;
+  }
 
-      #albumContainer {
-        /* For a grid iron-list the list template item must have both a fixed
-           width and height. */
-        height: 236px;
-        line-height: 20px;
-      }
+  #albumContainer.art-album {
+    margin: 8px auto;
+    width: 280px;
+  }
 
-      #albumContainer.personal-album {
-        margin: 8px 0;
-        width: 184px;
-      }
+  #imageContainer {
+    background-color: rgba(var(--google-blue-50-rgb), .8);
+    border-radius: 8px;
+    display: block;
+    margin: 0 12px;
+    position: relative;
+  }
 
-      #albumContainer.art-album {
-        margin: 8px auto;
-        width: 280px;
-      }
+  #imageContainer.personal-album {
+    height: 160px;
+    width: 160px;
+  }
 
-      #imageContainer {
-        background-color: rgba(var(--google-blue-50-rgb), .8);
-        border-radius: 8px;
-        display: block;
-        margin: 0 12px;
-        position: relative;
-      }
+  #imageContainer.art-album {
+    height: 160px;
+    width: 256px;
+  }
 
-      #imageContainer.personal-album {
-        height: 160px;
-        width: 160px;
-      }
+  #albumInfo {
+    margin: 16px 12px 0 12px;
+  }
 
-      #imageContainer.art-album {
-        height: 160px;
-        width: 256px;
-      }
+  #image {
+    border-radius: 8px;
+    display: block;
+    height: 160px;
+    object-fit: cover;
+    position: absolute;
+    transform: scale(1.0);
+    transition: transform 240ms;
+  }
 
-      #albumInfo {
-        margin: 16px 12px 0 12px;
-      }
+  #image.personal-album {
+    width: 160px;
+  }
 
-      #image {
-        border-radius: 8px;
-        display: block;
-        height: 160px;
-        object-fit: cover;
-        position: absolute;
-        transform: scale(1.0);
-        transition: transform 240ms;
-      }
+  #image.art-album {
+    width: 256px;
+  }
 
-      #image.personal-album {
-        width: 160px;
-      }
+  #rhImages {
+    border-radius: 8px;
+    display: block;
+    height: 160px;
+    position: absolute;
+    transform: scale(1.0);
+    transition: transform 240ms;
+    width: 160px;
+  }
 
-      #image.art-album {
-        width: 256px;
-      }
+  .image-rh {
+    border-radius: 8px;
+    display: block;
+    height: 78px;
+    position: absolute;
+    width: 78px;
+  }
 
-      #rhImages {
-        border-radius: 8px;
-        display: block;
-        height: 160px;
-        position: absolute;
-        transform: scale(1.0);
-        transition: transform 240ms;
-        width: 160px;
-      }
+  .top-left {
+    left: 0;
+    top: 0;
+  }
 
-      .image-rh {
-        border-radius: 8px;
-        display: block;
-        height: 78px;
-        position: absolute;
-        width: 78px;
-      }
+  .top-right {
+    left: 82px;
+    top: 0;
+  }
 
-      .top-left {
-        left: 0;
-        top: 0;
-      }
+  .bottom-left {
+    left: 0;
+    top: 82px;
+  }
 
-      .top-right {
-        left: 82px;
-        top: 0;
-      }
+  .bottom-right {
+    left: 82px;
+    top: 82px;
+  }
 
-      .bottom-left {
-        left: 0;
-        top: 82px;
-      }
+  .check {
+    display: block;
+    position: absolute;
+    z-index: 1;
+  }
 
-      .bottom-right {
-        left: 82px;
-        top: 82px;
-      }
+  .check.personal-album {
+    left: 134px;
+    top: 6px;
+  }
 
-      .check {
-        display: block;
-        position: absolute;
-        z-index: 1;
-      }
+  .check.art-album {
+    left: 230px;
+    top: 0;
+  }
 
-      .check.personal-album {
-        left: 134px;
-        top: 6px;
-      }
+  :host([checked]) #image {
+    box-shadow: 0 4px 8px rgba(32, 33, 36, 0.17);
+    transition: transform 240ms;
+  }
 
-      .check.art-album {
-        left: 230px;
-        top: 0;
-      }
+  :host([checked]) #image.personal-album {
+    transform: scale(0.8);
+  }
 
-      :host([checked]) #image {
-        box-shadow: 0 4px 8px rgba(32, 33, 36, 0.17);
-        transition: transform 240ms;
-      }
+  :host([checked]) #image.art-album {
+    transform: scale(0.875);
+  }
 
-      :host([checked]) #image.personal-album {
-        transform: scale(0.8);
-      }
-
-      :host([checked]) #image.art-album {
-        transform: scale(0.875);
-      }
-
-      :host([checked]) #rhImages {
-        transform: scale(0.8);
-      }
-    </style>
-    <div id="albumContainer" class$="[[computeClass_(topicSource)]]">
-      <div id="imageContainer" class$="[[computeClass_(topicSource)]]"
-          aria-hidden="true">
-        <!-- Only shows the images and icon when the URLs are successfully
-             fetched -->
-        <template is="dom-if" if="[[album.recentHighlightsUrls]]">
-          <div id="rhImages" actionable on-click="onImageClick_">
-            <img class="image-rh top-left"
-                src="[[album.recentHighlightsUrls.0]]"
-                hidden="[[!album.recentHighlightsUrls.0]]">
-            </img>
-            <img class="image-rh top-right"
-                src="[[album.recentHighlightsUrls.1]]"
-                hidden="[[!album.recentHighlightsUrls.1]]">
-            </img>
-            <img class="image-rh bottom-left"
-                src="[[album.recentHighlightsUrls.2]]"
-                hidden="[[!album.recentHighlightsUrls.2]]">
-            </img>
-            <img class="image-rh bottom-right"
-                src="[[album.recentHighlightsUrls.3]]"
-                hidden="[[!album.recentHighlightsUrls.3]]">
-            </img>
-          </div>
-          <iron-icon icon="os-settings:ic-checked-filled"
-              class$="check [[computeClass_(topicSource)]]"
-              hidden="[[!album.checked]]">
-          </iron-icon>
-        </template>
-        <template is="dom-if" if="[[album.url]]">
-          <img id="image" class$="[[computeClass_(topicSource)]]" actionable
-              src="[[album.url]]" on-click="onImageClick_">
-          </img>
-          <iron-icon icon="os-settings:ic-checked-filled"
-              class$="check [[computeClass_(topicSource)]]"
-              hidden="[[!album.checked]]">
-          </iron-icon>
-        </template>
+  :host([checked]) #rhImages {
+    transform: scale(0.8);
+  }
+</style>
+<div id="albumContainer" class$="[[computeClass_(topicSource)]]">
+  <div id="imageContainer" class$="[[computeClass_(topicSource)]]"
+      aria-hidden="true">
+    <!-- Only shows the images and icon when the URLs are successfully
+         fetched -->
+    <template is="dom-if" if="[[album.recentHighlightsUrls]]">
+      <div id="rhImages" actionable on-click="onImageClick_">
+        <img class="image-rh top-left"
+            src="[[album.recentHighlightsUrls.0]]"
+            hidden="[[!album.recentHighlightsUrls.0]]">
+        </img>
+        <img class="image-rh top-right"
+            src="[[album.recentHighlightsUrls.1]]"
+            hidden="[[!album.recentHighlightsUrls.1]]">
+        </img>
+        <img class="image-rh bottom-left"
+            src="[[album.recentHighlightsUrls.2]]"
+            hidden="[[!album.recentHighlightsUrls.2]]">
+        </img>
+        <img class="image-rh bottom-right"
+            src="[[album.recentHighlightsUrls.3]]"
+            hidden="[[!album.recentHighlightsUrls.3]]">
+        </img>
       </div>
+      <iron-icon icon="os-settings:ic-checked-filled"
+          class$="check [[computeClass_(topicSource)]]"
+          hidden="[[!album.checked]]">
+      </iron-icon>
+    </template>
+    <template is="dom-if" if="[[album.url]]">
+      <img id="image" class$="[[computeClass_(topicSource)]]" actionable
+          src="[[album.url]]" on-click="onImageClick_">
+      </img>
+      <iron-icon icon="os-settings:ic-checked-filled"
+          class$="check [[computeClass_(topicSource)]]"
+          hidden="[[!album.checked]]">
+      </iron-icon>
+    </template>
+  </div>
 
-      <div id="albumInfo" class$="[[computeClass_(topicSource)]]"
-          aria-hidden="true">
-        <text-with-tooltip id="albumTitle" text="[[album.title]]"
-          tooltip-is-visible="{{titleTooltipIsVisible}}"></text-with-tooltip>
-        <text-with-tooltip id="albumDescription"
-          text="[[album.description]]" line-clamp="2"
-          tooltip-is-visible="{{descriptionTooltipIsVisible}}"
-          text-style="cr-secondary-text"></text-with-tooltip>
-      </div>
-    </div>
-  </template>
-  <script src="album_item.js"></script>
-</dom-module>
+  <div id="albumInfo" class$="[[computeClass_(topicSource)]]"
+      aria-hidden="true">
+    <text-with-tooltip id="albumTitle" text="[[album.title]]"
+      tooltip-is-visible="{{titleTooltipIsVisible}}"></text-with-tooltip>
+    <text-with-tooltip id="albumDescription"
+      text="[[album.description]]" line-clamp="2"
+      tooltip-is-visible="{{descriptionTooltipIsVisible}}"
+      text-style="cr-secondary-text"></text-with-tooltip>
+  </div>
+</div>
diff --git a/chrome/browser/resources/settings/chromeos/ambient_mode_page/album_item.js b/chrome/browser/resources/settings/chromeos/ambient_mode_page/album_item.js
index 5e5b774a..5d4522c 100644
--- a/chrome/browser/resources/settings/chromeos/ambient_mode_page/album_item.js
+++ b/chrome/browser/resources/settings/chromeos/ambient_mode_page/album_item.js
@@ -6,7 +6,19 @@
  * @fileoverview Polymer element for displaying photos preview in a list.
  */
 
+import '//resources/polymer/v3_0/iron-flex-layout/iron-flex-layout-classes.js';
+import '//resources/cr_elements/shared_style_css.m.js';
+import '//resources/cr_elements/shared_vars_css.m.js';
+import './text_with_tooltip.js';
+import '../../settings_shared_css.js';
+
+import {I18nBehavior} from '//resources/js/i18n_behavior.m.js';
+import {html, Polymer} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {AmbientModeAlbum, AmbientModeTopicSource} from './constants.js';
+
 Polymer({
+  _template: html`{__html_template__}`,
   is: 'album-item',
 
   behaviors: [I18nBehavior],
diff --git a/chrome/browser/resources/settings/chromeos/ambient_mode_page/album_list.html b/chrome/browser/resources/settings/chromeos/ambient_mode_page/album_list.html
index b0f45107..0016c47a 100644
--- a/chrome/browser/resources/settings/chromeos/ambient_mode_page/album_list.html
+++ b/chrome/browser/resources/settings/chromeos/ambient_mode_page/album_list.html
@@ -1,46 +1,28 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
+<style include="settings-shared cr-shared-style iron-flex">
+  :host {
+    display: block;
+  }
 
-<link rel="import" href="album_item.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_style_css.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/iron-flex-layout-classes.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-list/iron-list.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/paper-spinner/paper-spinner-lite.html">
-<link rel="import" href="constants.html">
-<link rel="import" href="../os_route.html">
-<link rel="import" href="../../global_scroll_target_behavior.html">
-<link rel="import" href="../../settings_shared_css.html">
+  paper-spinner-lite {
+    display: block;
+    height: 28px;
+    margin: 150px auto;
+    width: 28px;
+  }
 
-<dom-module id="album-list">
+  iron-list > :focus {
+    background-color: var(--cr-focused-item-color);
+  }
+</style>
+<paper-spinner-lite active="[[!albums]]" hidden="[[albums]]">
+</paper-spinner-lite>
+
+<iron-list scroll-target="[[subpageScrollTarget]]"
+  grid items="{{albums}}">
   <template>
-    <style include="settings-shared cr-shared-style iron-flex">
-      :host {
-        display: block;
-      }
-
-      paper-spinner-lite {
-        display: block;
-        height: 28px;
-        margin: 150px auto;
-        width: 28px;
-      }
-
-      iron-list > :focus {
-        background-color: var(--cr-focused-item-color);
-      }
-    </style>
-    <paper-spinner-lite active="[[!albums]]" hidden="[[albums]]">
-    </paper-spinner-lite>
-
-    <iron-list scroll-target="[[subpageScrollTarget]]"
-      grid items="{{albums}}">
-      <template>
-        <album-item album="{{item}}" checked="{{item.checked}}"
-            topic-source="[[topicSource]]" tabindex$="[[tabIndex]]"
-            role="checkbox">
-        </album-item>
-      </template>
-    </iron-list>
+    <album-item album="{{item}}" checked="{{item.checked}}"
+        topic-source="[[topicSource]]" tabindex$="[[tabIndex]]"
+        role="checkbox">
+    </album-item>
   </template>
-  <script src="album_list.js"></script>
-</dom-module>
+</iron-list>
diff --git a/chrome/browser/resources/settings/chromeos/ambient_mode_page/album_list.js b/chrome/browser/resources/settings/chromeos/ambient_mode_page/album_list.js
index e4e04d22..53966b70 100644
--- a/chrome/browser/resources/settings/chromeos/ambient_mode_page/album_list.js
+++ b/chrome/browser/resources/settings/chromeos/ambient_mode_page/album_list.js
@@ -9,11 +9,27 @@
 /**
  * Polymer class definition for 'album-list'.
  */
+import './album_item.js';
+import '//resources/cr_elements/shared_style_css.m.js';
+import '//resources/cr_elements/shared_vars_css.m.js';
+import '//resources/polymer/v3_0/iron-flex-layout/iron-flex-layout-classes.js';
+import '//resources/polymer/v3_0/iron-list/iron-list.js';
+import '//resources/polymer/v3_0/paper-spinner/paper-spinner-lite.js';
+import '../../settings_shared_css.js';
+
+import {html, Polymer} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {GlobalScrollTargetBehavior} from '../../global_scroll_target_behavior.js';
+import {routes} from '../os_route.m.js';
+
+import {AmbientModeAlbum, AmbientModeTopicSource} from './constants.js';
+
 Polymer({
+  _template: html`{__html_template__}`,
   is: 'album-list',
 
   behaviors: [
-    settings.GlobalScrollTargetBehavior,
+    GlobalScrollTargetBehavior,
   ],
 
   properties: {
@@ -38,7 +54,7 @@
      */
     subpageRoute: {
       type: Object,
-      value: settings.routes.AMBIENT_MODE_PHOTOS,
+      value: routes.AMBIENT_MODE_PHOTOS,
     },
   },
 
diff --git a/chrome/browser/resources/settings/chromeos/ambient_mode_page/ambient_mode_browser_proxy.html b/chrome/browser/resources/settings/chromeos/ambient_mode_page/ambient_mode_browser_proxy.html
deleted file mode 100644
index 07419df..0000000
--- a/chrome/browser/resources/settings/chromeos/ambient_mode_page/ambient_mode_browser_proxy.html
+++ /dev/null
@@ -1,2 +0,0 @@
-<link rel="import" href="chrome://resources/html/cr.html">
-<script src="ambient_mode_browser_proxy.js"></script>
diff --git a/chrome/browser/resources/settings/chromeos/ambient_mode_page/ambient_mode_browser_proxy.js b/chrome/browser/resources/settings/chromeos/ambient_mode_page/ambient_mode_browser_proxy.js
index cbbcf250..49433f58 100644
--- a/chrome/browser/resources/settings/chromeos/ambient_mode_page/ambient_mode_browser_proxy.js
+++ b/chrome/browser/resources/settings/chromeos/ambient_mode_page/ambient_mode_browser_proxy.js
@@ -2,77 +2,68 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-// clang-format off
-// #import {addSingletonGetter} from 'chrome://resources/js/cr.m.js';
-// #import {AmbientModeTopicSource, AmbientModeTemperatureUnit, AmbientModeSettings} from './constants.m.js';
-// clang-format on
+import {addSingletonGetter} from 'chrome://resources/js/cr.m.js';
+
+import {AmbientModeSettings, AmbientModeTemperatureUnit, AmbientModeTopicSource} from './constants.js';
 
 /**
  * @fileoverview A helper object used from the ambient mode section to interact
  * with the browser.
  */
 
-cr.define('settings', function() {
-  /** @interface */
-  /* #export */ class AmbientModeBrowserProxy {
-    /**
-     * Retrieves the AmbientModeTopicSource and AmbientModeTemperatureUnit from
-     * server. As a response, the C++ sends the 'topic-source-changed' and
-     * 'temperature-unit-changed' events.
-     */
-    requestSettings() {}
+/** @interface */
+export class AmbientModeBrowserProxy {
+  /**
+   * Retrieves the AmbientModeTopicSource and AmbientModeTemperatureUnit from
+   * server. As a response, the C++ sends the 'topic-source-changed' and
+   * 'temperature-unit-changed' events.
+   */
+  requestSettings() {}
 
-    /**
-     * Retrieves the albums from server. As a response, the C++ sends either the
-     * 'albums-changed' WebUIListener event.
-     * @param {!AmbientModeTopicSource} topicSource the topic source for which
-     *     the albums requested.
-     */
-    requestAlbums(topicSource) {}
+  /**
+   * Retrieves the albums from server. As a response, the C++ sends either the
+   * 'albums-changed' WebUIListener event.
+   * @param {!AmbientModeTopicSource} topicSource the topic source for which
+   *     the albums requested.
+   */
+  requestAlbums(topicSource) {}
 
-    /**
-     * Updates the selected temperature unit to server.
-     * @param {!AmbientModeTemperatureUnit} temperatureUnit
-     */
-    setSelectedTemperatureUnit(temperatureUnit) {}
+  /**
+   * Updates the selected temperature unit to server.
+   * @param {!AmbientModeTemperatureUnit} temperatureUnit
+   */
+  setSelectedTemperatureUnit(temperatureUnit) {}
 
-    /**
-     * Updates the selected albums of Google Photos or art categories to server.
-     * @param {!AmbientModeSettings} settings the selected albums or categeries.
-     */
-    setSelectedAlbums(settings) {}
+  /**
+   * Updates the selected albums of Google Photos or art categories to server.
+   * @param {!AmbientModeSettings} settings the selected albums or categeries.
+   */
+  setSelectedAlbums(settings) {}
+}
+
+/** @implements {AmbientModeBrowserProxy} */
+export class AmbientModeBrowserProxyImpl {
+  /** @override */
+  requestSettings() {
+    chrome.send('requestSettings');
   }
 
-  /** @implements {settings.AmbientModeBrowserProxy} */
-  /* #export */ class AmbientModeBrowserProxyImpl {
-    /** @override */
-    requestSettings() {
-      chrome.send('requestSettings');
-    }
-
-    /** @override */
-    requestAlbums(topicSource) {
-      chrome.send('requestAlbums', [topicSource]);
-    }
-
-    /** @override */
-    setSelectedTemperatureUnit(temperatureUnit) {
-      chrome.send('setSelectedTemperatureUnit', [temperatureUnit]);
-    }
-
-    /** @override */
-    setSelectedAlbums(settings) {
-      chrome.send('setSelectedAlbums', [settings]);
-    }
+  /** @override */
+  requestAlbums(topicSource) {
+    chrome.send('requestAlbums', [topicSource]);
   }
 
-  // The singleton instance_ is replaced with a test version of this wrapper
-  // during testing.
-  cr.addSingletonGetter(AmbientModeBrowserProxyImpl);
+  /** @override */
+  setSelectedTemperatureUnit(temperatureUnit) {
+    chrome.send('setSelectedTemperatureUnit', [temperatureUnit]);
+  }
 
-  // #cr_define_end
-  return {
-    AmbientModeBrowserProxy: AmbientModeBrowserProxy,
-    AmbientModeBrowserProxyImpl: AmbientModeBrowserProxyImpl,
-  };
-});
+  /** @override */
+  setSelectedAlbums(settings) {
+    chrome.send('setSelectedAlbums', [settings]);
+  }
+}
+
+// The singleton instance_ is replaced with a test version of this wrapper
+// during testing.
+addSingletonGetter(AmbientModeBrowserProxyImpl);
diff --git a/chrome/browser/resources/settings/chromeos/ambient_mode_page/ambient_mode_page.html b/chrome/browser/resources/settings/chromeos/ambient_mode_page/ambient_mode_page.html
index e4e6951a..a22b6c2 100644
--- a/chrome/browser/resources/settings/chromeos/ambient_mode_page/ambient_mode_page.html
+++ b/chrome/browser/resources/settings/chromeos/ambient_mode_page/ambient_mode_page.html
@@ -1,116 +1,91 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
+<style include="cr-shared-style settings-shared iron-flex">
+  #ambientModeEnable {
+    border-bottom: var(--cr-separator-line);
+    border-top: var(--cr-separator-line);
+  }
 
-<link rel="import" href="ambient_mode_browser_proxy.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_radio_button/cr_radio_button.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_style_css.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/paper-spinner/paper-spinner-lite.html">
-<link rel="import" href="constants.html">
-<link rel="import" href="topic_source_list.html">
-<link rel="import" href="../deep_linking_behavior.html">
-<link rel="import" href="../os_route.html">
-<link rel="import" href="../../router.html">
-<link rel="import" href="../../prefs/prefs.html">
-<link rel="import" href="../../prefs/prefs_behavior.html">
-<link rel="import" href="../../controls/settings_radio_group.html">
-<link rel="import" href="../../controls/settings_toggle_button.html">
-<link rel="import" href="../../settings_shared_css.html">
+  #pageDescription {
+    padding-inline-end: var(--cr-section-padding);
+    padding-inline-start: var(--cr-section-padding);
+    padding-top: 0;
+  }
 
-<dom-module id="settings-ambient-mode-page">
-  <template>
-    <style include="cr-shared-style settings-shared iron-flex">
-      #ambientModeEnable {
-        border-bottom: var(--cr-separator-line);
-        border-top: var(--cr-separator-line);
-      }
+  #topicSourceListDiv {
+    border-bottom: var(--cr-separator-line);
+  }
 
-      #pageDescription {
-        padding-inline-end: var(--cr-section-padding);
-        padding-inline-start: var(--cr-section-padding);
-        padding-top: 0;
-      }
+  /* Set padding on children instead of the container itself to ensure that
+   * separator lines can fill the entire width of the page. */
+  #topicSourceListDiv > *,
+  #weatherDiv > * {
+    /* Padded to the right to allow space for a ripple */
+    padding-inline-end: calc(var(--cr-section-padding) -
+        var(--cr-icon-ripple-padding));
+    padding-inline-start: var(--cr-section-padding);
+  }
 
-      #topicSourceListDiv {
-        border-bottom: var(--cr-separator-line);
-      }
+  #weatherTitle {
+    padding-bottom: 16px;
+    padding-top: 16px;
+  }
 
-      /* Set padding on children instead of the container itself to ensure that
-       * separator lines can fill the entire width of the page. */
-      #topicSourceListDiv > *,
-      #weatherDiv > * {
-        /* Padded to the right to allow space for a ripple */
-        padding-inline-end: calc(var(--cr-section-padding) -
-            var(--cr-icon-ripple-padding));
-        padding-inline-start: var(--cr-section-padding);
-      }
+  .list-item {
+    padding-inline-start: var(--cr-section-padding);
+  }
 
-      #weatherTitle {
-        padding-bottom: 16px;
-        padding-top: 16px;
-      }
+  paper-spinner-lite {
+    display: block;
+    height: 28px;
+    margin: 100px auto;
+    width: 28px;
+  }
+</style>
+<h2 id="pageDescription">
+  $i18n{ambientModePageDescription}
+</h2>
 
-      .list-item {
-        padding-inline-start: var(--cr-section-padding);
-      }
+<settings-toggle-button id="ambientModeEnable"
+    class="primary-toggle"
+    pref="{{prefs.settings.ambient_mode.enabled}}"
+    label="[[getAmbientModeOnOffLabel_(
+        prefs.settings.ambient_mode.enabled.value)]]"
+    deep-link-focus-id$="[[Setting.kAmbientModeOnOff]]">
+</settings-toggle-button>
 
-      paper-spinner-lite {
-        display: block;
-        height: 28px;
-        margin: 100px auto;
-        width: 28px;
-      }
-    </style>
-    <h2 id="pageDescription">
-      $i18n{ambientModePageDescription}
+<paper-spinner-lite active='[[!showSettings_]]' hidden='[[showSettings_]]'>
+</paper-spinner-lite>
+
+<template is="dom-if" if="[[showSettings_]]">
+  <div id="topicSourceListDiv" class="layout vertical flex"
+      aria-hidden="[[disableSettings_]]">
+    <topic-source-list topic-sources="[[topicSources_]]"
+        selected-topic-source="[[selectedTopicSource_]]"
+        has-google-photos-albums="[[hasGooglePhotosAlbums_]]"
+        disabled='[[disableSettings_]]'>
+    </topic-source-list>
+  </div>
+  <div id="weatherDiv" class="layout vertical flex"
+      aria-hidden="[[disableSettings_]]">
+    <h2 id="weatherTitle" aria-hidden="true">
+      $i18n{ambientModeWeatherTitle}
     </h2>
-
-    <settings-toggle-button id="ambientModeEnable"
-        class="primary-toggle"
-        pref="{{prefs.settings.ambient_mode.enabled}}"
-        label="[[getAmbientModeOnOffLabel_(
-            prefs.settings.ambient_mode.enabled.value)]]"
-        deep-link-focus-id$="[[Setting.kAmbientModeOnOff]]">
-    </settings-toggle-button>
-
-    <paper-spinner-lite active='[[!showSettings_]]' hidden='[[showSettings_]]'>
-    </paper-spinner-lite>
-
-    <template is="dom-if" if="[[showSettings_]]">
-      <div id="topicSourceListDiv" class="layout vertical flex"
-          aria-hidden="[[disableSettings_]]">
-        <topic-source-list topic-sources="[[topicSources_]]"
-            selected-topic-source="[[selectedTopicSource_]]"
-            has-google-photos-albums="[[hasGooglePhotosAlbums_]]"
-            disabled='[[disableSettings_]]'>
-        </topic-source-list>
-      </div>
-      <div id="weatherDiv" class="layout vertical flex"
-          aria-hidden="[[disableSettings_]]">
-        <h2 id="weatherTitle" aria-hidden="true">
-          $i18n{ambientModeWeatherTitle}
-        </h2>
-        <div class="list-frame">
-          <cr-radio-group
-              id="ambientTemperatureUnit"
-              selected="{{selectedTemperatureUnit_}}"
-              disabled$='[[disableSettings_]]'
-              aria-labelledby="weatherTitle">
-            <cr-radio-button
-                name="[[AmbientModeTemperatureUnit_.FAHRENHEIT]]"
-                class="list-item underbar"
-                label="$i18n{ambientModeTemperatureUnitFahrenheit}">
-            </cr-radio-button>
-            <cr-radio-button
-                name="[[AmbientModeTemperatureUnit_.CELSIUS]]"
-                class="list-item"
-                label="$i18n{ambientModeTemperatureUnitCelsius}">
-            </cr-radio-button>
-          </cr-radio-group>
-        </div>
-      </div>
-    </template>
-  </template>
-  <script src="ambient_mode_page.js"></script>
-</dom-module>
+    <div class="list-frame">
+      <cr-radio-group
+          id="ambientTemperatureUnit"
+          selected="{{selectedTemperatureUnit_}}"
+          disabled$='[[disableSettings_]]'
+          aria-labelledby="weatherTitle">
+        <cr-radio-button
+            name="[[AmbientModeTemperatureUnit_.FAHRENHEIT]]"
+            class="list-item underbar"
+            label="$i18n{ambientModeTemperatureUnitFahrenheit}">
+        </cr-radio-button>
+        <cr-radio-button
+            name="[[AmbientModeTemperatureUnit_.CELSIUS]]"
+            class="list-item"
+            label="$i18n{ambientModeTemperatureUnitCelsius}">
+        </cr-radio-button>
+      </cr-radio-group>
+    </div>
+  </div>
+</template>
diff --git a/chrome/browser/resources/settings/chromeos/ambient_mode_page/ambient_mode_page.js b/chrome/browser/resources/settings/chromeos/ambient_mode_page/ambient_mode_page.js
index 6cb95ac6..84c49f9 100644
--- a/chrome/browser/resources/settings/chromeos/ambient_mode_page/ambient_mode_page.js
+++ b/chrome/browser/resources/settings/chromeos/ambient_mode_page/ambient_mode_page.js
@@ -6,12 +6,35 @@
  * @fileoverview 'settings-ambient-mode-page' is the settings page containing
  * ambient mode settings.
  */
+import '//resources/cr_elements/cr_radio_button/cr_radio_button.m.js';
+import '//resources/cr_elements/shared_style_css.m.js';
+import '//resources/cr_elements/shared_vars_css.m.js';
+import '//resources/polymer/v3_0/paper-spinner/paper-spinner-lite.js';
+import './topic_source_list.js';
+import '../../prefs/prefs.js';
+import '../../controls/settings_radio_group.js';
+import '../../controls/settings_toggle_button.js';
+import '../../settings_shared_css.js';
+
+import {I18nBehavior} from '//resources/js/i18n_behavior.m.js';
+import {WebUIListenerBehavior} from '//resources/js/web_ui_listener_behavior.m.js';
+import {afterNextRender, flush, html, Polymer} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {PrefsBehavior} from '../../prefs/prefs_behavior.js';
+import {Route, RouteObserverBehavior, Router} from '../../router.js';
+import {DeepLinkingBehavior} from '../deep_linking_behavior.m.js';
+import {routes} from '../os_route.m.js';
+
+import {AmbientModeBrowserProxy, AmbientModeBrowserProxyImpl} from './ambient_mode_browser_proxy.js';
+import {AmbientModeTemperatureUnit, AmbientModeTopicSource, TopicSourceItem} from './constants.js';
+
 Polymer({
+  _template: html`{__html_template__}`,
   is: 'settings-ambient-mode-page',
 
   behaviors: [
-    DeepLinkingBehavior, I18nBehavior, PrefsBehavior,
-    settings.RouteObserverBehavior, WebUIListenerBehavior
+    DeepLinkingBehavior, I18nBehavior, PrefsBehavior, RouteObserverBehavior,
+    WebUIListenerBehavior
   ],
 
   properties: {
@@ -91,12 +114,12 @@
     'show-albums': 'onShowAlbums_',
   },
 
-  /** @private {?settings.AmbientModeBrowserProxy} */
+  /** @private {?AmbientModeBrowserProxy} */
   browserProxy_: null,
 
   /** @override */
   created() {
-    this.browserProxy_ = settings.AmbientModeBrowserProxyImpl.getInstance();
+    this.browserProxy_ = AmbientModeBrowserProxyImpl.getInstance();
   },
 
   /** @override */
@@ -127,8 +150,8 @@
     }
 
     // Wait for element to load.
-    Polymer.RenderStatus.afterNextRender(this, () => {
-      Polymer.dom.flush();
+    afterNextRender(this, () => {
+      flush();
 
       const topicList = this.$$('topic-source-list');
       const listItem = topicList && topicList.$$('topic-source-item');
@@ -145,11 +168,11 @@
 
   /**
    * RouteObserverBehavior
-   * @param {!settings.Route} currentRoute
+   * @param {!Route} currentRoute
    * @protected
    */
   currentRouteChanged(currentRoute) {
-    if (currentRoute !== settings.routes.AMBIENT_MODE) {
+    if (currentRoute !== routes.AMBIENT_MODE) {
       return;
     }
 
@@ -205,8 +228,7 @@
   onShowAlbums_(event) {
     const params = new URLSearchParams();
     params.append('topicSource', JSON.stringify(event.detail));
-    settings.Router.getInstance().navigateTo(
-        settings.routes.AMBIENT_MODE_PHOTOS, params);
+    Router.getInstance().navigateTo(routes.AMBIENT_MODE_PHOTOS, params);
   },
 
   /**
diff --git a/chrome/browser/resources/settings/chromeos/ambient_mode_page/ambient_mode_photos_page.html b/chrome/browser/resources/settings/chromeos/ambient_mode_page/ambient_mode_photos_page.html
index 4399ce56..00559fb 100644
--- a/chrome/browser/resources/settings/chromeos/ambient_mode_page/ambient_mode_photos_page.html
+++ b/chrome/browser/resources/settings/chromeos/ambient_mode_page/ambient_mode_photos_page.html
@@ -1,63 +1,42 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
+<style include="settings-shared">
+  #pageDescription {
+    display: flex;
+    min-height: 32px;
+    padding: 0 var(--cr-section-padding);
+  }
+</style>
+<settings-localized-link id="pageDescription"
+    localized-string="[[getTitleInnerHtml_(topicSource)]]">
+</settings-localized-link>
 
-<link rel="import" href="album_list.html">
-<link rel="import" href="ambient_mode_browser_proxy.html">
-<link rel="import" href="art_album_dialog.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_checkbox/cr_checkbox.html">
-<link rel="import" href="chrome://resources/html/assert.html">
-<link rel="import" href="chrome://resources/html/load_time_data.html">
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
-<link rel="import" href="constants.html">
-<link rel="import" href="../localized_link/localized_link.html">
-<link rel="import" href="../os_route.html">
-<link rel="import" href="../../router.html">
-<link rel="import" href="../../settings_shared_css.html">
+<template is="dom-if" if="[[hasNoAlbums_(albums)]]">
+  <settings-localized-link class="cr-row first"
+      localized-string=
+          $i18nPolymer{ambientModeAlbumsSubpageGooglePhotosNoAlbum}
+  </settings-localized-link>
+</template>
 
-<dom-module id="settings-ambient-mode-photos-page">
-  <template>
-    <style include="settings-shared">
-      #pageDescription {
-        display: flex;
-        min-height: 32px;
-        padding: 0 var(--cr-section-padding);
-      }
-    </style>
-    <settings-localized-link id="pageDescription"
-        localized-string="[[getTitleInnerHtml_(topicSource)]]">
-    </settings-localized-link>
+<template is="dom-if" if="[[showArtAlbumDialog_]]" restamp>
+  <art-album-dialog on-close="onArtAlbumDialogClose_"></art-album-dialog>
+</template>
 
-    <template is="dom-if" if="[[hasNoAlbums_(albums)]]">
-      <settings-localized-link class="cr-row first"
-          localized-string=
-              $i18nPolymer{ambientModeAlbumsSubpageGooglePhotosNoAlbum}
-      </settings-localized-link>
+<!-- Text based album selection. -->
+<template is="dom-if" if="[[!photoPreviewEnabled]]">
+  <iron-list id="albums" class="list-frame" items="[[albums]]">
+    <template>
+      <cr-checkbox class="list-item"
+          checked="[[item.checked]]"
+          on-change="onCheckboxChange_"
+          data-id$="[[item.albumId]]"
+          label="[[item.title]]">
+        [[item.title]]
+      </cr-checkbox>
     </template>
+  </iron-list>
+</template>
 
-    <template is="dom-if" if="[[showArtAlbumDialog_]]" restamp>
-      <art-album-dialog on-close="onArtAlbumDialogClose_"></art-album-dialog>
-    </template>
-
-    <!-- Text based album selection. -->
-    <template is="dom-if" if="[[!photoPreviewEnabled]]">
-      <iron-list id="albums" class="list-frame" items="[[albums]]">
-        <template>
-          <cr-checkbox class="list-item"
-              checked="[[item.checked]]"
-              on-change="onCheckboxChange_"
-              data-id$="[[item.albumId]]"
-              label="[[item.title]]">
-            [[item.title]]
-          </cr-checkbox>
-        </template>
-      </iron-list>
-    </template>
-
-    <!-- Photo based album selection. -->
-    <template is="dom-if" if="[[photoPreviewEnabled]]">
-      <album-list albums="{{albums}}" topic-source="[[topicSource]]">
-      </album-list>
-    </template>
-  </template>
-  <script src="ambient_mode_photos_page.js"></script>
-</dom-module>
+<!-- Photo based album selection. -->
+<template is="dom-if" if="[[photoPreviewEnabled]]">
+  <album-list albums="{{albums}}" topic-source="[[topicSource]]">
+  </album-list>
+</template>
diff --git a/chrome/browser/resources/settings/chromeos/ambient_mode_page/ambient_mode_photos_page.js b/chrome/browser/resources/settings/chromeos/ambient_mode_page/ambient_mode_photos_page.js
index 4421621f..996cdeb 100644
--- a/chrome/browser/resources/settings/chromeos/ambient_mode_page/ambient_mode_photos_page.js
+++ b/chrome/browser/resources/settings/chromeos/ambient_mode_page/ambient_mode_photos_page.js
@@ -6,11 +6,29 @@
  * @fileoverview 'settings-ambient-mode-photos-page' is the settings page to
  * select personal albums in Google Photos or categories in Art gallary.
  */
+import './album_list.js';
+import './art_album_dialog.js';
+import '//resources/cr_elements/cr_checkbox/cr_checkbox.m.js';
+import '../localized_link/localized_link.js';
+import '../../settings_shared_css.js';
+
+import {assert, assertNotReached} from '//resources/js/assert.m.js';
+import {I18nBehavior} from '//resources/js/i18n_behavior.m.js';
+import {loadTimeData} from '//resources/js/load_time_data.m.js';
+import {WebUIListenerBehavior} from '//resources/js/web_ui_listener_behavior.m.js';
+import {html, Polymer} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {Route, RouteObserverBehavior, Router} from '../../router.js';
+import {routes} from '../os_route.m.js';
+
+import {AmbientModeBrowserProxy, AmbientModeBrowserProxyImpl} from './ambient_mode_browser_proxy.js';
+import {AmbientModeAlbum, AmbientModeSettings, AmbientModeTopicSource} from './constants.js';
+
 Polymer({
+  _template: html`{__html_template__}`,
   is: 'settings-ambient-mode-photos-page',
 
-  behaviors:
-      [I18nBehavior, settings.RouteObserverBehavior, WebUIListenerBehavior],
+  behaviors: [I18nBehavior, RouteObserverBehavior, WebUIListenerBehavior],
 
   properties: {
     photoPreviewEnabled: {
@@ -46,12 +64,12 @@
     'selected-albums-changed': 'onSelectedAlbumsChanged_',
   },
 
-  /** @private {?settings.AmbientModeBrowserProxy} */
+  /** @private {?AmbientModeBrowserProxy} */
   browserProxy_: null,
 
   /** @override */
   created() {
-    this.browserProxy_ = settings.AmbientModeBrowserProxyImpl.getInstance();
+    this.browserProxy_ = AmbientModeBrowserProxyImpl.getInstance();
   },
 
   /** @override */
@@ -63,16 +81,16 @@
 
   /**
    * RouteObserverBehavior
-   * @param {!settings.Route} currentRoute
+   * @param {!Route} currentRoute
    * @protected
    */
   currentRouteChanged(currentRoute) {
-    if (currentRoute !== settings.routes.AMBIENT_MODE_PHOTOS) {
+    if (currentRoute !== routes.AMBIENT_MODE_PHOTOS) {
       return;
     }
 
     const topicSourceParam =
-        settings.Router.getInstance().getQueryParameters().get('topicSource');
+        Router.getInstance().getQueryParameters().get('topicSource');
     const topicSourceInt = parseInt(topicSourceParam, 10);
 
     if (isNaN(topicSourceInt)) {
diff --git a/chrome/browser/resources/settings/chromeos/ambient_mode_page/art_album_dialog.html b/chrome/browser/resources/settings/chromeos/ambient_mode_page/art_album_dialog.html
index c5c0eef..29aef94 100644
--- a/chrome/browser/resources/settings/chromeos/ambient_mode_page/art_album_dialog.html
+++ b/chrome/browser/resources/settings/chromeos/ambient_mode_page/art_album_dialog.html
@@ -1,27 +1,14 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_dialog/cr_dialog.html">
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="../../settings_shared_css.html">
-<link rel="import" href="../../i18n_setup.html">
-
-<dom-module id="art-album-dialog">
-  <template>
-    <style include="settings-shared">
-      cr-dialog::part(dialog) {
-        min-width: 288px;
-        width: 288px;
-      }
-    </style>
-    <cr-dialog id="dialog">
-      <div slot="body">$i18n{ambientModeLastArtAlbumMessage}</div>
-      <div slot="button-container">
-        <cr-button class="action-button" on-click="onClose_">
-          $i18n{ambientModeArtAlbumDialogCloseButtonLabel}
-        </cr-button>
-      </div>
-    </cr-dialog>
-  </template>
-  <script src="art_album_dialog.js"></script>
-</dom-module>
+<style include="settings-shared">
+  cr-dialog::part(dialog) {
+    min-width: 288px;
+    width: 288px;
+  }
+</style>
+<cr-dialog id="dialog">
+  <div slot="body">$i18n{ambientModeLastArtAlbumMessage}</div>
+  <div slot="button-container">
+    <cr-button class="action-button" on-click="onClose_">
+      $i18n{ambientModeArtAlbumDialogCloseButtonLabel}
+    </cr-button>
+  </div>
+</cr-dialog>
diff --git a/chrome/browser/resources/settings/chromeos/ambient_mode_page/art_album_dialog.js b/chrome/browser/resources/settings/chromeos/ambient_mode_page/art_album_dialog.js
index 9b439102..1911a7b 100644
--- a/chrome/browser/resources/settings/chromeos/ambient_mode_page/art_album_dialog.js
+++ b/chrome/browser/resources/settings/chromeos/ambient_mode_page/art_album_dialog.js
@@ -6,7 +6,15 @@
  * @fileoverview Polymer element for displaying information for art albums.
  */
 
+import '//resources/cr_elements/cr_button/cr_button.m.js';
+import '//resources/cr_elements/cr_dialog/cr_dialog.m.js';
+import '../../settings_shared_css.js';
+
+import {I18nBehavior} from '//resources/js/i18n_behavior.m.js';
+import {html, Polymer} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
 Polymer({
+  _template: html`{__html_template__}`,
   is: 'art-album-dialog',
 
   behaviors: [I18nBehavior],
diff --git a/chrome/browser/resources/settings/chromeos/ambient_mode_page/constants.html b/chrome/browser/resources/settings/chromeos/ambient_mode_page/constants.html
deleted file mode 100644
index 2b6bc6b..0000000
--- a/chrome/browser/resources/settings/chromeos/ambient_mode_page/constants.html
+++ /dev/null
@@ -1 +0,0 @@
-<script src="constants.js"></script>
diff --git a/chrome/browser/resources/settings/chromeos/ambient_mode_page/constants.js b/chrome/browser/resources/settings/chromeos/ambient_mode_page/constants.js
index a9e207a..2ec002c 100644
--- a/chrome/browser/resources/settings/chromeos/ambient_mode_page/constants.js
+++ b/chrome/browser/resources/settings/chromeos/ambient_mode_page/constants.js
@@ -7,14 +7,14 @@
  * Values need to stay in sync with the enum |ash::AmbientModeTopicSource|.
  * @enum {number}
  */
-/* #export */ const AmbientModeTopicSource = {
+export const AmbientModeTopicSource = {
   UNKNOWN: -1,
   GOOGLE_PHOTOS: 0,
   ART_GALLERY: 1,
 };
 
 /** @enum {string} */
-/* #export */ const AmbientModeTemperatureUnit = {
+export const AmbientModeTemperatureUnit = {
   UNKNOWN: 'unknown',
   FAHRENHEIT: 'fahrenheit',
   CELSIUS: 'celsius',
@@ -28,7 +28,7 @@
  *   hasGooglePhotosAlbums: boolean,
  * }}
  */
-/* #export */ let TopicSourceItem;
+export let TopicSourceItem;
 
 /**
  * Album metadata for UI.
@@ -43,7 +43,7 @@
  *   recentHighlightsUrls: Array<string>,
  * }}
  */
-/* #export */ let AmbientModeAlbum;
+export let AmbientModeAlbum;
 
 /**
  * Settings containing topic source and the albums.
@@ -53,4 +53,4 @@
  *   topicSource: !AmbientModeTopicSource,
  * }}
  */
-/* #export */ let AmbientModeSettings;
+export let AmbientModeSettings;
diff --git a/chrome/browser/resources/settings/chromeos/ambient_mode_page/text_with_tooltip.html b/chrome/browser/resources/settings/chromeos/ambient_mode_page/text_with_tooltip.html
index 68d55826..34257911 100644
--- a/chrome/browser/resources/settings/chromeos/ambient_mode_page/text_with_tooltip.html
+++ b/chrome/browser/resources/settings/chromeos/ambient_mode_page/text_with_tooltip.html
@@ -1,26 +1,16 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_style_css.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
-<link rel="import" href="../../settings_shared_css.html">
-
-<dom-module id="text-with-tooltip">
-  <template>
-    <style include="settings-shared cr-shared-style">
-      .line-clamp {
-        -webkit-box-orient: vertical;
-        display: -webkit-box;
-        overflow: hidden;
-      }
-    </style>
-    <div id="textDiv" style$="-webkit-line-clamp: [[lineClamp]];"
-      class$="line-clamp [[textStyle]]">
-      [[text]]
-    </div>
-    <template is="dom-if" if="[[textOverflowing_]]">
-      <paper-tooltip for="textDiv" fit-to-visible-bounds>
-        [[text]]
-      </paper-tooltip>
-    </template>
-  </template>
-  <script src="text_with_tooltip.js"></script>
-</dom-module>
+<style include="settings-shared cr-shared-style">
+  .line-clamp {
+    -webkit-box-orient: vertical;
+    display: -webkit-box;
+    overflow: hidden;
+  }
+</style>
+<div id="textDiv" style$="-webkit-line-clamp: [[lineClamp]];"
+  class$="line-clamp [[textStyle]]">
+  [[text]]
+</div>
+<template is="dom-if" if="[[textOverflowing_]]">
+  <paper-tooltip for="textDiv" fit-to-visible-bounds>
+    [[text]]
+  </paper-tooltip>
+</template>
diff --git a/chrome/browser/resources/settings/chromeos/ambient_mode_page/text_with_tooltip.js b/chrome/browser/resources/settings/chromeos/ambient_mode_page/text_with_tooltip.js
index df1e6a0a..3d927e3 100644
--- a/chrome/browser/resources/settings/chromeos/ambient_mode_page/text_with_tooltip.js
+++ b/chrome/browser/resources/settings/chromeos/ambient_mode_page/text_with_tooltip.js
@@ -9,7 +9,13 @@
 const TOOLTIP_ANIMATE_IN_DELAY = 500;
 const TOOLTIP_ANIMATE_OUT_DURATION = 500;
 
+import {Polymer, html} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import '//resources/cr_elements/shared_style_css.m.js';
+import '//resources/cr_elements/shared_vars_css.m.js';
+import '../../settings_shared_css.js';
+
 Polymer({
+  _template: html`{__html_template__}`,
   is: 'text-with-tooltip',
 
   properties: {
diff --git a/chrome/browser/resources/settings/chromeos/ambient_mode_page/topic_source_item.html b/chrome/browser/resources/settings/chromeos/ambient_mode_page/topic_source_item.html
index 149a856..4ae9c84 100644
--- a/chrome/browser/resources/settings/chromeos/ambient_mode_page/topic_source_item.html
+++ b/chrome/browser/resources/settings/chromeos/ambient_mode_page/topic_source_item.html
@@ -1,52 +1,36 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
+<style include="settings-shared cr-shared-style iron-flex
+    cr-radio-button-style">
+  :host {
+    display: inline-flex;
+  }
 
-<link rel="import" href="chrome://resources/cr_elements/cr_radio_button/cr_radio_button_style_css.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/iron-flex-layout-classes.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_style_css.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="../../settings_shared_css.html">
-<link rel="import" href="./constants.html">
+  #rowContainer {
+    /* Set height to 100% of parent to always capture click events. */
+    height: 100%;
+    padding-inline-end: var(--cr-icon-ripple-padding);
+    padding-inline-start: var(--cr-section-padding);
+  }
+</style>
 
-<dom-module id="topic-source-item">
-  <template>
-    <style include="settings-shared cr-shared-style iron-flex
-        cr-radio-button-style">
-      :host {
-        display: inline-flex;
-      }
+<div id="rowContainer" class="layout horizontal center flex"
+    on-click="onItemClick_">
+  <!-- This item behaviors similar to a radio button and reuses disc and
+       label style in cr-radio-button-style. -->
+  <div class="disc-wrapper" aria-hidden="true">
+    <div class="disc-border"></div>
+    <div class="disc"></div>
+  </div>
 
-      #rowContainer {
-        /* Set height to 100% of parent to always capture click events. */
-        height: 100%;
-        padding-inline-end: var(--cr-icon-ripple-padding);
-        padding-inline-start: var(--cr-section-padding);
-      }
-    </style>
-
-    <div id="rowContainer" class="layout horizontal center flex"
-        on-click="onItemClick_">
-      <!-- This item behaviors similar to a radio button and reuses disc and
-           label style in cr-radio-button-style. -->
-      <div class="disc-wrapper" aria-hidden="true">
-        <div class="disc-border"></div>
-        <div class="disc"></div>
-      </div>
-
-      <div id="labelWrapper" aria-hidden="true">
-        <div>[[getItemName_(item)]]</div>
-        <div class="cr-secondary-text">
-          [[getItemDescription_(item, hasGooglePhotosAlbums)]]
-        </div>
-      </div>
-
-      <cr-icon-button class="subpage-arrow" id="subpage-button"
-          on-click="onSubpageArrowClick_" tabindex$="[[tabindex]]"
-          aria-label$="[[buttonLabel]]"
-          disabled$="[[disabled]]">
-      </cr-icon-button>
+  <div id="labelWrapper" aria-hidden="true">
+    <div>[[getItemName_(item)]]</div>
+    <div class="cr-secondary-text">
+      [[getItemDescription_(item, hasGooglePhotosAlbums)]]
     </div>
-  </template>
-  <script src="topic_source_item.js"></script>
-</dom-module>
+  </div>
+
+  <cr-icon-button class="subpage-arrow" id="subpage-button"
+      on-click="onSubpageArrowClick_" tabindex$="[[tabindex]]"
+      aria-label$="[[buttonLabel]]"
+      disabled$="[[disabled]]">
+  </cr-icon-button>
+</div>
diff --git a/chrome/browser/resources/settings/chromeos/ambient_mode_page/topic_source_item.js b/chrome/browser/resources/settings/chromeos/ambient_mode_page/topic_source_item.js
index d2ab673..fc31405 100644
--- a/chrome/browser/resources/settings/chromeos/ambient_mode_page/topic_source_item.js
+++ b/chrome/browser/resources/settings/chromeos/ambient_mode_page/topic_source_item.js
@@ -7,7 +7,20 @@
  * AmbientModeTopicSource in a list.
  */
 
+import '//resources/cr_elements/cr_radio_button/cr_radio_button_style_css.m.js';
+import '//resources/polymer/v3_0/iron-flex-layout/iron-flex-layout-classes.js';
+import '//resources/cr_elements/cr_icon_button/cr_icon_button.m.js';
+import '//resources/cr_elements/shared_style_css.m.js';
+import '//resources/cr_elements/shared_vars_css.m.js';
+import '../../settings_shared_css.js';
+
+import {I18nBehavior} from '//resources/js/i18n_behavior.m.js';
+import {html, Polymer} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {AmbientModeTopicSource} from './constants.js';
+
 Polymer({
+  _template: html`{__html_template__}`,
   is: 'topic-source-item',
 
   behaviors: [I18nBehavior],
diff --git a/chrome/browser/resources/settings/chromeos/ambient_mode_page/topic_source_list.html b/chrome/browser/resources/settings/chromeos/ambient_mode_page/topic_source_list.html
index 5cfa45f9..86b1a9d 100644
--- a/chrome/browser/resources/settings/chromeos/ambient_mode_page/topic_source_list.html
+++ b/chrome/browser/resources/settings/chromeos/ambient_mode_page/topic_source_list.html
@@ -1,47 +1,32 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
+<style include="settings-shared cr-shared-style iron-flex">
+  #topicSourceTitle {
+    padding-bottom: 16px;
+    padding-top: 16px;
+  }
 
-<link rel="import" href="topic_source_item.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_style_css.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/iron-flex-layout-classes.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-list/iron-list.html">
-<link rel="import" href="../../settings_shared_css.html">
-<link rel="import" href="./constants.html">
+  topic-source-item {
+    align-items: center;
+    height: 64px;
+  }
 
-<dom-module id="topic-source-list">
+  iron-list > *:not(:first-of-type) {
+    border-top: var(--cr-separator-line);
+  }
+
+  iron-list > :focus {
+    background-color: var(--cr-focused-item-color);
+  }
+</style>
+<h2 id="topicSourceTitle" aria-hidden="true">
+  $i18n{ambientModeTopicSourceTitle}
+</h2>
+
+<iron-list id="topicSourceList" items="[[topicSources]]">
   <template>
-    <style include="settings-shared cr-shared-style iron-flex">
-      #topicSourceTitle {
-        padding-bottom: 16px;
-        padding-top: 16px;
-      }
-
-      topic-source-item {
-        align-items: center;
-        height: 64px;
-      }
-
-      iron-list > *:not(:first-of-type) {
-        border-top: var(--cr-separator-line);
-      }
-
-      iron-list > :focus {
-        background-color: var(--cr-focused-item-color);
-      }
-    </style>
-    <h2 id="topicSourceTitle" aria-hidden="true">
-      $i18n{ambientModeTopicSourceTitle}
-    </h2>
-
-    <iron-list id="topicSourceList" items="[[topicSources]]">
-      <template>
-        <topic-source-item item="[[item]]" disabled$="[[disabled]]"
-            tabindex$="[[computeTabIndex_(tabIndex, disabled)]]"
-            has-google-photos-albums="[[hasGooglePhotosAlbums]]"
-            checked="[[isSelected_(item, selectedTopicSource)]]">
-        </topic-source-item>
-      </template>
-    </iron-list>
+    <topic-source-item item="[[item]]" disabled$="[[disabled]]"
+        tabindex$="[[computeTabIndex_(tabIndex, disabled)]]"
+        has-google-photos-albums="[[hasGooglePhotosAlbums]]"
+        checked="[[isSelected_(item, selectedTopicSource)]]">
+    </topic-source-item>
   </template>
-  <script src="topic_source_list.js"></script>
-</dom-module>
+</iron-list>
diff --git a/chrome/browser/resources/settings/chromeos/ambient_mode_page/topic_source_list.js b/chrome/browser/resources/settings/chromeos/ambient_mode_page/topic_source_list.js
index ddb7ee51..d1a9bc0f 100644
--- a/chrome/browser/resources/settings/chromeos/ambient_mode_page/topic_source_list.js
+++ b/chrome/browser/resources/settings/chromeos/ambient_mode_page/topic_source_list.js
@@ -10,7 +10,19 @@
 /**
  * Polymer class definition for 'topic-source-list'.
  */
+import './topic_source_item.js';
+import '//resources/cr_elements/shared_style_css.m.js';
+import '//resources/cr_elements/shared_vars_css.m.js';
+import '//resources/polymer/v3_0/iron-flex-layout/iron-flex-layout-classes.js';
+import '//resources/polymer/v3_0/iron-list/iron-list.js';
+import '../../settings_shared_css.js';
+
+import {html, Polymer} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {AmbientModeTopicSource} from './constants.js';
+
 Polymer({
+  _template: html`{__html_template__}`,
   is: 'topic-source-list',
 
   properties: {
diff --git a/chrome/browser/resources/settings/chromeos/os_settings.gni b/chrome/browser/resources/settings/chromeos/os_settings.gni
index b32b754..90425fe6 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings.gni
+++ b/chrome/browser/resources/settings/chromeos/os_settings.gni
@@ -290,6 +290,14 @@
 os_settings_closure_flags = settings_closure_flags
 
 os_settings_migrated_imports = [
+  "chrome/browser/resources/settings/chromeos/ambient_mode_page/album_item.html",
+  "chrome/browser/resources/settings/chromeos/ambient_mode_page/album_list.html",
+  "chrome/browser/resources/settings/chromeos/ambient_mode_page/ambient_mode_page.html",
+  "chrome/browser/resources/settings/chromeos/ambient_mode_page/ambient_mode_photos_page.html",
+  "chrome/browser/resources/settings/chromeos/ambient_mode_page/art_album_dialog.html",
+  "chrome/browser/resources/settings/chromeos/ambient_mode_page/text_with_tooltip.html",
+  "chrome/browser/resources/settings/chromeos/ambient_mode_page/topic_source_item.html",
+  "chrome/browser/resources/settings/chromeos/ambient_mode_page/topic_source_list.html",
   "chrome/browser/resources/settings/a11y_page/captions_subpage.html",
   "chrome/browser/resources/settings/about_page/about_page_browser_proxy.html",
   "chrome/browser/resources/settings/appearance_page/fonts_browser_proxy.html",
diff --git a/chrome/browser/resources/settings/chromeos/os_settings.js b/chrome/browser/resources/settings/chromeos/os_settings.js
index 37a5b18..8fce24d 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings.js
+++ b/chrome/browser/resources/settings/chromeos/os_settings.js
@@ -3,7 +3,7 @@
 // found in the LICENSE file.
 
 import '../prefs/prefs.js';
-import './ambient_mode_page/ambient_mode_page.m.js';
+import './ambient_mode_page/ambient_mode_page.js';
 import './bluetooth_page/bluetooth_device_list_item.js';
 import './bluetooth_page/bluetooth_page.js';
 import './bluetooth_page/bluetooth_subpage.js';
@@ -93,8 +93,8 @@
 export {getContactManager, observeContactManager, setContactManagerForTesting} from '../shared/nearby_contact_manager.m.js';
 export {getNearbyShareSettings, observeNearbyShareSettings, setNearbyShareSettingsForTesting} from '../shared/nearby_share_settings.m.js';
 export {NearbySettings, NearbyShareSettingsBehavior} from '../shared/nearby_share_settings_behavior.m.js';
-export {AmbientModeBrowserProxyImpl} from './ambient_mode_page/ambient_mode_browser_proxy.m.js';
-export {AmbientModeTemperatureUnit, AmbientModeTopicSource} from './ambient_mode_page/constants.m.js';
+export {AmbientModeBrowserProxyImpl} from './ambient_mode_page/ambient_mode_browser_proxy.js';
+export {AmbientModeTemperatureUnit, AmbientModeTopicSource} from './ambient_mode_page/constants.js';
 export {bluetoothApis} from './bluetooth_page/bluetooth_page.js';
 export {DevicePageBrowserProxy, DevicePageBrowserProxyImpl, IdleBehavior, LidClosedBehavior, NoteAppLockScreenSupport, setDisplayApiForTesting, StorageSpaceState} from './device_page/device_page_browser_proxy.m.js';
 export {GoogleAssistantBrowserProxyImpl} from './google_assistant_page/google_assistant_browser_proxy.js';
diff --git a/chrome/browser/resources/settings/chromeos/personalization_page/BUILD.gn b/chrome/browser/resources/settings/chromeos/personalization_page/BUILD.gn
index 8c6463d..77fd91a 100644
--- a/chrome/browser/resources/settings/chromeos/personalization_page/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/personalization_page/BUILD.gn
@@ -54,7 +54,7 @@
     "../..:router",
     "../../prefs:prefs",
     "../../settings_page:settings_animated_pages",
-    "../ambient_mode_page:ambient_mode_browser_proxy.m",
+    "../ambient_mode_page:ambient_mode_browser_proxy",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
     "//ui/webui/resources/js:load_time_data.m",
   ]
diff --git a/chrome/browser/resources/settings/privacy_page/security_keys_set_pin_dialog.js b/chrome/browser/resources/settings/privacy_page/security_keys_set_pin_dialog.js
index a2fbb0d..4db7224 100644
--- a/chrome/browser/resources/settings/privacy_page/security_keys_set_pin_dialog.js
+++ b/chrome/browser/resources/settings/privacy_page/security_keys_set_pin_dialog.js
@@ -500,7 +500,8 @@
    */
   newMinPinLengthChanged_() {
     PluralStringProxyImpl.getInstance()
-        .getPluralString('securityKeysNewPIN', this.newMinPinLength_)
+        .getPluralString('securityKeysNewPIN',
+                         /** @type {number} */ (this.newMinPinLength_))
         .then(string => this.newPINDialogDescription_ = string);
   },
 
diff --git a/chrome/browser/signin/dice_web_signin_interceptor.cc b/chrome/browser/signin/dice_web_signin_interceptor.cc
index 22f7d14f..eb582d3 100644
--- a/chrome/browser/signin/dice_web_signin_interceptor.cc
+++ b/chrome/browser/signin/dice_web_signin_interceptor.cc
@@ -18,6 +18,7 @@
 #include "build/build_config.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/password_manager/chrome_password_manager_client.h"
+#include "chrome/browser/policy/profile_policy_connector.h"
 #include "chrome/browser/profiles/profile_attributes_entry.h"
 #include "chrome/browser/profiles/profile_attributes_storage.h"
 #include "chrome/browser/profiles/profile_avatar_icon_util.h"
@@ -41,9 +42,16 @@
 #include "chrome/common/themes/autogenerated_theme_util.h"
 #include "components/password_manager/core/browser/password_manager.h"
 #include "components/password_manager/core/common/password_manager_ui.h"
+#include "components/policy/core/browser/browser_policy_connector.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/core/common/policy_namespace.h"
+#include "components/policy/core/common/policy_service.h"
+#include "components/policy/policy_constants.h"
 #include "components/pref_registry/pref_registry_syncable.h"
 #include "components/prefs/pref_service.h"
 #include "components/prefs/scoped_user_pref_update.h"
+#include "components/signin/public/base/signin_pref_names.h"
+#include "components/signin/public/identity_manager/accounts_mutator.h"
 #include "components/signin/public/identity_manager/identity_manager.h"
 #include "google_apis/gaia/gaia_auth_util.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
@@ -174,6 +182,16 @@
     // interception is missed.
     return SigninInterceptionHeuristicOutcome::kAbortSyncSignin;
   }
+
+  // If the email is an not an customer email and enterprise profile separation
+  // is mandatory, return `absl::nullopt` so that more information on the
+  // account is fetched.
+  if (!policy::BrowserPolicyConnector::IsNonEnterpriseUser(email) &&
+      profile_->GetPrefs()->HasPrefPath(
+          prefs::kManagedAccountsSigninRestriction)) {
+    return absl::nullopt;
+  }
+
   if (!is_new_account) {
     // Do not intercept reauth.
     return SigninInterceptionHeuristicOutcome::kAbortAccountNotNew;
@@ -271,6 +289,7 @@
                           &entry);
   account_id_ = account_id;
   is_interception_in_progress_ = true;
+  new_account_interception_ = is_new_account;
   Observe(web_contents);
 
   if (heuristic_outcome) {
@@ -315,9 +334,13 @@
     CoreAccountId account_id,
     content::WebContents* intercepted_contents,
     std::unique_ptr<ScopedDiceWebSigninInterceptionBubbleHandle> bubble_handle,
+    bool managed_account_interception,
     bool is_new_profile) {
   DCHECK(!session_startup_helper_);
-  DCHECK(bubble_handle);
+  DCHECK(bubble_handle || managed_account_interception);
+  DCHECK_EQ(managed_account_interception, !bubble_handle)
+      << "There should be no handle for managed accout interception";
+  managed_account_interception_ = managed_account_interception;
   interception_bubble_handle_ = std::move(bubble_handle);
   session_startup_helper_ =
       std::make_unique<DiceInterceptedSessionStartupHelper>(
@@ -341,6 +364,9 @@
   on_account_info_update_timeout_.Cancel();
   is_interception_in_progress_ = false;
   account_id_ = CoreAccountId();
+  new_account_interception_ = false;
+  managed_account_interception_ = false;
+  intercepted_account_management_accepted_ = false;
   dice_signed_in_profile_creator_.reset();
   was_interception_ui_displayed_ = false;
   account_info_fetch_start_time_ = base::TimeTicks();
@@ -366,6 +392,34 @@
   return nullptr;
 }
 
+bool DiceWebSigninInterceptor::ShouldEnforceEnterpriseProfileSeparation(
+    const AccountInfo& intercepted_account_info) const {
+  DCHECK(intercepted_account_info.IsValid());
+
+  CoreAccountInfo primary_core_account_info =
+      identity_manager_->GetPrimaryAccountInfo(signin::ConsentLevel::kSignin);
+  // In case of re-auth, do not show the enterprise separation dialog if the
+  // user already consented to enterprise management.
+  if (!new_account_interception_ && primary_core_account_info.account_id ==
+                                        intercepted_account_info.account_id) {
+    return base::FeatureList::IsEnabled(kAccountPoliciesLoadedWithoutSync) &&
+           !profile_->GetPrefs()->GetBoolean(
+               prefs::kUserAcceptedAccountManagement);
+  }
+
+  std::string account_restriction =
+      profile_->GetPrefs()->GetString(prefs::kManagedAccountsSigninRestriction);
+  if ((!account_restriction.empty() &&
+       profile_->GetPrefs()->GetBoolean(
+           prefs::kManagedAccountsSigninRestrictionScopeMachine)) ||
+      account_restriction == "primary_account_strict") {
+    return intercepted_account_info.IsManaged();
+  }
+
+  // TODO(crbug/1163117) Look for the policy value for the intercepted account.
+  return false;
+}
+
 bool DiceWebSigninInterceptor::ShouldShowEnterpriseBubble(
     const AccountInfo& intercepted_account_info) const {
   DCHECK(intercepted_account_info.IsValid());
@@ -424,6 +478,47 @@
 
   absl::optional<SigninInterceptionType> interception_type;
 
+  ProfileAttributesEntry* entry =
+      g_browser_process->profile_manager()
+          ->GetProfileAttributesStorage()
+          .GetProfileAttributesWithPath(profile_->GetPath());
+  SkColor profile_color = GenerateNewProfileColor(entry).color;
+
+  if (ShouldEnforceEnterpriseProfileSeparation(info)) {
+    // TODO (crbug/1163117): Create a new SigninInterceptionType and use
+    // interception_bubble_handle_.
+    DCHECK(base::FeatureList::IsEnabled(kAccountPoliciesLoadedWithoutSync) ||
+           !profile_->GetPrefs()
+                ->GetString(prefs::kManagedAccountsSigninRestriction)
+                .empty());
+    managed_account_interception_ = true;
+    // Immediately switch to an already existing profile.
+    const ProfileAttributesEntry* switch_to_entry =
+        ShouldShowProfileSwitchBubble(info.email,
+                                      &g_browser_process->profile_manager()
+                                           ->GetProfileAttributesStorage());
+    if (switch_to_entry) {
+      // TODO (crbug/1163117): There should be a UI notifying the user of the
+      // profile switch.
+      RecordSigninInterceptionHeuristicOutcome(
+          SigninInterceptionHeuristicOutcome::
+              kInterceptEnterpriseForcedProfileSwitch);
+      OnProfileSwitchChoice(info.email, switch_to_entry->GetPath(),
+                            SigninInterceptionResult::kAccepted);
+    } else {
+      RecordSigninInterceptionHeuristicOutcome(
+          SigninInterceptionHeuristicOutcome::kInterceptEnterpriseForced);
+      Browser* browser = chrome::FindBrowserWithProfile(profile_);
+      DCHECK(browser);
+      delegate_->ShowEnterpriseProfileInterceptionDialog(
+          info.email,
+          base::BindOnce(
+              &DiceWebSigninInterceptor::OnEnterpriseProfileCreationResult,
+              base::Unretained(this), info, profile_color),
+          browser);
+    }
+    return;
+  }
   if (ShouldShowEnterpriseBubble(info))
     interception_type = SigninInterceptionType::kEnterprise;
   else if (ShouldShowMultiUserBubble(info))
@@ -437,11 +532,6 @@
     return;
   }
 
-  ProfileAttributesEntry* entry =
-      g_browser_process->profile_manager()
-          ->GetProfileAttributesStorage()
-          .GetProfileAttributesWithPath(profile_->GetPath());
-  SkColor profile_color = GenerateNewProfileColor(entry).color;
   auto guest_option_availability = GetGuestOptionAvailablity();
   Delegate::BubbleParameters bubble_parameters{
       *interception_type, info, GetPrimaryAccountInfo(identity_manager_),
@@ -478,7 +568,7 @@
     return;
   }
 
-  DCHECK(interception_bubble_handle_);
+  DCHECK(interception_bubble_handle_ || managed_account_interception_);
   profile_creation_start_time_ = base::TimeTicks::Now();
   std::u16string profile_name;
   profile_name = profiles::GetDefaultNameForNewSignedInProfile(account_info);
@@ -505,7 +595,7 @@
     return;
   }
 
-  DCHECK(interception_bubble_handle_);
+  DCHECK(interception_bubble_handle_ || managed_account_interception_);
   DCHECK(!dice_signed_in_profile_creator_);
   profile_creation_start_time_ = base::TimeTicks::Now();
   // Unretained is fine because the profile creator is owned by this.
@@ -549,17 +639,49 @@
         base::TimeTicks::Now() - profile_creation_start_time_);
   }
 
+  new_profile->GetPrefs()->SetBoolean(prefs::kUserAcceptedAccountManagement,
+                                      intercepted_account_management_accepted_);
+
   // Work is done in this profile, the flow continues in the
   // DiceWebSigninInterceptor that is attached to the new profile.
   DiceWebSigninInterceptorFactory::GetForProfile(new_profile)
       ->CreateBrowserAfterSigninInterception(
           account_id_, web_contents(), std::move(interception_bubble_handle_),
-          is_new_profile);
+          managed_account_interception_, is_new_profile);
   Reset();
 }
 
+void DiceWebSigninInterceptor::OnEnterpriseProfileCreationResult(
+    const AccountInfo& account_info,
+    SkColor profile_color,
+    bool create) {
+  intercepted_account_management_accepted_ = create;
+  if (create) {
+    // In case of a reauth if there was no consent for management, do not create
+    // a new profile.
+    if (!new_account_interception_ &&
+        GetPrimaryAccountInfo(identity_manager_).account_id ==
+            account_info.account_id) {
+      profile_->GetPrefs()->SetBoolean(
+          prefs::kUserAcceptedAccountManagement,
+          intercepted_account_management_accepted_);
+    } else {
+      OnProfileCreationChoice(account_info, profile_color,
+                              SigninInterceptionResult::kAccepted);
+    }
+  } else {
+    OnProfileCreationChoice(account_info, profile_color,
+                            SigninInterceptionResult::kDeclined);
+    auto* accounts_mutator = identity_manager_->GetAccountsMutator();
+    accounts_mutator->RemoveAccount(
+        account_info.account_id,
+        signin_metrics::SourceForRefreshTokenOperation::
+            kDiceTurnOnSyncHelper_Abort);
+  }
+}
+
 void DiceWebSigninInterceptor::OnNewBrowserCreated(bool is_new_profile) {
-  DCHECK(interception_bubble_handle_);
+  DCHECK(interception_bubble_handle_ || managed_account_interception_);
   interception_bubble_handle_.reset();  // Close the bubble now.
   session_startup_helper_.reset();
 
diff --git a/chrome/browser/signin/dice_web_signin_interceptor.h b/chrome/browser/signin/dice_web_signin_interceptor.h
index 22978f7..86fed40 100644
--- a/chrome/browser/signin/dice_web_signin_interceptor.h
+++ b/chrome/browser/signin/dice_web_signin_interceptor.h
@@ -13,6 +13,7 @@
 #include "base/gtest_prod_util.h"
 #include "base/scoped_observation.h"
 #include "base/time/time.h"
+#include "chrome/browser/ui/webui/signin/enterprise_profile_welcome_ui.h"
 #include "components/keyed_service/core/keyed_service.h"
 #include "components/signin/public/identity_manager/identity_manager.h"
 #include "content/public/browser/web_contents_observer.h"
@@ -81,7 +82,11 @@
   // Signin interception is disabled by the SigninInterceptionEnabled policy.
   kAbortInterceptionDisabled = 15,
 
-  kMaxValue = kAbortInterceptionDisabled,
+  // Interception succeeded when enteprise account separation is mandatory.
+  kInterceptEnterpriseForced = 16,
+  kInterceptEnterpriseForcedProfileSwitch = 17,
+
+  kMaxValue = kInterceptEnterpriseForcedProfileSwitch,
 };
 
 // User selection in the interception bubble.
@@ -172,6 +177,14 @@
         const BubbleParameters& bubble_parameters,
         base::OnceCallback<void(SigninInterceptionResult)> callback) = 0;
 
+    // Shows the enterprise profile confirmation dialog to notify the user that
+    // a managed profile will be created or that their account needs a new
+    // profile to be created.
+    virtual void ShowEnterpriseProfileInterceptionDialog(
+        const std::string& email,
+        base::OnceCallback<void(bool)> callback,
+        Browser* browser) = 0;
+
     // Shows the profile customization bubble.
     virtual void ShowProfileCustomizationBubble(Browser* browser) = 0;
   };
@@ -211,6 +224,7 @@
       content::WebContents* intercepted_contents,
       std::unique_ptr<ScopedDiceWebSigninInterceptionBubbleHandle>
           bubble_handle,
+      bool managed_account_interception,
       bool is_new_profile);
 
   // Returns the outcome of the interception heuristic.
@@ -244,7 +258,15 @@
                            ShouldShowEnterpriseBubbleWithoutUPA);
   FRIEND_TEST_ALL_PREFIXES(DiceWebSigninInterceptorTest,
                            ShouldShowMultiUserBubble);
+  FRIEND_TEST_ALL_PREFIXES(DiceWebSigninInterceptorTest,
+                           ShouldEnforceEnterpriseProfileSeparation);
+  FRIEND_TEST_ALL_PREFIXES(DiceWebSigninInterceptorTest,
+                           ShouldEnforceEnterpriseProfileSeparationWithoutUPA);
+  FRIEND_TEST_ALL_PREFIXES(DiceWebSigninInterceptorTest,
+                           ShouldEnforceEnterpriseProfileSeparationReauth);
   FRIEND_TEST_ALL_PREFIXES(DiceWebSigninInterceptorTest, PersistentHash);
+  FRIEND_TEST_ALL_PREFIXES(DiceWebSigninInterceptorTest,
+                           EnforceManagedAccountAsPrimary);
 
   // Cancels any current signin interception and resets the interceptor to its
   // initial state.
@@ -254,6 +276,8 @@
   const ProfileAttributesEntry* ShouldShowProfileSwitchBubble(
       const std::string& intercepted_email,
       ProfileAttributesStorage* profile_attribute_storage) const;
+  bool ShouldEnforceEnterpriseProfileSeparation(
+      const AccountInfo& intercepted_account_info) const;
   bool ShouldShowEnterpriseBubble(
       const AccountInfo& intercepted_account_info) const;
   bool ShouldShowMultiUserBubble(
@@ -281,6 +305,13 @@
   void OnNewSignedInProfileCreated(absl::optional<SkColor> profile_color,
                                    Profile* new_profile);
 
+  // Called after the user choses whether the session should continue in a new
+  // work profile or not. If the user choses not to continue in a work profile,
+  // the account is signed out.
+  void OnEnterpriseProfileCreationResult(const AccountInfo& account_info,
+                                         SkColor profile_color,
+                                         bool create);
+
   // Called when the new browser is created after interception. Passed as
   // callback to `session_startup_helper_`.
   void OnNewBrowserCreated(bool is_new_profile);
@@ -315,6 +346,9 @@
   // Members below are related to the interception in progress.
   bool is_interception_in_progress_ = false;
   CoreAccountId account_id_;
+  bool new_account_interception_ = false;
+  bool managed_account_interception_ = false;
+  bool intercepted_account_management_accepted_ = false;
   base::ScopedObservation<signin::IdentityManager,
                           signin::IdentityManager::Observer>
       account_info_update_observation_{this};
diff --git a/chrome/browser/signin/dice_web_signin_interceptor_browsertest.cc b/chrome/browser/signin/dice_web_signin_interceptor_browsertest.cc
index feefd78a..df5525fe 100644
--- a/chrome/browser/signin/dice_web_signin_interceptor_browsertest.cc
+++ b/chrome/browser/signin/dice_web_signin_interceptor_browsertest.cc
@@ -32,11 +32,13 @@
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_list.h"
 #include "chrome/common/chrome_features.h"
+#include "chrome/common/pref_names.h"
 #include "chrome/test/base/in_process_browser_test.h"
 #include "chrome/test/base/profile_waiter.h"
 #include "chrome/test/base/ui_test_utils.h"
 #include "components/account_id/account_id.h"
 #include "components/keyed_service/content/browser_context_dependency_manager.h"
+#include "components/prefs/pref_service.h"
 #include "components/signin/public/identity_manager/accounts_mutator.h"
 #include "components/signin/public/identity_manager/identity_manager.h"
 #include "components/signin/public/identity_manager/identity_test_environment.h"
@@ -98,6 +100,13 @@
     return bubble_handle;
   }
 
+  void ShowEnterpriseProfileInterceptionDialog(
+      const std::string& email,
+      base::OnceCallback<void(bool)> callback,
+      Browser* browser) override {
+    std::move(callback).Run(expected_enteprise_confirmation_result_);
+  }
+
   void ShowProfileCustomizationBubble(Browser* browser) override {
     EXPECT_FALSE(customized_browser_)
         << "Customization must be shown only once.";
@@ -115,6 +124,10 @@
     expected_interception_result_ = result;
   }
 
+  void set_expected_enteprise_confirmation_result(bool result) {
+    expected_enteprise_confirmation_result_ = result;
+  }
+
   bool intercept_bubble_shown() const { return weak_bubble_handle_.get(); }
 
   bool intercept_bubble_destroyed() const {
@@ -127,6 +140,7 @@
       DiceWebSigninInterceptor::SigninInterceptionType::kMultiUser;
   SigninInterceptionResult expected_interception_result_ =
       SigninInterceptionResult::kAccepted;
+  bool expected_enteprise_confirmation_result_ = false;
   base::WeakPtr<FakeBubbleHandle> weak_bubble_handle_;
 };
 
@@ -172,15 +186,22 @@
                      SigninInterceptionHeuristicOutcome outcome,
                      bool intercept_to_guest = false) {
   int profile_switch_count =
-      outcome == SigninInterceptionHeuristicOutcome::kInterceptProfileSwitch
+      outcome == SigninInterceptionHeuristicOutcome::kInterceptProfileSwitch ||
+              outcome == SigninInterceptionHeuristicOutcome::
+                             kInterceptEnterpriseForcedProfileSwitch
           ? 1
           : 0;
   int profile_creation_count = 1 - profile_switch_count;
+  int fetched_account_count =
+      outcome == SigninInterceptionHeuristicOutcome::
+                     kInterceptEnterpriseForcedProfileSwitch
+          ? 1
+          : profile_creation_count;
 
   histogram_tester.ExpectUniqueSample("Signin.Intercept.HeuristicOutcome",
                                       outcome, 1);
   histogram_tester.ExpectTotalCount("Signin.Intercept.AccountInfoFetchDuration",
-                                    profile_creation_count);
+                                    fetched_account_count);
   histogram_tester.ExpectTotalCount("Signin.Intercept.ProfileCreationDuration",
                                     profile_creation_count);
   histogram_tester.ExpectTotalCount("Signin.Intercept.ProfileSwitchDuration",
@@ -240,6 +261,9 @@
     return interceptor_delegate;
   }
 
+ protected:
+  base::test::ScopedFeatureList feature_list_;
+
  private:
   // InProcessBrowserTest:
   void SetUpOnMainThread() override {
@@ -287,7 +311,6 @@
         Profile::FromBrowserContext(context), std::move(fake_delegate));
   }
 
-  base::test::ScopedFeatureList feature_list_;
   network::TestURLLoaderFactory test_url_loader_factory_;
   std::unique_ptr<IdentityTestEnvironmentProfileAdaptor>
       identity_test_env_profile_adaptor_;
@@ -385,6 +408,100 @@
   EXPECT_EQ(source_interceptor_delegate->customized_browser(), nullptr);
 }
 
+// Tests the complete interception flow including profile and browser creation.
+IN_PROC_BROWSER_TEST_F(DiceWebSigninInterceptorBrowserTest,
+                       ForcedEnterpriseInterceptionTest) {
+  base::HistogramTester histogram_tester;
+  AccountInfo account_info =
+      identity_test_env()->MakeAccountAvailable("alice@example.com");
+  // Fill the account info, in particular for the hosted_domain field.
+  account_info.full_name = "fullname";
+  account_info.given_name = "givenname";
+  account_info.hosted_domain = "example.com";
+  account_info.locale = "en";
+  account_info.picture_url = "https://example.com";
+  account_info.is_child_account = false;
+  DCHECK(account_info.IsValid());
+  identity_test_env()->UpdateAccountInfoForAccount(account_info);
+
+  // Enforce enterprise profile sepatation.
+  profile()->GetPrefs()->SetString(prefs::kManagedAccountsSigninRestriction,
+                                   "primary_account_strict");
+
+  // Instantly return from Gaia calls, to avoid timing out when injecting the
+  // account in the new profile.
+  network::TestURLLoaderFactory* loader_factory = test_url_loader_factory();
+  loader_factory->SetInterceptor(base::BindLambdaForTesting(
+      [loader_factory](const network::ResourceRequest& request) {
+        std::string path = request.url.path();
+        if (path == "/ListAccounts" || path == "/GetCheckConnectionInfo") {
+          loader_factory->AddResponse(request.url.spec(), std::string());
+          return;
+        }
+        if (path == "/oauth/multilogin") {
+          loader_factory->AddResponse(request.url.spec(),
+                                      kMultiloginSuccessResponse);
+          return;
+        }
+      }));
+
+  // Add a tab.
+  GURL intercepted_url = embedded_test_server()->GetURL("/defaultresponse");
+  content::WebContents* web_contents = AddTab(intercepted_url);
+  int original_tab_count = browser()->tab_strip_model()->count();
+
+  // Do the signin interception.
+  EXPECT_EQ(BrowserList::GetInstance()->size(), 1u);
+  FakeDiceWebSigninInterceptorDelegate* source_interceptor_delegate =
+      GetInterceptorDelegate(profile());
+  source_interceptor_delegate->set_expected_enteprise_confirmation_result(true);
+  Profile* new_profile =
+      InterceptAndWaitProfileCreation(web_contents, account_info.account_id);
+  EXPECT_TRUE(new_profile->GetPrefs()->GetBoolean(
+      prefs::kUserAcceptedAccountManagement));
+  ASSERT_TRUE(new_profile);
+  EXPECT_FALSE(source_interceptor_delegate->intercept_bubble_shown());
+  signin::IdentityManager* new_identity_manager =
+      IdentityManagerFactory::GetForProfile(new_profile);
+  EXPECT_TRUE(new_identity_manager->HasAccountWithRefreshToken(
+      account_info.account_id));
+
+  IdentityTestEnvironmentProfileAdaptor adaptor(new_profile);
+  adaptor.identity_test_env()->SetAutomaticIssueOfAccessTokens(true);
+
+  // Check the profile name.
+  ProfileAttributesStorage& storage =
+      g_browser_process->profile_manager()->GetProfileAttributesStorage();
+  ProfileAttributesEntry* entry =
+      storage.GetProfileAttributesWithPath(new_profile->GetPath());
+  ASSERT_TRUE(entry);
+  EXPECT_EQ("example.com", base::UTF16ToUTF8(entry->GetLocalProfileName()));
+  // Check the profile color.
+  EXPECT_TRUE(ThemeServiceFactory::GetForProfile(new_profile)
+                  ->UsingAutogeneratedTheme());
+
+  // A browser has been created for the new profile and the tab was moved there.
+  Browser* added_browser = ui_test_utils::WaitForBrowserToOpen();
+  ASSERT_TRUE(added_browser);
+  ASSERT_EQ(BrowserList::GetInstance()->size(), 2u);
+  EXPECT_EQ(added_browser->profile(), new_profile);
+  EXPECT_EQ(browser()->tab_strip_model()->count(), original_tab_count - 1);
+  EXPECT_EQ(added_browser->tab_strip_model()->GetActiveWebContents()->GetURL(),
+            intercepted_url);
+
+  CheckHistograms(
+      histogram_tester,
+      SigninInterceptionHeuristicOutcome::kInterceptEnterpriseForced);
+  // Interception bubble is destroyed in the source profile, and was not shown
+  // in the new profile.
+  FakeDiceWebSigninInterceptorDelegate* new_interceptor_delegate =
+      GetInterceptorDelegate(new_profile);
+
+  // Profile customization UI was shown exactly once in the new profile.
+  EXPECT_EQ(new_interceptor_delegate->customized_browser(), added_browser);
+  EXPECT_EQ(source_interceptor_delegate->customized_browser(), nullptr);
+}
+
 // Tests the complete profile switch flow when the profile is not loaded.
 IN_PROC_BROWSER_TEST_F(DiceWebSigninInterceptorBrowserTest, SwitchAndLoad) {
   base::HistogramTester histogram_tester;
@@ -656,3 +773,180 @@
                   SigninInterceptionHeuristicOutcome::kInterceptMultiUser,
                   /*intercept_to_guest =*/true);
 }
+
+class DiceWebSigninInterceptorEnterpriseSwitchBrowserTest
+    : public DiceWebSigninInterceptorBrowserTest {
+ public:
+  DiceWebSigninInterceptorEnterpriseSwitchBrowserTest() {
+    enterprise_feature_list_.InitAndEnableFeature(
+        kAccountPoliciesLoadedWithoutSync);
+  }
+
+ private:
+  base::test::ScopedFeatureList enterprise_feature_list_;
+};
+
+// Tests the complete profile switch flow when the profile is not loaded.
+IN_PROC_BROWSER_TEST_F(DiceWebSigninInterceptorEnterpriseSwitchBrowserTest,
+                       EnterpriseSwitchAndLoad) {
+  base::HistogramTester histogram_tester;
+  // Enforce enterprise profile sepatation.
+  profile()->GetPrefs()->SetString(prefs::kManagedAccountsSigninRestriction,
+                                   "primary_account_strict");
+  AccountInfo account_info =
+      identity_test_env()->MakeAccountAvailable("alice@example.com");
+  // Fill the account info, in particular for the hosted_domain field.
+  account_info.full_name = "fullname";
+  account_info.given_name = "givenname";
+  account_info.hosted_domain = "example.com";
+  account_info.locale = "en";
+  account_info.picture_url = "https://example.com";
+  account_info.is_child_account = false;
+  DCHECK(account_info.IsValid());
+  identity_test_env()->UpdateAccountInfoForAccount(account_info);
+
+  // Add a profile in the cache (simulate the profile on disk).
+  ProfileManager* profile_manager = g_browser_process->profile_manager();
+  ProfileAttributesStorage* profile_storage =
+      &profile_manager->GetProfileAttributesStorage();
+  const base::FilePath profile_path =
+      profile_manager->GenerateNextProfileDirectoryPath();
+  ProfileAttributesInitParams params;
+  params.profile_path = profile_path;
+  params.profile_name = u"TestProfileName";
+  params.gaia_id = account_info.gaia;
+  params.user_name = base::UTF8ToUTF16(account_info.email);
+  profile_storage->AddProfile(std::move(params));
+  ProfileAttributesEntry* entry =
+      profile_storage->GetProfileAttributesWithPath(profile_path);
+  ASSERT_TRUE(entry);
+  ASSERT_EQ(entry->GetGAIAId(), account_info.gaia);
+
+  // Add a tab.
+  GURL intercepted_url = embedded_test_server()->GetURL("/defaultresponse");
+  content::WebContents* web_contents = AddTab(intercepted_url);
+  int original_tab_count = browser()->tab_strip_model()->count();
+
+  // Do the signin interception.
+  FakeDiceWebSigninInterceptorDelegate* source_interceptor_delegate =
+      GetInterceptorDelegate(profile());
+  source_interceptor_delegate->set_expected_interception_type(
+      DiceWebSigninInterceptor::SigninInterceptionType::kProfileSwitch);
+  Profile* new_profile =
+      InterceptAndWaitProfileCreation(web_contents, account_info.account_id);
+  ASSERT_TRUE(new_profile);
+  EXPECT_FALSE(source_interceptor_delegate->intercept_bubble_shown());
+  signin::IdentityManager* new_identity_manager =
+      IdentityManagerFactory::GetForProfile(new_profile);
+  EXPECT_TRUE(new_identity_manager->HasAccountWithRefreshToken(
+      account_info.account_id));
+
+  // Check that the right profile was opened.
+  EXPECT_EQ(new_profile->GetPath(), profile_path);
+
+  // Add the account to the cookies (simulates the account reconcilor).
+  EXPECT_EQ(BrowserList::GetInstance()->size(), 1u);
+  signin::SetCookieAccounts(new_identity_manager, test_url_loader_factory(),
+                            {{account_info.email, account_info.gaia}});
+
+  // A browser has been created for the new profile and the tab was moved there.
+  ASSERT_EQ(BrowserList::GetInstance()->size(), 2u);
+  Browser* added_browser = BrowserList::GetInstance()->get(1);
+  ASSERT_TRUE(added_browser);
+  EXPECT_EQ(added_browser->profile(), new_profile);
+  EXPECT_EQ(browser()->tab_strip_model()->count(), original_tab_count - 1);
+  EXPECT_EQ(added_browser->tab_strip_model()->GetActiveWebContents()->GetURL(),
+            intercepted_url);
+
+  CheckHistograms(histogram_tester,
+                  SigninInterceptionHeuristicOutcome::
+                      kInterceptEnterpriseForcedProfileSwitch);
+
+  // Profile customization was not shown.
+  EXPECT_EQ(GetInterceptorDelegate(new_profile)->customized_browser(), nullptr);
+  EXPECT_EQ(source_interceptor_delegate->customized_browser(), nullptr);
+}
+
+// Tests the complete profile switch flow when the profile is already loaded.
+IN_PROC_BROWSER_TEST_F(DiceWebSigninInterceptorEnterpriseSwitchBrowserTest,
+                       EnterpriseSwitchAlreadyOpen) {
+  base::HistogramTester histogram_tester;
+  // Enforce enterprise profile sepatation.
+  profile()->GetPrefs()->SetString(prefs::kManagedAccountsSigninRestriction,
+                                   "primary_account_strict");
+  AccountInfo account_info =
+      identity_test_env()->MakeAccountAvailable("alice@example.com");
+  // Fill the account info, in particular for the hosted_domain field.
+  account_info.full_name = "fullname";
+  account_info.given_name = "givenname";
+  account_info.hosted_domain = "example.com";
+  account_info.locale = "en";
+  account_info.picture_url = "https://example.com";
+  account_info.is_child_account = false;
+  DCHECK(account_info.IsValid());
+  identity_test_env()->UpdateAccountInfoForAccount(account_info);
+  // Create another profile with a browser window.
+  ProfileManager* profile_manager = g_browser_process->profile_manager();
+  const base::FilePath profile_path =
+      profile_manager->GenerateNextProfileDirectoryPath();
+  base::RunLoop loop;
+  Profile* other_profile = nullptr;
+  ProfileManager::CreateCallback callback = base::BindLambdaForTesting(
+      [&other_profile, &loop](Profile* profile, Profile::CreateStatus status) {
+        DCHECK_EQ(status, Profile::CREATE_STATUS_INITIALIZED);
+        other_profile = profile;
+        loop.Quit();
+      });
+  profiles::SwitchToProfile(profile_path, /*always_create=*/true,
+                            std::move(callback));
+  loop.Run();
+  ASSERT_TRUE(other_profile);
+  ASSERT_EQ(BrowserList::GetInstance()->size(), 2u);
+  Browser* other_browser = BrowserList::GetInstance()->get(1);
+  ASSERT_TRUE(other_browser);
+  ASSERT_EQ(other_browser->profile(), other_profile);
+  // Add the account to the other profile.
+  signin::IdentityManager* other_identity_manager =
+      IdentityManagerFactory::GetForProfile(other_profile);
+  other_identity_manager->GetAccountsMutator()->AddOrUpdateAccount(
+      account_info.gaia, account_info.email, "dummy_refresh_token",
+      /*is_under_advanced_protection=*/false,
+      signin_metrics::SourceForRefreshTokenOperation::kUnknown);
+  other_identity_manager->GetPrimaryAccountMutator()->SetPrimaryAccount(
+      account_info.account_id, signin::ConsentLevel::kSync);
+
+  // Add a tab.
+  GURL intercepted_url = embedded_test_server()->GetURL("/defaultresponse");
+  content::WebContents* web_contents = AddTab(intercepted_url);
+  int original_tab_count = browser()->tab_strip_model()->count();
+  int other_original_tab_count = other_browser->tab_strip_model()->count();
+
+  // Start the interception.
+  GetInterceptorDelegate(profile())->set_expected_interception_type(
+      DiceWebSigninInterceptor::SigninInterceptionType::kProfileSwitch);
+  DiceWebSigninInterceptor* interceptor =
+      DiceWebSigninInterceptorFactory::GetForProfile(profile());
+  interceptor->MaybeInterceptWebSignin(web_contents, account_info.account_id,
+                                       /*is_new_account=*/true,
+                                       /*is_sync_signin=*/false);
+
+  // Add the account to the cookies (simulates the account reconcilor).
+  signin::SetCookieAccounts(other_identity_manager, test_url_loader_factory(),
+                            {{account_info.email, account_info.gaia}});
+
+  // The tab was moved to the new browser.
+  ASSERT_EQ(BrowserList::GetInstance()->size(), 2u);
+  EXPECT_EQ(browser()->tab_strip_model()->count(), original_tab_count - 1);
+  EXPECT_EQ(other_browser->tab_strip_model()->count(),
+            other_original_tab_count + 1);
+  EXPECT_EQ(other_browser->tab_strip_model()->GetActiveWebContents()->GetURL(),
+            intercepted_url);
+
+  CheckHistograms(histogram_tester,
+                  SigninInterceptionHeuristicOutcome::
+                      kInterceptEnterpriseForcedProfileSwitch);
+  // Profile customization was not shown.
+  EXPECT_EQ(GetInterceptorDelegate(other_profile)->customized_browser(),
+            nullptr);
+  EXPECT_EQ(GetInterceptorDelegate(profile())->customized_browser(), nullptr);
+}
diff --git a/chrome/browser/signin/dice_web_signin_interceptor_unittest.cc b/chrome/browser/signin/dice_web_signin_interceptor_unittest.cc
index b499455..08ee2a9 100644
--- a/chrome/browser/signin/dice_web_signin_interceptor_unittest.cc
+++ b/chrome/browser/signin/dice_web_signin_interceptor_unittest.cc
@@ -26,11 +26,13 @@
 #include "chrome/test/base/testing_profile.h"
 #include "chrome/test/base/testing_profile_manager.h"
 #include "components/prefs/pref_service.h"
+#include "components/signin/public/base/signin_pref_names.h"
 #include "components/signin/public/identity_manager/account_info.h"
 #include "components/signin/public/identity_manager/identity_test_environment.h"
 #include "services/network/test/test_url_loader_factory.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 #include "url/gurl.h"
 
 namespace {
@@ -44,6 +46,12 @@
                const BubbleParameters& bubble_parameters,
                base::OnceCallback<void(SigninInterceptionResult)> callback),
               (override));
+  MOCK_METHOD(void,
+              ShowEnterpriseProfileInterceptionDialog,
+              (const std::string& email,
+               base::OnceCallback<void(bool)> callback,
+               Browser* browser),
+              (override));
   void ShowProfileCustomizationBubble(Browser* browser) override {}
 };
 
@@ -144,6 +152,29 @@
               SigninInterceptionHeuristicOutcomeIsSuccess(expected_outcome));
   }
 
+  // Calls MaybeInterceptWebSignin and verifies the heuristic outcome and the
+  // histograms.
+  // This function only works if the interception decision cannot be made
+  // synchronously (GetHeuristicOutcome() returns no value).
+  void TestAsynchronousInterception(
+      AccountInfo account_info,
+      bool is_new_account,
+      bool is_sync_signin,
+      SigninInterceptionHeuristicOutcome expected_outcome) {
+    ASSERT_EQ(interceptor()->GetHeuristicOutcome(is_new_account, is_sync_signin,
+                                                 account_info.email,
+                                                 /*entry=*/nullptr),
+              absl::nullopt);
+    base::HistogramTester histogram_tester;
+    interceptor()->MaybeInterceptWebSignin(web_contents(),
+                                           account_info.account_id,
+                                           is_new_account, is_sync_signin);
+    testing::Mock::VerifyAndClearExpectations(mock_delegate());
+    histogram_tester.ExpectUniqueSample("Signin.Intercept.HeuristicOutcome",
+                                        expected_outcome, 1);
+    EXPECT_TRUE(interceptor()->is_interception_in_progress());
+  }
+
  private:
   // testing::Test:
   void SetUp() override {
@@ -288,6 +319,161 @@
   EXPECT_TRUE(interceptor()->ShouldShowEnterpriseBubble(account_info));
 }
 
+TEST_F(DiceWebSigninInterceptorTest, ShouldEnforceEnterpriseProfileSeparation) {
+  profile()->GetPrefs()->SetBoolean(
+      prefs::kManagedAccountsSigninRestrictionScopeMachine, true);
+  profile()->GetPrefs()->SetString(prefs::kManagedAccountsSigninRestriction,
+                                   "primary_account_strict");
+
+  // Setup 3 accounts in the profile:
+  // - primary account
+  // - other enterprise account that is not primary (should be ignored)
+  // - intercepted account.
+  AccountInfo primary_account_info =
+      identity_test_env()->MakeUnconsentedPrimaryAccountAvailable(
+          "alice@gmail.com");
+
+  AccountInfo other_account_info =
+      identity_test_env()->MakeAccountAvailable("dummy@example.com");
+  MakeValidAccountInfo(&other_account_info);
+  other_account_info.hosted_domain = "example.com";
+  identity_test_env()->UpdateAccountInfoForAccount(other_account_info);
+  AccountInfo account_info =
+      identity_test_env()->MakeAccountAvailable("bob@example.com");
+  MakeValidAccountInfo(&account_info);
+  identity_test_env()->UpdateAccountInfoForAccount(account_info);
+  ASSERT_EQ(identity_test_env()->identity_manager()->GetPrimaryAccountId(
+                signin::ConsentLevel::kSignin),
+            primary_account_info.account_id);
+  // Consumer account not intercepted.
+  EXPECT_FALSE(
+      interceptor()->ShouldEnforceEnterpriseProfileSeparation(account_info));
+  account_info.hosted_domain = "example.com";
+  identity_test_env()->UpdateAccountInfoForAccount(account_info);
+  // Managed account intercepted.
+  EXPECT_TRUE(
+      interceptor()->ShouldEnforceEnterpriseProfileSeparation(account_info));
+}
+
+TEST_F(DiceWebSigninInterceptorTest,
+       ShouldEnforceEnterpriseProfileSeparationWithoutUPA) {
+  profile()->GetPrefs()->SetBoolean(
+      prefs::kManagedAccountsSigninRestrictionScopeMachine, true);
+  profile()->GetPrefs()->SetString(prefs::kManagedAccountsSigninRestriction,
+                                   "primary_account_strict");
+  AccountInfo account_info_1 =
+      identity_test_env()->MakeAccountAvailable("bob@example.com");
+  MakeValidAccountInfo(&account_info_1);
+  account_info_1.hosted_domain = "example.com";
+  identity_test_env()->UpdateAccountInfoForAccount(account_info_1);
+
+  // Primary account is not set.
+  ASSERT_FALSE(identity_test_env()->identity_manager()->HasPrimaryAccount(
+      signin::ConsentLevel::kSignin));
+  EXPECT_TRUE(
+      interceptor()->ShouldEnforceEnterpriseProfileSeparation(account_info_1));
+}
+
+TEST_F(DiceWebSigninInterceptorTest,
+       ShouldEnforceEnterpriseProfileSeparationReauth) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndEnableFeature(kAccountPoliciesLoadedWithoutSync);
+  profile()->GetPrefs()->SetString(prefs::kManagedAccountsSigninRestriction,
+                                   "primary_account_strict");
+  AccountInfo primary_account_info =
+      identity_test_env()->MakeUnconsentedPrimaryAccountAvailable(
+          "alice@example.com");
+  MakeValidAccountInfo(&primary_account_info);
+  primary_account_info.hosted_domain = "example.com";
+  identity_test_env()->UpdateAccountInfoForAccount(primary_account_info);
+
+  // Primary account is set.
+  ASSERT_TRUE(identity_test_env()->identity_manager()->HasPrimaryAccount(
+      signin::ConsentLevel::kSignin));
+  EXPECT_TRUE(interceptor()->ShouldEnforceEnterpriseProfileSeparation(
+      primary_account_info));
+
+  profile()->GetPrefs()->SetBoolean(prefs::kUserAcceptedAccountManagement,
+                                    true);
+  EXPECT_FALSE(interceptor()->ShouldEnforceEnterpriseProfileSeparation(
+      primary_account_info));
+}
+
+TEST_F(DiceWebSigninInterceptorTest, EnforceManagedAccountAsPrimaryReauth) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndEnableFeature(kAccountPoliciesLoadedWithoutSync);
+  profile()->GetPrefs()->SetBoolean(
+      prefs::kManagedAccountsSigninRestrictionScopeMachine, true);
+  profile()->GetPrefs()->SetString(prefs::kManagedAccountsSigninRestriction,
+                                   "primary_account");
+
+  // Reauth intercepted if enterprise confirmation not shown yet for forced
+  // managed separation.
+  AccountInfo account_info =
+      identity_test_env()->MakeUnconsentedPrimaryAccountAvailable(
+          "alice@example.com");
+  MakeValidAccountInfo(&account_info);
+  account_info.hosted_domain = "example.com";
+  identity_test_env()->UpdateAccountInfoForAccount(account_info);
+  profile()->GetPrefs()->SetString(prefs::kManagedAccountsSigninRestriction,
+                                   "primary_account");
+  EXPECT_CALL(*mock_delegate(), ShowEnterpriseProfileInterceptionDialog(
+                                    testing::_, testing::_, testing::_))
+      .WillOnce(testing::Invoke(
+          [](const std::string& email, base::OnceCallback<void(bool)> callback,
+             Browser* browser) { std::move(callback).Run(true); }));
+  TestAsynchronousInterception(
+      account_info, /*is_new_account=*/false, /*is_sync_signin=*/false,
+      SigninInterceptionHeuristicOutcome::kInterceptEnterpriseForced);
+}
+
+TEST_F(DiceWebSigninInterceptorTest, EnforceManagedAccountAsPrimaryManaged) {
+  AccountInfo account_info =
+      identity_test_env()->MakeAccountAvailable("alice@example.com");
+  MakeValidAccountInfo(&account_info);
+  account_info.hosted_domain = "example.com";
+  identity_test_env()->UpdateAccountInfoForAccount(account_info);
+
+  profile()->GetPrefs()->SetString(prefs::kManagedAccountsSigninRestriction,
+                                   "primary_account_strict");
+
+  EXPECT_CALL(*mock_delegate(), ShowEnterpriseProfileInterceptionDialog(
+                                    testing::_, testing::_, testing::_))
+      .WillOnce(testing::Invoke(
+          [](const std::string& email, base::OnceCallback<void(bool)> callback,
+             Browser* browser) { std::move(callback).Run(true); }));
+  TestAsynchronousInterception(
+      account_info, /*is_new_account=*/true, /*is_sync_signin=*/false,
+      SigninInterceptionHeuristicOutcome::kInterceptEnterpriseForced);
+}
+
+TEST_F(DiceWebSigninInterceptorTest,
+       EnforceManagedAccountAsPrimaryProfileSwitch) {
+  AccountInfo account_info =
+      identity_test_env()->MakeAccountAvailable("alice@example.com");
+  MakeValidAccountInfo(&account_info);
+  account_info.hosted_domain = "example.com";
+  identity_test_env()->UpdateAccountInfoForAccount(account_info);
+
+  profile()->GetPrefs()->SetBoolean(
+      prefs::kManagedAccountsSigninRestrictionScopeMachine, true);
+  profile()->GetPrefs()->SetString(prefs::kManagedAccountsSigninRestriction,
+                                   "primary_account_strict");
+
+  // Setup for profile switch interception.
+  Profile* profile_2 = CreateTestingProfile("Profile 2");
+  ProfileAttributesEntry* entry =
+      profile_attributes_storage()->GetProfileAttributesWithPath(
+          profile_2->GetPath());
+  ASSERT_NE(entry, nullptr);
+  entry->SetAuthInfo(account_info.gaia, base::UTF8ToUTF16(account_info.email),
+                     /*is_consented_primary_account=*/false);
+  TestAsynchronousInterception(account_info, /*is_new_account=*/true,
+                               /*is_sync_signin=*/false,
+                               SigninInterceptionHeuristicOutcome::
+                                   kInterceptEnterpriseForcedProfileSwitch);
+}
+
 TEST_F(DiceWebSigninInterceptorTest, ShouldShowEnterpriseBubbleWithoutUPA) {
   AccountInfo account_info_1 =
       identity_test_env()->MakeAccountAvailable("bob@example.com");
diff --git a/chrome/browser/signin/process_dice_header_delegate_impl_unittest.cc b/chrome/browser/signin/process_dice_header_delegate_impl_unittest.cc
index d362eba8..47fbdd15 100644
--- a/chrome/browser/signin/process_dice_header_delegate_impl_unittest.cc
+++ b/chrome/browser/signin/process_dice_header_delegate_impl_unittest.cc
@@ -50,6 +50,14 @@
     std::move(callback).Run(SigninInterceptionResult::kDeclined);
     return nullptr;
   }
+
+  void ShowEnterpriseProfileInterceptionDialog(
+      const std::string& email,
+      base::OnceCallback<void(bool)> callback,
+      Browser* browser) override {
+    std::move(callback).Run(false);
+  }
+
   void ShowProfileCustomizationBubble(Browser* browser) override {}
 };
 
diff --git a/chrome/browser/signin/signin_manager.cc b/chrome/browser/signin/signin_manager.cc
index bba04a5..10a3188 100644
--- a/chrome/browser/signin/signin_manager.cc
+++ b/chrome/browser/signin/signin_manager.cc
@@ -10,10 +10,7 @@
 
 SigninManager::SigninManager(signin::IdentityManager* identity_manager)
     : identity_manager_(identity_manager) {
-  if (identity_manager_->AreRefreshTokensLoaded()) {
-    UpdateUnconsentedPrimaryAccount();
-  }
-
+  UpdateUnconsentedPrimaryAccount();
   identity_manager_->AddObserver(this);
 }
 
@@ -22,6 +19,11 @@
 }
 
 void SigninManager::UpdateUnconsentedPrimaryAccount() {
+  // Only update the unconsented primary account only after accounts are loaded.
+  if (!identity_manager_->AreRefreshTokensLoaded()) {
+    return;
+  }
+
   absl::optional<CoreAccountInfo> account =
       ComputeUnconsentedPrimaryAccountInfo();
 
@@ -45,6 +47,8 @@
 
 absl::optional<CoreAccountInfo>
 SigninManager::ComputeUnconsentedPrimaryAccountInfo() const {
+  DCHECK(identity_manager_->AreRefreshTokensLoaded());
+
   // UPA is equal to the primary account with sync consent if it exists.
   if (identity_manager_->HasPrimaryAccount(signin::ConsentLevel::kSync)) {
     return identity_manager_->GetPrimaryAccountInfo(
@@ -57,10 +61,8 @@
   std::vector<gaia::ListedAccount> cookie_accounts =
       cookie_info.signed_in_accounts;
 
-  bool are_refresh_tokens_loaded = identity_manager_->AreRefreshTokensLoaded();
-
   // Fresh cookies and loaded tokens are needed to compute the UPA.
-  if (are_refresh_tokens_loaded && cookie_info.accounts_are_fresh) {
+  if (cookie_info.accounts_are_fresh) {
     // Cookies are fresh and tokens are loaded, UPA is the first account
     // in cookies if it exists and has a refresh token.
     if (cookie_accounts.empty()) {
@@ -91,20 +93,12 @@
   CoreAccountId current_account =
       identity_manager_->GetPrimaryAccountId(signin::ConsentLevel::kSignin);
 
-  if (are_refresh_tokens_loaded &&
-      !identity_manager_->HasAccountWithRefreshToken(current_account)) {
+  if (!identity_manager_->HasAccountWithRefreshToken(current_account)) {
     // Tokens are loaded, but the current UPA doesn't have a refresh token.
     // Clear the current UPA.
     return absl::nullopt;
   }
 
-  if (!are_refresh_tokens_loaded &&
-      unconsented_primary_account_revoked_during_load_) {
-    // Tokens are not loaded, but the current UPA's refresh token has been
-    // revoked. Clear the current UPA.
-    return absl::nullopt;
-  }
-
   if (cookie_info.accounts_are_fresh) {
     if (cookie_accounts.empty() || cookie_accounts[0].id != current_account) {
       // The current UPA is not the first in fresh cookies. It needs to be
@@ -148,12 +142,6 @@
 
 void SigninManager::OnRefreshTokenRemovedForAccount(
     const CoreAccountId& account_id) {
-  if (!identity_manager_->AreRefreshTokensLoaded() &&
-      identity_manager_->HasPrimaryAccount(signin::ConsentLevel::kSignin) &&
-      account_id == identity_manager_->GetPrimaryAccountId(
-                        signin::ConsentLevel::kSignin)) {
-    unconsented_primary_account_revoked_during_load_ = true;
-  }
   UpdateUnconsentedPrimaryAccount();
 }
 
diff --git a/chrome/browser/signin/signin_manager.h b/chrome/browser/signin/signin_manager.h
index f08c790..0bafc6c4 100644
--- a/chrome/browser/signin/signin_manager.h
+++ b/chrome/browser/signin/signin_manager.h
@@ -52,7 +52,6 @@
       const GoogleServiceAuthError& error) override;
 
   signin::IdentityManager* identity_manager_;
-  bool unconsented_primary_account_revoked_during_load_ = false;
 
   base::WeakPtrFactory<SigninManager> weak_ptr_factory_{this};
 
diff --git a/chrome/browser/signin/signin_manager_unittest.cc b/chrome/browser/signin/signin_manager_unittest.cc
index 482de60..45b76a4 100644
--- a/chrome/browser/signin/signin_manager_unittest.cc
+++ b/chrome/browser/signin/signin_manager_unittest.cc
@@ -302,15 +302,18 @@
             identity_manager()->GetPrimaryAccountInfo(ConsentLevel::kSignin));
   EXPECT_TRUE(observer().events().empty());
 
-  // Revoke the unconsented primary account while tokens are not loaded.
+  // Revoking the token of the unconsented primary account while the tokens
+  // are still loading does not change the unconsented primary account.
   identity_test_env()->RemoveRefreshTokenForAccount(main_account.account_id);
-  EXPECT_FALSE(identity_manager()->HasPrimaryAccount(ConsentLevel::kSignin));
-  ExpectUnconsentedPrimaryAccountClearedEvent(main_account);
+  EXPECT_EQ(main_account,
+            identity_manager()->GetPrimaryAccountInfo(ConsentLevel::kSignin));
+  EXPECT_TRUE(observer().events().empty());
 
-  // Finish the token load.
+  // Finish the token load should clear the primary account as the token of the
+  // primary account was revoked.
   identity_test_env()->ReloadAccountsFromDisk();
   EXPECT_FALSE(identity_manager()->HasPrimaryAccount(ConsentLevel::kSignin));
-  EXPECT_TRUE(observer().events().empty());
+  ExpectUnconsentedPrimaryAccountClearedEvent(main_account);
 }
 
 TEST_F(SigninManagerTest,
diff --git a/chrome/browser/signin/ui/android/BUILD.gn b/chrome/browser/signin/ui/android/BUILD.gn
index 6afe3a8..3e3b26a 100644
--- a/chrome/browser/signin/ui/android/BUILD.gn
+++ b/chrome/browser/signin/ui/android/BUILD.gn
@@ -82,9 +82,7 @@
     "java/res/layout/account_picker_bottom_sheet_view.xml",
     "java/res/layout/account_picker_dialog_body.xml",
     "java/res/layout/account_picker_new_account_row.xml",
-    "java/res/layout/account_picker_new_account_row_legacy.xml",
     "java/res/layout/account_picker_row.xml",
-    "java/res/layout/account_picker_row_legacy.xml",
     "java/res/layout/account_picker_state_auth_error.xml",
     "java/res/layout/account_picker_state_collapsed.xml",
     "java/res/layout/account_picker_state_expanded.xml",
diff --git a/chrome/browser/signin/ui/android/java/res/layout/account_picker_new_account_row_legacy.xml b/chrome/browser/signin/ui/android/java/res/layout/account_picker_new_account_row_legacy.xml
deleted file mode 100644
index 886b8858..0000000
--- a/chrome/browser/signin/ui/android/java/res/layout/account_picker_new_account_row_legacy.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright 2018 The Chromium Authors. All rights reserved.
-     Use of this source code is governed by a BSD-style license that can be
-     found in the LICENSE file. -->
-<TextView
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:drawablePadding="16dp"
-    android:gravity="center_vertical"
-    android:paddingStart="24dp"
-    android:paddingEnd="24dp"
-    android:paddingTop="8dp"
-    android:paddingBottom="8dp"
-    android:text="@string/signin_add_account"
-    android:textAppearance="@style/TextAppearance.TextMedium.Primary"
-    app:drawableStartCompat="@drawable/ic_add_circle_40dp" />
diff --git a/chrome/browser/signin/ui/android/java/res/layout/account_picker_row_legacy.xml b/chrome/browser/signin/ui/android/java/res/layout/account_picker_row_legacy.xml
deleted file mode 100644
index 32ec3f4..0000000
--- a/chrome/browser/signin/ui/android/java/res/layout/account_picker_row_legacy.xml
+++ /dev/null
@@ -1,52 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright 2018 The Chromium Authors. All rights reserved.
-     Use of this source code is governed by a BSD-style license that can be
-     found in the LICENSE file. -->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:tools="http://schemas.android.com/tools"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:gravity="center_vertical"
-    android:orientation="horizontal"
-    android:paddingStart="24dp"
-    android:paddingEnd="24dp"
-    android:paddingTop="8dp"
-    android:paddingBottom="8dp">
-    <ImageView
-        android:id="@+id/account_image"
-        android:layout_width="40dp"
-        android:layout_height="40dp"
-        android:layout_marginEnd="16dp"
-        tools:ignore="ContentDescription"
-        tools:src="@drawable/logo_avatar_anonymous"/>
-    <LinearLayout
-        android:layout_width="0dp"
-        android:layout_height="wrap_content"
-        android:layout_weight="1"
-        android:orientation="vertical">
-        <TextView
-            android:id="@+id/account_text_primary"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:gravity="center_vertical"
-            android:minHeight="20dp"
-            android:textAppearance="@style/TextAppearance.TextMedium.Primary"
-            tools:text="John Doe"/>
-        <TextView
-            android:id="@+id/account_text_secondary"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:gravity="center_vertical"
-            android:minHeight="20dp"
-            android:textAppearance="@style/TextAppearance.TextSmall.Secondary"
-            tools:text="john.doe@example.com"/>
-    </LinearLayout>
-    <ImageView
-        android:id="@+id/account_selection_mark"
-        android:layout_width="24dp"
-        android:layout_height="24dp"
-        android:layout_marginStart="16dp"
-        tools:ignore="ContentDescription"
-        tools:src="@drawable/ic_check_googblue_24dp"
-        tools:tint="@color/default_icon_color_blue" />
-</LinearLayout>
diff --git a/chrome/browser/signin/ui/android/java/src/org/chromium/chrome/browser/signin/ui/SigninPromoController.java b/chrome/browser/signin/ui/android/java/src/org/chromium/chrome/browser/signin/ui/SigninPromoController.java
index 32b89af..22094a849 100644
--- a/chrome/browser/signin/ui/android/java/src/org/chromium/chrome/browser/signin/ui/SigninPromoController.java
+++ b/chrome/browser/signin/ui/android/java/src/org/chromium/chrome/browser/signin/ui/SigninPromoController.java
@@ -60,8 +60,6 @@
 
     private @Nullable DisplayableProfileData mProfileData;
     private @Nullable ImpressionTracker mImpressionTracker;
-    private final OneShotImpressionListener mImpressionFilter =
-            new OneShotImpressionListener(this::recordSigninPromoImpression);
     private final @AccessPoint int mAccessPoint;
     private final @Nullable String mImpressionCountName;
     private final String mImpressionUserActionName;
@@ -111,7 +109,7 @@
     /**
      * Creates a new SigninPromoController.
      * @param accessPoint Specifies the AccessPoint from which the promo is to be shown.
-     * @param syncConsentActivityLauncher Launcher of {@link SigninActivity}.
+     * @param syncConsentActivityLauncher Launcher of {@link SyncConsentActivity}.
      */
     public SigninPromoController(
             @AccessPoint int accessPoint, SyncConsentActivityLauncher syncConsentActivityLauncher) {
@@ -278,20 +276,20 @@
     private void setupPromoView(PersonalizedSigninPromoView view,
             final @Nullable DisplayableProfileData profileData,
             final @Nullable OnDismissListener onDismissListener) {
-        detach();
+        if (mImpressionTracker != null) {
+            mImpressionTracker.setListener(null);
+            mImpressionTracker = null;
+        }
+        mImpressionTracker = new ImpressionTracker(view);
+        mImpressionTracker.setListener(
+                new OneShotImpressionListener(this::recordSigninPromoImpression));
+
         mProfileData = profileData;
         mWasDisplayed = true;
-
-        assert mImpressionTracker
-                == null : "detach() should be called before setting up a new view";
-        mImpressionTracker = new ImpressionTracker(view);
-        mImpressionTracker.setListener(mImpressionFilter);
-
-        final Context context = view.getContext();
         if (mProfileData == null) {
-            setupColdState(context, view);
+            setupColdState(view);
         } else {
-            setupHotState(context, view);
+            setupHotState(view);
         }
 
         if (onDismissListener != null) {
@@ -318,12 +316,8 @@
         }
     }
 
-    /** @return the resource used for the text displayed as promo description. */
-    public @StringRes int getDescriptionStringId() {
-        return mProfileData == null ? mDescriptionStringIdNoAccount : mDescriptionStringId;
-    }
-
-    private void setupColdState(final Context context, PersonalizedSigninPromoView view) {
+    private void setupColdState(PersonalizedSigninPromoView view) {
+        final Context context = view.getContext();
         view.getImage().setImageResource(R.drawable.chrome_sync_logo);
         setImageSize(context, view, R.dimen.signin_promo_cold_state_image_size);
 
@@ -335,7 +329,8 @@
         view.getSecondaryButton().setVisibility(View.GONE);
     }
 
-    private void setupHotState(final Context context, PersonalizedSigninPromoView view) {
+    private void setupHotState(PersonalizedSigninPromoView view) {
+        final Context context = view.getContext();
         Drawable accountImage = mProfileData.getImage();
         view.getImage().setImageDrawable(accountImage);
         setImageSize(context, view, R.dimen.signin_promo_account_image_size);
diff --git a/chrome/browser/signin/ui/android/java/src/org/chromium/chrome/browser/signin/ui/account_picker/AccountPickerBottomSheetTest.java b/chrome/browser/signin/ui/android/java/src/org/chromium/chrome/browser/signin/ui/account_picker/AccountPickerBottomSheetTest.java
index 76544657..aefe0e3 100644
--- a/chrome/browser/signin/ui/android/java/src/org/chromium/chrome/browser/signin/ui/account_picker/AccountPickerBottomSheetTest.java
+++ b/chrome/browser/signin/ui/android/java/src/org/chromium/chrome/browser/signin/ui/account_picker/AccountPickerBottomSheetTest.java
@@ -78,7 +78,6 @@
  */
 @RunWith(ChromeJUnit4ClassRunner.class)
 @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
-@Features.EnableFeatures({ChromeFeatureList.MOBILE_IDENTITY_CONSISTENCY})
 @Features.DisableFeatures({ChromeFeatureList.DEPRECATE_MENAGERIE_API})
 @Batch(Batch.PER_CLASS)
 public class AccountPickerBottomSheetTest {
diff --git a/chrome/browser/signin/ui/android/java/src/org/chromium/chrome/browser/signin/ui/account_picker/AccountPickerCoordinator.java b/chrome/browser/signin/ui/android/java/src/org/chromium/chrome/browser/signin/ui/account_picker/AccountPickerCoordinator.java
index eb1603c02..f26851f 100644
--- a/chrome/browser/signin/ui/android/java/src/org/chromium/chrome/browser/signin/ui/account_picker/AccountPickerCoordinator.java
+++ b/chrome/browser/signin/ui/android/java/src/org/chromium/chrome/browser/signin/ui/account_picker/AccountPickerCoordinator.java
@@ -7,7 +7,6 @@
 import androidx.annotation.MainThread;
 import androidx.recyclerview.widget.RecyclerView;
 
-import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.signin.ui.R;
 import org.chromium.chrome.browser.signin.ui.account_picker.AccountPickerProperties.AddAccountRowProperties;
 import org.chromium.chrome.browser.signin.ui.account_picker.AccountPickerProperties.ItemType;
@@ -56,17 +55,11 @@
 
         SimpleRecyclerViewAdapter adapter = new SimpleRecyclerViewAdapter(listModel);
 
-        final boolean isMobileIdentityConsistencyEnabled =
-                ChromeFeatureList.isEnabled(ChromeFeatureList.MOBILE_IDENTITY_CONSISTENCY);
         adapter.registerType(ItemType.ADD_ACCOUNT_ROW,
-                new LayoutViewBuilder<>(isMobileIdentityConsistencyEnabled
-                                ? R.layout.account_picker_new_account_row
-                                : R.layout.account_picker_new_account_row_legacy),
+                new LayoutViewBuilder<>(R.layout.account_picker_new_account_row),
                 new OnClickListenerViewBinder(AddAccountRowProperties.ON_CLICK_LISTENER));
         adapter.registerType(ItemType.EXISTING_ACCOUNT_ROW,
-                new LayoutViewBuilder<>(isMobileIdentityConsistencyEnabled
-                                ? R.layout.account_picker_row
-                                : R.layout.account_picker_row_legacy),
+                new LayoutViewBuilder<>(R.layout.account_picker_row),
                 new ExistingAccountRowViewBinder());
 
         view.setAdapter(adapter);
diff --git a/chrome/browser/signin/ui/android/java/src/org/chromium/chrome/browser/signin/ui/account_picker/AccountPickerDialogTest.java b/chrome/browser/signin/ui/android/java/src/org/chromium/chrome/browser/signin/ui/account_picker/AccountPickerDialogTest.java
index 923369a..f49a263 100644
--- a/chrome/browser/signin/ui/android/java/src/org/chromium/chrome/browser/signin/ui/account_picker/AccountPickerDialogTest.java
+++ b/chrome/browser/signin/ui/android/java/src/org/chromium/chrome/browser/signin/ui/account_picker/AccountPickerDialogTest.java
@@ -53,8 +53,7 @@
  */
 @RunWith(ChromeJUnit4ClassRunner.class)
 @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
-@DisableFeatures(
-        {ChromeFeatureList.MOBILE_IDENTITY_CONSISTENCY, ChromeFeatureList.DEPRECATE_MENAGERIE_API})
+@DisableFeatures(ChromeFeatureList.DEPRECATE_MENAGERIE_API)
 @Batch(Batch.PER_CLASS)
 public class AccountPickerDialogTest extends DummyUiActivityTestCase {
     @Rule
@@ -113,7 +112,7 @@
     @Test
     @MediumTest
     public void testAddAccount() {
-        onView(withText(R.string.signin_add_account)).perform(click());
+        onView(withText(R.string.signin_add_account_to_device)).perform(click());
         verify(mListenerMock).addAccount();
     }
 
@@ -135,16 +134,6 @@
     @Test
     @LargeTest
     @Feature("RenderTest")
-    public void testAccountPickerDialogViewLegacy() throws IOException {
-        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
-        mRenderTestRule.render(
-                mCoordinator.getAccountPickerViewForTests(), "account_picker_dialog_legacy");
-    }
-
-    @Test
-    @LargeTest
-    @Feature("RenderTest")
-    @Features.EnableFeatures({ChromeFeatureList.MOBILE_IDENTITY_CONSISTENCY})
     public void testAccountPickerDialogView() throws IOException {
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
         mRenderTestRule.render(
diff --git a/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/Tab.java b/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/Tab.java
index 8e58a68..7bad7cdf 100644
--- a/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/Tab.java
+++ b/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/Tab.java
@@ -113,15 +113,6 @@
     int getId();
 
     /**
-     * @return The URL that is loaded in the current tab. This may not be the same as
-     *         the last committed URL if a new navigation is in progress.
-     *
-     * @deprecated Please use {@link #getUrl()} instead.
-     */
-    @Deprecated
-    String getUrlString();
-
-    /**
      * @return Parameters that should be used for a lazily loaded Tab.  May be null.
      */
     LoadUrlParams getPendingLoadParams();
diff --git a/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/state/StorePersistedTabData.java b/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/state/StorePersistedTabData.java
index e0729f2..0dcf15fb 100644
--- a/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/state/StorePersistedTabData.java
+++ b/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/state/StorePersistedTabData.java
@@ -288,8 +288,8 @@
                                         build(tab, endpointResponse.getResponseString()));
                             },
                             Profile.getLastUsedRegularProfile(), PERSISTED_TAB_DATA_ID,
-                            String.format(Locale.US, ENDPOINT, tab.getUrlString()), HTTPS_METHOD,
-                            CONTENT_TYPE, SCOPES, EMPTY_POST_DATA, TIMEOUT_MS);
+                            String.format(Locale.US, ENDPOINT, tab.getUrl().getSpec()),
+                            HTTPS_METHOD, CONTENT_TYPE, SCOPES, EMPTY_POST_DATA, TIMEOUT_MS);
                 },
                 StorePersistedTabData.class, callback);
     }
diff --git a/chrome/browser/ui/android/color_chooser_dialog_android.cc b/chrome/browser/ui/android/color_chooser_dialog_android.cc
index bf8fb29..58063d0 100644
--- a/chrome/browser/ui/android/color_chooser_dialog_android.cc
+++ b/chrome/browser/ui/android/color_chooser_dialog_android.cc
@@ -4,13 +4,16 @@
 
 #include "chrome/browser/ui/color_chooser.h"
 
+#include "content/public/browser/color_chooser.h"
+
 // The actual android color chooser is at
 // components/embedder_support/android/delegate/color_chooser_android.cc
 
 namespace chrome {
 
-content::ColorChooser* ShowColorChooser(content::WebContents* web_contents,
-                                        SkColor initial_color) {
+std::unique_ptr<content::ColorChooser> ShowColorChooser(
+    content::WebContents* web_contents,
+    SkColor initial_color) {
   return NULL;
 }
 
diff --git a/chrome/browser/ui/android/layouts/java/src/org/chromium/chrome/browser/layouts/LayoutStateProvider.java b/chrome/browser/ui/android/layouts/java/src/org/chromium/chrome/browser/layouts/LayoutStateProvider.java
index 20735887..bd9a9ac6b 100644
--- a/chrome/browser/ui/android/layouts/java/src/org/chromium/chrome/browser/layouts/LayoutStateProvider.java
+++ b/chrome/browser/ui/android/layouts/java/src/org/chromium/chrome/browser/layouts/LayoutStateProvider.java
@@ -60,13 +60,6 @@
     boolean isLayoutVisible(@LayoutType int layoutType);
 
     /**
-     * Get the type of the layout that is currently active.
-     * @return The {@link LayoutType} of the active layout.
-     */
-    @LayoutType
-    int getActiveLayoutType();
-
-    /**
      * @param listener Registers {@code listener} for all layout status changes.
      */
     void addObserver(LayoutStateObserver listener);
diff --git a/chrome/browser/ui/android/layouts/java/src/org/chromium/chrome/browser/layouts/LayoutType.java b/chrome/browser/ui/android/layouts/java/src/org/chromium/chrome/browser/layouts/LayoutType.java
index fe26a24f..212219b8 100644
--- a/chrome/browser/ui/android/layouts/java/src/org/chromium/chrome/browser/layouts/LayoutType.java
+++ b/chrome/browser/ui/android/layouts/java/src/org/chromium/chrome/browser/layouts/LayoutType.java
@@ -13,11 +13,10 @@
  * The type info of the Layout. These types are bit flags, so they can be or-ed together to test for
  * multiple.
  */
-@IntDef({LayoutType.NONE, LayoutType.BROWSING, LayoutType.TAB_SWITCHER, LayoutType.TOOLBAR_SWIPE,
+@IntDef({LayoutType.BROWSING, LayoutType.TAB_SWITCHER, LayoutType.TOOLBAR_SWIPE,
         LayoutType.SIMPLE_ANIMATION})
 @Retention(RetentionPolicy.SOURCE)
 public @interface LayoutType {
-    int NONE = 0;
     int BROWSING = 1;
     int TAB_SWITCHER = 2;
     int TOOLBAR_SWIPE = 4;
diff --git a/chrome/browser/ui/android/native_page/BUILD.gn b/chrome/browser/ui/android/native_page/BUILD.gn
index cf88512..c678449 100644
--- a/chrome/browser/ui/android/native_page/BUILD.gn
+++ b/chrome/browser/ui/android/native_page/BUILD.gn
@@ -16,6 +16,7 @@
     "//components/embedder_support/android:util_java",
     "//content/public/android:content_java",
     "//third_party/androidx:androidx_annotation_annotation_java",
+    "//url:gurl_java",
   ]
 }
 
diff --git a/chrome/browser/ui/android/native_page/DEPS b/chrome/browser/ui/android/native_page/DEPS
index 7a742fc..68105d6 100644
--- a/chrome/browser/ui/android/native_page/DEPS
+++ b/chrome/browser/ui/android/native_page/DEPS
@@ -5,4 +5,5 @@
   "+components/browser_ui/styles/android",
   "+components/embedder_support/android",
   "+content/public/android",
+  "+url",
 ]
diff --git a/chrome/browser/ui/android/native_page/java/src/org/chromium/chrome/browser/ui/native_page/NativePage.java b/chrome/browser/ui/android/native_page/java/src/org/chromium/chrome/browser/ui/native_page/NativePage.java
index c02aea2..ba2539b 100644
--- a/chrome/browser/ui/android/native_page/java/src/org/chromium/chrome/browser/ui/native_page/NativePage.java
+++ b/chrome/browser/ui/android/native_page/java/src/org/chromium/chrome/browser/ui/native_page/NativePage.java
@@ -11,6 +11,7 @@
 import androidx.annotation.IntDef;
 
 import org.chromium.components.embedder_support.util.UrlConstants;
+import org.chromium.url.GURL;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -115,16 +116,23 @@
      * @return Whether the host and the scheme of the passed in URL matches one of the supported
      *         native pages.
      */
+    @Deprecated // Use GURL-variant instead.
     public static boolean isNativePageUrl(String url, boolean isIncognito) {
         return nativePageType(url, null, isIncognito) != NativePageType.NONE;
     }
 
+    public static boolean isNativePageUrl(GURL url, boolean isIncognito) {
+        return url != null
+                && nativePageType(url.getSpec(), null, isIncognito) != NativePageType.NONE;
+    }
+
     /**
      * @param url The URL to be checked.
      * @param candidatePage NativePage to return as result if the host is matched.
      * @param isIncognito Whether the page will be displayed in incognito mode.
      * @return Type of the native page defined in {@link NativePageType}.
      */
+    // TODO(crbug/783819) - Convert to using GURL.
     public static @NativePageType int nativePageType(
             String url, NativePage candidatePage, boolean isIncognito) {
         if (url == null) return NativePageType.NONE;
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/load_progress/LoadProgressMediator.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/load_progress/LoadProgressMediator.java
index 196f6c37..6579011d 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/load_progress/LoadProgressMediator.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/load_progress/LoadProgressMediator.java
@@ -47,7 +47,7 @@
                     return;
                 }
 
-                if (NativePage.isNativePageUrl(navigation.getUrl().getSpec(), tab.isIncognito())) {
+                if (NativePage.isNativePageUrl(navigation.getUrl(), tab.isIncognito())) {
                     finishLoadProgress(false);
                     return;
                 }
@@ -71,8 +71,8 @@
 
             @Override
             public void onLoadProgressChanged(Tab tab, float progress) {
-                if (UrlUtilities.isNTPUrl(tab.getUrlString())
-                        || NativePage.isNativePageUrl(tab.getUrlString(), tab.isIncognito())) {
+                if (tab.getUrl() == null || UrlUtilities.isNTPUrl(tab.getUrl())
+                        || NativePage.isNativePageUrl(tab.getUrl(), tab.isIncognito())) {
                     return;
                 }
 
@@ -123,7 +123,7 @@
         }
 
         if (tab.isLoading()) {
-            if (NativePage.isNativePageUrl(tab.getUrlString(), tab.isIncognito())) {
+            if (NativePage.isNativePageUrl(tab.getUrl(), tab.isIncognito())) {
                 finishLoadProgress(false);
             } else {
                 startLoadProgress();
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/load_progress/LoadProgressMediatorTest.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/load_progress/LoadProgressMediatorTest.java
index 7880969..7d4b16e 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/load_progress/LoadProgressMediatorTest.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/load_progress/LoadProgressMediatorTest.java
@@ -40,7 +40,7 @@
 @RunWith(BaseJUnit4ClassRunner.class)
 @Batch(Batch.UNIT_TESTS)
 public class LoadProgressMediatorTest {
-    private static final String URL_1 = "http://starting.url";
+    private static final GURL URL_1 = new GURL("http://starting.url");
     private static final GURL NATIVE_PAGE_URL = new GURL("chrome-native://newtab");
 
     @Mock
@@ -60,6 +60,7 @@
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         mModel = new PropertyModel(LoadProgressProperties.ALL_KEYS);
+        when(mTab.getUrl()).thenReturn(URL_1);
     }
 
     private void initMediator() {
@@ -79,7 +80,7 @@
         assertEquals(mModel.get(LoadProgressProperties.COMPLETION_STATE),
                 CompletionState.FINISHED_DONT_ANIMATE);
 
-        NavigationHandle navigation = new NavigationHandle(0, new GURL(URL_1), true, false, false);
+        NavigationHandle navigation = new NavigationHandle(0, URL_1, true, false, false);
         mTabObserver.onDidStartNavigation(mTab, navigation);
         assertEquals(
                 mModel.get(LoadProgressProperties.COMPLETION_STATE), CompletionState.UNFINISHED);
@@ -115,7 +116,7 @@
     @UiThreadTest
     public void switchToLoadedTab() {
         initMediator();
-        NavigationHandle navigation = new NavigationHandle(0, new GURL(URL_1), true, false, false);
+        NavigationHandle navigation = new NavigationHandle(0, URL_1, true, false, false);
         mTabObserver.onDidStartNavigation(mTab, navigation);
         assertEquals(
                 mModel.get(LoadProgressProperties.COMPLETION_STATE), CompletionState.UNFINISHED);
@@ -134,7 +135,7 @@
     public void loadNativePage() {
         initMediator();
         doReturn(0.1f).when(mTab).getProgress();
-        NavigationHandle navigation = new NavigationHandle(0, new GURL(URL_1), true, false, false);
+        NavigationHandle navigation = new NavigationHandle(0, URL_1, true, false, false);
         mTabObserver.onDidStartNavigation(mTab, navigation);
         assertEquals(
                 mModel.get(LoadProgressProperties.COMPLETION_STATE), CompletionState.UNFINISHED);
@@ -151,7 +152,7 @@
     @UiThreadTest
     public void switchToTabWithNativePage() {
         initMediator();
-        NavigationHandle navigation = new NavigationHandle(0, new GURL(URL_1), true, false, false);
+        NavigationHandle navigation = new NavigationHandle(0, URL_1, true, false, false);
         mTabObserver.onDidStartNavigation(mTab, navigation);
         assertEquals(
                 mModel.get(LoadProgressProperties.COMPLETION_STATE), CompletionState.UNFINISHED);
@@ -172,7 +173,7 @@
     @UiThreadTest
     public void pageCrashes() {
         initMediator();
-        NavigationHandle navigation = new NavigationHandle(0, new GURL(URL_1), true, false, false);
+        NavigationHandle navigation = new NavigationHandle(0, URL_1, true, false, false);
         mTabObserver.onDidStartNavigation(mTab, navigation);
         assertEquals(
                 mModel.get(LoadProgressProperties.COMPLETION_STATE), CompletionState.UNFINISHED);
@@ -216,7 +217,7 @@
     @UiThreadTest
     public void testSameDocumentLoad_afterFinishedLoading() {
         initMediator();
-        GURL gurl = new GURL(URL_1);
+        GURL gurl = URL_1;
         assertEquals(mModel.get(LoadProgressProperties.COMPLETION_STATE),
                 CompletionState.FINISHED_DONT_ANIMATE);
 
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/TopToolbarOverlayMediator.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/TopToolbarOverlayMediator.java
index f4ec1fe..2d199ee 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/TopToolbarOverlayMediator.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/TopToolbarOverlayMediator.java
@@ -85,9 +85,6 @@
         mModel = model;
         mIsVisibilityManuallyControlled = manualVisibilityControl;
 
-        mIsOnValidLayout = (mLayoutStateProvider.getActiveLayoutType() & layoutsToShowOn) > 0;
-        updateVisibility();
-
         mSceneChangeObserver = new LayoutStateObserver() {
             @Override
             public void onStartedShowing(@LayoutType int layout, boolean showToolbar) {
diff --git a/chrome/browser/ui/apps/chrome_app_delegate.cc b/chrome/browser/ui/apps/chrome_app_delegate.cc
index f44d8e12..dce5802 100644
--- a/chrome/browser/ui/apps/chrome_app_delegate.cc
+++ b/chrome/browser/ui/apps/chrome_app_delegate.cc
@@ -37,6 +37,7 @@
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
+#include "content/public/browser/color_chooser.h"
 #include "content/public/browser/file_select_listener.h"
 #include "content/public/browser/host_zoom_map.h"
 #include "content/public/browser/media_stream_request.h"
@@ -294,7 +295,7 @@
                          target_url, disposition, initial_rect);
 }
 
-content::ColorChooser* ChromeAppDelegate::ShowColorChooser(
+std::unique_ptr<content::ColorChooser> ChromeAppDelegate::ShowColorChooser(
     content::WebContents* web_contents,
     SkColor initial_color) {
   return chrome::ShowColorChooser(web_contents, initial_color);
diff --git a/chrome/browser/ui/apps/chrome_app_delegate.h b/chrome/browser/ui/apps/chrome_app_delegate.h
index fa96b6f..245525c 100644
--- a/chrome/browser/ui/apps/chrome_app_delegate.h
+++ b/chrome/browser/ui/apps/chrome_app_delegate.h
@@ -55,8 +55,9 @@
                       WindowOpenDisposition disposition,
                       const gfx::Rect& initial_rect,
                       bool user_gesture) override;
-  content::ColorChooser* ShowColorChooser(content::WebContents* web_contents,
-                                          SkColor initial_color) override;
+  std::unique_ptr<content::ColorChooser> ShowColorChooser(
+      content::WebContents* web_contents,
+      SkColor initial_color) override;
   void RunFileChooser(content::RenderFrameHost* render_frame_host,
                       scoped_refptr<content::FileSelectListener> listener,
                       const blink::mojom::FileChooserParams& params) override;
diff --git a/chrome/browser/ui/ash/holding_space/holding_space_client_impl.cc b/chrome/browser/ui/ash/holding_space/holding_space_client_impl.cc
index 93730a0a..c0803f0f 100644
--- a/chrome/browser/ui/ash/holding_space/holding_space_client_impl.cc
+++ b/chrome/browser/ui/ash/holding_space/holding_space_client_impl.cc
@@ -67,6 +67,11 @@
 
 HoldingSpaceClientImpl::~HoldingSpaceClientImpl() = default;
 
+void HoldingSpaceClientImpl::AddDiagnosticsLog(
+    const base::FilePath& file_path) {
+  GetHoldingSpaceKeyedService(profile_)->AddDiagnosticsLog(file_path);
+}
+
 void HoldingSpaceClientImpl::AddScreenshot(const base::FilePath& file_path) {
   GetHoldingSpaceKeyedService(profile_)->AddScreenshot(file_path);
 }
diff --git a/chrome/browser/ui/ash/holding_space/holding_space_client_impl.h b/chrome/browser/ui/ash/holding_space/holding_space_client_impl.h
index 1c1c1ec..5ea0d06 100644
--- a/chrome/browser/ui/ash/holding_space/holding_space_client_impl.h
+++ b/chrome/browser/ui/ash/holding_space/holding_space_client_impl.h
@@ -25,6 +25,7 @@
   ~HoldingSpaceClientImpl() override;
 
   // HoldingSpaceClient:
+  void AddDiagnosticsLog(const base::FilePath& file_path) override;
   void AddScreenRecording(const base::FilePath& file_path) override;
   void AddScreenshot(const base::FilePath& file_path) override;
   void CancelItems(const std::vector<const HoldingSpaceItem*>& items) override;
diff --git a/chrome/browser/ui/ash/holding_space/holding_space_client_impl_browsertest.cc b/chrome/browser/ui/ash/holding_space/holding_space_client_impl_browsertest.cc
index b0ae2743..d2411b41 100644
--- a/chrome/browser/ui/ash/holding_space/holding_space_client_impl_browsertest.cc
+++ b/chrome/browser/ui/ash/holding_space/holding_space_client_impl_browsertest.cc
@@ -81,6 +81,28 @@
 
 using HoldingSpaceClientImplTest = HoldingSpaceBrowserTestBase;
 
+// Verifies that `HoldingSpaceClient::AddDiagnosticsLog()` works as intended.
+IN_PROC_BROWSER_TEST_F(HoldingSpaceClientImplTest, AddDiagnosticsLog) {
+  ASSERT_TRUE(HoldingSpaceController::Get());
+
+  auto* holding_space_client = HoldingSpaceController::Get()->client();
+  ASSERT_TRUE(holding_space_client);
+  auto* holding_space_model = HoldingSpaceController::Get()->model();
+  ASSERT_TRUE(holding_space_model);
+
+  // Create a diagnostics log item and verify that it is in the holding space.
+
+  ASSERT_EQ(0u, holding_space_model->items().size());
+  base::FilePath log_path = TestFile(GetProfile(), kTextFilePath);
+  holding_space_client->AddDiagnosticsLog(log_path);
+  ASSERT_EQ(1u, holding_space_model->items().size());
+  HoldingSpaceItem* diagnostics_log_item =
+      holding_space_model->items()[0].get();
+  EXPECT_EQ(diagnostics_log_item->type(),
+            HoldingSpaceItem::Type::kDiagnosticsLog);
+  EXPECT_EQ(diagnostics_log_item->file_path(), log_path);
+}
+
 // Verifies that `HoldingSpaceClient::CopyImageToClipboard()` works as intended
 // when attempting to copy both image backed and non-image backed holding space
 // items.
diff --git a/chrome/browser/ui/ash/holding_space/holding_space_keyed_service.h b/chrome/browser/ui/ash/holding_space/holding_space_keyed_service.h
index 1532126..0659e9b 100644
--- a/chrome/browser/ui/ash/holding_space/holding_space_keyed_service.h
+++ b/chrome/browser/ui/ash/holding_space/holding_space_keyed_service.h
@@ -128,9 +128,7 @@
   // Returns the `profile_` associated with this service.
   Profile* profile() { return profile_; }
 
-  const HoldingSpaceClient* client_for_testing() const {
-    return &holding_space_client_;
-  }
+  HoldingSpaceClient* client() { return &holding_space_client_; }
 
   const HoldingSpaceModel* model_for_testing() const {
     return &holding_space_model_;
diff --git a/chrome/browser/ui/ash/holding_space/holding_space_keyed_service_unittest.cc b/chrome/browser/ui/ash/holding_space/holding_space_keyed_service_unittest.cc
index 5903e2c..081a7070 100644
--- a/chrome/browser/ui/ash/holding_space/holding_space_keyed_service_unittest.cc
+++ b/chrome/browser/ui/ash/holding_space/holding_space_keyed_service_unittest.cc
@@ -550,7 +550,7 @@
 
   // Just creating a secondary profile shouldn't change the active client/model.
   EXPECT_EQ(HoldingSpaceController::Get()->client(),
-            primary_holding_space_service->client_for_testing());
+            primary_holding_space_service->client());
   EXPECT_EQ(HoldingSpaceController::Get()->model(),
             primary_holding_space_service->model_for_testing());
 
@@ -558,7 +558,7 @@
   // support).
   ActivateSecondaryProfile();
   EXPECT_EQ(HoldingSpaceController::Get()->client(),
-            secondary_holding_space_service->client_for_testing());
+            secondary_holding_space_service->client());
   EXPECT_EQ(HoldingSpaceController::Get()->model(),
             secondary_holding_space_service->model_for_testing());
 }
diff --git a/chrome/browser/ui/browser.cc b/chrome/browser/ui/browser.cc
index 268f654..335611d 100644
--- a/chrome/browser/ui/browser.cc
+++ b/chrome/browser/ui/browser.cc
@@ -193,6 +193,7 @@
 #include "components/viz/common/surfaces/surface_id.h"
 #include "components/web_modal/web_contents_modal_dialog_manager.h"
 #include "components/zoom/zoom_controller.h"
+#include "content/public/browser/color_chooser.h"
 #include "content/public/browser/devtools_agent_host.h"
 #include "content/public/browser/file_select_listener.h"
 #include "content/public/browser/invalidate_type.h"
@@ -1885,7 +1886,7 @@
   return guest_view && guest_view->PluginDoSave();
 }
 
-content::ColorChooser* Browser::OpenColorChooser(
+std::unique_ptr<content::ColorChooser> Browser::OpenColorChooser(
     WebContents* web_contents,
     SkColor initial_color,
     const std::vector<blink::mojom::ColorSuggestionPtr>& suggestions) {
diff --git a/chrome/browser/ui/browser.h b/chrome/browser/ui/browser.h
index 946c447a..25422f4 100644
--- a/chrome/browser/ui/browser.h
+++ b/chrome/browser/ui/browser.h
@@ -824,7 +824,7 @@
   content::JavaScriptDialogManager* GetJavaScriptDialogManager(
       content::WebContents* source) override;
   bool GuestSaveFrame(content::WebContents* guest_web_contents) override;
-  content::ColorChooser* OpenColorChooser(
+  std::unique_ptr<content::ColorChooser> OpenColorChooser(
       content::WebContents* web_contents,
       SkColor color,
       const std::vector<blink::mojom::ColorSuggestionPtr>& suggestions)
diff --git a/chrome/browser/ui/cocoa/color_chooser_mac.h b/chrome/browser/ui/cocoa/color_chooser_mac.h
index 5bcbf720..de77c98 100644
--- a/chrome/browser/ui/cocoa/color_chooser_mac.h
+++ b/chrome/browser/ui/cocoa/color_chooser_mac.h
@@ -21,8 +21,11 @@
   // call End() when done to free it. Each call to Open() returns a new
   // instance after freeing the previous one (i.e. it does not reuse the
   // previous instance even if it still exists).
-  static ColorChooserMac* Open(content::WebContents* web_contents,
-                               SkColor initial_color);
+  static std::unique_ptr<ColorChooserMac> Open(
+      content::WebContents* web_contents,
+      SkColor initial_color);
+
+  ~ColorChooserMac() override;
 
   // content::ColorChooser.
   void SetSelectedColor(SkColor color) override;
@@ -35,8 +38,6 @@
 
   ColorChooserMac(content::WebContents* tab, SkColor initial_color);
 
-  ~ColorChooserMac() override;
-
   // The web contents invoking the color chooser.  No ownership because it will
   // outlive this class.
   content::WebContents* web_contents_;
diff --git a/chrome/browser/ui/cocoa/color_chooser_mac.mm b/chrome/browser/ui/cocoa/color_chooser_mac.mm
index b4d20f03c..94aefdc 100644
--- a/chrome/browser/ui/cocoa/color_chooser_mac.mm
+++ b/chrome/browser/ui/cocoa/color_chooser_mac.mm
@@ -5,6 +5,7 @@
 #import "chrome/browser/ui/cocoa/color_chooser_mac.h"
 
 #include "base/check_op.h"
+#include "base/memory/ptr_util.h"
 #include "chrome/browser/ui/color_chooser.h"
 #include "components/remote_cocoa/app_shim/color_panel_bridge.h"
 #include "components/remote_cocoa/browser/application_host.h"
@@ -18,15 +19,16 @@
 }  // namespace
 
 // static
-ColorChooserMac* ColorChooserMac::Open(content::WebContents* web_contents,
-                                       SkColor initial_color) {
+std::unique_ptr<ColorChooserMac> ColorChooserMac::Open(
+    content::WebContents* web_contents,
+    SkColor initial_color) {
   if (g_current_color_chooser)
     g_current_color_chooser->End();
   DCHECK(!g_current_color_chooser);
   // Note that WebContentsImpl::ColorChooser ultimately takes ownership (and
   // deletes) the returned pointer.
   g_current_color_chooser = new ColorChooserMac(web_contents, initial_color);
-  return g_current_color_chooser;
+  return base::WrapUnique(g_current_color_chooser);
 }
 
 ColorChooserMac::ColorChooserMac(content::WebContents* web_contents,
@@ -76,8 +78,9 @@
 }
 
 namespace chrome {
-content::ColorChooser* ShowColorChooser(content::WebContents* web_contents,
-                                        SkColor initial_color) {
+std::unique_ptr<content::ColorChooser> ShowColorChooser(
+    content::WebContents* web_contents,
+    SkColor initial_color) {
   return ColorChooserMac::Open(web_contents, initial_color);
 }
 }  // namepace chrome
diff --git a/chrome/browser/ui/cocoa/color_panel_cocoa_unittest.mm b/chrome/browser/ui/cocoa/color_panel_cocoa_unittest.mm
index 7dedf159..ab824b1 100644
--- a/chrome/browser/ui/cocoa/color_panel_cocoa_unittest.mm
+++ b/chrome/browser/ui/cocoa/color_panel_cocoa_unittest.mm
@@ -37,7 +37,7 @@
     EXPECT_TRUE([nscolor_panel respondsToSelector:@selector(__target)]);
 
     // Create a ColorPanelCocoa.
-    ColorChooserMac* color_chooser_mac =
+    std::unique_ptr<ColorChooserMac> color_chooser_mac =
         ColorChooserMac::Open(nullptr, SK_ColorBLACK);
     base::RunLoop().RunUntilIdle();
 
@@ -60,7 +60,7 @@
   // Create a ColorChooserMac and confirm the NSColorPanel gets its initial
   // color.
   SkColor initial_color = SK_ColorBLACK;
-  ColorChooserMac* color_chooser_mac =
+  std::unique_ptr<ColorChooserMac> color_chooser_mac =
       ColorChooserMac::Open(nullptr, SK_ColorBLACK);
   base::RunLoop().RunUntilIdle();
 
diff --git a/chrome/browser/ui/color_chooser.h b/chrome/browser/ui/color_chooser.h
index 4b9fa4d..dbd793d3 100644
--- a/chrome/browser/ui/color_chooser.h
+++ b/chrome/browser/ui/color_chooser.h
@@ -5,6 +5,8 @@
 #ifndef CHROME_BROWSER_UI_COLOR_CHOOSER_H_
 #define CHROME_BROWSER_UI_COLOR_CHOOSER_H_
 
+#include <memory>
+
 #include "third_party/skia/include/core/SkColor.h"
 
 namespace content {
@@ -14,8 +16,9 @@
 
 namespace chrome {
 // Shows a color chooser that reports to the given WebContents.
-content::ColorChooser* ShowColorChooser(content::WebContents* web_contents,
-                                        SkColor initial_color);
+std::unique_ptr<content::ColorChooser> ShowColorChooser(
+    content::WebContents* web_contents,
+    SkColor initial_color);
 }  // namespace chrome
 
 #endif  // CHROME_BROWSER_UI_COLOR_CHOOSER_H_
diff --git a/chrome/browser/ui/signin/dice_web_signin_interceptor_delegate.cc b/chrome/browser/ui/signin/dice_web_signin_interceptor_delegate.cc
index 2a30ffc..ce8f4c0 100644
--- a/chrome/browser/ui/signin/dice_web_signin_interceptor_delegate.cc
+++ b/chrome/browser/ui/signin/dice_web_signin_interceptor_delegate.cc
@@ -8,6 +8,7 @@
 
 #include "base/callback.h"
 #include "chrome/browser/ui/browser_finder.h"
+#include "chrome/browser/ui/webui/signin/dice_turn_sync_on_helper.h"
 
 DiceWebSigninInterceptorDelegate::DiceWebSigninInterceptorDelegate() = default;
 
@@ -31,3 +32,24 @@
     Browser* browser) {
   ShowProfileCustomizationBubbleInternal(browser);
 }
+
+void DiceWebSigninInterceptorDelegate::ShowEnterpriseProfileInterceptionDialog(
+    const std::string& email,
+    base::OnceCallback<void(bool)> callback,
+    Browser* browser) {
+  // TODO (crbug/1163117): Replace this temporary solution with the spaces
+  // enterprise welcome screen inside a dialog.
+  DiceTurnSyncOnHelper::Delegate::ShowEnterpriseAccountConfirmationForBrowser(
+      email, true,
+      base::BindOnce(
+          [](base::OnceCallback<void(bool)> callback,
+             DiceTurnSyncOnHelper::SigninChoice choice) {
+            std::move(callback).Run(
+                choice == DiceTurnSyncOnHelper::SigninChoice::
+                              SIGNIN_CHOICE_CONTINUE ||
+                choice == DiceTurnSyncOnHelper::SigninChoice::
+                              SIGNIN_CHOICE_NEW_PROFILE);
+          },
+          std::move(callback)),
+      browser);
+}
diff --git a/chrome/browser/ui/signin/dice_web_signin_interceptor_delegate.h b/chrome/browser/ui/signin/dice_web_signin_interceptor_delegate.h
index b796032..1b0be3bd 100644
--- a/chrome/browser/ui/signin/dice_web_signin_interceptor_delegate.h
+++ b/chrome/browser/ui/signin/dice_web_signin_interceptor_delegate.h
@@ -29,6 +29,11 @@
       base::OnceCallback<void(SigninInterceptionResult)> callback) override;
   void ShowProfileCustomizationBubble(Browser* browser) override;
 
+  void ShowEnterpriseProfileInterceptionDialog(
+      const std::string& email,
+      base::OnceCallback<void(bool)> callback,
+      Browser* browser) override;
+
  private:
   // Implemented in dice_web_signin_interception_bubble_view.cc
   std::unique_ptr<ScopedDiceWebSigninInterceptionBubbleHandle>
diff --git a/chrome/browser/ui/views/color_chooser_aura.cc b/chrome/browser/ui/views/color_chooser_aura.cc
index 25daee9..ed94e12 100644
--- a/chrome/browser/ui/views/color_chooser_aura.cc
+++ b/chrome/browser/ui/views/color_chooser_aura.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/ui/views/color_chooser_aura.h"
 
+#include "base/memory/ptr_util.h"
 #include "build/build_config.h"
 #include "chrome/browser/ui/color_chooser.h"
 #include "content/public/browser/web_contents.h"
@@ -49,15 +50,17 @@
 }
 
 // static
-ColorChooserAura* ColorChooserAura::Open(
-    content::WebContents* web_contents, SkColor initial_color) {
-  return new ColorChooserAura(web_contents, initial_color);
+std::unique_ptr<ColorChooserAura> ColorChooserAura::Open(
+    content::WebContents* web_contents,
+    SkColor initial_color) {
+  return base::WrapUnique(new ColorChooserAura(web_contents, initial_color));
 }
 
 namespace chrome {
 
-content::ColorChooser* ShowColorChooser(content::WebContents* web_contents,
-                                        SkColor initial_color) {
+std::unique_ptr<content::ColorChooser> ShowColorChooser(
+    content::WebContents* web_contents,
+    SkColor initial_color) {
   return ColorChooserAura::Open(web_contents, initial_color);
 }
 
diff --git a/chrome/browser/ui/views/color_chooser_aura.h b/chrome/browser/ui/views/color_chooser_aura.h
index 086aee49..3008c17 100644
--- a/chrome/browser/ui/views/color_chooser_aura.h
+++ b/chrome/browser/ui/views/color_chooser_aura.h
@@ -24,12 +24,13 @@
 class ColorChooserAura : public content::ColorChooser,
                          public views::ColorChooserListener {
  public:
-  static ColorChooserAura* Open(content::WebContents* web_contents,
-                                SkColor initial_color);
+  static std::unique_ptr<ColorChooserAura> Open(
+      content::WebContents* web_contents,
+      SkColor initial_color);
+  ~ColorChooserAura() override;
 
  private:
   ColorChooserAura(content::WebContents* web_contents, SkColor initial_color);
-  ~ColorChooserAura() override;
 
   // content::ColorChooser overrides:
   void End() override;
diff --git a/chrome/browser/ui/views/color_chooser_win.cc b/chrome/browser/ui/views/color_chooser_win.cc
index 778ef2b..b3fef082 100644
--- a/chrome/browser/ui/views/color_chooser_win.cc
+++ b/chrome/browser/ui/views/color_chooser_win.cc
@@ -4,6 +4,7 @@
 
 #include <windows.h>
 
+#include "base/memory/ptr_util.h"
 #include "chrome/browser/platform_util.h"
 #include "chrome/browser/ui/color_chooser.h"
 #include "chrome/browser/ui/views/color_chooser_dialog.h"
@@ -17,8 +18,9 @@
 class ColorChooserWin : public content::ColorChooser,
                         public views::ColorChooserListener {
  public:
-  static ColorChooserWin* Open(content::WebContents* web_contents,
-                               SkColor initial_color);
+  static std::unique_ptr<content::ColorChooser> Open(
+      content::WebContents* web_contents,
+      SkColor initial_color);
 
   ColorChooserWin(content::WebContents* web_contents,
                   SkColor initial_color);
@@ -45,12 +47,13 @@
 
 ColorChooserWin* ColorChooserWin::current_color_chooser_ = NULL;
 
-ColorChooserWin* ColorChooserWin::Open(content::WebContents* web_contents,
-                                       SkColor initial_color) {
+std::unique_ptr<content::ColorChooser> ColorChooserWin::Open(
+    content::WebContents* web_contents,
+    SkColor initial_color) {
   if (current_color_chooser_)
     return NULL;
   current_color_chooser_ = new ColorChooserWin(web_contents, initial_color);
-  return current_color_chooser_;
+  return base::WrapUnique(current_color_chooser_);
 }
 
 ColorChooserWin::ColorChooserWin(content::WebContents* web_contents,
@@ -98,8 +101,9 @@
 
 namespace chrome {
 
-content::ColorChooser* ShowColorChooser(content::WebContents* web_contents,
-                                        SkColor initial_color) {
+std::unique_ptr<content::ColorChooser> ShowColorChooser(
+    content::WebContents* web_contents,
+    SkColor initial_color) {
   return ColorChooserWin::Open(web_contents, initial_color);
 }
 
diff --git a/chrome/browser/ui/views/desktop_capture/desktop_media_list_controller.cc b/chrome/browser/ui/views/desktop_capture/desktop_media_list_controller.cc
index 1afd384d..1af567d 100644
--- a/chrome/browser/ui/views/desktop_capture/desktop_media_list_controller.cc
+++ b/chrome/browser/ui/views/desktop_capture/desktop_media_list_controller.cc
@@ -23,13 +23,16 @@
     std::unique_ptr<DesktopMediaList> media_list)
     : dialog_(parent),
       media_list_(std::move(media_list)),
+      auto_select_tab_(
+          base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
+              switches::kAutoSelectTabCaptureSourceByTitle)),
       auto_select_source_(
           base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
               switches::kAutoSelectDesktopCaptureSource)),
-      auto_accept_tab_capture_(
+      auto_accept_this_tab_capture_(
           base::CommandLine::ForCurrentProcess()->HasSwitch(
               switches::kThisTabCaptureAutoAccept)),
-      auto_reject_tab_capture_(
+      auto_reject_this_tab_capture_(
           base::CommandLine::ForCurrentProcess()->HasSwitch(
               switches::kThisTabCaptureAutoReject)) {}
 
@@ -171,7 +174,13 @@
 bool DesktopMediaListController::ShouldAutoAccept(
     const DesktopMediaList::Source& source) const {
   if (media_list_->GetMediaListType() == DesktopMediaList::Type::kCurrentTab) {
-    return auto_accept_tab_capture_;
+    return auto_accept_this_tab_capture_;
+  } else if (media_list_->GetMediaListType() ==
+                 DesktopMediaList::Type::kWebContents &&
+             !auto_select_tab_.empty() &&
+             source.name.find(base::ASCIIToUTF16(auto_select_tab_)) !=
+                 std::u16string::npos) {
+    return true;
   }
 
   return (!auto_select_source_.empty() &&
@@ -182,7 +191,7 @@
 bool DesktopMediaListController::ShouldAutoReject(
     const DesktopMediaList::Source& source) const {
   if (media_list_->GetMediaListType() == DesktopMediaList::Type::kCurrentTab) {
-    return auto_reject_tab_capture_;
+    return auto_reject_this_tab_capture_;
   }
   return false;
 }
diff --git a/chrome/browser/ui/views/desktop_capture/desktop_media_list_controller.h b/chrome/browser/ui/views/desktop_capture/desktop_media_list_controller.h
index f6636c40..3f03ab4 100644
--- a/chrome/browser/ui/views/desktop_capture/desktop_media_list_controller.h
+++ b/chrome/browser/ui/views/desktop_capture/desktop_media_list_controller.h
@@ -132,9 +132,10 @@
       view_observations_{this};
 
   // Auto-selection. Used only in tests.
-  const std::string auto_select_source_;
-  const bool auto_accept_tab_capture_;
-  const bool auto_reject_tab_capture_;
+  const std::string auto_select_tab_;        // Only tabs, by title.
+  const std::string auto_select_source_;     // Any source by its title.
+  const bool auto_accept_this_tab_capture_;  // Only for current-tab capture.
+  const bool auto_reject_this_tab_capture_;  // Only for current-tab capture.
 
   base::WeakPtrFactory<DesktopMediaListController> weak_factory_{this};
 };
diff --git a/chrome/browser/ui/views/extensions/extension_install_dialog_view.cc b/chrome/browser/ui/views/extensions/extension_install_dialog_view.cc
index 4947db60..a16a4ac 100644
--- a/chrome/browser/ui/views/extensions/extension_install_dialog_view.cc
+++ b/chrome/browser/ui/views/extensions/extension_install_dialog_view.cc
@@ -227,6 +227,12 @@
 
   int default_button = ui::DIALOG_BUTTON_CANCEL;
 
+  // If the prompt is related to requesting an extension, set the default button
+  // to OK.
+  if (prompt_->type() ==
+      ExtensionInstallPrompt::PromptType::EXTENSION_REQUEST_PROMPT)
+    default_button = ui::DIALOG_BUTTON_OK;
+
 #if BUILDFLAG(ENABLE_SUPERVISED_USERS)
   // When we require parent permission next, we
   // set the default button to OK.
diff --git a/chrome/browser/ui/views/extensions/extensions_menu_view_browsertest.cc b/chrome/browser/ui/views/extensions/extensions_menu_view_browsertest.cc
index 7b56d04..024c1cc 100644
--- a/chrome/browser/ui/views/extensions/extensions_menu_view_browsertest.cc
+++ b/chrome/browser/ui/views/extensions/extensions_menu_view_browsertest.cc
@@ -752,7 +752,7 @@
   ShowAndVerifyUi();
 }
 
-#if defined(OS_LINUX)
+#if defined(OS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS)
 // TODO(crbug.com/1173344): Flaky on Linux.
 #define MAYBE_InvokeUi_UninstallDialog_Cancel \
   DISABLED_InvokeUi_UninstallDialog_Cancel
diff --git a/chrome/browser/ui/views/profiles/avatar_toolbar_button_delegate.cc b/chrome/browser/ui/views/profiles/avatar_toolbar_button_delegate.cc
index 44d5be1..1358daa 100644
--- a/chrome/browser/ui/views/profiles/avatar_toolbar_button_delegate.cc
+++ b/chrome/browser/ui/views/profiles/avatar_toolbar_button_delegate.cc
@@ -139,13 +139,11 @@
       IdentityManagerFactory::GetForProfile(profile_);
   if (identity_manager &&
       identity_manager->HasPrimaryAccount(signin::ConsentLevel::kSignin)) {
-    absl::optional<AccountInfo> account_info =
-        identity_manager
-            ->FindExtendedAccountInfoForAccountWithRefreshTokenByAccountId(
-                identity_manager->GetPrimaryAccountId(
-                    signin::ConsentLevel::kSignin));
-    if (account_info.has_value())
-      return account_info->account_image;
+    return identity_manager
+        ->FindExtendedAccountInfoByAccountId(
+            identity_manager->GetPrimaryAccountId(
+                signin::ConsentLevel::kSignin))
+        .account_image;
   }
   return gfx::Image();
 }
diff --git a/chrome/browser/ui/views/profiles/profile_menu_view.cc b/chrome/browser/ui/views/profiles/profile_menu_view.cc
index 640acbd..9b777086 100644
--- a/chrome/browser/ui/views/profiles/profile_menu_view.cc
+++ b/chrome/browser/ui/views/profiles/profile_menu_view.cc
@@ -445,9 +445,7 @@
       IdentityManagerFactory::GetForProfile(profile);
   CoreAccountInfo account =
       identity_manager->GetPrimaryAccountInfo(signin::ConsentLevel::kSignin);
-  absl::optional<AccountInfo> account_info =
-      identity_manager->FindExtendedAccountInfoForAccountWithRefreshToken(
-          account);
+  AccountInfo account_info = identity_manager->FindExtendedAccountInfo(account);
   ProfileAttributesEntry* profile_attributes =
       g_browser_process->profile_manager()
           ->GetProfileAttributesStorage()
@@ -471,16 +469,16 @@
 
   SkColor background_color =
       profile_attributes->GetProfileThemeColors().profile_highlight_color;
-  if (account_info.has_value()) {
-    menu_title_ = base::UTF8ToUTF16(account_info.value().full_name);
+  if (!account_info.IsEmpty()) {
+    menu_title_ = base::UTF8ToUTF16(account_info.full_name);
     menu_subtitle_ =
         IsSyncPaused(profile)
             ? l10n_util::GetStringUTF16(IDS_PROFILES_LOCAL_PROFILE_STATE)
-            : base::UTF8ToUTF16(account_info.value().email);
+            : base::UTF8ToUTF16(account_info.email);
     SetProfileIdentityInfo(
         profile_name, background_color, edit_button_params,
-        ui::ImageModel::FromImage(account_info.value().account_image),
-        menu_title_, menu_subtitle_);
+        ui::ImageModel::FromImage(account_info.account_image), menu_title_,
+        menu_subtitle_);
   } else {
     menu_title_ = std::u16string();
     menu_subtitle_ =
@@ -567,17 +565,16 @@
   // Show sync promos.
   CoreAccountInfo unconsented_account =
       identity_manager->GetPrimaryAccountInfo(signin::ConsentLevel::kSignin);
-  absl::optional<AccountInfo> account_info =
-      identity_manager->FindExtendedAccountInfoForAccountWithRefreshToken(
-          unconsented_account);
+  AccountInfo account_info =
+      identity_manager->FindExtendedAccountInfo(unconsented_account);
 
-  if (account_info.has_value()) {
+  if (!account_info.IsEmpty()) {
     BuildSyncInfoWithCallToAction(
         l10n_util::GetStringUTF16(IDS_PROFILES_DICE_NOT_SYNCING_TITLE),
         l10n_util::GetStringUTF16(IDS_PROFILES_DICE_SIGNIN_BUTTON),
         ui::NativeTheme::kColorId_SyncInfoContainerNoPrimaryAccount,
         base::BindRepeating(&ProfileMenuView::OnSigninAccountButtonClicked,
-                            base::Unretained(this), account_info.value()),
+                            base::Unretained(this), account_info),
         /*show_badge=*/true);
   } else {
 #if BUILDFLAG(IS_CHROMEOS_ASH)
diff --git a/chrome/browser/ui/views/toolbar/read_later_toolbar_button.cc b/chrome/browser/ui/views/toolbar/read_later_toolbar_button.cc
index 02eff7c..82900736c 100644
--- a/chrome/browser/ui/views/toolbar/read_later_toolbar_button.cc
+++ b/chrome/browser/ui/views/toolbar/read_later_toolbar_button.cc
@@ -11,6 +11,8 @@
 #include "chrome/common/webui_url_constants.h"
 #include "chrome/grit/generated_resources.h"
 #include "ui/base/l10n/l10n_util.h"
+#include "ui/views/accessibility/view_accessibility.h"
+#include "ui/views/controls/button/button_controller.h"
 #include "ui/views/controls/webview/webview.h"
 
 namespace {
@@ -66,6 +68,9 @@
 
   SetVectorIcons(kSidePanelIcon, kSidePanelTouchIcon);
   SetTooltipText(l10n_util::GetStringUTF16(IDS_TOOLTIP_SIDE_PANEL_SHOW));
+  button_controller()->set_notify_action(
+      views::ButtonController::NotifyAction::kOnPress);
+  GetViewAccessibility().OverrideHasPopup(ax::mojom::HasPopup::kMenu);
 }
 
 ReadLaterToolbarButton::~ReadLaterToolbarButton() = default;
diff --git a/chrome/browser/ui/webui/print_preview/local_printer_handler_default.cc b/chrome/browser/ui/webui/print_preview/local_printer_handler_default.cc
index 302be10..4b80af8 100644
--- a/chrome/browser/ui/webui/print_preview/local_printer_handler_default.cc
+++ b/chrome/browser/ui/webui/print_preview/local_printer_handler_default.cc
@@ -62,15 +62,16 @@
 
 void OnDidGetDefaultPrinterName(
     PrinterHandler::DefaultPrinterCallback callback,
-    const absl::optional<std::string>& printer_name) {
-  if (!printer_name.has_value()) {
-    LOG(WARNING) << "Failure getting default printer";
+    mojom::DefaultPrinterNameResultPtr printer_name) {
+  if (printer_name->is_result_code()) {
+    PRINTER_LOG(ERROR) << "Failure getting default printer, result: "
+                       << printer_name->get_result_code();
     std::move(callback).Run(std::string());
     return;
   }
 
-  VLOG(1) << "Default Printer: " << printer_name.value();
-  std::move(callback).Run(printer_name.value());
+  VLOG(1) << "Default Printer: " << printer_name->get_default_printer_name();
+  std::move(callback).Run(printer_name->get_default_printer_name());
 }
 
 void OnDidEnumeratePrinters(
@@ -202,7 +203,14 @@
   scoped_refptr<PrintBackend> print_backend(
       PrintBackend::CreateInstance(locale));
 
-  std::string default_printer = print_backend->GetDefaultPrinterName();
+  std::string default_printer;
+  mojom::ResultCode result =
+      print_backend->GetDefaultPrinterName(default_printer);
+  if (result != mojom::ResultCode::kSuccess) {
+    PRINTER_LOG(ERROR) << "Failure getting default printer name, result: "
+                       << result;
+    return std::string();
+  }
   VLOG(1) << "Default Printer: " << default_printer;
   return default_printer;
 }
diff --git a/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc b/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc
index 373a81f..ea433770 100644
--- a/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc
+++ b/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc
@@ -271,6 +271,14 @@
       l10n_util::GetStringUTF16(IDS_SETTINGS_UPGRADE_UP_TO_DATE));
 #endif
 
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+  // On Lacros, we don't have the concept of channels, in their usual semantics.
+  // Replace the channel string with "Lacros". https://crbug.com/1215734.
+  std::string channel_name = "Lacros";
+#else
+  std::string channel_name =
+      chrome::GetChannelName(chrome::WithExtendedStable(true));
+#endif
   html_source->AddString(
       "aboutBrowserVersion",
       l10n_util::GetStringFUTF16(
@@ -279,8 +287,7 @@
           l10n_util::GetStringUTF16(version_info::IsOfficialBuild()
                                         ? IDS_VERSION_UI_OFFICIAL
                                         : IDS_VERSION_UI_UNOFFICIAL),
-          base::UTF8ToUTF16(
-              chrome::GetChannelName(chrome::WithExtendedStable(true))),
+          base::UTF8ToUTF16(channel_name),
           l10n_util::GetStringUTF16(VersionUI::VersionProcessorVariation())));
   html_source->AddString(
       "aboutProductCopyright",
diff --git a/chrome/browser/ui/webui/version/version_ui.cc b/chrome/browser/ui/webui/version/version_ui.cc
index e817fa6..19d8542 100644
--- a/chrome/browser/ui/webui/version/version_ui.cc
+++ b/chrome/browser/ui/webui/version/version_ui.cc
@@ -170,9 +170,15 @@
   // Data strings.
   html_source->AddString(version_ui::kVersion,
                          version_info::GetVersionNumber());
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+  // On Lacros, we don't have the concept of channels, in their usual semantics.
+  // Replace the channel string with "Lacros". https://crbug.com/1215734.
+  html_source->AddString(version_ui::kVersionModifier, "Lacros");
+#else
   html_source->AddString(
       version_ui::kVersionModifier,
       chrome::GetChannelName(chrome::WithExtendedStable(true)));
+#endif
   html_source->AddString(version_ui::kJSEngine, "V8");
   html_source->AddString(version_ui::kJSVersion, V8_VERSION_STRING);
   html_source->AddString(
diff --git a/chrome/browser/web_applications/README.md b/chrome/browser/web_applications/README.md
index 1a865bd..4dfc243 100644
--- a/chrome/browser/web_applications/README.md
+++ b/chrome/browser/web_applications/README.md
@@ -229,13 +229,13 @@
 ## Deep Dives
 
 * [Installation Sources & Pipeline](docs/installation_pipeline.md)
+* [Operating system integration](docs/os_integration.md)
 * TODO: Uninstallation
 * TODO: Manifest Update
 
 ## Testing
 
-TODO: How to write a unit test.
-TODO: How to write a browser test.
+See [the testing docs](docs/testing.md).
 
 ## Debugging
 
diff --git a/chrome/browser/web_applications/docs/installation_pipeline.md b/chrome/browser/web_applications/docs/installation_pipeline.md
index d17ff323..0240e9c 100644
--- a/chrome/browser/web_applications/docs/installation_pipeline.md
+++ b/chrome/browser/web_applications/docs/installation_pipeline.md
@@ -1,4 +1,4 @@
-# Installation
+# [Web Apps](../README.md) - Installation
 
 Installing a webapp can come from a variety of channels. This section serves to enumerate them all and show how they fit together in the installation pipeline. 
 
diff --git a/chrome/browser/web_applications/components/README.md b/chrome/browser/web_applications/docs/os_integration.md
similarity index 76%
rename from chrome/browser/web_applications/components/README.md
rename to chrome/browser/web_applications/docs/os_integration.md
index 9c3ec0b..e671b7e0 100644
--- a/chrome/browser/web_applications/components/README.md
+++ b/chrome/browser/web_applications/docs/os_integration.md
@@ -1,7 +1,15 @@
-This directory holds files for components that implement features for
-Progressive Web Apps on the browser side.
+# [Web Apps](../README.md) - Operating System Integration
 
-# Components
+The WebAppProvider system has to provide a lot of integrations with operating system surfaces for web apps. This functionality is usually different per operating system, and is usually invoked through the [`OsIntegrationManager`](../components/os_integration_manager.h).
+
+The [`OsIntegrationManager`](../components/os_integration_manager.h)'s main responsibility is support the following operations:
+1. Install operating system integration for a given web app.
+1. Update operating system integration for a given web app.
+1. Uninstall/remove operating system integration for a given web app.
+
+It owns sub-managers who are responsible for each individual operating system integration functionality (e.g. [`file_handler_manager.h`](../components/file_handler_manager.h) which owns the file handling feature). That manager will implement the non-os-specific logic, and then call into functions that have os-specific implementations (e.g. `web_app_file_handler_registration.h/_mac.h/_win.h/_linux.h` files).
+
+Below are sections describing how each OS integration works.
 
 ## Protocol Handler
 
diff --git a/chrome/browser/web_applications/docs/testing.md b/chrome/browser/web_applications/docs/testing.md
new file mode 100644
index 0000000..cf4ac0e2
--- /dev/null
+++ b/chrome/browser/web_applications/docs/testing.md
@@ -0,0 +1,77 @@
+# [Web Apps](../README.md) - Testing
+
+
+Testing in WebAppProvider falls into 3 different categories.
+1. Unit tests (`*_unittest.cc` files), which are the most efficient.
+1. Browser tests (`*_browsertest.cc` files), which run the whole Chrome browser for each test. This makes them less efficient, but possible to test interactions between different parts of Chrome.
+   * Note: These are currently not being run on Mac CQ trybots (see https://crbug.com/1042757), but they are run on the waterfall.
+1. [Integration tests](../../ui/views/web_apps/README.md), which are a special kind of browser-test-based framework to test our critical user journeys.
+
+When creating features in this system, it will probably involve creating a mixture of all 3 of these test types.
+
+Please read [Testing In Chromium](../../../../docs/testing/testing_in_chromium.md) for general guidance on writing tests in chromium.
+
+## Future Improvements
+
+* Allow easy population of a [`TestWebAppProvider`](../test/test_web_app_provider.h) from a [`TestWebAppRegistryController`](../test_web_app_registry_controller.h).
+
+## Terminology
+
+### `Fake*` or `Test*` classes
+
+A class that starts with `Fake` or `Test` is meant to completely replace a component of the system. They should be inheriting from a base class (often pure virtual) and then implement a version of that component that will seem to be working correctly to other system components, but not actually do anything.
+
+An example is [test_os_integration_manager.h](../test/test_os_integration_manager.h), which pretends to successfully do install, update, and uninstall operations, but actually just does nothing.
+
+### `Mock*` classes
+
+A class that start with `Mock` is a [gmock](https://github.com/google/googletest/tree/HEAD/googlemock) version of the class. This allows the user to have complete control of exactly what that class does, verify it is called exactly as expected, etc. These tend to be much more powerful to use than a `Fake`, as you can easily specify every possible case you might want to check, like which arguments are called and the exact calling order of multiple functions, even across multiple mocks. The downside is that they require creating a mock class & learning how to use gmock.
+
+An example is [MockOsIntegrationManager](../components/os_integration_manager_unittest.cc) inside of the unittest file.
+
+## Unit tests
+Unit tests have the following benefits
+* are very efficient,
+* run on all relevant CQ trybots (while https://crbug.com/1042757 is not fixed), and
+* will always be supported by the [code coverage](../../../../docs/testing/code_coverage.md) framework.
+
+The downside is that it can be difficult to test interactions between different parts of our system in Chrome (which can range from blink with the [`ManifestFetcher`](https://source.chromium.org/search?q=ManifestFetcher) to the install dialog in [`PWAInstallView`](https://source.chromium.org/search?q=PWAInstallView)).
+
+Unit tests usually rely on "faking" or "mocking" out dependencies to allow one specific class to be tested, without requiring the entire WebAppProvider (and thus Profile, Sync Service, etc) to be fully running. This is accomplished by having major components:
+1. declare all public methods as `virtual` so that a `Fake`, `Test`, or `Mock` version of the class can be used instead, and
+1. accept all dependencies in their constructor or `SetSubsystems` method.
+
+This allows a unittest to create a part of the WebAppProvider system that uses all mocked or faked dependencies, allowing easy testing.
+
+
+### Tool: `TestWebAppRegistryController`
+
+The [`TestWebAppRegistryController`](../test/test_web_app_registry_controller.h) is basically a fake version of the WebAppProvider system, without using the [`WebAppProvider`](../web_app_provider.h) root class. This works well as long as none of the parts of the system are using [`WebAppProvider::Get`](https://source.chromium.org/search?q=WebAppProvider::Get), as this uses they `KeyedService` functionality on the `Profile` object, and the `TestWebAppRegistryController` doesn't register itself with any of that.
+
+`TestWebAppRegistryController::database_factory()` is special: it allows you to programmatically create some LevelDB state (an offline registry snapshot) before any subsystem starts. This is useful to customize inputs and preconditions in unit tests. To do this, or for examples, see [`TestWebAppDatabaseFactory::WriteProtos`](https://source.chromium.org/search?q=TestWebAppDatabaseFactory::WriteProtos) and [`TestWebAppDatabaseFactory::WriteRegistry`](https://source.chromium.org/search?q=TestWebAppDatabaseFactory::WriteRegistry).
+
+### Common issues & solutions
+
+#### Dependency not passed in normally
+Sometimes classes have not used the dependency pattern, or rely on pulling things off of the `Profile` keyed services. This can be solved by
+1. Refactoring that class a little to have the dependency passed in the constructor / `SetSubsystems` method.
+1. There should be a way to register a keyed service factory on a given `Profile` to return what you want.
+1. If all else fails, use a browser test
+
+## Browser tests
+Browser tests are much more expensive to run, as they basically run a fully functional browser with it's own profile directory. These tests are usually only created to test functionality that requires multiple parts of the system to be running or dependencies like the Sync service to be fully running and functional.
+
+Browsertest are great as integration tests, as they are almost completely running the full Chrome environment, with a real profile on disk. It is good practice to have browsertests be as true-to-user-action as possible, to make sure that as much of our stack is exercised.
+
+A good example set of browser tests is in [`web_app_browsertest.cc`](../../ui/web_applications/web_app_browsertest.cc).
+
+### Tool: `TestWebAppProvider`
+
+The [`TestWebAppProvider`](../test/test_web_app_provider.h) is a nifty way to mock out pieces of the WebAppProvider system for a browser test. To use it, you put a [`TestWebAppProviderCreator`](../test/test_web_app_provider.h) in your test class, and give it a callback to create a `WebAppProvider` given a `Profile`. This allows you to create a [`TestWebAppProvider`](../test/test_web_app_provider.h) instead of the regular `WebAppProvider`, swapping out any part of the system.
+
+This means that all of the users of [`WebAppProvider::Get`](https://source.chromium.org/search?q=WebAppProvider::Get), [`WebAppProvider::GetForWebContents`](https://source.chromium.org/search?q=WebAppProvider::Get), and [`WebAppProviderBase::Get`](https://source.chromium.org/search?q=WebAppProviderBase::Get) (etc) will be talking to the `TestWebAppProvider` that the test created. This is perfect for a browsertest, as it runs the full browser.
+
+The other difference between this and the [`TestWebAppRegistryController`](#tool-testwebappregistrycontroller) above is that this, without any changes (and as long as the user calls `Start()`), will run the normal production `WebAppProvider` system. This means changes are written to disk, the OS integrations are triggered, etc.
+
+## Integration tests
+Due to the complexity of the WebApp feature space, a special testing framework was created to help list, minimize, and test all critical user journeys. See the [README.md here](../../ui/views/web_apps/README.md) about how to write these.
diff --git a/chrome/common/chrome_switches.cc b/chrome/common/chrome_switches.cc
index 314e552..e27edd3 100644
--- a/chrome/common/chrome_switches.cc
+++ b/chrome/common/chrome_switches.cc
@@ -96,6 +96,15 @@
 const char kAutoSelectDesktopCaptureSource[] =
     "auto-select-desktop-capture-source";
 
+// This flag makes Chrome auto-select a tab with the provided title when
+// the media-picker should otherwise be displayed to the user. This switch
+// is very similar to kAutoSelectDesktopCaptureSource, but limits selection
+// to tabs. This solves the issue of kAutoSelectDesktopCaptureSource being
+// liable to accidentally capturing the Chromium window instead of the tab,
+// as both have the same title if the tab is focused.
+const char kAutoSelectTabCaptureSourceByTitle[] =
+    "auto-select-tab-capture-source-by-title";
+
 // How often (in seconds) to check for updates. Should only be used for testing
 // purposes.
 const char kCheckForUpdateIntervalSec[]     = "check-for-update-interval";
diff --git a/chrome/common/chrome_switches.h b/chrome/common/chrome_switches.h
index 4a77263..6cd006e 100644
--- a/chrome/common/chrome_switches.h
+++ b/chrome/common/chrome_switches.h
@@ -53,6 +53,7 @@
 extern const char kAuthServerAllowlist[];
 extern const char kAutoOpenDevToolsForTabs[];
 extern const char kAutoSelectDesktopCaptureSource[];
+extern const char kAutoSelectTabCaptureSourceByTitle[];
 extern const char kCheckForUpdateIntervalSec[];
 extern const char kCipherSuiteBlacklist[];
 extern const char kCloudPrintFile[];
diff --git a/chrome/common/extensions/api/scripting.idl b/chrome/common/extensions/api/scripting.idl
index ec514a32..73d668d 100644
--- a/chrome/common/extensions/api/scripting.idl
+++ b/chrome/common/extensions/api/scripting.idl
@@ -94,7 +94,7 @@
 
   interface Functions {
     // Injects a script into a target context. The script will be run at
-    // <code>document_end</code>.
+    // <code>document_idle</code>.
     // |injection|: The details of the script which to inject.
     // |callback|: Invoked upon completion of the injection. The resulting
     // array contains the result of execution for each frame where the
diff --git a/chrome/services/printing/print_backend_service_impl.cc b/chrome/services/printing/print_backend_service_impl.cc
index f7c7152..d2a85b9 100644
--- a/chrome/services/printing/print_backend_service_impl.cc
+++ b/chrome/services/printing/print_backend_service_impl.cc
@@ -14,7 +14,6 @@
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "printing/backend/print_backend.h"
 #include "printing/mojom/print.mojom.h"
-#include "third_party/abseil-cpp/absl/types/optional.h"
 
 #if defined(OS_MAC)
 #include "base/threading/thread_restrictions.h"
@@ -58,10 +57,20 @@
   if (!print_backend_) {
     DLOG(ERROR)
         << "Print backend instance has not been initialized for locale.";
-    std::move(callback).Run(absl::nullopt);
+    std::move(callback).Run(mojom::DefaultPrinterNameResult::NewResultCode(
+        mojom::ResultCode::kFailed));
     return;
   }
-  std::move(callback).Run(print_backend_->GetDefaultPrinterName());
+  std::string default_printer;
+  mojom::ResultCode result =
+      print_backend_->GetDefaultPrinterName(default_printer);
+  if (result != mojom::ResultCode::kSuccess) {
+    std::move(callback).Run(
+        mojom::DefaultPrinterNameResult::NewResultCode(result));
+    return;
+  }
+  std::move(callback).Run(
+      mojom::DefaultPrinterNameResult::NewDefaultPrinterName(default_printer));
 }
 
 void PrintBackendServiceImpl::GetPrinterSemanticCapsAndDefaults(
diff --git a/chrome/services/printing/public/mojom/print_backend_service.mojom b/chrome/services/printing/public/mojom/print_backend_service.mojom
index 0be1ada..5cea403 100644
--- a/chrome/services/printing/public/mojom/print_backend_service.mojom
+++ b/chrome/services/printing/public/mojom/print_backend_service.mojom
@@ -7,6 +7,13 @@
 import "printing/backend/mojom/print_backend.mojom";
 import "printing/mojom/print.mojom";
 
+// The default printer name, or the `ResultCode` if there was an error when
+// trying to retrieve this data.
+union DefaultPrinterNameResult {
+  string default_printer_name;
+  ResultCode result_code;
+};
+
 // The list of installed printers, or the `ResultCode` if there was an error
 // when trying to retrieve this data.
 union PrinterListResult {
@@ -50,9 +57,8 @@
     => (PrinterListResult printer_list);
 
   // Gets the default printer name from the data source.
-  // No value for `printer_name` is provided if there is a failure.
   GetDefaultPrinterName()
-    => (string? printer_name);
+    => (DefaultPrinterNameResult printer_name);
 
   // Gets the semantic capabilities and defaults for a specific printer.
   GetPrinterSemanticCapsAndDefaults(string printer_name)
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index bc6b430..9dac70d 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -1444,6 +1444,7 @@
       "../browser/media/test_license_server.h",
       "../browser/media/test_license_server_config.h",
       "../browser/media/unified_autoplay_browsertest.cc",
+      "../browser/media/webrtc/capture_handle_browsertest.cc",
       "../browser/media/webrtc/media_stream_devices_controller_browsertest.cc",
       "../browser/media/webrtc/media_stream_permission_browsertest.cc",
       "../browser/media/webrtc/test_stats_dictionary.cc",
@@ -2059,6 +2060,10 @@
     if (enable_soda) {
       sources +=
           [ "../browser/speech/speech_recognition_service_browsertest.cc" ]
+      deps += [
+        "//chrome/services/speech:lib",
+        "//chrome/services/speech/soda",
+      ]
       if (is_mac) {
         data_deps += [ "//third_party/soda-mac64:soda_resources" ]
       } else if (is_win && target_cpu == "x86") {
diff --git a/chrome/test/data/extensions/api_test/native_bindings/extension/background.js b/chrome/test/data/extensions/api_test/native_bindings/extension/background.js
index 25ef4e20..37d2409a 100644
--- a/chrome/test/data/extensions/api_test/native_bindings/extension/background.js
+++ b/chrome/test/data/extensions/api_test/native_bindings/extension/background.js
@@ -155,8 +155,6 @@
     chrome.test.assertTrue(!!chrome.storage.managed, 'managed');
     chrome.test.assertFalse(!!chrome.storage.managed.QUOTA_BYTES,
                             'managed quota bytes');
-    chrome.test.assertTrue(!!chrome.storage.session.QUOTA_BYTES,
-                           'session quota bytes');
     chrome.storage.local.set({foo: 'bar', nullkey: null}, () => {
       chrome.storage.local.get(['foo', 'nullkey'], (results) => {
         chrome.test.assertTrue(!!results, 'no results');
diff --git a/chrome/test/data/extensions/api_test/service_worker/worker_based_background/storage/manifest.json b/chrome/test/data/extensions/api_test/service_worker/worker_based_background/storage/manifest.json
index b4fac20f..c38af8cf 100644
--- a/chrome/test/data/extensions/api_test/service_worker/worker_based_background/storage/manifest.json
+++ b/chrome/test/data/extensions/api_test/service_worker/worker_based_background/storage/manifest.json
@@ -1,7 +1,7 @@
 {
   "name": "Service Worker-based background script",
   "version": "0.1",
-  "manifest_version": 2,
+  "manifest_version": 3,
   "description": "Test storage APIs for service worker-based background scripts.",
   "permissions": ["storage"],
   "background": {"service_worker": "service_worker_background.js"}
diff --git a/chrome/test/data/extensions/api_test/service_worker/worker_based_background/storage_no_permissions/manifest.json b/chrome/test/data/extensions/api_test/service_worker/worker_based_background/storage_no_permissions/manifest.json
index 6135a65..31f02e5 100644
--- a/chrome/test/data/extensions/api_test/service_worker/worker_based_background/storage_no_permissions/manifest.json
+++ b/chrome/test/data/extensions/api_test/service_worker/worker_based_background/storage_no_permissions/manifest.json
@@ -1,7 +1,7 @@
 {
   "name": "Service Worker-based background script",
   "version": "0.1",
-  "manifest_version": 2,
+  "manifest_version": 3,
   "description": "Test storage APIs for service worker-based background scripts.",
   "permissions": [ ],
   "background": {"service_worker": "service_worker_background.js"}
diff --git a/chrome/test/data/extensions/api_test/settings/simple_test/manifest.json b/chrome/test/data/extensions/api_test/settings/simple_test/manifest.json
index 55aed8f..573dd11 100644
--- a/chrome/test/data/extensions/api_test/settings/simple_test/manifest.json
+++ b/chrome/test/data/extensions/api_test/settings/simple_test/manifest.json
@@ -1,10 +1,10 @@
 {
   "name": "settings",
   "version": "1.0",
-  "manifest_version": 2,
+  "manifest_version": 3,
   "description": "Tests that the Storage API works.",
   "permissions": ["storage"],
   "background": {
-    "scripts": ["background.js"]
-  }
+    "service_worker": "background.js"
+  } 
 }
diff --git a/chrome/test/data/extensions/api_test/settings/split_incognito/manifest.json b/chrome/test/data/extensions/api_test/settings/split_incognito/manifest.json
index 27e7759..5813e83 100644
--- a/chrome/test/data/extensions/api_test/settings/split_incognito/manifest.json
+++ b/chrome/test/data/extensions/api_test/settings/split_incognito/manifest.json
@@ -1,11 +1,11 @@
 {
   "name": "Split-mode incognito storage test",
   "version": "1.0",
-  "manifest_version": 2,
+  "manifest_version": 3,
   "description": "Tests the Storage API in split incognito mode",
   "permissions": ["storage"],
   "background": {
-    "scripts": ["background.js"]
+    "service_worker": "background.js"
   },
   "incognito": "split"
 }
diff --git a/chrome/test/data/extensions/api_test/settings/storage_area/manifest.json b/chrome/test/data/extensions/api_test/settings/storage_area/manifest.json
index 6895ca1..ff36e2c 100644
--- a/chrome/test/data/extensions/api_test/settings/storage_area/manifest.json
+++ b/chrome/test/data/extensions/api_test/settings/storage_area/manifest.json
@@ -1,10 +1,10 @@
 {
   "name": "StorageArea.OnChanged",
   "version": "0.1",
-  "manifest_version": 2,
+  "manifest_version": 3,
   "description": "Tests that the StorageArea.onChanged event works.",
   "permissions": ["storage"],
   "background": {
-    "scripts": ["background.js"]
+    "service_worker": "background.js"
   }
 }
diff --git a/chrome/test/data/extensions/api_test/stubs_app/background.js b/chrome/test/data/extensions/api_test/stubs_app/background.js
index 465150ff..2af398ac 100644
--- a/chrome/test/data/extensions/api_test/stubs_app/background.js
+++ b/chrome/test/data/extensions/api_test/stubs_app/background.js
@@ -36,7 +36,11 @@
     // Get the API properties.
     if (module.properties) {
       Object.getOwnPropertyNames(module.properties).forEach(function(propName) {
-        apiPaths.push(namespace + "." + propName);
+        const fullPath = namespace + '.' + propName;
+        // Skip storage.session, since it's restricted to MV3.
+        if (fullPath != 'storage.session') {
+          apiPaths.push(fullPath);
+        }
       });
     }
   });
diff --git a/chrome/test/data/webrtc/captured_page_main.html b/chrome/test/data/webrtc/captured_page_main.html
new file mode 100644
index 0000000..27cb73f
--- /dev/null
+++ b/chrome/test/data/webrtc/captured_page_main.html
@@ -0,0 +1,62 @@
+<html>
+  <head>
+    <!-- The test scans for which source to share according to the title. -->
+    <title>
+      Capture Handle Test - Captured Page (totally-unique-captured-page-title)
+    </title>
+    <link rel="icon" href="data:," />
+    <script>
+      "use strict";
+
+      function callSetCaptureHandleConfig(
+        exposeOrigin,
+        handle,
+        permittedOrigins
+      ) {
+        navigator.mediaDevices.setCaptureHandleConfig({
+          exposeOrigin: exposeOrigin,
+          handle: handle,
+          permittedOrigins: permittedOrigins,
+        });
+        window.domAutomationController.send("capture-handle-set");
+      }
+
+      function clickLinkToPageBottom() {
+        document.getElementById("link_to_bottom").click();
+        window.domAutomationController.send("navigated");
+      }
+
+      function clickLinkToUrl(url) {
+        let link = document.getElementById("link");
+        link.href = url;
+        link.click();
+        window.domAutomationController.send("link-success");
+      }
+    </script>
+  </head>
+  <body>
+    <h1 id="page_top">Capture Handle Test - Captured Page</h1>
+    <a href="#" id="link">Link</a>
+    <a href="#page_bottom" id="link_to_bottom">bottom</a>
+    <br/><br/><br/><br/><br/><br/><br/><br/><br/><br/>
+    <br/><br/><br/><br/><br/><br/><br/><br/><br/><br/>
+    <br/><br/><br/><br/><br/><br/><br/><br/><br/><br/>
+    <br/><br/><br/><br/><br/><br/><br/><br/><br/><br/>
+    <br/><br/><br/><br/><br/><br/><br/><br/><br/><br/>
+    <br/><br/><br/><br/><br/><br/><br/><br/><br/><br/>
+    <br/><br/><br/><br/><br/><br/><br/><br/><br/><br/>
+    <br/><br/><br/><br/><br/><br/><br/><br/><br/><br/>
+    <br/><br/><br/><br/><br/><br/><br/><br/><br/><br/>
+    <br/><br/><br/><br/><br/><br/><br/><br/><br/><br/>
+    <br/><br/><br/><br/><br/><br/><br/><br/><br/><br/>
+    <br/><br/><br/><br/><br/><br/><br/><br/><br/><br/>
+    <br/><br/><br/><br/><br/><br/><br/><br/><br/><br/>
+    <br/><br/><br/><br/><br/><br/><br/><br/><br/><br/>
+    <br/><br/><br/><br/><br/><br/><br/><br/><br/><br/>
+    <br/><br/><br/><br/><br/><br/><br/><br/><br/><br/>
+    <br/><br/><br/><br/><br/><br/><br/><br/><br/><br/>
+    <br/><br/><br/><br/><br/><br/><br/><br/><br/><br/>
+    <h2 id="page_bottom">Bottom</h2>
+    <a href="#page_top" id="link_to_top">top</a>
+  </body>
+</html>
diff --git a/chrome/test/data/webrtc/captured_page_other.html b/chrome/test/data/webrtc/captured_page_other.html
new file mode 100644
index 0000000..b3c6e95
--- /dev/null
+++ b/chrome/test/data/webrtc/captured_page_other.html
@@ -0,0 +1,28 @@
+<html>
+  <head>
+    <!-- The test scans for which source to share according to the title. -->
+    <title>
+      Capture Handle Test - Captured Page Copy (totally-unique-DIFFERENT-captured-page-title)
+    </title>
+    <link rel="icon" href="data:," />
+    <script>
+      "use strict";
+
+      function callSetCaptureHandleConfig(
+        exposeOrigin,
+        handle,
+        permittedOrigins
+      ) {
+        navigator.mediaDevices.setCaptureHandleConfig({
+          exposeOrigin: exposeOrigin,
+          handle: handle,
+          permittedOrigins: permittedOrigins,
+        });
+        window.domAutomationController.send("capture-handle-set");
+      }
+    </script>
+  </head>
+  <body>
+    <h1>Capture Handle Test - Captured Page</h1>
+  </body>
+</html>
diff --git a/chrome/test/data/webrtc/capturing_page_main.html b/chrome/test/data/webrtc/capturing_page_main.html
new file mode 100644
index 0000000..f223bf8
--- /dev/null
+++ b/chrome/test/data/webrtc/capturing_page_main.html
@@ -0,0 +1,89 @@
+<html>
+  <head>
+    <title>Capture Handle Test - Capturing Page</title>
+    <link rel="icon" href="data:," />
+    <script>
+      "use strict";
+
+      let capturedStream;
+      let capturedVideoTrack;
+
+      function captureOtherTab() {
+        navigator.mediaDevices
+          .getDisplayMedia({ video: true })
+          .then(handleCaptureSuccess)
+          .catch(handleCaptureError);
+      }
+
+      function readLastEvent() {
+        // Blocks until onCaptureHandleChange() unblocks.
+      }
+
+      function readCaptureHandleFromSettings() {
+        if (!capturedVideoTrack) {
+          window.domAutomationController.send("error-no-video-track");
+          return;
+        }
+
+        let settings = capturedVideoTrack.getSettings();
+        if (!settings.captureHandle) {
+          window.domAutomationController.send("no-capture-handle");
+          return;
+        }
+
+        window.domAutomationController.send(
+          JSON.stringify(settings.captureHandle)
+        );
+      }
+
+      function handleCaptureSuccess(stream) {
+        if (capturedStream) {
+          window.domAutomationController.send("error-multiple-captures");
+          return;
+        }
+
+        capturedStream = stream;
+        capturedVideoTrack = stream.getVideoTracks()[0];
+
+        capturedVideoTrack.oncapturehandlechange = onCaptureHandleChange;
+
+        window.domAutomationController.send("capture-success");
+      }
+
+      function handleCaptureError(error) {
+        window.domAutomationController.send("capture-failure");
+      }
+
+      function onCaptureHandleChange(event) {
+        if (event == undefined || event.captureHandle == undefined) {
+          throw "Unexpected event type.";
+        }
+        window.domAutomationController.send(
+          JSON.stringify(event.captureHandle)
+        );
+      }
+
+      function setTitle(title) {
+        document.title = title;
+        window.domAutomationController.send("title-changed");
+      }
+
+      // Duplicated from the captured-page in order to test self-capture.
+      function callSetCaptureHandleConfig(
+        exposeOrigin,
+        handle,
+        permittedOrigins
+      ) {
+        navigator.mediaDevices.setCaptureHandleConfig({
+          exposeOrigin: exposeOrigin,
+          handle: handle,
+          permittedOrigins: permittedOrigins,
+        });
+        window.domAutomationController.send("capture-handle-set");
+      }
+    </script>
+  </head>
+  <body>
+    <h1>Capture Handle Test - Capturing Page</h1>
+  </body>
+</html>
diff --git a/chrome/test/data/webui/cr_components/cr_components_mojo_browsertest.js b/chrome/test/data/webui/cr_components/cr_components_mojo_browsertest.js
index d4bf9575..1d7f2618 100644
--- a/chrome/test/data/webui/cr_components/cr_components_mojo_browsertest.js
+++ b/chrome/test/data/webui/cr_components/cr_components_mojo_browsertest.js
@@ -38,6 +38,7 @@
   }
 };
 
-TEST_F('CrComponentsMostVisitedTest', 'All', function() {
+// The test is failing on dbg builds: https://crbug.com/1216019
+TEST_F('CrComponentsMostVisitedTest', 'DISABLED_All', function() {
   mocha.run();
 });
diff --git a/chrome/test/data/webui/print_preview/header_test.js b/chrome/test/data/webui/print_preview/header_test.js
index 6c1ccca9..5b9cf47 100644
--- a/chrome/test/data/webui/print_preview/header_test.js
+++ b/chrome/test/data/webui/print_preview/header_test.js
@@ -31,7 +31,7 @@
     document.body.innerHTML = '';
 
     pluralString = new TestPluralStringProxy();
-    PrintPreviewPluralStringProxyImpl.instance_ = pluralString;
+    PrintPreviewPluralStringProxyImpl.setInstance(pluralString);
     pluralString.text = '1 sheet of paper';
 
     const model = /** @type {!PrintPreviewModelElement} */ (
diff --git a/chrome/test/data/webui/print_preview/policy_test.js b/chrome/test/data/webui/print_preview/policy_test.js
index 36b9110..e246961d 100644
--- a/chrome/test/data/webui/print_preview/policy_test.js
+++ b/chrome/test/data/webui/print_preview/policy_test.js
@@ -275,7 +275,7 @@
 
   test(assert(policy_tests.TestNames.SheetsPolicy), async () => {
     const pluralString = new PolicyTestPluralStringProxy();
-    PrintPreviewPluralStringProxyImpl.instance_ = pluralString;
+    PrintPreviewPluralStringProxyImpl.setInstance(pluralString);
     pluralString.text = 'Exceeds limit of 1 sheet of paper';
 
     const tests = [
diff --git a/chrome/test/data/webui/settings/autofill_page_test.js b/chrome/test/data/webui/settings/autofill_page_test.js
index 7c8b740..fba8145 100644
--- a/chrome/test/data/webui/settings/autofill_page_test.js
+++ b/chrome/test/data/webui/settings/autofill_page_test.js
@@ -307,7 +307,7 @@
     passwordManager = new TestPasswordManagerProxy();
     PasswordManagerImpl.instance_ = passwordManager;
     pluralString = new TestPluralStringProxy();
-    SettingsPluralStringProxyImpl.instance_ = pluralString;
+    SettingsPluralStringProxyImpl.setInstance(pluralString);
 
     autofillPage = createAutofillPageSection();
   });
diff --git a/chrome/test/data/webui/settings/passwords_section_test.js b/chrome/test/data/webui/settings/passwords_section_test.js
index 72e5b13..8bf0adb 100644
--- a/chrome/test/data/webui/settings/passwords_section_test.js
+++ b/chrome/test/data/webui/settings/passwords_section_test.js
@@ -320,7 +320,7 @@
     // Override the PasswordManagerImpl for testing.
     passwordManager = new TestPasswordManagerProxy();
     pluralString = new TestPluralStringProxy();
-    SettingsPluralStringProxyImpl.instance_ = pluralString;
+    SettingsPluralStringProxyImpl.setInstance(pluralString);
 
     PasswordManagerImpl.instance_ = passwordManager;
     elementFactory = new PasswordSectionElementFactory(document);
diff --git a/chromeos/CHROMEOS_LKGM b/chromeos/CHROMEOS_LKGM
index 5f9243f..1998f8ec 100644
--- a/chromeos/CHROMEOS_LKGM
+++ b/chromeos/CHROMEOS_LKGM
@@ -1 +1 @@
-14009.0.0
\ No newline at end of file
+14010.0.0
\ No newline at end of file
diff --git a/chromeos/components/proximity_auth/proximity_auth_local_state_pref_manager.cc b/chromeos/components/proximity_auth/proximity_auth_local_state_pref_manager.cc
index 7680bf35..bfcac43 100644
--- a/chromeos/components/proximity_auth/proximity_auth_local_state_pref_manager.cc
+++ b/chromeos/components/proximity_auth/proximity_auth_local_state_pref_manager.cc
@@ -74,27 +74,29 @@
 }
 
 bool ProximityAuthLocalStatePrefManager::IsEasyUnlockAllowed() const {
-  bool pref_value;
   const base::DictionaryValue* user_prefs = GetActiveUserPrefsDictionary();
-  if (!user_prefs || !user_prefs->GetBooleanWithoutPathExpansion(
-                         chromeos::multidevice_setup::kSmartLockAllowedPrefName,
-                         &pref_value)) {
-    PA_LOG(ERROR) << "Failed to get easyunlock_allowed.";
-    return true;
+  if (user_prefs) {
+    absl::optional<bool> pref_value = user_prefs->FindBoolKey(
+        chromeos::multidevice_setup::kSmartLockAllowedPrefName);
+    if (pref_value.has_value()) {
+      return pref_value.value();
+    }
   }
-  return pref_value;
+  PA_LOG(ERROR) << "Failed to get easyunlock_allowed.";
+  return true;
 }
 
 bool ProximityAuthLocalStatePrefManager::IsEasyUnlockEnabled() const {
-  bool pref_value;
   const base::DictionaryValue* user_prefs = GetActiveUserPrefsDictionary();
-  if (!user_prefs || !user_prefs->GetBooleanWithoutPathExpansion(
-                         chromeos::multidevice_setup::kSmartLockEnabledPrefName,
-                         &pref_value)) {
-    PA_LOG(ERROR) << "Failed to get easyunlock_enabled.";
-    return false;
+  if (user_prefs) {
+    absl::optional<bool> pref_value = user_prefs->FindBoolKey(
+        chromeos::multidevice_setup::kSmartLockEnabledPrefName);
+    if (pref_value.has_value()) {
+      return pref_value.value();
+    }
   }
-  return pref_value;
+  PA_LOG(ERROR) << "Failed to get easyunlock_enabled.";
+  return false;
 }
 
 bool ProximityAuthLocalStatePrefManager::IsEasyUnlockEnabledStateSet() const {
@@ -103,16 +105,16 @@
 }
 
 bool ProximityAuthLocalStatePrefManager::IsChromeOSLoginAllowed() const {
-  bool pref_value;
   const base::DictionaryValue* user_prefs = GetActiveUserPrefsDictionary();
-  if (!user_prefs ||
-      !user_prefs->GetBooleanWithoutPathExpansion(
-          chromeos::multidevice_setup::kSmartLockSigninAllowedPrefName,
-          &pref_value)) {
-    PA_LOG(VERBOSE) << "Failed to get is_chrome_login_allowed, not disallowing";
-    return true;
+  if (user_prefs) {
+    absl::optional<bool> pref_value = user_prefs->FindBoolKey(
+        chromeos::multidevice_setup::kSmartLockSigninAllowedPrefName);
+    if (pref_value.has_value()) {
+      return pref_value.value();
+    }
   }
-  return pref_value;
+  PA_LOG(VERBOSE) << "Failed to get is_chrome_login_allowed, not disallowing";
+  return true;
 }
 
 void ProximityAuthLocalStatePrefManager::SetIsChromeOSLoginEnabled(
@@ -121,15 +123,16 @@
 }
 
 bool ProximityAuthLocalStatePrefManager::IsChromeOSLoginEnabled() const {
-  bool pref_value;
   const base::DictionaryValue* user_prefs = GetActiveUserPrefsDictionary();
-  if (!user_prefs ||
-      !user_prefs->GetBooleanWithoutPathExpansion(
-          prefs::kProximityAuthIsChromeOSLoginEnabled, &pref_value)) {
-    PA_LOG(ERROR) << "Failed to get is_chrome_login_enabled.";
-    return false;
+  if (user_prefs) {
+    absl::optional<bool> pref_value =
+        user_prefs->FindBoolKey(prefs::kProximityAuthIsChromeOSLoginEnabled);
+    if (pref_value.has_value()) {
+      return pref_value.value();
+    }
   }
-  return pref_value;
+  PA_LOG(ERROR) << "Failed to get is_chrome_login_enabled.";
+  return false;
 }
 
 void ProximityAuthLocalStatePrefManager::SetHasShownLoginDisabledMessage(
@@ -164,10 +167,9 @@
   if (!user_prefs)
     return false;
 
-  bool pref_value = false;
-  user_prefs->GetBooleanWithoutPathExpansion(
-      prefs::kProximityAuthHasShownLoginDisabledMessage, &pref_value);
-  return pref_value;
+  return user_prefs
+      ->FindBoolKey(prefs::kProximityAuthHasShownLoginDisabledMessage)
+      .value_or(false);
 }
 
 const base::DictionaryValue*
diff --git a/chromeos/components/proximity_auth/proximity_auth_profile_pref_manager.cc b/chromeos/components/proximity_auth/proximity_auth_profile_pref_manager.cc
index fa9fede..71020a0e 100644
--- a/chromeos/components/proximity_auth/proximity_auth_profile_pref_manager.cc
+++ b/chromeos/components/proximity_auth/proximity_auth_profile_pref_manager.cc
@@ -195,10 +195,9 @@
     return false;
   }
 
-  bool pref_value = false;
-  current_user_prefs->GetBooleanWithoutPathExpansion(
-      prefs::kProximityAuthHasShownLoginDisabledMessage, &pref_value);
-  return pref_value;
+  return current_user_prefs
+      ->FindBoolKey(prefs::kProximityAuthHasShownLoginDisabledMessage)
+      .value_or(false);
 }
 
 void ProximityAuthProfilePrefManager::OnFeatureStatesChanged(
diff --git a/chromeos/dbus/cros_disks_client.cc b/chromeos/dbus/cros_disks_client.cc
index 40c6e7f..b0664ee 100644
--- a/chromeos/dbus/cros_disks_client.cc
+++ b/chromeos/dbus/cros_disks_client.cc
@@ -708,22 +708,23 @@
   if (!value || !value->GetAsDictionary(&properties))
     return;
 
-  properties->GetBooleanWithoutPathExpansion(
-      cros_disks::kDeviceIsDrive, &is_drive_);
-  properties->GetBooleanWithoutPathExpansion(
-      cros_disks::kDeviceIsReadOnly, &is_read_only_);
-  properties->GetBooleanWithoutPathExpansion(
-      cros_disks::kDevicePresentationHide, &is_hidden_);
-  properties->GetBooleanWithoutPathExpansion(
-      cros_disks::kDeviceIsMediaAvailable, &has_media_);
-  properties->GetBooleanWithoutPathExpansion(
-      cros_disks::kDeviceIsOnBootDevice, &on_boot_device_);
-  properties->GetBooleanWithoutPathExpansion(
-      cros_disks::kDeviceIsOnRemovableDevice, &on_removable_device_);
-  properties->GetBooleanWithoutPathExpansion(cros_disks::kDeviceIsVirtual,
-                                             &is_virtual_);
-  properties->GetBooleanWithoutPathExpansion(cros_disks::kIsAutoMountable,
-                                             &is_auto_mountable_);
+  is_drive_ =
+      properties->FindBoolKey(cros_disks::kDeviceIsDrive).value_or(is_drive_);
+  is_read_only_ = properties->FindBoolKey(cros_disks::kDeviceIsReadOnly)
+                      .value_or(is_read_only_);
+  is_hidden_ = properties->FindBoolKey(cros_disks::kDevicePresentationHide)
+                   .value_or(is_hidden_);
+  has_media_ = properties->FindBoolKey(cros_disks::kDeviceIsMediaAvailable)
+                   .value_or(has_media_);
+  on_boot_device_ = properties->FindBoolKey(cros_disks::kDeviceIsOnBootDevice)
+                        .value_or(on_boot_device_);
+  on_removable_device_ =
+      properties->FindBoolKey(cros_disks::kDeviceIsOnRemovableDevice)
+          .value_or(on_removable_device_);
+  is_virtual_ = properties->FindBoolKey(cros_disks::kDeviceIsVirtual)
+                    .value_or(is_virtual_);
+  is_auto_mountable_ = properties->FindBoolKey(cros_disks::kIsAutoMountable)
+                           .value_or(is_auto_mountable_);
   properties->GetStringWithoutPathExpansion(cros_disks::kStorageDevicePath,
                                             &storage_device_path_);
   properties->GetStringWithoutPathExpansion(
diff --git a/chromeos/network/cellular_inhibitor_unittest.cc b/chromeos/network/cellular_inhibitor_unittest.cc
index a7726fa..d142908 100644
--- a/chromeos/network/cellular_inhibitor_unittest.cc
+++ b/chromeos/network/cellular_inhibitor_unittest.cc
@@ -122,11 +122,11 @@
     if (!properties_)
       return GetInhibitedPropertyResult::kOperationFailed;
 
-    bool inhibited;
-    EXPECT_TRUE(properties_->GetBooleanWithoutPathExpansion(
-        shill::kInhibitedProperty, &inhibited));
-    return inhibited ? GetInhibitedPropertyResult::kTrue
-                     : GetInhibitedPropertyResult::kFalse;
+    absl::optional<bool> inhibited =
+        properties_->FindBoolKey(shill::kInhibitedProperty);
+    EXPECT_TRUE(inhibited.has_value());
+    return inhibited.value() ? GetInhibitedPropertyResult::kTrue
+                             : GetInhibitedPropertyResult::kFalse;
   }
 
   absl::optional<CellularInhibitor::InhibitReason> GetInhibitReason() const {
diff --git a/chromeos/network/network_device_handler_unittest.cc b/chromeos/network/network_device_handler_unittest.cc
index 302549e..22a98dd 100644
--- a/chromeos/network/network_device_handler_unittest.cc
+++ b/chromeos/network/network_device_handler_unittest.cc
@@ -210,10 +210,10 @@
   // Roaming should be enabled now.
   GetDeviceProperties(kDefaultCellularDevicePath, kResultSuccess);
 
-  bool allow_roaming;
-  EXPECT_TRUE(properties_->GetBooleanWithoutPathExpansion(
-      shill::kCellularAllowRoamingProperty, &allow_roaming));
-  EXPECT_TRUE(allow_roaming);
+  absl::optional<bool> allow_roaming =
+      properties_->FindBoolKey(shill::kCellularAllowRoamingProperty);
+  EXPECT_TRUE(allow_roaming.has_value());
+  EXPECT_TRUE(allow_roaming.value());
 
   network_device_handler_->SetCellularAllowRoaming(false);
   base::RunLoop().RunUntilIdle();
@@ -221,9 +221,10 @@
   // Roaming should be disable again.
   GetDeviceProperties(kDefaultCellularDevicePath, kResultSuccess);
 
-  EXPECT_TRUE(properties_->GetBooleanWithoutPathExpansion(
-      shill::kCellularAllowRoamingProperty, &allow_roaming));
-  EXPECT_FALSE(allow_roaming);
+  allow_roaming =
+      properties_->FindBoolKey(shill::kCellularAllowRoamingProperty);
+  EXPECT_TRUE(allow_roaming.has_value());
+  EXPECT_FALSE(allow_roaming.value());
 }
 
 TEST_F(NetworkDeviceHandlerTest,
diff --git a/chromeos/network/network_state_unittest.cc b/chromeos/network/network_state_unittest.cc
index cfae02c..d8ace66 100644
--- a/chromeos/network/network_state_unittest.cc
+++ b/chromeos/network/network_state_unittest.cc
@@ -356,10 +356,10 @@
   EXPECT_TRUE(battery_percentage.has_value());
   EXPECT_EQ(85, battery_percentage.value());
 
-  bool tether_has_connected_to_host;
-  EXPECT_TRUE(dictionary.GetBooleanWithoutPathExpansion(
-      kTetherHasConnectedToHost, &tether_has_connected_to_host));
-  EXPECT_TRUE(tether_has_connected_to_host);
+  absl::optional<bool> tether_has_connected_to_host =
+      dictionary.FindBoolKey(kTetherHasConnectedToHost);
+  EXPECT_TRUE(tether_has_connected_to_host.has_value());
+  EXPECT_TRUE(tether_has_connected_to_host.value());
 
   std::string carrier;
   EXPECT_TRUE(
diff --git a/chromeos/network/onc/onc_normalizer.cc b/chromeos/network/onc/onc_normalizer.cc
index 7a1d5cf4..b8bf8277 100644
--- a/chromeos/network/onc/onc_normalizer.cc
+++ b/chromeos/network/onc/onc_normalizer.cc
@@ -164,8 +164,7 @@
 }
 
 void Normalizer::NormalizeNetworkConfiguration(base::DictionaryValue* network) {
-  bool remove = false;
-  network->GetBooleanWithoutPathExpansion(::onc::kRemove, &remove);
+  bool remove = network->FindBoolKey(::onc::kRemove).value_or(false);
   if (remove) {
     network->RemoveKey(::onc::network_config::kStaticIPConfig);
     network->RemoveKey(::onc::network_config::kName);
diff --git a/chromeos/network/onc/onc_utils.cc b/chromeos/network/onc/onc_utils.cc
index 116a6c7..babf6c50 100644
--- a/chromeos/network/onc/onc_utils.cc
+++ b/chromeos/network/onc/onc_utils.cc
@@ -1321,11 +1321,10 @@
   if (!global_config)
     return false;  // By default, all networks are allowed to autoconnect.
 
-  bool only_policy_autoconnect = false;
-  global_config->GetBooleanWithoutPathExpansion(
-      ::onc::global_network_config::kAllowOnlyPolicyNetworksToAutoconnect,
-      &only_policy_autoconnect);
-  return only_policy_autoconnect;
+  return global_config
+      ->FindBoolKey(
+          ::onc::global_network_config::kAllowOnlyPolicyNetworksToAutoconnect)
+      .value_or(false);
 }
 
 const base::DictionaryValue* GetPolicyForNetwork(
diff --git a/chromeos/network/onc/onc_validator.cc b/chromeos/network/onc/onc_validator.cc
index 77b91544..c377f7957 100644
--- a/chromeos/network/onc/onc_validator.cc
+++ b/chromeos/network/onc/onc_validator.cc
@@ -665,8 +665,7 @@
 
   bool all_required_exist = RequireField(*result, ::onc::network_config::kGUID);
 
-  bool remove = false;
-  result->GetBooleanWithoutPathExpansion(::onc::kRemove, &remove);
+  bool remove = result->FindBoolKey(::onc::kRemove).value_or(false);
   if (!remove) {
     all_required_exist &= RequireField(*result, ::onc::network_config::kName) &&
                           RequireField(*result, ::onc::network_config::kType);
@@ -1150,8 +1149,7 @@
 
   bool all_required_exist = RequireField(*result, ::onc::certificate::kGUID);
 
-  bool remove = false;
-  result->GetBooleanWithoutPathExpansion(::onc::kRemove, &remove);
+  bool remove = result->FindBoolKey(::onc::kRemove).value_or(false);
   if (remove) {
     path_.push_back(::onc::kRemove);
     std::ostringstream msg;
diff --git a/chromeos/network/policy_util.cc b/chromeos/network/policy_util.cc
index 90f5999e..c03acb9 100644
--- a/chromeos/network/policy_util.cc
+++ b/chromeos/network/policy_util.cc
@@ -152,9 +152,7 @@
     return false;
   }
 
-  bool autoconnect = false;
-  network_dict->GetBooleanWithoutPathExpansion(autoconnect_key, &autoconnect);
-  return autoconnect;
+  return network_dict->FindBoolKey(autoconnect_key).value_or(false);
 }
 
 base::Value* GetOrCreateNestedDictionary(const std::string& key1,
@@ -245,10 +243,11 @@
   // If present, apply the Autoconnect policy only to networks that are not
   // managed by policy.
   if (!network_policy && global_policy && profile) {
-    bool allow_only_policy_autoconnect = false;
-    global_policy->GetBooleanWithoutPathExpansion(
-        ::onc::global_network_config::kAllowOnlyPolicyNetworksToAutoconnect,
-        &allow_only_policy_autoconnect);
+    bool allow_only_policy_autoconnect =
+        global_policy
+            ->FindBoolKey(::onc::global_network_config::
+                              kAllowOnlyPolicyNetworksToAutoconnect)
+            .value_or(false);
     if (allow_only_policy_autoconnect) {
       ApplyGlobalAutoconnectPolicy(profile->type(),
                                    augmented_onc_network.get());
@@ -270,17 +269,17 @@
     return;  // Autoconnect for Ethernet cannot be configured.
 
   // By default all networks are allowed to autoconnect.
-  bool only_policy_autoconnect = false;
-  global_network_policy.GetBooleanWithoutPathExpansion(
-      ::onc::global_network_config::kAllowOnlyPolicyNetworksToAutoconnect,
-      &only_policy_autoconnect);
+  bool only_policy_autoconnect =
+      global_network_policy
+          .FindBoolKey(::onc::global_network_config::
+                           kAllowOnlyPolicyNetworksToAutoconnect)
+          .value_or(false);
   if (!only_policy_autoconnect)
     return;
 
-  bool old_autoconnect = false;
-  if (shill_dictionary.GetBooleanWithoutPathExpansion(
-          shill::kAutoConnectProperty, &old_autoconnect) &&
-      !old_autoconnect) {
+  bool old_autoconnect =
+      shill_dictionary.FindBoolKey(shill::kAutoConnectProperty).value_or(false);
+  if (!old_autoconnect) {
     // Autoconnect is already explicitly disabled. No need to set it again.
     return;
   }
diff --git a/chromeos/services/libassistant/grpc/BUILD.gn b/chromeos/services/libassistant/grpc/BUILD.gn
index aeda997..11b6e5bf 100644
--- a/chromeos/services/libassistant/grpc/BUILD.gn
+++ b/chromeos/services/libassistant/grpc/BUILD.gn
@@ -2,15 +2,37 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+source_set("grpc_client") {
+  sources = [
+    "grpc_client_cq_tag.h",
+    "grpc_client_thread.cc",
+    "grpc_client_thread.h",
+    "grpc_state.h",
+  ]
+
+  public_deps = [
+    ":grpc_util",
+    "//third_party/grpc:grpc++",
+  ]
+
+  deps = [
+    "//base",
+    "//third_party/protobuf:protobuf_lite",
+  ]
+}
+
 source_set("libassistant_client") {
   sources = [
     "grpc_libassistant_client.cc",
     "grpc_libassistant_client.h",
   ]
 
-  public_deps = [ "//third_party/grpc:grpc++" ]
+  public_deps = [ "//chromeos/assistant/internal:support" ]
 
-  deps = [ "//base" ]
+  deps = [
+    ":grpc_client",
+    "//base",
+  ]
 }
 
 source_set("grpc_util") {
@@ -22,6 +44,7 @@
   deps = [
     "//base",
     "//third_party/grpc:grpc++",
+    "//third_party/protobuf:protobuf_lite",
   ]
 }
 
diff --git a/chromeos/services/libassistant/grpc/external_services/grpc_services_initializer.cc b/chromeos/services/libassistant/grpc/external_services/grpc_services_initializer.cc
index 1d97204..2247f6e 100644
--- a/chromeos/services/libassistant/grpc/external_services/grpc_services_initializer.cc
+++ b/chromeos/services/libassistant/grpc/external_services/grpc_services_initializer.cc
@@ -4,7 +4,6 @@
 
 #include "chromeos/services/libassistant/grpc/external_services/grpc_services_initializer.h"
 
-#include "base/containers/flat_set.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/memory/weak_ptr.h"
 #include "chromeos/services/libassistant/grpc/grpc_libassistant_client.h"
diff --git a/chromeos/services/libassistant/grpc/grpc_client_cq_tag.h b/chromeos/services/libassistant/grpc/grpc_client_cq_tag.h
new file mode 100644
index 0000000..9beb410d
--- /dev/null
+++ b/chromeos/services/libassistant/grpc/grpc_client_cq_tag.h
@@ -0,0 +1,37 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMEOS_SERVICES_LIBASSISTANT_GRPC_GRPC_CLIENT_CQ_TAG_H_
+#define CHROMEOS_SERVICES_LIBASSISTANT_GRPC_GRPC_CLIENT_CQ_TAG_H_
+
+#include "base/macros.h"
+#include "third_party/grpc/src/include/grpcpp/grpcpp.h"
+
+namespace chromeos {
+namespace libassistant {
+
+// Represents a pending asynchronous client call as a tag that can be
+// stored in a |grpc::CompletionQueue|.
+class GrpcClientCQTag {
+ public:
+  enum class State {
+    kOk,
+    kFailed,    // RPC failed.
+    kShutdown,  // Client CQ has been shutdown.
+  };
+
+  GrpcClientCQTag() = default;
+  GrpcClientCQTag(const GrpcClientCQTag&) = delete;
+  GrpcClientCQTag& operator=(const GrpcClientCQTag&) = delete;
+  virtual ~GrpcClientCQTag() = default;
+
+  // OnCompleted is invoked when the RPC has finished.
+  // Implementations of OnCompleted can delete *this.
+  virtual void OnCompleted(State state) = 0;
+};
+
+}  // namespace libassistant
+}  // namespace chromeos
+
+#endif  // CHROMEOS_SERVICES_LIBASSISTANT_GRPC_GRPC_CLIENT_CQ_TAG_H_
diff --git a/chromeos/services/libassistant/grpc/grpc_client_thread.cc b/chromeos/services/libassistant/grpc/grpc_client_thread.cc
new file mode 100644
index 0000000..e5719931
--- /dev/null
+++ b/chromeos/services/libassistant/grpc/grpc_client_thread.cc
@@ -0,0 +1,84 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromeos/services/libassistant/grpc/grpc_client_thread.h"
+
+#include <memory>
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/single_thread_task_runner.h"
+#include "chromeos/services/libassistant/grpc/grpc_client_cq_tag.h"
+
+namespace chromeos {
+namespace libassistant {
+
+GrpcClientThread::GrpcClientThread(const std::string& thread_name,
+                                   base::ThreadPriority priority)
+    : thread_(thread_name) {
+  base::Thread::Options thread_options = {/*type=*/base::MessagePumpType::IO,
+                                          /*size=*/0};
+  thread_options.priority = priority;
+  thread_.StartWithOptions(thread_options);
+  StartCQ();
+}
+
+GrpcClientThread::~GrpcClientThread() {
+  StopCQ();
+}
+
+void GrpcClientThread::StartCQ() {
+  is_cq_shutdown_ = false;
+  thread_.task_runner()->PostTask(
+      FROM_HERE, base::BindOnce(&GrpcClientThread::ScanCQInternal,
+                                base::Unretained(this)));
+}
+
+void GrpcClientThread::StopCQ() {
+  {
+    // The lock prevents the following scenario (events listed in time order)
+    // 1. CQ thread takes a tag out from completion queue
+    // 2. CQ shutdowns
+    // 3. CQ thread schedules a gRPC call retry because the call failed
+    // Step 3 is problematic because CQ should not be used after shutdown
+    base::AutoLock lock(cq_shutdown_lock_);
+    completion_queue_.Shutdown();
+    is_cq_shutdown_ = true;
+  }
+
+  thread_.Stop();
+}
+
+void GrpcClientThread::ScanCQInternal() {
+  void* tag;
+  bool ok;
+  while (true) {
+    // Block waiting for the completion of the next operation in the completion
+    // queue. The completing operation is uniquely identified by its tag, which
+    // in this case is a pointer to |GrpcClientCQTag| implementing the next step
+    // of RPC execution. Next() is thread-safe, and will return true if an event
+    // is available, false if the queue is fully drained and shut down.
+    if (!completion_queue_.Next(&tag, &ok)) {
+      DVLOG(3) << "Completion queue shutdown.";
+      break;
+    }
+
+    DVLOG(3) << "Read a completion queue event. Invoking its callback.";
+    GrpcClientCQTag* callback_tag = static_cast<GrpcClientCQTag*>(tag);
+    {
+      base::AutoLock lock(cq_shutdown_lock_);
+      if (is_cq_shutdown_) {
+        callback_tag->OnCompleted(GrpcClientCQTag::State::kShutdown);
+      } else {
+        callback_tag->OnCompleted(ok ? GrpcClientCQTag::State::kOk
+                                     : GrpcClientCQTag::State::kFailed);
+      }
+    }
+  }
+}
+
+}  // namespace libassistant
+}  // namespace chromeos
diff --git a/chromeos/services/libassistant/grpc/grpc_client_thread.h b/chromeos/services/libassistant/grpc/grpc_client_thread.h
new file mode 100644
index 0000000..460c77a
--- /dev/null
+++ b/chromeos/services/libassistant/grpc/grpc_client_thread.h
@@ -0,0 +1,51 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMEOS_SERVICES_LIBASSISTANT_GRPC_GRPC_CLIENT_THREAD_H_
+#define CHROMEOS_SERVICES_LIBASSISTANT_GRPC_GRPC_CLIENT_THREAD_H_
+
+#include <memory>
+#include <string>
+
+#include "base/macros.h"
+#include "base/synchronization/lock.h"
+#include "base/threading/platform_thread.h"
+#include "base/threading/thread.h"
+#include "third_party/grpc/src/include/grpcpp/completion_queue.h"
+
+namespace chromeos {
+namespace libassistant {
+
+// This thread could be shared by multiple grpc clients. It needs to be
+// destroyed after the clients.
+class GrpcClientThread {
+ public:
+  explicit GrpcClientThread(
+      const std::string& thread_name,
+      base::ThreadPriority priority = base::ThreadPriority::NORMAL);
+  GrpcClientThread(const GrpcClientThread&) = delete;
+  GrpcClientThread& operator=(const GrpcClientThread&) = delete;
+  ~GrpcClientThread();
+
+  grpc::CompletionQueue* completion_queue() { return &completion_queue_; }
+
+ private:
+  // Start polling the completion queue.
+  void StartCQ();
+  // Shutdown the CQ, stop CQ thread, then drain CQ
+  void StopCQ();
+
+  void ScanCQInternal();
+
+  grpc::CompletionQueue completion_queue_;
+  base::Thread thread_;
+
+  base::Lock cq_shutdown_lock_;
+  bool is_cq_shutdown_ = false;
+};
+
+}  // namespace libassistant
+}  // namespace chromeos
+
+#endif  // CHROMEOS_SERVICES_LIBASSISTANT_GRPC_GRPC_CLIENT_THREAD_H_
diff --git a/chromeos/services/libassistant/grpc/grpc_libassistant_client.cc b/chromeos/services/libassistant/grpc/grpc_libassistant_client.cc
index 4e0361bf..76308332 100644
--- a/chromeos/services/libassistant/grpc/grpc_libassistant_client.cc
+++ b/chromeos/services/libassistant/grpc/grpc_libassistant_client.cc
@@ -9,21 +9,42 @@
 #include "base/check.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/notreached.h"
+#include "chromeos/assistant/internal/libassist_util.h"
 
 namespace chromeos {
 namespace libassistant {
 
+namespace {
+
+// Implements one async client method. ResponseCallback will run on completion
+// queue thread |client_thread_|, so caller should bind its callback to its
+// current sequence using BindToCurrentSequence() to make sure callback will
+// always be invoked from the right sequence. The raw pointer will be handled by
+// |RPCState| internally and gets deleted upon completion of the RPC call.
+#define LIBAS_GRPC_CLIENT_METHOD(service, method)                            \
+  void GrpcLibassistantClient::method(                                       \
+      const chromeos::assistant::shared::method##Request& request,           \
+      chromeos::libassistant::ResponseCallback<                              \
+          grpc::Status, chromeos::assistant::shared::method##Response> done, \
+      chromeos::libassistant::StateConfig state_config) {                    \
+    new chromeos::libassistant::RPCState<                                    \
+        chromeos::assistant::shared::method##Response>(                      \
+        channel_, client_thread_.completion_queue(),                         \
+        chromeos::assistant::GetLibassistGrpcMethodName(service, #method),   \
+        request, std::move(done), state_config);                             \
+  }
+
+}  // namespace
+
 GrpcLibassistantClient::GrpcLibassistantClient(
     std::shared_ptr<grpc::Channel> channel)
-    : channel_(std::move(channel)) {
+    : channel_(std::move(channel)), client_thread_("gRPCLibassistantClient") {
   DCHECK(channel_);
 }
 
 GrpcLibassistantClient::~GrpcLibassistantClient() = default;
 
-void GrpcLibassistantClient::RegisterCustomer() {
-  NOTIMPLEMENTED();
-}
+LIBAS_GRPC_CLIENT_METHOD("CustomerRegistrationService", RegisterCustomer)
 
 }  // namespace libassistant
 }  // namespace chromeos
diff --git a/chromeos/services/libassistant/grpc/grpc_libassistant_client.h b/chromeos/services/libassistant/grpc/grpc_libassistant_client.h
index 7e4a45d2..7baa22d 100644
--- a/chromeos/services/libassistant/grpc/grpc_libassistant_client.h
+++ b/chromeos/services/libassistant/grpc/grpc_libassistant_client.h
@@ -7,13 +7,31 @@
 
 #include <memory>
 
+#include "chromeos/assistant/internal/util_headers.h"
+#include "chromeos/services/libassistant/grpc/grpc_client_thread.h"
+#include "chromeos/services/libassistant/grpc/grpc_state.h"
+#include "chromeos/services/libassistant/grpc/grpc_util.h"
 #include "third_party/grpc/src/include/grpcpp/channel.h"
 
 namespace chromeos {
 namespace libassistant {
 
-// Defines grpc client for ipc. All client methods should be implemented here to
-// send the requests to server.
+namespace {
+
+// Defines one async client method.
+#define LIBAS_GRPC_CLIENT_INTERFACE(method)                                  \
+  void method(                                                               \
+      const chromeos::assistant::shared::method##Request& request,           \
+      chromeos::libassistant::ResponseCallback<                              \
+          grpc::Status, chromeos::assistant::shared::method##Response> done, \
+      chromeos::libassistant::StateConfig state_config =                     \
+          chromeos::libassistant::StateConfig());
+
+}  // namespace
+
+// Interface for all methods we as a client can invoke from Libassistant gRPC
+// services. All client methods should be implemented here to send the requests
+// to server. We only introduce methods that are currently in use.
 class GrpcLibassistantClient {
  public:
   explicit GrpcLibassistantClient(std::shared_ptr<grpc::Channel> channel);
@@ -24,13 +42,16 @@
   // CustomerRegistrationService:
   // Handles CustomerRegistrationRequest sent from libassistant customers to
   // register themselves before allowing to use libassistant services.
-  void RegisterCustomer();
+  LIBAS_GRPC_CLIENT_INTERFACE(RegisterCustomer)
 
  private:
   // This channel will be shared between all stubs used to communicate with
   // multiple services. All channels are reference counted and will be freed
   // automatically.
   std::shared_ptr<grpc::Channel> channel_;
+
+  // Thread running the completion queue.
+  chromeos::libassistant::GrpcClientThread client_thread_;
 };
 
 }  // namespace libassistant
diff --git a/chromeos/services/libassistant/grpc/grpc_state.h b/chromeos/services/libassistant/grpc/grpc_state.h
new file mode 100644
index 0000000..bdf02f5
--- /dev/null
+++ b/chromeos/services/libassistant/grpc/grpc_state.h
@@ -0,0 +1,204 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMEOS_SERVICES_LIBASSISTANT_GRPC_GRPC_STATE_H_
+#define CHROMEOS_SERVICES_LIBASSISTANT_GRPC_GRPC_STATE_H_
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/check.h"
+#include "base/logging.h"
+#include "chromeos/services/libassistant/grpc/grpc_client_cq_tag.h"
+#include "chromeos/services/libassistant/grpc/grpc_util.h"
+#include "third_party/grpc/src/include/grpcpp/client_context.h"
+#include "third_party/grpc/src/include/grpcpp/generic/generic_stub.h"
+#include "third_party/grpc/src/include/grpcpp/impl/codegen/client_context.h"
+#include "third_party/grpc/src/include/grpcpp/support/status.h"
+
+namespace google {
+namespace protobuf {
+class MessageLite;
+}  // namespace protobuf
+}  // namespace google
+
+namespace chromeos {
+namespace libassistant {
+
+// Configs which dictate options for an RPCState instance.
+struct StateConfig {
+  StateConfig() = default;
+  ~StateConfig() = default;
+
+  // The maximum retry attempts for the client call if it failed.
+  int32_t max_retries = 0;
+
+  // Deadline for the client call.
+  int64_t timeout_in_ms = 2000;
+
+  // If set to true, the RPC will be queued and not "fail fast" if the channel
+  // is in TRANSIENT_FAILURE or CONNECTING state, and wait until the channel
+  // turns READY. Otherwise, such gRPCs will be failed immediately.
+  bool wait_for_ready = true;
+};
+
+// Object allocated per active RPC.
+// Manage the state of a single asynchronous RPC request. If `max_retries`
+// is greater than 0, the request will be retried for any transient failures
+// as long as the overall deadline has not elapsed.
+template <class Response>
+class RPCState : public GrpcClientCQTag {
+ public:
+  // Async RPCState constructor.
+  // Default behavior is to set wait_for_ready = true and handle timeouts
+  // manually.
+  RPCState(std::shared_ptr<grpc::Channel> channel,
+           grpc::CompletionQueue* cq,
+           const grpc::string& method,
+           const google::protobuf::MessageLite& request,
+           ResponseCallback<grpc::Status, Response> done,
+           StateConfig config)
+      : async_cb_(std::move(done)),
+        cq_(cq),
+        stub_(channel),
+        method_(method),
+        timeout_in_ms_(config.timeout_in_ms),
+        max_retries_(config.max_retries),
+        wait_for_ready_(config.wait_for_ready) {
+    DCHECK(cq);
+    grpc::Status s = GrpcSerializeProto(request, &request_buf_);
+    if (!s.ok()) {
+      LOG(ERROR) << "GrpcSerializeProto returned with non-ok status: "
+                 << s.error_message();
+      // Skip retry logic if we fail to parse our request.
+      StateDone();
+      return;
+    }
+
+    StartCall();
+  }
+
+  RPCState(const RPCState&) = delete;
+  RPCState& operator=(const RPCState&) = delete;
+  ~RPCState() override = default;
+
+  void StartCall() {
+    context_ = std::make_unique<grpc::ClientContext>();
+    context_->set_wait_for_ready(wait_for_ready_);
+
+    if (timeout_in_ms_ > 0) {
+      context_->set_deadline(
+          gpr_time_from_millis(timeout_in_ms_, GPR_TIMESPAN));
+    }
+
+    VLOG(3) << "Starting call: " << method_;
+    call_ = stub_.PrepareUnaryCall(context_.get(), method_, request_buf_, cq_);
+    call_->StartCall();
+    // Request that upon the completion of an RPC call, |response_buf_| will be
+    // updated with server's response. Tag the call with |this| to identify this
+    // request.
+    call_->Finish(&response_buf_, &status_, /*tag=*/this);
+  }
+
+  // GrpcClientCQTag overrides:
+  void OnCompleted(State state) override {
+    VLOG(3) << "Completed call: " << method_;
+
+    if (state == State::kShutdown) {
+      LOG(WARNING) << "Unary RPC done with CQ has been shutting down.";
+      ParseAndCallDone();
+      return;
+    }
+
+    if (status_.ok() || status_.error_code() == grpc::StatusCode::CANCELLED) {
+      ParseAndCallDone();
+      return;
+    }
+
+    LOG_IF(WARNING, ShouldLogGrpcError())
+        << method_ << " returned with non-ok status: " << status_.error_code()
+        << " Retries: " << num_retries_ << " Max: " << max_retries_ << "\n";
+    // TODO(nanping): Retry only for logical errors by having them in the
+    // config.
+    // Retry if we have any attempts left
+    if (num_retries_ < max_retries_) {
+      ++num_retries_;
+      response_buf_.Clear();
+      LOG_IF(WARNING, ShouldLogGrpcError())
+          << "Retrying call for " << method_ << "Retry: " << num_retries_
+          << " of " << max_retries_;
+      StartCall();
+    } else {
+      // Attach additional GRPC error information if any to the final status
+      LOG_IF(ERROR, ShouldLogGrpcError()) << "RPC call failed :\n";
+      StateDone();
+    }
+  }
+
+  void ParseAndCallDone() {
+    if (!GrpcParseProto(&response_buf_, &async_response_)) {
+      LOG(ERROR) << "RPC parse response failed.";
+    }
+    StateDone();
+  }
+
+  void StateDone() {
+    DCHECK(async_cb_);
+    std::move(async_cb_).Run(status_, std::move(async_response_));
+
+    delete this;
+  }
+
+ private:
+  bool ShouldLogGrpcError() {
+    // Some grpc errors are legitimate/expected. Ex: ReadSecureFile() may return
+    // NOT_FOUND if the file doesn't exist. Do not log warning/errors since it's
+    // just spam. The caller can log the error if desired.
+    return status_.error_code() != grpc::StatusCode::NOT_FOUND;
+  }
+
+  // An instance managing the context settings, e.g. deadline, relevant to the
+  // call they are invoked with. Same object should not be reused across RPCs.
+  std::unique_ptr<::grpc::ClientContext> context_;
+
+  // Message response of type |Response| received from the server.
+  Response async_response_;
+
+  // Buffer filled in with request/response.
+  grpc::ByteBuffer request_buf_;
+  grpc::ByteBuffer response_buf_;
+
+  // Status of a RPC call. The status is OK if the call finished with no errors.
+  grpc::Status status_;
+
+  ResponseCallback<grpc::Status, Response> async_cb_;
+
+  // An instance used by an async gRPC client to manage asynchronous rpc
+  // operations. An RPC call is bound to a CompletionQueue when performed
+  // using the stub.
+  grpc::CompletionQueue* cq_ = nullptr;
+
+  // An instance used by a gRPC client to invoke rpc methods implemented in
+  // the server.
+  grpc::GenericStub stub_;
+
+  // An instance held a unary RPC call and exposes methods to start and finish
+  // the call with server's response.
+  std::unique_ptr<::grpc::GenericClientAsyncResponseReader> call_;
+
+  // The name of a RPC method.
+  grpc::string method_;
+
+  // Config options for a RPC call.
+  int64_t timeout_in_ms_;
+  size_t max_retries_;
+  bool wait_for_ready_;
+  size_t num_retries_ = 0;
+};
+
+}  // namespace libassistant
+}  // namespace chromeos
+
+#endif  // CHROMEOS_SERVICES_LIBASSISTANT_GRPC_GRPC_STATE_H_
diff --git a/chromeos/services/libassistant/grpc/grpc_util.cc b/chromeos/services/libassistant/grpc/grpc_util.cc
index f954b48d..4c61327 100644
--- a/chromeos/services/libassistant/grpc/grpc_util.cc
+++ b/chromeos/services/libassistant/grpc/grpc_util.cc
@@ -5,6 +5,9 @@
 #include "chromeos/services/libassistant/grpc/grpc_util.h"
 
 #include "base/check_op.h"
+#include "third_party/grpc/src/include/grpcpp/impl/codegen/proto_utils.h"
+#include "third_party/grpc/src/include/grpcpp/support/proto_buffer_reader.h"
+#include "third_party/grpc/src/include/grpcpp/support/proto_buffer_writer.h"
 
 namespace chromeos {
 namespace libassistant {
@@ -16,5 +19,18 @@
   return UDS;
 }
 
+grpc::Status GrpcSerializeProto(const google::protobuf::MessageLite& src,
+                                grpc::ByteBuffer* dst) {
+  bool own_buffer;
+  return grpc::GenericSerialize<grpc::ProtoBufferWriter,
+                                google::protobuf::MessageLite>(src, dst,
+                                                               &own_buffer);
+}
+
+bool GrpcParseProto(grpc::ByteBuffer* src, google::protobuf::MessageLite* dst) {
+  grpc::ProtoBufferReader reader(src);
+  return dst->ParseFromZeroCopyStream(&reader);
+}
+
 }  // namespace libassistant
 }  // namespace chromeos
diff --git a/chromeos/services/libassistant/grpc/grpc_util.h b/chromeos/services/libassistant/grpc/grpc_util.h
index 696cbd8..6df5f43 100644
--- a/chromeos/services/libassistant/grpc/grpc_util.h
+++ b/chromeos/services/libassistant/grpc/grpc_util.h
@@ -5,17 +5,31 @@
 #ifndef CHROMEOS_SERVICES_LIBASSISTANT_GRPC_GRPC_UTIL_H_
 #define CHROMEOS_SERVICES_LIBASSISTANT_GRPC_GRPC_UTIL_H_
 
-#include "third_party/grpc/src/include/grpc/grpc_security_constants.h"
-
 #include <string>
 
+#include "base/callback.h"
+#include "third_party/grpc/src/include/grpc/grpc_security_constants.h"
+#include "third_party/grpc/src/include/grpcpp/support/byte_buffer.h"
+#include "third_party/grpc/src/include/grpcpp/support/status.h"
+#include "third_party/protobuf/src/google/protobuf/message_lite.h"
+
 namespace chromeos {
 namespace libassistant {
 
+template <class Status, class Response>
+using ResponseCallback = base::OnceCallback<void(const Status&, Response)>;
+
 // Returns the local connection type for the given server address.
 grpc_local_connect_type GetGrpcLocalConnectType(
     const std::string& server_address);
 
+// Serialize src and store in *dst.
+grpc::Status GrpcSerializeProto(const google::protobuf::MessageLite& src,
+                                grpc::ByteBuffer* dst);
+
+// Parse contents of src and initialize *dst with them.
+bool GrpcParseProto(grpc::ByteBuffer* src, google::protobuf::MessageLite* dst);
+
 }  // namespace libassistant
 }  // namespace chromeos
 
diff --git a/components/autofill_assistant/browser/BUILD.gn b/components/autofill_assistant/browser/BUILD.gn
index e2eb5f0..12b16d0 100644
--- a/components/autofill_assistant/browser/BUILD.gn
+++ b/components/autofill_assistant/browser/BUILD.gn
@@ -382,6 +382,7 @@
     "element_precondition_unittest.cc",
     "event_handler_unittest.cc",
     "field_formatter_unittest.cc",
+    "full_card_requester_unittest.cc",
     "generic_ui_replace_placeholders_unittest.cc",
     "mock_client_context.cc",
     "mock_client_context.h",
diff --git a/components/autofill_assistant/browser/actions/action_delegate_util.cc b/components/autofill_assistant/browser/actions/action_delegate_util.cc
index 5033cdb..4ae8a2d 100644
--- a/components/autofill_assistant/browser/actions/action_delegate_util.cc
+++ b/components/autofill_assistant/browser/actions/action_delegate_util.cc
@@ -195,9 +195,10 @@
         perform,
     const ElementFinder::Result& element,
     base::OnceCallback<void(const ClientStatus&)> done) {
-  ResolveTextValue(text_value, element, delegate,
-                   base::BindOnce(&OnResolveTextValue, std::move(perform),
-                                  element, std::move(done)));
+  user_data::ResolveTextValue(
+      text_value, element, delegate,
+      base::BindOnce(&OnResolveTextValue, std::move(perform), element,
+                     std::move(done)));
 }
 
 void PerformWithElementValue(
diff --git a/components/autofill_assistant/browser/actions/collect_user_data_action.cc b/components/autofill_assistant/browser/actions/collect_user_data_action.cc
index 4fc74a8..f29340d1 100644
--- a/components/autofill_assistant/browser/actions/collect_user_data_action.cc
+++ b/components/autofill_assistant/browser/actions/collect_user_data_action.cc
@@ -1281,11 +1281,11 @@
   for (const auto* profile :
        delegate_->GetPersonalDataManager()->GetProfilesToSuggest()) {
     user_data->available_profiles_.emplace_back(
-        MakeUniqueFromProfile(*profile));
+        user_data::MakeUniqueFromProfile(*profile));
 
     if (selected_profile != nullptr &&
-        CompareContactDetails(*collect_user_data_options_, profile,
-                              selected_profile)) {
+        user_data::CompareContactDetails(*collect_user_data_options_, profile,
+                                         selected_profile)) {
       found_profile = true;
     }
 
@@ -1311,7 +1311,7 @@
     if (default_selection != -1) {
       delegate_->GetUserModel()->SetSelectedAutofillProfile(
           collect_user_data_options_->contact_details_name,
-          MakeUniqueFromProfile(
+          user_data::MakeUniqueFromProfile(
               *(user_data->available_profiles_[default_selection])),
           user_data);
     }
@@ -1330,7 +1330,7 @@
     if (default_selection != -1) {
       delegate_->GetUserModel()->SetSelectedAutofillProfile(
           collect_user_data_options_->shipping_address_name,
-          MakeUniqueFromProfile(
+          user_data::MakeUniqueFromProfile(
               *(user_data->available_profiles_[default_selection])),
           user_data);
     }
@@ -1366,7 +1366,7 @@
                 card->billing_address_id());
         if (billing_address != nullptr) {
           payment_instrument->billing_address =
-              MakeUniqueFromProfile(*billing_address);
+              user_data::MakeUniqueFromProfile(*billing_address);
         }
       }
 
diff --git a/components/autofill_assistant/browser/actions/get_element_status_action.cc b/components/autofill_assistant/browser/actions/get_element_status_action.cc
index 4aec666..c5dc673 100644
--- a/components/autofill_assistant/browser/actions/get_element_status_action.cc
+++ b/components/autofill_assistant/browser/actions/get_element_status_action.cc
@@ -165,7 +165,7 @@
       CompareResult(text, expected_match.re2(), /* is_re2= */ true);
       return;
     case GetElementStatusProto::TextMatch::kTextValue:
-      ResolveTextValue(
+      user_data::ResolveTextValue(
           expected_match.text_value(), *element_, delegate_,
           base::BindOnce(&GetElementStatusAction::OnResolveTextValue,
                          weak_ptr_factory_.GetWeakPtr(), text));
diff --git a/components/autofill_assistant/browser/actions/select_option_action.cc b/components/autofill_assistant/browser/actions/select_option_action.cc
index 3bf4a27..3fc2a64 100644
--- a/components/autofill_assistant/browser/actions/select_option_action.cc
+++ b/components/autofill_assistant/browser/actions/select_option_action.cc
@@ -54,9 +54,9 @@
       case_sensitive_ = select_option.text_filter_value().case_sensitive();
       break;
     case SelectOptionProto::kAutofillRegexpValue: {
-      ClientStatus autofill_status =
-          GetFormattedAutofillValue(select_option.autofill_regexp_value(),
-                                    delegate_->GetUserData(), &value_);
+      ClientStatus autofill_status = user_data::GetFormattedAutofillValue(
+          select_option.autofill_regexp_value(), delegate_->GetUserData(),
+          &value_);
       if (!autofill_status.ok()) {
         EndAction(autofill_status);
         return;
diff --git a/components/autofill_assistant/browser/actions/set_form_field_value_action.cc b/components/autofill_assistant/browser/actions/set_form_field_value_action.cc
index f4bf474..c12fc672 100644
--- a/components/autofill_assistant/browser/actions/set_form_field_value_action.cc
+++ b/components/autofill_assistant/browser/actions/set_form_field_value_action.cc
@@ -113,8 +113,9 @@
         break;
       case SetFormFieldValueProto_KeyPress::kClientMemoryKey: {
         std::string value;
-        ClientStatus client_memory_status = GetClientMemoryStringValue(
-            keypress.client_memory_key(), delegate_->GetUserData(), &value);
+        ClientStatus client_memory_status =
+            user_data::GetClientMemoryStringValue(
+                keypress.client_memory_key(), delegate_->GetUserData(), &value);
         if (!client_memory_status.ok()) {
           VLOG(1) << "SetFormFieldValueAction: bad |client_memory_key|";
           FailAction(client_memory_status, keypress_index);
@@ -125,7 +126,7 @@
       }
       case SetFormFieldValueProto_KeyPress::kAutofillValue: {
         std::string value;
-        ClientStatus autofill_status = GetFormattedAutofillValue(
+        ClientStatus autofill_status = user_data::GetFormattedAutofillValue(
             keypress.autofill_value(), delegate_->GetUserData(), &value);
         if (!autofill_status.ok()) {
           FailAction(autofill_status, keypress_index);
@@ -196,7 +197,7 @@
         std::move(next_field_callback));
   } else if (field_input.password_manager_value.credential_type() !=
              PasswordManagerValue::NOT_SET) {
-    GetPasswordManagerValue(
+    user_data::GetPasswordManagerValue(
         field_input.password_manager_value, *element_, delegate_->GetUserData(),
         delegate_->GetWebsiteLoginManager(),
         base::BindOnce(&SetFormFieldValueAction::OnGetPasswordManagerValue,
diff --git a/components/autofill_assistant/browser/actions/show_generic_ui_action.cc b/components/autofill_assistant/browser/actions/show_generic_ui_action.cc
index 816677b..f39b9ab 100644
--- a/components/autofill_assistant/browser/actions/show_generic_ui_action.cc
+++ b/components/autofill_assistant/browser/actions/show_generic_ui_action.cc
@@ -350,7 +350,7 @@
         std::vector<std::unique_ptr<autofill::AutofillProfile>>>();
     for (const auto* profile :
          delegate_->GetPersonalDataManager()->GetProfilesToSuggest()) {
-      profiles->emplace_back(MakeUniqueFromProfile(*profile));
+      profiles->emplace_back(user_data::MakeUniqueFromProfile(*profile));
     }
     WriteProfilesToUserModel(std::move(profiles),
                              proto_.show_generic_ui().request_profiles(),
diff --git a/components/autofill_assistant/browser/actions/use_address_action.cc b/components/autofill_assistant/browser/actions/use_address_action.cc
index 7cc2242..1c46e3d 100644
--- a/components/autofill_assistant/browser/actions/use_address_action.cc
+++ b/components/autofill_assistant/browser/actions/use_address_action.cc
@@ -70,7 +70,7 @@
         EndAction(ClientStatus(PRECONDITION_FAILED));
         return;
       }
-      profile_ = MakeUniqueFromProfile(*profile);
+      profile_ = user_data::MakeUniqueFromProfile(*profile);
       break;
     }
     case UseAddressProto::kModelIdentifier: {
@@ -103,7 +103,7 @@
         EndAction(ClientStatus(PRECONDITION_FAILED));
         return;
       }
-      profile_ = MakeUniqueFromProfile(*profile);
+      profile_ = user_data::MakeUniqueFromProfile(*profile);
       break;
     }
     case UseAddressProto::ADDRESS_SOURCE_NOT_SET:
diff --git a/components/autofill_assistant/browser/full_card_requester_unittest.cc b/components/autofill_assistant/browser/full_card_requester_unittest.cc
new file mode 100644
index 0000000..5c0b5a3a
--- /dev/null
+++ b/components/autofill_assistant/browser/full_card_requester_unittest.cc
@@ -0,0 +1,115 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/autofill_assistant/browser/full_card_requester.h"
+
+#include "base/memory/ref_counted.h"
+#include "base/test/gmock_callback_support.h"
+#include "base/test/mock_callback.h"
+#include "components/autofill/content/browser/content_autofill_driver.h"
+#include "components/autofill/content/browser/content_autofill_driver_factory.h"
+#include "components/autofill/core/browser/autofill_manager.h"
+#include "components/autofill/core/browser/autofill_test_utils.h"
+#include "components/autofill/core/browser/data_model/credit_card.h"
+#include "components/autofill/core/browser/payments/card_unmask_delegate.h"
+#include "components/autofill/core/browser/payments/test_payments_client.h"
+#include "components/autofill/core/browser/test_autofill_client.h"
+#include "components/autofill/core/browser/test_autofill_driver.h"
+#include "components/autofill_assistant/browser/mock_personal_data_manager.h"
+#include "content/public/test/browser_task_environment.h"
+#include "content/public/test/test_browser_context.h"
+#include "content/public/test/test_renderer_host.h"
+#include "content/public/test/web_contents_tester.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace autofill_assistant {
+namespace {
+
+using ::testing::_;
+using ::testing::Eq;
+using ::testing::Property;
+using ::testing::WithArg;
+
+class MockAutofillClient : public autofill::TestAutofillClient {
+ public:
+  MOCK_METHOD3(ShowUnmaskPrompt,
+               void(const autofill::CreditCard& card,
+                    autofill::AutofillClient::UnmaskCardReason reason,
+                    base::WeakPtr<autofill::CardUnmaskDelegate> delegate));
+};
+
+class FullCardRequesterTest : public testing::Test {
+ public:
+  FullCardRequesterTest() = default;
+
+  void SetUp() override {
+    web_contents_ = content::WebContentsTester::CreateTestWebContents(
+        &browser_context_, nullptr);
+    autofill_driver_ =
+        std::make_unique<testing::NiceMock<autofill::TestAutofillDriver>>();
+    autofill_client_.SetPrefs(autofill::test::PrefServiceForTesting());
+    autofill::ContentAutofillDriverFactory::CreateForWebContentsAndDelegate(
+        web_contents_.get(), &autofill_client_, "en-US",
+        autofill::AutofillManager::DISABLE_AUTOFILL_DOWNLOAD_MANAGER);
+    autofill_client_.set_test_payments_client(
+        std::make_unique<autofill::payments::TestPaymentsClient>(
+            autofill_driver_->GetURLLoaderFactory(),
+            autofill_client_.GetIdentityManager(), &personal_data_manager_));
+  }
+
+ protected:
+  content::BrowserTaskEnvironment task_environment_;
+  content::RenderViewHostTestEnabler rvh_test_enabler_;
+  content::TestBrowserContext browser_context_;
+  std::unique_ptr<content::WebContents> web_contents_;
+  std::unique_ptr<autofill::TestAutofillDriver> autofill_driver_;
+  MockAutofillClient autofill_client_;
+  scoped_refptr<autofill::AutofillWebDataService> database_;
+  MockPersonalDataManager personal_data_manager_;
+  FullCardRequester full_card_requester_;
+  base::MockCallback<
+      base::OnceCallback<void(const ClientStatus& status,
+                              std::unique_ptr<autofill::CreditCard> card,
+                              const std::u16string& cvc)>>
+      mock_callback_;
+};
+
+TEST_F(FullCardRequesterTest, SuccessfulCardRequest) {
+  EXPECT_CALL(autofill_client_, ShowUnmaskPrompt(_, _, _))
+      .WillOnce(
+          WithArg<2>([](base::WeakPtr<autofill::CardUnmaskDelegate> delegate) {
+            autofill::CardUnmaskDelegate::UserProvidedUnmaskDetails details;
+            details.cvc = u"123";
+            details.exp_month = u"1";
+            details.exp_year = u"2050";
+            delegate->OnUnmaskPromptAccepted(details);
+          }));
+  EXPECT_CALL(mock_callback_,
+              Run(Property(&ClientStatus::proto_status, ACTION_APPLIED), _,
+                  Eq(u"123")));
+
+  autofill::CreditCard result(/* guid= */ std::string(),
+                              autofill::test::kEmptyOrigin);
+  full_card_requester_.GetFullCard(web_contents_.get(), &result,
+                                   mock_callback_.Get());
+}
+
+TEST_F(FullCardRequesterTest, ClosedUnmaskPrompt) {
+  EXPECT_CALL(autofill_client_, ShowUnmaskPrompt(_, _, _))
+      .WillOnce(
+          WithArg<2>([](base::WeakPtr<autofill::CardUnmaskDelegate> delegate) {
+            delegate->OnUnmaskPromptClosed();
+          }));
+  EXPECT_CALL(mock_callback_,
+              Run(Property(&ClientStatus::proto_status, GET_FULL_CARD_FAILED),
+                  _, Eq(std::u16string())));
+
+  autofill::CreditCard result(/* guid= */ std::string(),
+                              autofill::test::kEmptyOrigin);
+  full_card_requester_.GetFullCard(web_contents_.get(), &result,
+                                   mock_callback_.Get());
+}
+
+}  // namespace
+}  // namespace autofill_assistant
diff --git a/components/autofill_assistant/browser/user_data_util.cc b/components/autofill_assistant/browser/user_data_util.cc
index 1776816b..a2fd6d4 100644
--- a/components/autofill_assistant/browser/user_data_util.cc
+++ b/components/autofill_assistant/browser/user_data_util.cc
@@ -27,7 +27,6 @@
 
 constexpr char kDefaultLocale[] = "en-US";
 
-// TODO: Share this helper function with use_address_action.
 std::u16string GetProfileFullName(const autofill::AutofillProfile& profile) {
   return autofill::data_util::JoinNameParts(
       profile.GetRawInfo(autofill::NAME_FIRST),
@@ -439,8 +438,6 @@
   return sorted_indices[0];
 }
 
-}  // namespace user_data
-
 std::unique_ptr<autofill::AutofillProfile> MakeUniqueFromProfile(
     const autofill::AutofillProfile& profile) {
   auto unique_profile = std::make_unique<autofill::AutofillProfile>(profile);
@@ -592,4 +589,5 @@
   std::move(callback).Run(status, value);
 }
 
+}  // namespace user_data
 }  // namespace autofill_assistant
diff --git a/components/autofill_assistant/browser/user_data_util.h b/components/autofill_assistant/browser/user_data_util.h
index d8955d4..1822b04 100644
--- a/components/autofill_assistant/browser/user_data_util.h
+++ b/components/autofill_assistant/browser/user_data_util.h
@@ -76,8 +76,6 @@
     const CollectUserDataOptions& collect_user_data_options,
     const std::vector<std::unique_ptr<PaymentInstrument>>& payment_instruments);
 
-}  // namespace user_data
-
 std::unique_ptr<autofill::AutofillProfile> MakeUniqueFromProfile(
     const autofill::AutofillProfile& profile);
 
@@ -90,7 +88,10 @@
     const autofill::AutofillProfile* b);
 
 // Get a formatted autofill value. The replacement is treated as strict,
-// meaning a missing value will lead to a failed ClientStatus.
+// meaning a missing value will lead to a failed ClientStatus. If the value
+// or the profile is empty, fails with |INVALID_ACTION|. If the requested
+// profile does not exist, fails with |PRECONDITION FAILED|. If the value
+// cannot be fully resolved, fails with |AUTOFILL_INFO_NOT_AVAILABLE|.
 ClientStatus GetFormattedAutofillValue(const AutofillValue& autofill_value,
                                        const UserData* user_data,
                                        std::string* out_value);
@@ -99,6 +100,11 @@
     const UserData* user_data,
     std::string* out_value);
 
+// Get a password manager value from the |UserData|. Returns the user name
+// directly and resolves the password from the |WebsiteLoginManager|. If the
+// login credentials do not exist, fails with |PRECONDITION_FAILED|. If the
+// origin of the |target_element| does not match the origin of the login
+// credentials, fails with |PASSWORD_ORIGIN_MISMATCH|.
 void GetPasswordManagerValue(
     const PasswordManagerValue& password_manager_value,
     const ElementFinder::Result& target_element,
@@ -106,6 +112,9 @@
     WebsiteLoginManager* website_login_manager,
     base::OnceCallback<void(const ClientStatus&, const std::string&)> callback);
 
+// Retrieve a single string value stored in |UserData| under
+// |client_memory_key|. If the value is not present or not a single string,
+// fails with |PRECONDITION_FAILED|.
 ClientStatus GetClientMemoryStringValue(const std::string& client_memory_key,
                                         const UserData* user_data,
                                         std::string* out_value);
@@ -118,6 +127,7 @@
     const ActionDelegate* action_delegate,
     base::OnceCallback<void(const ClientStatus&, const std::string&)> callback);
 
+}  // namespace user_data
 }  // namespace autofill_assistant
 
 #endif  // COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_USER_DATA_UTIL_H_
diff --git a/components/autofill_assistant/browser/user_data_util_unittest.cc b/components/autofill_assistant/browser/user_data_util_unittest.cc
index 7b113e9..2ea0e92 100644
--- a/components/autofill_assistant/browser/user_data_util_unittest.cc
+++ b/components/autofill_assistant/browser/user_data_util_unittest.cc
@@ -30,6 +30,7 @@
 #include "url/gurl.h"
 
 namespace autofill_assistant {
+namespace user_data {
 namespace {
 
 using ::base::test::RunOnceCallback;
@@ -80,7 +81,7 @@
       MakeRequiredDataPiece(autofill::ServerFieldType::EMAIL_ADDRESS));
 
   std::vector<int> profile_indices =
-      user_data::SortContactsByCompleteness(options, profiles);
+      SortContactsByCompleteness(options, profiles);
   EXPECT_THAT(profile_indices, SizeIs(profiles.size()));
   EXPECT_THAT(profile_indices, ElementsAre(2, 1, 0));
 }
@@ -117,7 +118,7 @@
       autofill::ServerFieldType::PHONE_HOME_WHOLE_NUMBER));
 
   std::vector<int> profile_indices =
-      user_data::SortContactsByCompleteness(options, profiles);
+      SortContactsByCompleteness(options, profiles);
   EXPECT_THAT(profile_indices, SizeIs(profiles.size()));
   EXPECT_THAT(profile_indices, ElementsAre(2, 1, 0));
 }
@@ -126,7 +127,7 @@
   std::vector<std::unique_ptr<autofill::AutofillProfile>> profiles;
   CollectUserDataOptions options;
 
-  EXPECT_THAT(user_data::GetDefaultContactProfile(options, profiles), -1);
+  EXPECT_THAT(GetDefaultContactProfile(options, profiles), -1);
 }
 
 TEST(UserDataUtilTest,
@@ -152,7 +153,7 @@
   options.required_contact_data_pieces.push_back(
       MakeRequiredDataPiece(autofill::ServerFieldType::EMAIL_ADDRESS));
 
-  EXPECT_THAT(user_data::GetDefaultContactProfile(options, profiles), 1);
+  EXPECT_THAT(GetDefaultContactProfile(options, profiles), 1);
 }
 
 TEST(UserDataUtilTest, GetDefaultSelectionForDefaultEmail) {
@@ -188,7 +189,7 @@
       autofill::ServerFieldType::PHONE_HOME_WHOLE_NUMBER));
   options.default_email = "adam.west@gmail.com";
 
-  EXPECT_THAT(user_data::GetDefaultContactProfile(options, profiles), 2);
+  EXPECT_THAT(GetDefaultContactProfile(options, profiles), 2);
 }
 
 TEST(UserDataUtilTest, SortsCompleteAddressesAlphabetically) {
@@ -210,7 +211,7 @@
   CollectUserDataOptions options;
 
   std::vector<int> profile_indices =
-      user_data::SortShippingAddressesByCompleteness(options, profiles);
+      SortShippingAddressesByCompleteness(options, profiles);
   EXPECT_THAT(profile_indices, SizeIs(profiles.size()));
   EXPECT_THAT(profile_indices, ElementsAre(1, 0));
 }
@@ -236,7 +237,7 @@
   CollectUserDataOptions options;
 
   std::vector<int> profile_indices =
-      user_data::SortShippingAddressesByCompleteness(options, profiles);
+      SortShippingAddressesByCompleteness(options, profiles);
   EXPECT_THAT(profile_indices, SizeIs(profiles.size()));
   EXPECT_THAT(profile_indices, ElementsAre(1, 0));
 }
@@ -262,7 +263,7 @@
       MakeRequiredDataPiece(autofill::ServerFieldType::EMAIL_ADDRESS));
 
   std::vector<int> profile_indices =
-      user_data::SortShippingAddressesByCompleteness(options, profiles);
+      SortShippingAddressesByCompleteness(options, profiles);
   EXPECT_THAT(profile_indices, SizeIs(profiles.size()));
   EXPECT_THAT(profile_indices, ElementsAre(1, 0));
 }
@@ -271,8 +272,7 @@
   std::vector<std::unique_ptr<autofill::AutofillProfile>> profiles;
   CollectUserDataOptions options;
 
-  EXPECT_THAT(user_data::GetDefaultShippingAddressProfile(options, profiles),
-              -1);
+  EXPECT_THAT(GetDefaultShippingAddressProfile(options, profiles), -1);
 }
 
 TEST(UserDataUtilTest, GetDefaultAddressSelectionForCompleteProfiles) {
@@ -297,8 +297,7 @@
 
   CollectUserDataOptions options;
 
-  EXPECT_THAT(user_data::GetDefaultShippingAddressProfile(options, profiles),
-              1);
+  EXPECT_THAT(GetDefaultShippingAddressProfile(options, profiles), 1);
 }
 
 TEST(UserDataUtilTest, SortsCreditCardsByCompleteness) {
@@ -328,8 +327,7 @@
       MakeRequiredDataPiece(autofill::ServerFieldType::CREDIT_CARD_EXP_MONTH));
 
   std::vector<int> sorted_indices =
-      user_data::SortPaymentInstrumentsByCompleteness(options,
-                                                      payment_instruments);
+      SortPaymentInstrumentsByCompleteness(options, payment_instruments);
   EXPECT_THAT(sorted_indices, SizeIs(payment_instruments.size()));
   EXPECT_THAT(sorted_indices, ElementsAre(1, 0));
 }
@@ -357,8 +355,7 @@
   CollectUserDataOptions options;
 
   std::vector<int> sorted_indices =
-      user_data::SortPaymentInstrumentsByCompleteness(options,
-                                                      payment_instruments);
+      SortPaymentInstrumentsByCompleteness(options, payment_instruments);
   EXPECT_THAT(sorted_indices, SizeIs(payment_instruments.size()));
   EXPECT_THAT(sorted_indices, ElementsAre(1, 0));
 }
@@ -386,8 +383,7 @@
   CollectUserDataOptions options;
 
   std::vector<int> sorted_indices =
-      user_data::SortPaymentInstrumentsByCompleteness(options,
-                                                      payment_instruments);
+      SortPaymentInstrumentsByCompleteness(options, payment_instruments);
   EXPECT_THAT(sorted_indices, SizeIs(payment_instruments.size()));
   EXPECT_THAT(sorted_indices, ElementsAre(1, 0));
 }
@@ -415,8 +411,7 @@
   CollectUserDataOptions options;
 
   std::vector<int> sorted_indices =
-      user_data::SortPaymentInstrumentsByCompleteness(options,
-                                                      payment_instruments);
+      SortPaymentInstrumentsByCompleteness(options, payment_instruments);
   EXPECT_THAT(sorted_indices, SizeIs(payment_instruments.size()));
   EXPECT_THAT(sorted_indices, ElementsAre(1, 0));
 }
@@ -470,8 +465,7 @@
       MakeRequiredDataPiece(autofill::ServerFieldType::ADDRESS_HOME_ZIP));
 
   std::vector<int> sorted_indices =
-      user_data::SortPaymentInstrumentsByCompleteness(options,
-                                                      payment_instruments);
+      SortPaymentInstrumentsByCompleteness(options, payment_instruments);
   EXPECT_THAT(sorted_indices, SizeIs(payment_instruments.size()));
   EXPECT_THAT(sorted_indices, ElementsAre(2, 1, 0));
 }
@@ -480,8 +474,7 @@
   std::vector<std::unique_ptr<PaymentInstrument>> payment_instruments;
   CollectUserDataOptions options;
 
-  EXPECT_THAT(
-      user_data::GetDefaultPaymentInstrument(options, payment_instruments), -1);
+  EXPECT_THAT(GetDefaultPaymentInstrument(options, payment_instruments), -1);
 }
 
 TEST(UserDataUtilTest, GetDefaultSelectionForCompletePaymentInstruments) {
@@ -506,8 +499,7 @@
 
   CollectUserDataOptions options;
 
-  EXPECT_THAT(
-      user_data::GetDefaultPaymentInstrument(options, payment_instruments), 1);
+  EXPECT_THAT(GetDefaultPaymentInstrument(options, payment_instruments), 1);
 }
 
 TEST(UserDataUtilTest, CompareContactDetailsMatch) {
@@ -622,9 +614,8 @@
 
 TEST(UserDataUtilTest, ContactCompletenessNotRequired) {
   CollectUserDataOptions not_required_options;
-  EXPECT_THAT(
-      user_data::GetContactValidationErrors(nullptr, not_required_options),
-      IsEmpty());
+  EXPECT_THAT(GetContactValidationErrors(nullptr, not_required_options),
+              IsEmpty());
 }
 
 TEST(UserDataUtilTest, ContactCompletenessRequireName) {
@@ -635,27 +626,23 @@
   require_name_options.required_contact_data_pieces.push_back(
       MakeRequiredDataPiece(autofill::ServerFieldType::NAME_LAST));
 
-  EXPECT_THAT(
-      user_data::GetContactValidationErrors(nullptr, require_name_options),
-      ElementsAre("3", "5"));
+  EXPECT_THAT(GetContactValidationErrors(nullptr, require_name_options),
+              ElementsAre("3", "5"));
   autofill::test::SetProfileInfo(&contact, /* first_name= */ "",
                                  /* middle_name= */ "",
                                  /* last_name= */ "", "adam.west@gmail.com", "",
                                  "", "", "", "", "", "", "+41");
-  EXPECT_THAT(
-      user_data::GetContactValidationErrors(&contact, require_name_options),
-      ElementsAre("3", "5"));
+  EXPECT_THAT(GetContactValidationErrors(&contact, require_name_options),
+              ElementsAre("3", "5"));
   autofill::test::SetProfileInfo(&contact, "John", /* middle_name= */ "",
                                  /* last_name= */ "", "", "", "", "", "", "",
                                  "", "", "");
-  EXPECT_THAT(
-      user_data::GetContactValidationErrors(&contact, require_name_options),
-      ElementsAre("5"));
+  EXPECT_THAT(GetContactValidationErrors(&contact, require_name_options),
+              ElementsAre("5"));
   autofill::test::SetProfileInfo(&contact, "John", /* middle_name= */ "", "Doe",
                                  "", "", "", "", "", "", "", "", "");
-  EXPECT_THAT(
-      user_data::GetContactValidationErrors(&contact, require_name_options),
-      IsEmpty());
+  EXPECT_THAT(GetContactValidationErrors(&contact, require_name_options),
+              IsEmpty());
 }
 
 TEST(UserDataUtilTest, ContactCompletenessRequireEmail) {
@@ -664,21 +651,18 @@
   require_email_options.required_contact_data_pieces.push_back(
       MakeRequiredDataPiece(autofill::ServerFieldType::EMAIL_ADDRESS));
 
-  EXPECT_THAT(
-      user_data::GetContactValidationErrors(nullptr, require_email_options),
-      ElementsAre("9"));
+  EXPECT_THAT(GetContactValidationErrors(nullptr, require_email_options),
+              ElementsAre("9"));
   autofill::test::SetProfileInfo(&contact, "John", "", "Doe",
                                  /* email= */ "", "", "", "", "", "", "", "",
                                  "+41");
-  EXPECT_THAT(
-      user_data::GetContactValidationErrors(&contact, require_email_options),
-      ElementsAre("9"));
+  EXPECT_THAT(GetContactValidationErrors(&contact, require_email_options),
+              ElementsAre("9"));
   autofill::test::SetProfileInfo(&contact, "John", "", "Doe",
                                  "john.doe@gmail.com", "", "", "", "", "", "",
                                  "", "+41");
-  EXPECT_THAT(
-      user_data::GetContactValidationErrors(&contact, require_email_options),
-      IsEmpty());
+  EXPECT_THAT(GetContactValidationErrors(&contact, require_email_options),
+              IsEmpty());
 }
 
 TEST(UserDataUtilTest, ContactCompletenessRequirePhone) {
@@ -693,34 +677,29 @@
       MakeRequiredDataPiece(
           autofill::ServerFieldType::PHONE_HOME_COUNTRY_CODE));
 
-  EXPECT_THAT(
-      user_data::GetContactValidationErrors(nullptr, require_phone_options),
-      ElementsAre("14", "10", "12"));
+  EXPECT_THAT(GetContactValidationErrors(nullptr, require_phone_options),
+              ElementsAre("14", "10", "12"));
   autofill::test::SetProfileInfo(&contact, "John", "", "Doe",
                                  "john.doe@gmail.com", "", "", "", "", "", "",
                                  "",
                                  /* phone= */ "");
-  EXPECT_THAT(
-      user_data::GetContactValidationErrors(&contact, require_phone_options),
-      ElementsAre("14", "10", "12"));
+  EXPECT_THAT(GetContactValidationErrors(&contact, require_phone_options),
+              ElementsAre("14", "10", "12"));
   autofill::test::SetProfileInfo(&contact, "", "", "", "", "", "", "", "", "",
                                  "", "", "079 123 45 67");
-  EXPECT_THAT(
-      user_data::GetContactValidationErrors(&contact, require_phone_options),
-      ElementsAre("12"));
+  EXPECT_THAT(GetContactValidationErrors(&contact, require_phone_options),
+              ElementsAre("12"));
   autofill::test::SetProfileInfo(&contact, "", "", "", "", "", "", "", "", "",
                                  "", "", "+41 79 123 45 67");
-  EXPECT_THAT(
-      user_data::GetContactValidationErrors(&contact, require_phone_options),
-      IsEmpty());
+  EXPECT_THAT(GetContactValidationErrors(&contact, require_phone_options),
+              IsEmpty());
 }
 
 TEST(UserDataUtilTest, CompleteShippingAddressNotRequired) {
   CollectUserDataOptions not_required_options;
   not_required_options.request_shipping = false;
 
-  EXPECT_THAT(user_data::GetShippingAddressValidationErrors(
-                  nullptr, not_required_options),
+  EXPECT_THAT(GetShippingAddressValidationErrors(nullptr, not_required_options),
               IsEmpty());
 }
 
@@ -736,32 +715,32 @@
   require_shipping_options.required_shipping_address_data_pieces.push_back(
       MakeRequiredDataPiece(autofill::ServerFieldType::ADDRESS_HOME_COUNTRY));
 
-  EXPECT_THAT(user_data::GetShippingAddressValidationErrors(
-                  nullptr, require_shipping_options),
-              ElementsAre("77", "35", "36"));
+  EXPECT_THAT(
+      GetShippingAddressValidationErrors(nullptr, require_shipping_options),
+      ElementsAre("77", "35", "36"));
   autofill::test::SetProfileInfo(&address, "John", "", "Doe",
                                  "john.doe@gmail.com", "", /* address1= */ "",
                                  /* address2= */ "", /* city= */ "",
                                  /* state=  */ "", /* zip_code=  */ "",
                                  /* country= */ "", /* phone= */ "");
-  EXPECT_THAT(user_data::GetShippingAddressValidationErrors(
-                  &address, require_shipping_options),
-              ElementsAre("77", "35", "36"));
+  EXPECT_THAT(
+      GetShippingAddressValidationErrors(&address, require_shipping_options),
+      ElementsAre("77", "35", "36"));
   autofill::test::SetProfileInfo(&address, "John", "", "Doe",
                                  /* email= */ "", "", "Brandschenkestrasse 110",
                                  "", "Zurich", "Zurich", /* zip_code= */ "",
                                  "CH",
                                  /* phone= */ "");
-  EXPECT_THAT(user_data::GetShippingAddressValidationErrors(
-                  &address, require_shipping_options),
-              ElementsAre("35"));
+  EXPECT_THAT(
+      GetShippingAddressValidationErrors(&address, require_shipping_options),
+      ElementsAre("35"));
   autofill::test::SetProfileInfo(&address, "John", "", "Doe",
                                  /* email= */ "", "", "Brandschenkestrasse 110",
                                  "", "Zurich", "Zurich", "8002", "CH",
                                  /* phone= */ "");
-  EXPECT_THAT(user_data::GetShippingAddressValidationErrors(
-                  &address, require_shipping_options),
-              IsEmpty());
+  EXPECT_THAT(
+      GetShippingAddressValidationErrors(&address, require_shipping_options),
+      IsEmpty());
 }
 
 TEST(UserDataUtilTest, CompleteShippingAddressForEditor) {
@@ -769,32 +748,32 @@
   CollectUserDataOptions require_shipping_options;
   require_shipping_options.request_shipping = true;
 
-  EXPECT_THAT(user_data::GetShippingAddressValidationErrors(
-                  nullptr, require_shipping_options),
-              ElementsAre(_));
+  EXPECT_THAT(
+      GetShippingAddressValidationErrors(nullptr, require_shipping_options),
+      ElementsAre(_));
   autofill::test::SetProfileInfo(&address, "John", "", "Doe",
                                  /* email= */ "", "", "Brandschenkestrasse 110",
                                  "", "Zurich", "Zurich", /* zip_code= */ "",
                                  "CH",
                                  /* phone= */ "");
-  EXPECT_THAT(user_data::GetShippingAddressValidationErrors(
-                  &address, require_shipping_options),
-              ElementsAre(_));
+  EXPECT_THAT(
+      GetShippingAddressValidationErrors(&address, require_shipping_options),
+      ElementsAre(_));
   autofill::test::SetProfileInfo(&address, "John", "", "Doe",
                                  /* email= */ "", "", "Brandschenkestrasse 110",
                                  "", "Zurich", "Zurich", "8002", "CH",
                                  /* phone= */ "");
-  EXPECT_THAT(user_data::GetShippingAddressValidationErrors(
-                  &address, require_shipping_options),
-              IsEmpty());
+  EXPECT_THAT(
+      GetShippingAddressValidationErrors(&address, require_shipping_options),
+      IsEmpty());
 }
 
 TEST(UserDataUtilTest, CompleteCreditCardNotRequired) {
   CollectUserDataOptions not_required_options;
   not_required_options.request_payment_method = false;
 
-  EXPECT_THAT(user_data::GetPaymentInstrumentValidationErrors(
-                  nullptr, nullptr, not_required_options),
+  EXPECT_THAT(GetPaymentInstrumentValidationErrors(nullptr, nullptr,
+                                                   not_required_options),
               IsEmpty());
 }
 
@@ -808,37 +787,37 @@
                                     "2050",
                                     /* billing_address_id= */ "id");
 
-  EXPECT_THAT(user_data::GetPaymentInstrumentValidationErrors(nullptr, nullptr,
-                                                              payment_options),
-              ElementsAre(_));
-  EXPECT_THAT(user_data::GetPaymentInstrumentValidationErrors(&card, nullptr,
-                                                              payment_options),
-              ElementsAre(_));
-  EXPECT_THAT(user_data::GetPaymentInstrumentValidationErrors(&card, &address,
-                                                              payment_options),
-              ElementsAre(_));
+  EXPECT_THAT(
+      GetPaymentInstrumentValidationErrors(nullptr, nullptr, payment_options),
+      ElementsAre(_));
+  EXPECT_THAT(
+      GetPaymentInstrumentValidationErrors(&card, nullptr, payment_options),
+      ElementsAre(_));
+  EXPECT_THAT(
+      GetPaymentInstrumentValidationErrors(&card, &address, payment_options),
+      ElementsAre(_));
   // CH addresses require a zip code to be complete. This check outranks the
   // our validation.
   autofill::test::SetProfileInfo(&address, "John", "", "Doe",
                                  /* email= */ "", "", "Brandschenkestrasse 110",
                                  "", "Zurich", "Zurich",
                                  /* zipcode= */ "", "CH", /* phone= */ "");
-  EXPECT_THAT(user_data::GetPaymentInstrumentValidationErrors(&card, &address,
-                                                              payment_options),
-              ElementsAre(_));
+  EXPECT_THAT(
+      GetPaymentInstrumentValidationErrors(&card, &address, payment_options),
+      ElementsAre(_));
   // UK addresses do not require a zip code, they are complete without it.
   autofill::test::SetProfileInfo(&address, "John", "", "Doe",
                                  /* email= */ "", "", "Baker Street 221b", "",
                                  "London", /* state= */ "",
                                  /* zipcode= */ "", "UK", /* phone= */ "");
-  EXPECT_THAT(user_data::GetPaymentInstrumentValidationErrors(&card, &address,
-                                                              payment_options),
-              IsEmpty());
+  EXPECT_THAT(
+      GetPaymentInstrumentValidationErrors(&card, &address, payment_options),
+      IsEmpty());
   payment_options.required_billing_address_data_pieces.push_back(
       MakeRequiredDataPiece(autofill::ServerFieldType::ADDRESS_HOME_ZIP));
-  EXPECT_THAT(user_data::GetPaymentInstrumentValidationErrors(&card, &address,
-                                                              payment_options),
-              ElementsAre("35"));
+  EXPECT_THAT(
+      GetPaymentInstrumentValidationErrors(&card, &address, payment_options),
+      ElementsAre("35"));
 }
 
 TEST(UserDataUtilTest, CompleteExpiredCreditCard) {
@@ -855,15 +834,15 @@
   autofill::test::SetCreditCardInfo(&card, "Adam West", "4111111111111111", "1",
                                     "2000",
                                     /* billing_address_id= */ "id");
-  EXPECT_THAT(user_data::GetPaymentInstrumentValidationErrors(&card, &address,
-                                                              payment_options),
-              ElementsAre("expired"));
+  EXPECT_THAT(
+      GetPaymentInstrumentValidationErrors(&card, &address, payment_options),
+      ElementsAre("expired"));
   autofill::test::SetCreditCardInfo(&card, "Adam West", "4111111111111111", "1",
                                     "2050",
                                     /* billing_address_id= */ "id");
-  EXPECT_THAT(user_data::GetPaymentInstrumentValidationErrors(&card, &address,
-                                                              payment_options),
-              IsEmpty());
+  EXPECT_THAT(
+      GetPaymentInstrumentValidationErrors(&card, &address, payment_options),
+      IsEmpty());
 }
 
 TEST(UserDataUtilTest, CompleteCreditCardWithBadNetwork) {
@@ -890,8 +869,8 @@
       required_data_piece);
   payment_options_mastercard.supported_basic_card_networks.emplace_back(
       "mastercard");
-  EXPECT_THAT(user_data::GetPaymentInstrumentValidationErrors(
-                  &card, &address, payment_options_mastercard),
+  EXPECT_THAT(GetPaymentInstrumentValidationErrors(&card, &address,
+                                                   payment_options_mastercard),
               ElementsAre("network"));
 
   required_data_piece.mutable_condition()
@@ -902,8 +881,8 @@
   payment_options_visa.request_payment_method = true;
   payment_options_visa.required_credit_card_data_pieces.push_back(
       required_data_piece);
-  EXPECT_THAT(user_data::GetPaymentInstrumentValidationErrors(
-                  &card, &address, payment_options_visa),
+  EXPECT_THAT(GetPaymentInstrumentValidationErrors(&card, &address,
+                                                   payment_options_visa),
               IsEmpty());
 }
 
@@ -919,15 +898,15 @@
 
   autofill::test::SetCreditCardInfo(&card, "Adam West", "4111", "1", "2050",
                                     /* billing_address_id= */ "id");
-  EXPECT_THAT(user_data::GetPaymentInstrumentValidationErrors(&card, &address,
-                                                              payment_options),
-              ElementsAre(_));
+  EXPECT_THAT(
+      GetPaymentInstrumentValidationErrors(&card, &address, payment_options),
+      ElementsAre(_));
   autofill::test::SetCreditCardInfo(&card, "Adam West", "4111111111111111", "1",
                                     "2050",
                                     /* billing_address_id= */ "id");
-  EXPECT_THAT(user_data::GetPaymentInstrumentValidationErrors(&card, &address,
-                                                              payment_options),
-              IsEmpty());
+  EXPECT_THAT(
+      GetPaymentInstrumentValidationErrors(&card, &address, payment_options),
+      IsEmpty());
 }
 
 class UserDataUtilTextValueTest : public testing::Test {
@@ -1246,4 +1225,5 @@
 }
 
 }  // namespace
+}  // namespace user_data
 }  // namespace autofill_assistant
diff --git a/components/autofill_assistant/browser/web/web_controller.cc b/components/autofill_assistant/browser/web/web_controller.cc
index e956567..ffe575b 100644
--- a/components/autofill_assistant/browser/web/web_controller.cc
+++ b/components/autofill_assistant/browser/web/web_controller.cc
@@ -707,7 +707,7 @@
     base::OnceCallback<void(const ClientStatus&)> callback) {
   VLOG(3) << __func__ << " " << selector;
   auto data_to_autofill = std::make_unique<FillFormInputData>();
-  data_to_autofill->profile = MakeUniqueFromProfile(*profile);
+  data_to_autofill->profile = user_data::MakeUniqueFromProfile(*profile);
   GetElementFormAndFieldData(
       selector,
       base::BindOnce(&WebController::OnGetFormAndFieldDataForFilling,
diff --git a/components/content_creation/notes/core/server/BUILD.gn b/components/content_creation/notes/core/server/BUILD.gn
index cfcf23e..016522e 100644
--- a/components/content_creation/notes/core/server/BUILD.gn
+++ b/components/content_creation/notes/core/server/BUILD.gn
@@ -6,8 +6,13 @@
 
 static_library("server") {
   sources = [
+    "note_data.cc",
+    "note_data.h",
     "notes_server_base.cc",
     "notes_server_base.h",
+    "notes_server_saver.cc",
+    "notes_server_saver.h",
+    "save_note_response.h",
   ]
 
   deps = [
diff --git a/components/content_creation/notes/core/server/note_data.cc b/components/content_creation/notes/core/server/note_data.cc
new file mode 100644
index 0000000..b9bb056
--- /dev/null
+++ b/components/content_creation/notes/core/server/note_data.cc
@@ -0,0 +1,22 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/content_creation/notes/core/server/note_data.h"
+
+namespace content_creation {
+
+NoteData::NoteData(std::string comment,
+                   std::string quote,
+                   std::string webpage_url,
+                   std::string highlight_directive)
+    : comment(comment),
+      quote(quote),
+      webpage_url(webpage_url),
+      highlight_directive(highlight_directive) {}
+
+NoteData::NoteData(NoteData const& note_data) = default;
+
+NoteData::~NoteData() {}
+
+}  // namespace content_creation
diff --git a/components/content_creation/notes/core/server/note_data.h b/components/content_creation/notes/core/server/note_data.h
new file mode 100644
index 0000000..deff7a8
--- /dev/null
+++ b/components/content_creation/notes/core/server/note_data.h
@@ -0,0 +1,29 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_CONTENT_CREATION_NOTES_CORE_SERVER_NOTE_DATA_H_
+#define COMPONENTS_CONTENT_CREATION_NOTES_CORE_SERVER_NOTE_DATA_H_
+
+#include <string>
+
+namespace content_creation {
+
+// Struct containing the data of a note.
+struct NoteData {
+  NoteData(std::string comment,
+           std::string quote,
+           std::string webpage_url,
+           std::string highlight_directive);
+  NoteData(NoteData const& note_data);
+  ~NoteData();
+
+  std::string comment;
+  std::string quote;
+  std::string webpage_url;
+  std::string highlight_directive;
+};
+
+}  // namespace content_creation
+
+#endif  // COMPONENTS_CONTENT_CREATION_NOTES_CORE_SERVER_NOTE_DATA_H_
diff --git a/components/content_creation/notes/core/server/notes_server_base.cc b/components/content_creation/notes/core/server/notes_server_base.cc
index 59ed7eb..eb939dd 100644
--- a/components/content_creation/notes/core/server/notes_server_base.cc
+++ b/components/content_creation/notes/core/server/notes_server_base.cc
@@ -2,12 +2,14 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#import "components/content_creation/notes/core/server/notes_server_base.h"
+#include "components/content_creation/notes/core/server/notes_server_base.h"
 
-#import "components/signin/public/identity_manager/primary_account_access_token_fetcher.h"
-#import "google_apis/google_api_keys.h"
-#import "services/network/public/cpp/shared_url_loader_factory.h"
-#import "services/network/public/cpp/simple_url_loader.h"
+#include "components/signin/public/identity_manager/primary_account_access_token_fetcher.h"
+#include "google_apis/google_api_keys.h"
+#include "services/network/public/cpp/shared_url_loader_factory.h"
+#include "services/network/public/cpp/simple_url_loader.h"
+
+namespace content_creation {
 
 NotesServerBase::NotesServerBase(
     scoped_refptr<network::SharedURLLoaderFactory> loader_factory,
@@ -48,3 +50,5 @@
 void NotesServerBase::StartAccessTokenFetch() {
   NOTIMPLEMENTED();
 }
+
+}  // namespace content_creation
diff --git a/components/content_creation/notes/core/server/notes_server_base.h b/components/content_creation/notes/core/server/notes_server_base.h
index ad504af..ec9e902 100644
--- a/components/content_creation/notes/core/server/notes_server_base.h
+++ b/components/content_creation/notes/core/server/notes_server_base.h
@@ -7,10 +7,10 @@
 
 #include <string>
 
-#import "components/signin/public/identity_manager/access_token_info.h"
-#import "components/signin/public/identity_manager/scope_set.h"
-#import "google_apis/gaia/google_service_auth_error.h"
-#import "services/network/public/cpp/resource_request.h"
+#include "components/signin/public/identity_manager/access_token_info.h"
+#include "components/signin/public/identity_manager/scope_set.h"
+#include "google_apis/gaia/google_service_auth_error.h"
+#include "services/network/public/cpp/resource_request.h"
 
 namespace network {
 class SimpleURLLoader;
@@ -22,6 +22,8 @@
 class PrimaryAccountAccessTokenFetcher;
 }  // namespace signin
 
+namespace content_creation {
+
 // Base class for interactions with the Notes Server.
 class NotesServerBase {
  public:
@@ -72,4 +74,6 @@
   std::unique_ptr<network::SimpleURLLoader> url_loader_;
 };
 
+}  // namespace content_creation
+
 #endif  // COMPONENTS_CONTENT_CREATION_NOTES_CORE_SERVER_NOTES_SERVER_BASE_H_
diff --git a/components/content_creation/notes/core/server/notes_server_saver.cc b/components/content_creation/notes/core/server/notes_server_saver.cc
new file mode 100644
index 0000000..6228029
--- /dev/null
+++ b/components/content_creation/notes/core/server/notes_server_saver.cc
@@ -0,0 +1,43 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/content_creation/notes/core/server/notes_server_saver.h"
+
+#include "components/signin/public/identity_manager/identity_manager.h"
+#include "services/network/public/cpp/shared_url_loader_factory.h"
+
+namespace content_creation {
+
+NotesServerSaver::NotesServerSaver(
+    scoped_refptr<network::SharedURLLoaderFactory> loader_factory,
+    signin::IdentityManager* identity_manager,
+    NoteData note_data,
+    base::OnceCallback<void(SaveNoteResponse)> callback)
+    : NotesServerBase{loader_factory, identity_manager},
+      note_data_(note_data),
+      save_callback_(std::move(callback)) {}
+
+NotesServerSaver::~NotesServerSaver() {}
+
+void NotesServerSaver::Start() {
+  NOTIMPLEMENTED();
+}
+
+void NotesServerSaver::SendSaveNoteRequest() {
+  NOTIMPLEMENTED();
+}
+
+void NotesServerSaver::OnSaveNoteComplete(
+    std::unique_ptr<std::string> response_body) {
+  NOTIMPLEMENTED();
+}
+
+void NotesServerSaver::AccessTokenFetchFinished(
+    base::TimeTicks token_start_ticks,
+    GoogleServiceAuthError error,
+    signin::AccessTokenInfo access_token_info) {
+  NOTIMPLEMENTED();
+}
+
+}  // namespace content_creation
diff --git a/components/content_creation/notes/core/server/notes_server_saver.h b/components/content_creation/notes/core/server/notes_server_saver.h
new file mode 100644
index 0000000..64c406b3
--- /dev/null
+++ b/components/content_creation/notes/core/server/notes_server_saver.h
@@ -0,0 +1,72 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_CONTENT_CREATION_NOTES_CORE_SERVER_NOTES_SERVER_SAVER_H_
+#define COMPONENTS_CONTENT_CREATION_NOTES_CORE_SERVER_NOTES_SERVER_SAVER_H_
+
+#include <string>
+
+#include "base/callback.h"
+#include "base/memory/weak_ptr.h"
+#include "components/content_creation/notes/core/server/note_data.h"
+#include "components/content_creation/notes/core/server/notes_server_base.h"
+#include "components/content_creation/notes/core/server/save_note_response.h"
+#include "components/signin/public/identity_manager/access_token_info.h"
+#include "google_apis/gaia/google_service_auth_error.h"
+
+namespace network {
+class SharedURLLoaderFactory;
+}
+
+namespace signin {
+class IdentityManager;
+}
+
+namespace content_creation {
+
+// Class used to save a note to the Notes Server.
+class NotesServerSaver : NotesServerBase {
+ public:
+  explicit NotesServerSaver(
+      scoped_refptr<network::SharedURLLoaderFactory> loader_factory,
+      signin::IdentityManager* identity_manager,
+      NoteData note_data,
+      base::OnceCallback<void(SaveNoteResponse)> callback);
+  ~NotesServerSaver() override;
+
+  // Starts the process of saving a note on the server.
+  void Start() override;
+
+  // Not copyable or movable.
+  NotesServerSaver(const NotesServerSaver&) = delete;
+  NotesServerSaver& operator=(const NotesServerSaver&) = delete;
+
+ private:
+  // Called when the note has been saved to the server. Calls back
+  // |save_callback_|.
+  void OnSaveNoteComplete(std::unique_ptr<std::string> response_body);
+
+  // Creates and sends the request to put the webnote to the server. Should be
+  // called after getting the access token.
+  void SendSaveNoteRequest();
+
+  // Called when the access token have finished fetching. Will start the process
+  // to save the note to the server.
+  void AccessTokenFetchFinished(
+      base::TimeTicks token_start_ticks,
+      GoogleServiceAuthError error,
+      signin::AccessTokenInfo access_token_info) override;
+
+  // The note data to post to the server.
+  NoteData note_data_;
+
+  // The callback used after saving the note.
+  base::OnceCallback<void(SaveNoteResponse)> save_callback_;
+
+  base::WeakPtrFactory<NotesServerSaver> weak_factory_{this};
+};
+
+}  // namespace content_creation
+
+#endif  // COMPONENTS_CONTENT_CREATION_NOTES_CORE_SERVER_NOTES_SERVER_SAVER_H_
diff --git a/components/content_creation/notes/core/server/save_note_response.h b/components/content_creation/notes/core/server/save_note_response.h
new file mode 100644
index 0000000..bba3b3e5
--- /dev/null
+++ b/components/content_creation/notes/core/server/save_note_response.h
@@ -0,0 +1,20 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_CONTENT_CREATION_NOTES_CORE_SERVER_SAVE_NOTE_RESPONSE_H_
+#define COMPONENTS_CONTENT_CREATION_NOTES_CORE_SERVER_SAVE_NOTE_RESPONSE_H_
+
+#include <string>
+
+namespace content_creation {
+
+// A struct holding the server response when saving a note.
+struct SaveNoteResponse {
+  std::string content_id;
+  std::string note_id;
+};
+
+}  // namespace content_creation
+
+#endif  // COMPONENTS_CONTENT_CREATION_NOTES_CORE_SERVER_SAVE_NOTE_RESPONSE_H_
diff --git a/components/content_settings/browser/content_settings_manager_impl.cc b/components/content_settings/browser/content_settings_manager_impl.cc
index 59fb642..ff698f2 100644
--- a/components/content_settings/browser/content_settings_manager_impl.cc
+++ b/components/content_settings/browser/content_settings_manager_impl.cc
@@ -85,8 +85,8 @@
     base::OnceCallback<void(bool)> callback) {
   GURL url = origin.GetURL();
 
-  bool allowed = cookie_settings_->IsCookieAccessAllowed(url, site_for_cookies,
-                                                         top_frame_origin);
+  bool allowed = cookie_settings_->IsFullCookieAccessAllowed(
+      url, site_for_cookies, top_frame_origin);
   if (delegate_->AllowStorageAccess(render_process_id_, render_frame_id,
                                     storage_type, url, allowed, &callback)) {
     DCHECK(!callback);
diff --git a/components/content_settings/core/browser/cookie_settings_unittest.cc b/components/content_settings/core/browser/cookie_settings_unittest.cc
index 3943d322..bbb3d0d 100644
--- a/components/content_settings/core/browser/cookie_settings_unittest.cc
+++ b/components/content_settings/core/browser/cookie_settings_unittest.cc
@@ -151,65 +151,68 @@
 
 TEST_F(CookieSettingsTest, TestAllowlistedScheme) {
   cookie_settings_->SetDefaultCookieSetting(CONTENT_SETTING_BLOCK);
-  EXPECT_FALSE(cookie_settings_->IsCookieAccessAllowed(kHttpSite, kChromeURL));
-  EXPECT_TRUE(cookie_settings_->IsCookieAccessAllowed(kHttpsSite, kChromeURL));
-  EXPECT_TRUE(cookie_settings_->IsCookieAccessAllowed(kChromeURL, kHttpSite));
-#if BUILDFLAG(ENABLE_EXTENSIONS)
-  EXPECT_TRUE(
-      cookie_settings_->IsCookieAccessAllowed(kExtensionURL, kExtensionURL));
-#else
   EXPECT_FALSE(
-      cookie_settings_->IsCookieAccessAllowed(kExtensionURL, kExtensionURL));
+      cookie_settings_->IsFullCookieAccessAllowed(kHttpSite, kChromeURL));
+  EXPECT_TRUE(
+      cookie_settings_->IsFullCookieAccessAllowed(kHttpsSite, kChromeURL));
+  EXPECT_TRUE(
+      cookie_settings_->IsFullCookieAccessAllowed(kChromeURL, kHttpSite));
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+  EXPECT_TRUE(cookie_settings_->IsFullCookieAccessAllowed(kExtensionURL,
+                                                          kExtensionURL));
+#else
+  EXPECT_FALSE(cookie_settings_->IsFullCookieAccessAllowed(kExtensionURL,
+                                                           kExtensionURL));
 #endif
   EXPECT_FALSE(
-      cookie_settings_->IsCookieAccessAllowed(kExtensionURL, kHttpSite));
+      cookie_settings_->IsFullCookieAccessAllowed(kExtensionURL, kHttpSite));
 }
 
 TEST_F(CookieSettingsTest, CookiesBlockSingle) {
   cookie_settings_->SetCookieSetting(kBlockedSite, CONTENT_SETTING_BLOCK);
   EXPECT_FALSE(
-      cookie_settings_->IsCookieAccessAllowed(kBlockedSite, kBlockedSite));
+      cookie_settings_->IsFullCookieAccessAllowed(kBlockedSite, kBlockedSite));
 }
 
 TEST_F(CookieSettingsTest, CookiesBlockThirdParty) {
   prefs_.SetInteger(prefs::kCookieControlsMode,
                     static_cast<int>(CookieControlsMode::kBlockThirdParty));
-  EXPECT_FALSE(
-      cookie_settings_->IsCookieAccessAllowed(kBlockedSite, kFirstPartySite));
+  EXPECT_FALSE(cookie_settings_->IsFullCookieAccessAllowed(kBlockedSite,
+                                                           kFirstPartySite));
   EXPECT_FALSE(cookie_settings_->IsCookieSessionOnly(kBlockedSite));
 }
 
 TEST_F(CookieSettingsTest, CookiesControlsDefault) {
-  EXPECT_TRUE(
-      cookie_settings_->IsCookieAccessAllowed(kBlockedSite, kFirstPartySite));
-  EXPECT_FALSE(cookie_settings_incognito_->IsCookieAccessAllowed(
+  EXPECT_TRUE(cookie_settings_->IsFullCookieAccessAllowed(kBlockedSite,
+                                                          kFirstPartySite));
+  EXPECT_FALSE(cookie_settings_incognito_->IsFullCookieAccessAllowed(
       kBlockedSite, kFirstPartySite));
 }
 
 TEST_F(CookieSettingsTest, CookiesControlsEnabled) {
   prefs_.SetInteger(prefs::kCookieControlsMode,
                     static_cast<int>(CookieControlsMode::kBlockThirdParty));
-  EXPECT_FALSE(
-      cookie_settings_->IsCookieAccessAllowed(kBlockedSite, kFirstPartySite));
-  EXPECT_FALSE(cookie_settings_incognito_->IsCookieAccessAllowed(
+  EXPECT_FALSE(cookie_settings_->IsFullCookieAccessAllowed(kBlockedSite,
+                                                           kFirstPartySite));
+  EXPECT_FALSE(cookie_settings_incognito_->IsFullCookieAccessAllowed(
       kBlockedSite, kFirstPartySite));
 }
 
 TEST_F(CookieSettingsTest, CookiesControlsDisabled) {
   prefs_.SetInteger(prefs::kCookieControlsMode,
                     static_cast<int>(CookieControlsMode::kOff));
-  EXPECT_TRUE(
-      cookie_settings_->IsCookieAccessAllowed(kBlockedSite, kFirstPartySite));
-  EXPECT_TRUE(cookie_settings_incognito_->IsCookieAccessAllowed(
+  EXPECT_TRUE(cookie_settings_->IsFullCookieAccessAllowed(kBlockedSite,
+                                                          kFirstPartySite));
+  EXPECT_TRUE(cookie_settings_incognito_->IsFullCookieAccessAllowed(
       kBlockedSite, kFirstPartySite));
 }
 
 TEST_F(CookieSettingsTest, CookiesControlsEnabledForIncognito) {
   prefs_.SetInteger(prefs::kCookieControlsMode,
                     static_cast<int>(CookieControlsMode::kIncognitoOnly));
-  EXPECT_TRUE(
-      cookie_settings_->IsCookieAccessAllowed(kBlockedSite, kFirstPartySite));
-  EXPECT_FALSE(cookie_settings_incognito_->IsCookieAccessAllowed(
+  EXPECT_TRUE(cookie_settings_->IsFullCookieAccessAllowed(kBlockedSite,
+                                                          kFirstPartySite));
+  EXPECT_FALSE(cookie_settings_incognito_->IsFullCookieAccessAllowed(
       kBlockedSite, kFirstPartySite));
 }
 
@@ -228,44 +231,44 @@
 
 TEST_F(ImprovedCookieControlsDisabledCookieSettingsTest,
        CookiesControlsEnabledButFeatureDisabled) {
-  EXPECT_TRUE(
-      cookie_settings_->IsCookieAccessAllowed(kBlockedSite, kFirstPartySite));
-  EXPECT_TRUE(cookie_settings_incognito_->IsCookieAccessAllowed(
+  EXPECT_TRUE(cookie_settings_->IsFullCookieAccessAllowed(kBlockedSite,
+                                                          kFirstPartySite));
+  EXPECT_TRUE(cookie_settings_incognito_->IsFullCookieAccessAllowed(
       kBlockedSite, kFirstPartySite));
   prefs_.SetInteger(prefs::kCookieControlsMode,
                     static_cast<int>(CookieControlsMode::kBlockThirdParty));
-  EXPECT_TRUE(
-      cookie_settings_->IsCookieAccessAllowed(kBlockedSite, kFirstPartySite));
-  EXPECT_TRUE(cookie_settings_incognito_->IsCookieAccessAllowed(
+  EXPECT_TRUE(cookie_settings_->IsFullCookieAccessAllowed(kBlockedSite,
+                                                          kFirstPartySite));
+  EXPECT_TRUE(cookie_settings_incognito_->IsFullCookieAccessAllowed(
       kBlockedSite, kFirstPartySite));
 }
 #endif
 
 TEST_F(CookieSettingsTest, CookiesAllowThirdParty) {
-  EXPECT_TRUE(
-      cookie_settings_->IsCookieAccessAllowed(kBlockedSite, kFirstPartySite));
+  EXPECT_TRUE(cookie_settings_->IsFullCookieAccessAllowed(kBlockedSite,
+                                                          kFirstPartySite));
   EXPECT_FALSE(cookie_settings_->IsCookieSessionOnly(kBlockedSite));
 }
 
 TEST_F(CookieSettingsTest, CookiesExplicitBlockSingleThirdParty) {
   cookie_settings_->SetCookieSetting(kBlockedSite, CONTENT_SETTING_BLOCK);
-  EXPECT_FALSE(
-      cookie_settings_->IsCookieAccessAllowed(kBlockedSite, kFirstPartySite));
-  EXPECT_TRUE(
-      cookie_settings_->IsCookieAccessAllowed(kAllowedSite, kFirstPartySite));
+  EXPECT_FALSE(cookie_settings_->IsFullCookieAccessAllowed(kBlockedSite,
+                                                           kFirstPartySite));
+  EXPECT_TRUE(cookie_settings_->IsFullCookieAccessAllowed(kAllowedSite,
+                                                          kFirstPartySite));
 }
 
 TEST_F(CookieSettingsTest, CookiesExplicitSessionOnly) {
   cookie_settings_->SetCookieSetting(kBlockedSite,
                                      CONTENT_SETTING_SESSION_ONLY);
-  EXPECT_TRUE(
-      cookie_settings_->IsCookieAccessAllowed(kBlockedSite, kFirstPartySite));
+  EXPECT_TRUE(cookie_settings_->IsFullCookieAccessAllowed(kBlockedSite,
+                                                          kFirstPartySite));
   EXPECT_TRUE(cookie_settings_->IsCookieSessionOnly(kBlockedSite));
 
   prefs_.SetInteger(prefs::kCookieControlsMode,
                     static_cast<int>(CookieControlsMode::kBlockThirdParty));
-  EXPECT_TRUE(
-      cookie_settings_->IsCookieAccessAllowed(kBlockedSite, kFirstPartySite));
+  EXPECT_TRUE(cookie_settings_->IsFullCookieAccessAllowed(kBlockedSite,
+                                                          kFirstPartySite));
   EXPECT_TRUE(cookie_settings_->IsCookieSessionOnly(kBlockedSite));
 }
 
@@ -382,13 +385,13 @@
   cookie_settings_->SetCookieSetting(kAllowedSite, CONTENT_SETTING_ALLOW);
   prefs_.SetInteger(prefs::kCookieControlsMode,
                     static_cast<int>(CookieControlsMode::kBlockThirdParty));
-  EXPECT_TRUE(
-      cookie_settings_->IsCookieAccessAllowed(kAllowedSite, kFirstPartySite));
+  EXPECT_TRUE(cookie_settings_->IsFullCookieAccessAllowed(kAllowedSite,
+                                                          kFirstPartySite));
   EXPECT_FALSE(cookie_settings_->IsCookieSessionOnly(kAllowedSite));
 
   // Extensions should always be allowed to use cookies.
   EXPECT_TRUE(
-      cookie_settings_->IsCookieAccessAllowed(kAllowedSite, kExtensionURL));
+      cookie_settings_->IsFullCookieAccessAllowed(kAllowedSite, kExtensionURL));
 }
 
 TEST_F(CookieSettingsTest, CookiesThirdPartyBlockedAllSitesAllowed) {
@@ -404,43 +407,44 @@
 
   // |kAllowedSite| should be allowed.
   EXPECT_TRUE(
-      cookie_settings_->IsCookieAccessAllowed(kAllowedSite, kBlockedSite));
+      cookie_settings_->IsFullCookieAccessAllowed(kAllowedSite, kBlockedSite));
   EXPECT_FALSE(cookie_settings_->IsCookieSessionOnly(kAllowedSite));
 
   // HTTPS sites should be allowed in a first-party context.
-  EXPECT_TRUE(cookie_settings_->IsCookieAccessAllowed(kHttpsSite, kHttpsSite));
+  EXPECT_TRUE(
+      cookie_settings_->IsFullCookieAccessAllowed(kHttpsSite, kHttpsSite));
   EXPECT_FALSE(cookie_settings_->IsCookieSessionOnly(kAllowedSite));
 
   // HTTP sites should be allowed, but session-only.
-  EXPECT_TRUE(cookie_settings_->IsCookieAccessAllowed(kFirstPartySite,
-                                                      kFirstPartySite));
+  EXPECT_TRUE(cookie_settings_->IsFullCookieAccessAllowed(kFirstPartySite,
+                                                          kFirstPartySite));
   EXPECT_TRUE(cookie_settings_->IsCookieSessionOnly(kFirstPartySite));
 
   // Third-party cookies should be blocked.
+  EXPECT_FALSE(cookie_settings_->IsFullCookieAccessAllowed(kFirstPartySite,
+                                                           kBlockedSite));
   EXPECT_FALSE(
-      cookie_settings_->IsCookieAccessAllowed(kFirstPartySite, kBlockedSite));
-  EXPECT_FALSE(
-      cookie_settings_->IsCookieAccessAllowed(kHttpsSite, kBlockedSite));
+      cookie_settings_->IsFullCookieAccessAllowed(kHttpsSite, kBlockedSite));
 }
 
 TEST_F(CookieSettingsTest, CookiesBlockEverything) {
   cookie_settings_->SetDefaultCookieSetting(CONTENT_SETTING_BLOCK);
 
-  EXPECT_FALSE(cookie_settings_->IsCookieAccessAllowed(kFirstPartySite,
-                                                       kFirstPartySite));
-  EXPECT_FALSE(
-      cookie_settings_->IsCookieAccessAllowed(kAllowedSite, kFirstPartySite));
+  EXPECT_FALSE(cookie_settings_->IsFullCookieAccessAllowed(kFirstPartySite,
+                                                           kFirstPartySite));
+  EXPECT_FALSE(cookie_settings_->IsFullCookieAccessAllowed(kAllowedSite,
+                                                           kFirstPartySite));
 }
 
 TEST_F(CookieSettingsTest, CookiesBlockEverythingExceptAllowed) {
   cookie_settings_->SetDefaultCookieSetting(CONTENT_SETTING_BLOCK);
   cookie_settings_->SetCookieSetting(kAllowedSite, CONTENT_SETTING_ALLOW);
-  EXPECT_FALSE(cookie_settings_->IsCookieAccessAllowed(kFirstPartySite,
-                                                       kFirstPartySite));
+  EXPECT_FALSE(cookie_settings_->IsFullCookieAccessAllowed(kFirstPartySite,
+                                                           kFirstPartySite));
+  EXPECT_TRUE(cookie_settings_->IsFullCookieAccessAllowed(kAllowedSite,
+                                                          kFirstPartySite));
   EXPECT_TRUE(
-      cookie_settings_->IsCookieAccessAllowed(kAllowedSite, kFirstPartySite));
-  EXPECT_TRUE(
-      cookie_settings_->IsCookieAccessAllowed(kAllowedSite, kAllowedSite));
+      cookie_settings_->IsFullCookieAccessAllowed(kAllowedSite, kAllowedSite));
   EXPECT_FALSE(cookie_settings_->IsCookieSessionOnly(kAllowedSite));
 }
 
@@ -663,7 +667,7 @@
 
   // Regular cookie settings also apply to extensions.
   EXPECT_FALSE(
-      cookie_settings_->IsCookieAccessAllowed(kBlockedSite, kExtensionURL));
+      cookie_settings_->IsFullCookieAccessAllowed(kBlockedSite, kExtensionURL));
 }
 
 TEST_F(CookieSettingsTest, ExtensionsOwnCookies) {
@@ -671,13 +675,13 @@
 
 #if BUILDFLAG(ENABLE_EXTENSIONS)
   // Extensions can always use cookies (and site data) in their own origin.
-  EXPECT_TRUE(
-      cookie_settings_->IsCookieAccessAllowed(kExtensionURL, kExtensionURL));
+  EXPECT_TRUE(cookie_settings_->IsFullCookieAccessAllowed(kExtensionURL,
+                                                          kExtensionURL));
 #else
   // Except if extensions are disabled. Then the extension-specific checks do
   // not exist and the default setting is to block.
-  EXPECT_FALSE(
-      cookie_settings_->IsCookieAccessAllowed(kExtensionURL, kExtensionURL));
+  EXPECT_FALSE(cookie_settings_->IsFullCookieAccessAllowed(kExtensionURL,
+                                                           kExtensionURL));
 #endif
 }
 
@@ -688,40 +692,40 @@
   // XHRs stemming from extensions are exempt from third-party cookie blocking
   // rules (as the first party is always the extension's security origin).
   EXPECT_TRUE(
-      cookie_settings_->IsCookieAccessAllowed(kBlockedSite, kExtensionURL));
+      cookie_settings_->IsFullCookieAccessAllowed(kBlockedSite, kExtensionURL));
 }
 
 TEST_F(CookieSettingsTest, ThirdPartyException) {
   EXPECT_TRUE(
       cookie_settings_->IsThirdPartyAccessAllowed(kFirstPartySite, nullptr));
   EXPECT_TRUE(
-      cookie_settings_->IsCookieAccessAllowed(kHttpsSite, kFirstPartySite));
+      cookie_settings_->IsFullCookieAccessAllowed(kHttpsSite, kFirstPartySite));
 
   prefs_.SetInteger(prefs::kCookieControlsMode,
                     static_cast<int>(CookieControlsMode::kBlockThirdParty));
   EXPECT_FALSE(
       cookie_settings_->IsThirdPartyAccessAllowed(kFirstPartySite, nullptr));
   EXPECT_FALSE(
-      cookie_settings_->IsCookieAccessAllowed(kHttpsSite, kFirstPartySite));
+      cookie_settings_->IsFullCookieAccessAllowed(kHttpsSite, kFirstPartySite));
 
   cookie_settings_->SetThirdPartyCookieSetting(kFirstPartySite,
                                                CONTENT_SETTING_ALLOW);
   EXPECT_TRUE(
       cookie_settings_->IsThirdPartyAccessAllowed(kFirstPartySite, nullptr));
   EXPECT_TRUE(
-      cookie_settings_->IsCookieAccessAllowed(kHttpsSite, kFirstPartySite));
+      cookie_settings_->IsFullCookieAccessAllowed(kHttpsSite, kFirstPartySite));
 
   cookie_settings_->ResetThirdPartyCookieSetting(kFirstPartySite);
   EXPECT_FALSE(
       cookie_settings_->IsThirdPartyAccessAllowed(kFirstPartySite, nullptr));
   EXPECT_FALSE(
-      cookie_settings_->IsCookieAccessAllowed(kHttpsSite, kFirstPartySite));
+      cookie_settings_->IsFullCookieAccessAllowed(kHttpsSite, kFirstPartySite));
 
   cookie_settings_->SetCookieSetting(kHttpsSite, CONTENT_SETTING_ALLOW);
   EXPECT_FALSE(
       cookie_settings_->IsThirdPartyAccessAllowed(kFirstPartySite, nullptr));
   EXPECT_TRUE(
-      cookie_settings_->IsCookieAccessAllowed(kHttpsSite, kFirstPartySite));
+      cookie_settings_->IsFullCookieAccessAllowed(kHttpsSite, kFirstPartySite));
 }
 
 TEST_F(CookieSettingsTest, ManagedThirdPartyException) {
@@ -729,7 +733,7 @@
   EXPECT_TRUE(
       cookie_settings_->IsThirdPartyAccessAllowed(kFirstPartySite, &source));
   EXPECT_TRUE(
-      cookie_settings_->IsCookieAccessAllowed(kHttpsSite, kFirstPartySite));
+      cookie_settings_->IsFullCookieAccessAllowed(kHttpsSite, kFirstPartySite));
   EXPECT_EQ(source, SettingSource::SETTING_SOURCE_USER);
 
   prefs_.SetManagedPref(prefs::kManagedDefaultCookiesSetting,
@@ -737,7 +741,7 @@
   EXPECT_FALSE(
       cookie_settings_->IsThirdPartyAccessAllowed(kFirstPartySite, &source));
   EXPECT_FALSE(
-      cookie_settings_->IsCookieAccessAllowed(kHttpsSite, kFirstPartySite));
+      cookie_settings_->IsFullCookieAccessAllowed(kHttpsSite, kFirstPartySite));
   EXPECT_EQ(source, SettingSource::SETTING_SOURCE_POLICY);
 }
 
diff --git a/components/content_settings/core/common/cookie_settings_base_unittest.cc b/components/content_settings/core/common/cookie_settings_base_unittest.cc
index 245d6344..301668a 100644
--- a/components/content_settings/core/common/cookie_settings_base_unittest.cc
+++ b/components/content_settings/core/common/cookie_settings_base_unittest.cc
@@ -139,19 +139,20 @@
 TEST(CookieSettingsBaseTest, CookieAccessNotAllowedWithBlockedSetting) {
   CallbackCookieSettings settings(
       base::BindRepeating([](const GURL&) { return CONTENT_SETTING_BLOCK; }));
-  EXPECT_FALSE(settings.IsCookieAccessAllowed(GURL(kDomain), GURL(kDomain)));
+  EXPECT_FALSE(
+      settings.IsFullCookieAccessAllowed(GURL(kDomain), GURL(kDomain)));
 }
 
 TEST(CookieSettingsBaseTest, CookieAccessAllowedWithAllowSetting) {
   CallbackCookieSettings settings(
       base::BindRepeating([](const GURL&) { return CONTENT_SETTING_ALLOW; }));
-  EXPECT_TRUE(settings.IsCookieAccessAllowed(GURL(kDomain), GURL(kDomain)));
+  EXPECT_TRUE(settings.IsFullCookieAccessAllowed(GURL(kDomain), GURL(kDomain)));
 }
 
 TEST(CookieSettingsBaseTest, CookieAccessAllowedWithSessionOnlySetting) {
   CallbackCookieSettings settings(base::BindRepeating(
       [](const GURL&) { return CONTENT_SETTING_SESSION_ONLY; }));
-  EXPECT_TRUE(settings.IsCookieAccessAllowed(GURL(kDomain), GURL(kDomain)));
+  EXPECT_TRUE(settings.IsFullCookieAccessAllowed(GURL(kDomain), GURL(kDomain)));
 }
 
 TEST(CookieSettingsBaseTest, LegacyCookieAccessSemantics) {
diff --git a/components/embedder_support/android/delegate/web_contents_delegate_android.cc b/components/embedder_support/android/delegate/web_contents_delegate_android.cc
index 821efb22..9364a722 100644
--- a/components/embedder_support/android/delegate/web_contents_delegate_android.cc
+++ b/components/embedder_support/android/delegate/web_contents_delegate_android.cc
@@ -56,11 +56,12 @@
 // WebContentsDelegate methods
 // ----------------------------------------------------------------------------
 
-ColorChooser* WebContentsDelegateAndroid::OpenColorChooser(
+std::unique_ptr<content::ColorChooser>
+WebContentsDelegateAndroid::OpenColorChooser(
     WebContents* source,
     SkColor color,
     const std::vector<blink::mojom::ColorSuggestionPtr>& suggestions) {
-  return new ColorChooserAndroid(source, color, suggestions);
+  return std::make_unique<ColorChooserAndroid>(source, color, suggestions);
 }
 
 // OpenURLFromTab() will be called when we're performing a browser-intiated
diff --git a/components/embedder_support/android/delegate/web_contents_delegate_android.h b/components/embedder_support/android/delegate/web_contents_delegate_android.h
index cf50204a..4b832f4 100644
--- a/components/embedder_support/android/delegate/web_contents_delegate_android.h
+++ b/components/embedder_support/android/delegate/web_contents_delegate_android.h
@@ -50,7 +50,7 @@
   content::WebContents* OpenURLFromTab(
       content::WebContents* source,
       const content::OpenURLParams& params) override;
-  content::ColorChooser* OpenColorChooser(
+  std::unique_ptr<content::ColorChooser> OpenColorChooser(
       content::WebContents* source,
       SkColor color,
       const std::vector<blink::mojom::ColorSuggestionPtr>& suggestions)
diff --git a/components/embedder_support/android/java/src/org/chromium/components/embedder_support/util/UrlUtilities.java b/components/embedder_support/android/java/src/org/chromium/components/embedder_support/util/UrlUtilities.java
index d551ad8..4d3ef6f 100644
--- a/components/embedder_support/android/java/src/org/chromium/components/embedder_support/util/UrlUtilities.java
+++ b/components/embedder_support/android/java/src/org/chromium/components/embedder_support/util/UrlUtilities.java
@@ -197,6 +197,7 @@
      * (i.e. no dots other than leading/trailing ones), or is itself a recognized registry
      * identifier.
      */
+    // TODO(crbug/783819): Convert to GURL.
     public static String getDomainAndRegistry(String uri, boolean includePrivateRegistries) {
         if (TextUtils.isEmpty(uri)) return uri;
         return UrlUtilitiesJni.get().getDomainAndRegistry(uri, includePrivateRegistries);
diff --git a/components/embedder_support/content_settings_utils.cc b/components/embedder_support/content_settings_utils.cc
index d2000698..099e09c 100644
--- a/components/embedder_support/content_settings_utils.cc
+++ b/components/embedder_support/content_settings_utils.cc
@@ -17,8 +17,8 @@
                    const GURL& site_for_cookies,
                    const absl::optional<url::Origin>& top_frame_origin,
                    const content_settings::CookieSettings* cookie_settings) {
-  return cookie_settings->IsCookieAccessAllowed(manifest_url, site_for_cookies,
-                                                top_frame_origin);
+  return cookie_settings->IsFullCookieAccessAllowed(
+      manifest_url, site_for_cookies, top_frame_origin);
 }
 
 content::AllowServiceWorkerResult AllowServiceWorker(
@@ -36,7 +36,7 @@
   bool allow_javascript = setting == CONTENT_SETTING_ALLOW;
 
   // Check if cookies are allowed.
-  bool allow_cookies = cookie_settings->IsCookieAccessAllowed(
+  bool allow_cookies = cookie_settings->IsFullCookieAccessAllowed(
       scope, site_for_cookies, top_frame_origin);
 
   return content::AllowServiceWorkerResult::FromPolicy(!allow_javascript,
@@ -52,7 +52,7 @@
     int render_process_id,
     int render_frame_id,
     const content_settings::CookieSettings* cookie_settings) {
-  bool allow = cookie_settings->IsCookieAccessAllowed(
+  bool allow = cookie_settings->IsFullCookieAccessAllowed(
       worker_url, site_for_cookies, top_frame_origin);
 
   content_settings::PageSpecificContentSettings::SharedWorkerAccessed(
@@ -65,8 +65,8 @@
     const GURL& url,
     const std::vector<content::GlobalFrameRoutingId>& render_frames,
     const content_settings::CookieSettings* cookie_settings) {
-  bool allow = cookie_settings->IsCookieAccessAllowed(url, url,
-                                                      url::Origin::Create(url));
+  bool allow = cookie_settings->IsFullCookieAccessAllowed(
+      url, url, url::Origin::Create(url));
   for (const auto& it : render_frames) {
     content_settings::PageSpecificContentSettings::FileSystemAccessed(
         it.child_id, it.frame_routing_id, url, !allow);
@@ -78,8 +78,8 @@
     const GURL& url,
     const std::vector<content::GlobalFrameRoutingId>& render_frames,
     const content_settings::CookieSettings* cookie_settings) {
-  bool allow = cookie_settings->IsCookieAccessAllowed(url, url,
-                                                      url::Origin::Create(url));
+  bool allow = cookie_settings->IsFullCookieAccessAllowed(
+      url, url, url::Origin::Create(url));
 
   for (const auto& it : render_frames) {
     content_settings::PageSpecificContentSettings::IndexedDBAccessed(
@@ -92,8 +92,8 @@
     const GURL& url,
     const std::vector<content::GlobalFrameRoutingId>& render_frames,
     const content_settings::CookieSettings* cookie_settings) {
-  bool allow = cookie_settings->IsCookieAccessAllowed(url, url,
-                                                      url::Origin::Create(url));
+  bool allow = cookie_settings->IsFullCookieAccessAllowed(
+      url, url, url::Origin::Create(url));
 
   for (const auto& it : render_frames) {
     content_settings::PageSpecificContentSettings::CacheStorageAccessed(
@@ -105,8 +105,8 @@
 bool AllowWorkerWebLocks(
     const GURL& url,
     const content_settings::CookieSettings* cookie_settings) {
-  return cookie_settings->IsCookieAccessAllowed(url, url,
-                                                url::Origin::Create(url));
+  return cookie_settings->IsFullCookieAccessAllowed(url, url,
+                                                    url::Origin::Create(url));
 }
 
 }  // namespace embedder_support
diff --git a/components/external_intents/android/java/src/org/chromium/components/external_intents/InterceptNavigationDelegateImpl.java b/components/external_intents/android/java/src/org/chromium/components/external_intents/InterceptNavigationDelegateImpl.java
index 44d7cdb..8f33c20 100644
--- a/components/external_intents/android/java/src/org/chromium/components/external_intents/InterceptNavigationDelegateImpl.java
+++ b/components/external_intents/android/java/src/org/chromium/components/external_intents/InterceptNavigationDelegateImpl.java
@@ -264,6 +264,9 @@
             PostTask.postTask(UiThreadTaskTraits.DEFAULT, new Runnable() {
                 @Override
                 public void run() {
+                    // Tab was destroyed before this task ran.
+                    if (mClient.getWebContents() == null) return;
+
                     // If the launch was from an External app, Chrome came from the background and
                     // acted as an intermediate link redirector between two apps (crbug.com/487938).
                     if (mClient.wasTabLaunchedFromExternalApp()) {
diff --git a/components/favicon/content/content_favicon_driver.cc b/components/favicon/content/content_favicon_driver.cc
index aafe1680..187b034 100644
--- a/components/favicon/content/content_favicon_driver.cc
+++ b/components/favicon/content/content_favicon_driver.cc
@@ -15,6 +15,7 @@
 #include "content/public/browser/navigation_details.h"
 #include "content/public/browser/navigation_entry.h"
 #include "content/public/browser/navigation_handle.h"
+#include "content/public/browser/page.h"
 #include "third_party/blink/public/common/manifest/manifest.h"
 #include "ui/gfx/image/image.h"
 
@@ -50,7 +51,8 @@
 GURL ContentFaviconDriver::GetManifestURL(content::RenderFrameHost* rfh) {
   DocumentManifestData* document_data =
       DocumentManifestData::GetOrCreateForCurrentDocument(rfh);
-  return document_data->has_manifest_url ? rfh->ManifestURL() : GURL();
+  return document_data->has_manifest_url ? rfh->GetPage().GetManifestURL()
+                                         : GURL();
 }
 
 ContentFaviconDriver::ContentFaviconDriver(content::WebContents* web_contents,
diff --git a/components/guest_view/browser/guest_view_base.cc b/components/guest_view/browser/guest_view_base.cc
index 6716d5e..e8242e87 100644
--- a/components/guest_view/browser/guest_view_base.cc
+++ b/components/guest_view/browser/guest_view_base.cc
@@ -18,6 +18,7 @@
 #include "components/guest_view/common/guest_view_messages.h"
 #include "components/zoom/page_zoom.h"
 #include "components/zoom/zoom_controller.h"
+#include "content/public/browser/color_chooser.h"
 #include "content/public/browser/file_select_listener.h"
 #include "content/public/browser/navigation_handle.h"
 #include "content/public/browser/render_frame_host.h"
@@ -632,7 +633,7 @@
       embedder_web_contents(), to_different_document);
 }
 
-content::ColorChooser* GuestViewBase::OpenColorChooser(
+std::unique_ptr<content::ColorChooser> GuestViewBase::OpenColorChooser(
     WebContents* web_contents,
     SkColor color,
     const std::vector<blink::mojom::ColorSuggestionPtr>& suggestions) {
diff --git a/components/guest_view/browser/guest_view_base.h b/components/guest_view/browser/guest_view_base.h
index 9a6c512..aa6b87d 100644
--- a/components/guest_view/browser/guest_view_base.h
+++ b/components/guest_view/browser/guest_view_base.h
@@ -344,7 +344,7 @@
   void ContentsZoomChange(bool zoom_in) final;
   void LoadingStateChanged(content::WebContents* source,
                            bool to_different_document) final;
-  content::ColorChooser* OpenColorChooser(
+  std::unique_ptr<content::ColorChooser> OpenColorChooser(
       content::WebContents* web_contents,
       SkColor color,
       const std::vector<blink::mojom::ColorSuggestionPtr>& suggestions) final;
diff --git a/components/password_manager/ios/password_form_helper.mm b/components/password_manager/ios/password_form_helper.mm
index 1f174f7..10780c3 100644
--- a/components/password_manager/ios/password_form_helper.mm
+++ b/components/password_manager/ios/password_form_helper.mm
@@ -61,7 +61,7 @@
 @interface PasswordFormHelper ()
 
 // Handler for injected JavaScript callbacks.
-- (BOOL)handleScriptCommand:(const base::DictionaryValue&)JSONCommand;
+- (BOOL)handleScriptCommand:(const base::Value&)JSONCommand;
 
 // Parses the |jsonString| which contatins the password forms found on a web
 // page to populate the |forms| vector.
@@ -113,9 +113,9 @@
     _fieldDataManager = uniqueIDDataTabHelper->GetFieldDataManager();
 
     __weak PasswordFormHelper* weakSelf = self;
-    auto callback = base::BindRepeating(
-        ^(const base::DictionaryValue& JSON, const GURL& originURL,
-          bool interacting, web::WebFrame* senderFrame) {
+    auto callback =
+        base::BindRepeating(^(const base::Value& JSON, const GURL& originURL,
+                              bool interacting, web::WebFrame* senderFrame) {
           // Passwords is only supported on main frame.
           if (senderFrame->IsMainFrame()) {
             // |originURL| and |interacting| aren't used.
@@ -184,13 +184,9 @@
 
 #pragma mark - Private methods
 
-- (BOOL)handleScriptCommand:(const base::DictionaryValue&)JSONCommand {
-  std::string command;
-  if (!JSONCommand.GetString("command", &command)) {
-    return NO;
-  }
-
-  if (command != "passwordForm.submitButtonClick") {
+- (BOOL)handleScriptCommand:(const base::Value&)JSONCommand {
+  const std::string* command = JSONCommand.FindStringKey("command");
+  if (!command || *command != "passwordForm.submitButtonClick") {
     return NO;
   }
 
diff --git a/components/services/storage/service_worker/service_worker_database.cc b/components/services/storage/service_worker/service_worker_database.cc
index 6c63464..d9dd558 100644
--- a/components/services/storage/service_worker/service_worker_database.cc
+++ b/components/services/storage/service_worker/service_worker_database.cc
@@ -18,6 +18,7 @@
 #include "base/strings/stringprintf.h"
 #include "components/services/storage/filesystem_proxy_factory.h"
 #include "components/services/storage/service_worker/service_worker_database.pb.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/blink/public/mojom/service_worker/service_worker_database.mojom.h"
 #include "third_party/leveldatabase/env_chromium.h"
 #include "third_party/leveldatabase/leveldb_chrome.h"
@@ -376,14 +377,15 @@
                         service_worker_internals::kUniqueOriginKey, &key_str))
         break;
 
-      blink::StorageKey key = blink::StorageKey::Deserialize(key_str);
-      if (key.opaque()) {
+      absl::optional<blink::StorageKey> key =
+          blink::StorageKey::Deserialize(key_str);
+      if (!key) {
         status = Status::kErrorCorrupted;
         keys->clear();
         break;
       }
 
-      keys->insert(key);
+      keys->insert(*key);
     }
   }
 
@@ -608,14 +610,15 @@
     return status;
   }
 
-  blink::StorageKey parsed = blink::StorageKey::Deserialize(value);
-  if (parsed.opaque()) {
+  absl::optional<blink::StorageKey> parsed =
+      blink::StorageKey::Deserialize(value);
+  if (!parsed) {
     status = Status::kErrorCorrupted;
     HandleReadResult(FROM_HERE, status);
     return status;
   }
 
-  *key = std::move(parsed);
+  *key = std::move(*parsed);
   HandleReadResult(FROM_HERE, Status::kOk);
   return Status::kOk;
 }
diff --git a/components/signin/internal/identity_manager/primary_account_manager.cc b/components/signin/internal/identity_manager/primary_account_manager.cc
index af05b989..0488736 100644
--- a/components/signin/internal/identity_manager/primary_account_manager.cc
+++ b/components/signin/internal/identity_manager/primary_account_manager.cc
@@ -56,6 +56,7 @@
   registry->RegisterBooleanPref(prefs::kAutologinEnabled, true);
   registry->RegisterListPref(prefs::kReverseAutologinRejectedEmailList);
   registry->RegisterBooleanPref(prefs::kSigninAllowed, true);
+  registry->RegisterBooleanPref(prefs::kSigninAllowedByPolicy, true);
   registry->RegisterBooleanPref(prefs::kSignedInWithCredentialProvider, false);
 }
 
@@ -125,13 +126,8 @@
   // It is important to only load credentials after starting to observe the
   // token service.
   token_service_->AddObserver(this);
-  signin::ConsentLevel consent_level = signin::ConsentLevel::kSync;
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-  // TODO(https://crbug.com/1196596): Use kSignin on all platforms.
-  if (base::FeatureList::IsEnabled(switches::kUseAccountManagerFacade))
-    consent_level = signin::ConsentLevel::kSignin;
-#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
-  token_service_->LoadCredentials(GetPrimaryAccountId(consent_level));
+  token_service_->LoadCredentials(
+      GetPrimaryAccountId(signin::ConsentLevel::kSignin));
 }
 
 bool PrimaryAccountManager::IsInitialized() const {
@@ -403,15 +399,10 @@
   if (token_service_->HasLoadCredentialsFinishedWithNoErrors()) {
     std::vector<AccountInfo> accounts_in_tracker_service =
         account_tracker_service_->GetAccounts();
-    signin::ConsentLevel consent_level = signin::ConsentLevel::kSync;
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-    // TODO(https://crbug.com/1196596): Use kSignin on all platforms.
-    if (base::FeatureList::IsEnabled(switches::kUseAccountManagerFacade))
-      consent_level = signin::ConsentLevel::kSignin;
-#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
-    const CoreAccountId sync_account_id = GetPrimaryAccountId(consent_level);
+    const CoreAccountId primary_account_id_ =
+        GetPrimaryAccountId(signin::ConsentLevel::kSignin);
     for (const auto& account : accounts_in_tracker_service) {
-      if (sync_account_id != account.account_id &&
+      if (primary_account_id_ != account.account_id &&
           !token_service_->RefreshTokenIsAvailable(account.account_id)) {
         VLOG(0) << "Removed account from account tracker service: "
                 << account.account_id;
diff --git a/components/signin/internal/identity_manager/primary_account_manager_unittest.cc b/components/signin/internal/identity_manager/primary_account_manager_unittest.cc
index 2797cf9..1bd2c94 100644
--- a/components/signin/internal/identity_manager/primary_account_manager_unittest.cc
+++ b/components/signin/internal/identity_manager/primary_account_manager_unittest.cc
@@ -103,19 +103,6 @@
     manager_.reset();
   }
 
-  void ExpectSignInWithRefreshTokenSuccess() {
-    EXPECT_TRUE(manager_->HasPrimaryAccount(ConsentLevel::kSync));
-    EXPECT_FALSE(manager_->GetPrimaryAccountId(ConsentLevel::kSync).empty());
-    EXPECT_FALSE(
-        manager_->GetPrimaryAccountInfo(ConsentLevel::kSync).email.empty());
-
-    EXPECT_TRUE(token_service_.RefreshTokenIsAvailable(
-        manager_->GetPrimaryAccountId(ConsentLevel::kSync)));
-
-    // Should go into token service and stop.
-    EXPECT_EQ(1, num_successful_signins_);
-  }
-
   void OnPrimaryAccountChanged(
       const signin::PrimaryAccountChangeEvent& event_details) override {
     DCHECK(event_details.GetEventTypeFor(signin::ConsentLevel::kSync) !=
diff --git a/components/signin/public/base/signin_pref_names.cc b/components/signin/public/base/signin_pref_names.cc
index bba71b0..1803fb0e 100644
--- a/components/signin/public/base/signin_pref_names.cc
+++ b/components/signin/public/base/signin_pref_names.cc
@@ -85,6 +85,10 @@
 // Boolean which stores if the user is allowed to signin to chrome.
 const char kSigninAllowed[] = "signin.allowed";
 
+// Boolean which stores if the user is allowed to signin to chrome with the
+// current applied policies.
+const char kSigninAllowedByPolicy[] = "signin.allowed_by_policy";
+
 // True if the token service has been prepared for Dice migration.
 const char kTokenServiceDiceCompatible[] = "token_service.dice_compatible";
 
diff --git a/components/signin/public/base/signin_pref_names.h b/components/signin/public/base/signin_pref_names.h
index 3230e853..8d49c5b 100644
--- a/components/signin/public/base/signin_pref_names.h
+++ b/components/signin/public/base/signin_pref_names.h
@@ -27,6 +27,7 @@
 extern const char kReverseAutologinRejectedEmailList[];
 extern const char kSignedInWithCredentialProvider[];
 extern const char kSigninAllowed[];
+extern const char kSigninAllowedByPolicy[];
 extern const char kTokenServiceDiceCompatible[];
 extern const char kGaiaCookieLastListAccountsData[];
 
diff --git a/components/sync_bookmarks/bookmark_specifics_conversions.cc b/components/sync_bookmarks/bookmark_specifics_conversions.cc
index 52e77e3..59d217d 100644
--- a/components/sync_bookmarks/bookmark_specifics_conversions.cc
+++ b/components/sync_bookmarks/bookmark_specifics_conversions.cc
@@ -57,6 +57,10 @@
   base::UmaHistogramEnumeration("Sync.InvalidBookmarkSpecifics", error);
 }
 
+void LogFaviconContainedInSpecifics(bool contains_favicon) {
+  base::UmaHistogramBoolean(
+      "Sync.BookmarkSpecificsExcludingFoldersContainFavicon", contains_favicon);
+}
 void UpdateBookmarkSpecificsMetaInfo(
     const bookmarks::BookmarkNode::MetaInfoMap* metainfo_map,
     sync_pb::BookmarkSpecifics* bm_specifics) {
@@ -87,6 +91,9 @@
   DCHECK(bookmark_node);
   DCHECK(favicon_service);
 
+  // TODO(crbug.com/1214843): Avoid invoking this function for folders, although
+  // it's harmless in practice due to later filtering via
+  // HistoryClient::CanAddURL().
   favicon_service->AddPageNoVisitForBookmark(bookmark_node->url(),
                                              bookmark_node->GetTitle());
 
@@ -98,12 +105,19 @@
   GURL icon_url(specifics.icon_url());
 
   if (icon_bytes->size() == 0 && icon_url.is_empty()) {
+    if (!bookmark_node->is_folder()) {
+      LogFaviconContainedInSpecifics(false);
+    }
     // Empty icon URL and no bitmap data means no icon mapping.
     favicon_service->DeleteFaviconMappings({bookmark_node->url()},
                                            favicon_base::IconType::kFavicon);
     return;
   }
 
+  if (!bookmark_node->is_folder()) {
+    LogFaviconContainedInSpecifics(true);
+  }
+
   if (icon_url.is_empty()) {
     // WebUI pages such as "chrome://bookmarks/" are missing a favicon URL but
     // they have a favicon. In addition, ancient clients (prior to M25) may not
@@ -287,6 +301,8 @@
       GetBookmarkMetaInfo(specifics);
   const bookmarks::BookmarkNode* node;
   if (is_folder) {
+    // TODO(crbug.com/1214840): Folders should propagate the creation time into
+    // BookmarkModel, just like non-folders.
     node = model->AddFolder(parent, index, NodeTitleFromSpecifics(specifics),
                             &metainfo, guid);
   } else {
diff --git a/components/sync_bookmarks/bookmark_specifics_conversions_unittest.cc b/components/sync_bookmarks/bookmark_specifics_conversions_unittest.cc
index 311e227..497edfd2 100644
--- a/components/sync_bookmarks/bookmark_specifics_conversions_unittest.cc
+++ b/components/sync_bookmarks/bookmark_specifics_conversions_unittest.cc
@@ -12,6 +12,7 @@
 #include "base/guid.h"
 #include "base/macros.h"
 #include "base/strings/utf_string_conversions.h"
+#include "base/test/metrics/histogram_tester.h"
 #include "base/time/time.h"
 #include "components/bookmarks/browser/bookmark_model.h"
 #include "components/bookmarks/browser/bookmark_node.h"
@@ -25,6 +26,7 @@
 
 using testing::_;
 using testing::Eq;
+using testing::Ge;
 using testing::NotNull;
 
 namespace sync_bookmarks {
@@ -169,7 +171,8 @@
   EXPECT_THAT(client_ptr->GetLoadFaviconRequestsForTest(), Eq(0));
 }
 
-TEST(BookmarkSpecificsConversionsTest, ShouldCreateBookmarkNodeFromSpecifics) {
+TEST(BookmarkSpecificsConversionsTest,
+     ShouldCreateNonFolderBookmarkNodeFromSpecifics) {
   const GURL kUrl("http://www.url.com");
   const base::GUID kGuid = base::GUID::GenerateRandomV4();
   const std::string kTitle = "Title";
@@ -203,6 +206,7 @@
   EXPECT_CALL(favicon_service,
               AddPageNoVisitForBookmark(kUrl, base::UTF8ToUTF16(kTitle)));
   EXPECT_CALL(favicon_service, MergeFavicon(kUrl, kIconUrl, _, _, _));
+  base::HistogramTester histogram_tester;
   const bookmarks::BookmarkNode* node = CreateBookmarkNodeFromSpecifics(
       *bm_specifics,
       /*parent=*/model->bookmark_bar_node(), /*index=*/0,
@@ -210,6 +214,7 @@
   ASSERT_THAT(node, NotNull());
   EXPECT_THAT(node->guid(), Eq(kGuid));
   EXPECT_THAT(node->GetTitle(), Eq(base::UTF8ToUTF16(kTitle)));
+  EXPECT_FALSE(node->is_folder());
   EXPECT_THAT(node->url(), Eq(kUrl));
   EXPECT_THAT(node->date_added(), Eq(kTime));
   std::string value1;
@@ -218,6 +223,66 @@
   std::string value2;
   node->GetMetaInfo(kKey2, &value2);
   EXPECT_THAT(value2, Eq(kValue2));
+
+  histogram_tester.ExpectUniqueSample(
+      "Sync.BookmarkSpecificsExcludingFoldersContainFavicon",
+      /*sample=*/true,
+      /*expected_bucket_count=*/1);
+}
+
+TEST(BookmarkSpecificsConversionsTest, ShouldCreateFolderFromSpecifics) {
+  const base::GUID kGuid = base::GUID::GenerateRandomV4();
+  const std::string kTitle = "Title";
+  const base::Time kTime = base::Time::Now();
+  const std::string kKey1 = "key1";
+  const std::string kValue1 = "value1";
+  const std::string kKey2 = "key2";
+  const std::string kValue2 = "value2";
+
+  sync_pb::EntitySpecifics specifics;
+  sync_pb::BookmarkSpecifics* bm_specifics = specifics.mutable_bookmark();
+  bm_specifics->set_guid(kGuid.AsLowercaseString());
+  bm_specifics->set_legacy_canonicalized_title(kTitle);
+  bm_specifics->set_creation_time_us(
+      kTime.ToDeltaSinceWindowsEpoch().InMicroseconds());
+  sync_pb::MetaInfo* meta_info1 = bm_specifics->add_meta_info();
+  meta_info1->set_key(kKey1);
+  meta_info1->set_value(kValue1);
+
+  sync_pb::MetaInfo* meta_info2 = bm_specifics->add_meta_info();
+  meta_info2->set_key(kKey2);
+  meta_info2->set_value(kValue2);
+
+  std::unique_ptr<bookmarks::BookmarkModel> model =
+      bookmarks::TestBookmarkClient::CreateModel();
+  testing::NiceMock<favicon::MockFaviconService> favicon_service;
+  // AddPageNoVisitForBookmark() is redundant and later filtered out by
+  // HistoryService, via HistoryClient::CanAddURL().
+  // TODO(crbug.com/1214843): Avoid this call for folders.
+  EXPECT_CALL(favicon_service, AddPageNoVisitForBookmark(_, _));
+  EXPECT_CALL(favicon_service, MergeFavicon(_, _, _, _, _)).Times(0);
+  base::HistogramTester histogram_tester;
+  const bookmarks::BookmarkNode* node = CreateBookmarkNodeFromSpecifics(
+      *bm_specifics,
+      /*parent=*/model->bookmark_bar_node(), /*index=*/0,
+      /*is_folder=*/true, model.get(), &favicon_service);
+  ASSERT_THAT(node, NotNull());
+  EXPECT_THAT(node->guid(), Eq(kGuid));
+  EXPECT_THAT(node->GetTitle(), Eq(base::UTF8ToUTF16(kTitle)));
+  EXPECT_TRUE(node->is_folder());
+  // TODO(crbug.com/1214840): Folders should propagate the creation time into
+  // BookmarkModel, just like non-folders.
+  EXPECT_THAT(node->date_added(), Ge(kTime));
+  std::string value1;
+  node->GetMetaInfo(kKey1, &value1);
+  EXPECT_THAT(value1, Eq(kValue1));
+  std::string value2;
+  node->GetMetaInfo(kKey2, &value2);
+  EXPECT_THAT(value2, Eq(kValue2));
+
+  // The histogram should not be recorded for folders.
+  histogram_tester.ExpectTotalCount(
+      "Sync.BookmarkSpecificsExcludingFoldersContainFavicon", 0);
 }
 
 TEST(BookmarkSpecificsConversionsTest,
diff --git a/components/translate/ios/browser/language_detection_controller.h b/components/translate/ios/browser/language_detection_controller.h
index bce042c..bf2eca6 100644
--- a/components/translate/ios/browser/language_detection_controller.h
+++ b/components/translate/ios/browser/language_detection_controller.h
@@ -18,10 +18,6 @@
 class GURL;
 class PrefService;
 
-namespace base {
-class DictionaryValue;
-}
-
 namespace net {
 class HttpResponseHeaders;
 }
@@ -48,7 +44,7 @@
 
   // Handles the "languageDetection.textCaptured" javascript command.
   // |interacting| is true if the user is currently interacting with the page.
-  void OnTextCaptured(const base::DictionaryValue& value,
+  void OnTextCaptured(const base::Value& value,
                       const GURL& url,
                       bool user_is_interacting,
                       web::WebFrame* sender_frame);
diff --git a/components/translate/ios/browser/language_detection_controller.mm b/components/translate/ios/browser/language_detection_controller.mm
index 035b8dbd..e68966cc 100644
--- a/components/translate/ios/browser/language_detection_controller.mm
+++ b/components/translate/ios/browser/language_detection_controller.mm
@@ -81,51 +81,46 @@
   web_frame->CallJavaScriptFunction("languageDetection.detectLanguage", {});
 }
 
-void LanguageDetectionController::OnTextCaptured(
-    const base::DictionaryValue& command,
-    const GURL& url,
-    bool user_is_interacting,
-    web::WebFrame* sender_frame) {
+void LanguageDetectionController::OnTextCaptured(const base::Value& command,
+                                                 const GURL& url,
+                                                 bool user_is_interacting,
+                                                 web::WebFrame* sender_frame) {
   if (!sender_frame->IsMainFrame()) {
     // Translate is only supported on main frame.
     return;
   }
-  std::string textCapturedCommand;
-  if (!command.GetString("command", &textCapturedCommand) ||
-      textCapturedCommand != "languageDetection.textCaptured" ||
-      !command.HasKey("translationAllowed")) {
-    NOTREACHED();
+  const std::string* text_captured_command = command.FindStringKey("command");
+  if (!text_captured_command ||
+      *text_captured_command != "languageDetection.textCaptured") {
     return;
   }
-  bool translation_allowed = false;
-  command.GetBoolean("translationAllowed", &translation_allowed);
-  if (!translation_allowed) {
+  absl::optional<bool> translation_allowed =
+      command.FindBoolKey("translationAllowed");
+  if (!translation_allowed.value_or(false)) {
     // Translation not allowed by the page. Done processing.
     return;
   }
-  if (!command.HasKey("captureTextTime") || !command.HasKey("htmlLang") ||
-      !command.HasKey("httpContentLanguage")) {
-    NOTREACHED();
+  absl::optional<double> capture_text_time =
+      command.FindDoubleKey("captureTextTime");
+  const std::string* html_lang = command.FindStringKey("htmlLang");
+  const std::string* http_content_language =
+      command.FindStringKey("httpContentLanguage");
+  if (!capture_text_time.has_value() || !html_lang || !http_content_language) {
     return;
   }
 
-  double capture_text_time = 0;
-  command.GetDouble("captureTextTime", &capture_text_time);
   UMA_HISTOGRAM_TIMES(kTranslateCaptureText,
-                      base::TimeDelta::FromMillisecondsD(capture_text_time));
-  std::string html_lang;
-  command.GetString("htmlLang", &html_lang);
-  std::string http_content_language;
-  command.GetString("httpContentLanguage", &http_content_language);
+                      base::TimeDelta::FromMillisecondsD(*capture_text_time));
+
   // If there is no language defined in httpEquiv, use the HTTP header.
-  if (http_content_language.empty())
-    http_content_language = content_language_header_;
+  if (http_content_language->empty())
+    http_content_language = &content_language_header_;
 
   sender_frame->CallJavaScriptFunction(
       "languageDetection.retrieveBufferedTextContent", {},
       base::BindRepeating(&LanguageDetectionController::OnTextRetrieved,
                           weak_method_factory_.GetWeakPtr(),
-                          http_content_language, html_lang, url),
+                          *http_content_language, *html_lang, url),
       base::TimeDelta::FromMilliseconds(
           web::kJavaScriptFunctionCallDefaultTimeout));
 }
diff --git a/components/translate/ios/browser/translate_controller.h b/components/translate/ios/browser/translate_controller.h
index 104a4b0..2f3b965 100644
--- a/components/translate/ios/browser/translate_controller.h
+++ b/components/translate/ios/browser/translate_controller.h
@@ -21,10 +21,6 @@
 @class JsTranslateManager;
 class GURL;
 
-namespace base {
-class DictionaryValue;
-}  // namespace base
-
 namespace web {
 class NavigationContext;
 }  // namespace web
@@ -92,16 +88,16 @@
                            OnTranslateSendRequestWithBadMethod);
 
   // Called when a JavaScript command is received.
-  bool OnJavascriptCommandReceived(const base::DictionaryValue& command,
+  bool OnJavascriptCommandReceived(const base::Value& command,
                                    const GURL& url,
                                    bool interacting,
                                    web::WebFrame* sender_frame);
   // Methods to handle specific JavaScript commands.
   // Return false if the command is invalid.
-  bool OnTranslateReady(const base::DictionaryValue& command);
-  bool OnTranslateComplete(const base::DictionaryValue& command);
-  bool OnTranslateLoadJavaScript(const base::DictionaryValue& command);
-  bool OnTranslateSendRequest(const base::DictionaryValue& command);
+  bool OnTranslateReady(const base::Value& command);
+  bool OnTranslateComplete(const base::Value& command);
+  bool OnTranslateLoadJavaScript(const base::Value& command);
+  bool OnTranslateSendRequest(const base::Value& command);
 
   // The callback when the script is fetched or a server error occurred.
   void OnScriptFetchComplete(std::unique_ptr<std::string> response_body);
diff --git a/components/translate/ios/browser/translate_controller.mm b/components/translate/ios/browser/translate_controller.mm
index d9835535..9a1d876 100644
--- a/components/translate/ios/browser/translate_controller.mm
+++ b/components/translate/ios/browser/translate_controller.mm
@@ -48,12 +48,7 @@
   web_state_->AddObserver(this);
   subscription_ = web_state_->AddScriptCommandCallback(
       base::BindRepeating(
-          [](TranslateController* ptr, const base::DictionaryValue& command,
-             const GURL& page_url, bool user_is_interacting,
-             web::WebFrame* sender_frame) {
-            ptr->OnJavascriptCommandReceived(command, page_url,
-                                             user_is_interacting, sender_frame);
-          },
+          base::IgnoreResult(&TranslateController::OnJavascriptCommandReceived),
           base::Unretained(this)),
       kCommandPrefix);
 }
@@ -85,7 +80,7 @@
 }
 
 bool TranslateController::OnJavascriptCommandReceived(
-    const base::DictionaryValue& command,
+    const base::Value& command,
     const GURL& page_url,
     bool user_is_interacting,
     web::WebFrame* sender_frame) {
@@ -93,97 +88,91 @@
     // Translate is only supported on main frame.
     return false;
   }
-  const base::Value* value = nullptr;
-  command.Get("command", &value);
-  if (!value) {
+  const std::string* command_string = command.FindStringKey("command");
+  if (!command_string) {
     return false;
   }
 
-  std::string out_string;
-  value->GetAsString(&out_string);
-  if (out_string == "translate.ready")
+  if (*command_string == "translate.ready")
     return OnTranslateReady(command);
-  if (out_string == "translate.status")
+  if (*command_string == "translate.status")
     return OnTranslateComplete(command);
-  if (out_string == "translate.loadjavascript")
+  if (*command_string == "translate.loadjavascript")
     return OnTranslateLoadJavaScript(command);
-  if (out_string == "translate.sendrequest")
+  if (*command_string == "translate.sendrequest")
     return OnTranslateSendRequest(command);
 
   return false;
 }
 
-bool TranslateController::OnTranslateReady(
-    const base::DictionaryValue& command) {
-  double error_code = 0.;
-  double load_time = 0.;
-  double ready_time = 0.;
-
-  if (!command.HasKey("errorCode") ||
-      !command.GetDouble("errorCode", &error_code) ||
-      error_code < TranslateErrors::NONE ||
-      error_code >= TranslateErrors::TRANSLATE_ERROR_MAX) {
+bool TranslateController::OnTranslateReady(const base::Value& command) {
+  absl::optional<double> error_code = command.FindDoubleKey("errorCode");
+  if (!error_code.has_value() || *error_code < TranslateErrors::NONE ||
+      *error_code >= TranslateErrors::TRANSLATE_ERROR_MAX) {
     return false;
   }
 
-  TranslateErrors::Type error_type =
-      static_cast<TranslateErrors::Type>(error_code);
+  absl::optional<double> load_time;
+  absl::optional<double> ready_time;
+
+  const TranslateErrors::Type error_type =
+      static_cast<TranslateErrors::Type>(*error_code);
   if (error_type == TranslateErrors::NONE) {
-    if (!command.HasKey("loadTime") || !command.HasKey("readyTime")) {
+    load_time = command.FindDoubleKey("loadTime");
+    ready_time = command.FindDoubleKey("readyTime");
+    if (!load_time.has_value() || !ready_time.has_value()) {
       return false;
     }
-    command.GetDouble("loadTime", &load_time);
-    command.GetDouble("readyTime", &ready_time);
   }
-  if (observer_)
-    observer_->OnTranslateScriptReady(error_type, load_time, ready_time);
+  if (observer_) {
+    observer_->OnTranslateScriptReady(error_type, load_time.value_or(0.),
+                                      ready_time.value_or(0.));
+  }
   return true;
 }
 
-bool TranslateController::OnTranslateComplete(
-    const base::DictionaryValue& command) {
-  double error_code = 0.;
-  std::string source_language;
-  double translation_time = 0.;
-
-  if (!command.HasKey("errorCode") ||
-      !command.GetDouble("errorCode", &error_code) ||
-      error_code < TranslateErrors::NONE ||
-      error_code >= TranslateErrors::TRANSLATE_ERROR_MAX) {
+bool TranslateController::OnTranslateComplete(const base::Value& command) {
+  absl::optional<double> error_code = command.FindDoubleKey("errorCode");
+  if (!error_code.has_value() || *error_code < TranslateErrors::NONE ||
+      *error_code >= TranslateErrors::TRANSLATE_ERROR_MAX) {
     return false;
   }
 
-  TranslateErrors::Type error_type =
-      static_cast<TranslateErrors::Type>(error_code);
+  const std::string* source_language = nullptr;
+  absl::optional<double> translation_time;
+
+  const TranslateErrors::Type error_type =
+      static_cast<TranslateErrors::Type>(*error_code);
   if (error_type == TranslateErrors::NONE) {
-    if (!command.HasKey("pageSourceLanguage") ||
-        !command.HasKey("translationTime")) {
+    source_language = command.FindStringKey("pageSourceLanguage");
+    translation_time = command.FindDoubleKey("translationTime");
+    if (!source_language || !translation_time.has_value()) {
       return false;
     }
-    command.GetString("pageSourceLanguage", &source_language);
-    command.GetDouble("translationTime", &translation_time);
   }
 
-  if (observer_)
-    observer_->OnTranslateComplete(error_type, source_language,
-                                   translation_time);
+  if (observer_) {
+    observer_->OnTranslateComplete(
+        error_type, source_language ? *source_language : std::string(),
+        translation_time.value_or(0.));
+  }
   return true;
 }
 
 bool TranslateController::OnTranslateLoadJavaScript(
-    const base::DictionaryValue& command) {
-  std::string url;
-  if (!command.HasKey("url") || !command.GetString("url", &url)) {
+    const base::Value& command) {
+  const std::string* url = command.FindStringKey("url");
+  if (!url) {
     return false;
   }
 
   GURL security_origin = translate::GetTranslateSecurityOrigin();
-  if (url.find(security_origin.spec()) || script_fetcher_) {
+  if (url->find(security_origin.spec()) || script_fetcher_) {
     return false;
   }
 
   auto resource_request = std::make_unique<network::ResourceRequest>();
-  resource_request->url = GURL(url);
+  resource_request->url = GURL(*url);
 
   script_fetcher_ = network::SimpleURLLoader::Create(
       std::move(resource_request), NO_TRAFFIC_ANNOTATION_YET);
@@ -195,45 +184,43 @@
   return true;
 }
 
-bool TranslateController::OnTranslateSendRequest(
-    const base::DictionaryValue& command) {
-  std::string method;
-  if (!command.HasKey("method") || !command.GetString("method", &method)) {
+bool TranslateController::OnTranslateSendRequest(const base::Value& command) {
+  const std::string* method = command.FindStringKey("method");
+  if (!method) {
     return false;
   }
-  std::string url;
-  if (!command.HasKey("url") || !command.GetString("url", &url)) {
+  const std::string* url = command.FindStringKey("url");
+  if (!url) {
     return false;
   }
-  std::string body;
-  if (!command.HasKey("body") || !command.GetString("body", &body)) {
+  const std::string* body = command.FindStringKey("body");
+  if (!body) {
     return false;
   }
-  double request_id;
-  if (!command.HasKey("requestID") ||
-      !command.GetDouble("requestID", &request_id)) {
+  absl::optional<double> request_id = command.FindDoubleKey("requestID");
+  if (!request_id.has_value()) {
     return false;
   }
 
   GURL security_origin = translate::GetTranslateSecurityOrigin();
-  if (url.find(security_origin.spec())) {
+  if (url->find(security_origin.spec())) {
     return false;
   }
 
   auto request = std::make_unique<network::ResourceRequest>();
-  request->method = method;
-  request->url = GURL(url);
+  request->method = *method;
+  request->url = GURL(*url);
   request->credentials_mode = network::mojom::CredentialsMode::kOmit;
   auto fetcher = network::SimpleURLLoader::Create(std::move(request),
                                                   NO_TRAFFIC_ANNOTATION_YET);
-  fetcher->AttachStringForUpload(body, "application/x-www-form-urlencoded");
+  fetcher->AttachStringForUpload(*body, "application/x-www-form-urlencoded");
   auto* raw_fetcher = fetcher.get();
   auto pair = request_fetchers_.insert(std::move(fetcher));
   raw_fetcher->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
       web_state_->GetBrowserState()->GetURLLoaderFactory(),
       base::BindOnce(&TranslateController::OnRequestFetchComplete,
-                     base::Unretained(this), pair.first, url,
-                     static_cast<int>(request_id)));
+                     base::Unretained(this), pair.first, *url,
+                     static_cast<int>(*request_id)));
   return true;
 }
 
diff --git a/components/user_manager/user_manager_base.cc b/components/user_manager/user_manager_base.cc
index e407397..0a8d1425 100644
--- a/components/user_manager/user_manager_base.cc
+++ b/components/user_manager/user_manager_base.cc
@@ -971,12 +971,11 @@
 
   const base::DictionaryValue* prefs_force_online =
       GetLocalState()->GetDictionary(kUserForceOnlineSignin);
-  bool force_online_signin = false;
   if (prefs_force_online) {
-    prefs_force_online->GetBooleanWithoutPathExpansion(
-        account_id.GetUserEmail(), &force_online_signin);
+    return prefs_force_online->FindBoolKey(account_id.GetUserEmail())
+        .value_or(false);
   }
-  return force_online_signin;
+  return false;
 }
 
 void UserManagerBase::RemoveNonCryptohomeData(const AccountId& account_id) {
diff --git a/content/browser/browser_context.cc b/content/browser/browser_context.cc
index cfc09d0..a7734981 100644
--- a/content/browser/browser_context.cc
+++ b/content/browser/browser_context.cc
@@ -64,9 +64,6 @@
 
 namespace {
 
-using perfetto::protos::pbzero::ChromeBrowserContext;
-using perfetto::protos::pbzero::ChromeTrackEvent;
-
 void SaveSessionStateOnIOThread(AppCacheServiceImpl* appcache_service) {
   appcache_service->set_force_keep_session_state();
 }
@@ -80,23 +77,33 @@
 }  // namespace
 
 BrowserContext::BrowserContext() {
-  impl_ = std::make_unique<Impl>(this);
   TRACE_EVENT("shutdown", "BrowserContext::BrowserContext",
-              ChromeTrackEvent::kChromeBrowserContext, *this);
-  TRACE_EVENT_BEGIN("shutdown", "Browser.BrowserContext",
-                    perfetto::Track::FromPointer(this),
-                    ChromeTrackEvent::kChromeBrowserContext, *this);
+              [&](perfetto::EventContext ctx) {
+                auto* event =
+                    ctx.event<perfetto::protos::pbzero::ChromeTrackEvent>();
+                event->set_chrome_browser_context()->set_ptr(
+                    reinterpret_cast<uint64_t>(this));
+              });
+  TRACE_EVENT_NESTABLE_ASYNC_BEGIN1("shutdown", "Browser.BrowserContext", this,
+                                    "browser_context",
+                                    static_cast<void*>(this));
+
+  impl_ = std::make_unique<Impl>(this);
 }
 
 BrowserContext::~BrowserContext() {
   TRACE_EVENT("shutdown", "BrowserContext::~BrowserContext",
-              ChromeTrackEvent::kChromeBrowserContext, *this);
-
-  // End for ASYNC event "Browser.BrowserContext".
-  TRACE_EVENT_END("shutdown", perfetto::Track::FromPointer(this),
-                  ChromeTrackEvent::kChromeBrowserContext, *this);
+              [&](perfetto::EventContext ctx) {
+                auto* event =
+                    ctx.event<perfetto::protos::pbzero::ChromeTrackEvent>();
+                event->set_chrome_browser_context()->set_ptr(
+                    reinterpret_cast<uint64_t>(this));
+              });
 
   impl_.reset();
+
+  TRACE_EVENT_NESTABLE_ASYNC_END1("shutdown", "Browser.BrowserContext", this,
+                                  "browser_context", static_cast<void*>(this));
 }
 
 DownloadManager* BrowserContext::GetDownloadManager() {
@@ -340,11 +347,6 @@
     dict.Add("id", impl()->UniqueId());
 }
 
-void BrowserContext::WriteIntoTrace(
-    perfetto::TracedProto<ChromeBrowserContext> proto) {
-  proto->set_id(impl()->UniqueId());
-}
-
 //////////////////////////////////////////////////////////////////////////////
 // The //content embedder can override the methods below to change or extend
 // how the //content layer interacts with a BrowserContext.  The code below
diff --git a/content/browser/browser_main_runner_impl.cc b/content/browser/browser_main_runner_impl.cc
index 4edb502..fde9208 100644
--- a/content/browser/browser_main_runner_impl.cc
+++ b/content/browser/browser_main_runner_impl.cc
@@ -169,7 +169,7 @@
   main_loop_->PreShutdown();
 
   // Finalize the startup tracing session if it is still active.
-  StartupTracingController::GetInstance().ShutdownAndWaitForStopIfNeeded();
+  StartupTracingController::GetInstance().WaitUntilStopped();
 
   {
     // The trace event has to stay between profiler creation and destruction.
diff --git a/content/browser/child_process_launcher.cc b/content/browser/child_process_launcher.cc
index ecc8ee0e..65099d93 100644
--- a/content/browser/child_process_launcher.cc
+++ b/content/browser/child_process_launcher.cc
@@ -16,7 +16,6 @@
 #include "base/process/launch.h"
 #include "base/process/process_metrics.h"
 #include "base/time/time.h"
-#include "base/tracing/protos/chrome_track_event.pbzero.h"
 #include "build/build_config.h"
 #include "content/public/browser/child_process_launcher_utils.h"
 #include "content/public/common/content_features.h"
@@ -30,28 +29,6 @@
 
 using internal::ChildProcessLauncherHelper;
 
-void ChildProcessLauncherPriority::WriteIntoTrace(
-    perfetto::TracedProto<
-        perfetto::protos::pbzero::ChildProcessLauncherPriority> proto) {
-  proto->set_is_backgrounded(is_background());
-  proto->set_has_pending_views(boost_for_pending_views);
-
-#if defined(OS_ANDROID)
-  using PriorityProto = perfetto::protos::pbzero::ChildProcessLauncherPriority;
-  switch (importance) {
-    case ChildProcessImportance::IMPORTANT:
-      proto->set_importance(PriorityProto::IMPORTANCE_IMPORTANT);
-      break;
-    case ChildProcessImportance::NORMAL:
-      proto->set_importance(PriorityProto::IMPORTANCE_NORMAL);
-      break;
-    case ChildProcessImportance::MODERATE:
-      proto->set_importance(PriorityProto::IMPORTANCE_MODERATE);
-      break;
-  }
-#endif
-}
-
 #if defined(OS_ANDROID)
 bool ChildProcessLauncher::Client::CanUseWarmUpConnection() {
   return true;
diff --git a/content/browser/child_process_launcher.h b/content/browser/child_process_launcher.h
index 7517fb03..7b299d9 100644
--- a/content/browser/child_process_launcher.h
+++ b/content/browser/child_process_launcher.h
@@ -23,7 +23,6 @@
 #include "content/public/browser/child_process_termination_info.h"
 #include "content/public/common/result_codes.h"
 #include "mojo/public/cpp/system/invitation.h"
-#include "third_party/perfetto/include/perfetto/tracing/traced_proto.h"
 
 #if defined(OS_ANDROID)
 #include "content/public/browser/android/child_process_importance.h"
@@ -33,14 +32,6 @@
 class CommandLine;
 }
 
-namespace perfetto {
-namespace protos {
-namespace pbzero {
-class ChildProcessLauncherPriority;
-}
-}  // namespace protos
-}  // namespace perfetto
-
 namespace content {
 
 class SandboxedProcessLauncherDelegate;
@@ -97,10 +88,6 @@
     return !(*this == other);
   }
 
-  void WriteIntoTrace(
-      perfetto::TracedProto<
-          perfetto::protos::pbzero::ChildProcessLauncherPriority> proto);
-
   // Prefer |is_background()| to inspecting these fields individually (to ensure
   // all logic uses the same notion of "backgrounded").
 
diff --git a/content/browser/picture_in_picture/picture_in_picture_content_browsertest.cc b/content/browser/picture_in_picture/picture_in_picture_content_browsertest.cc
index de71900..9c03db4 100644
--- a/content/browser/picture_in_picture/picture_in_picture_content_browsertest.cc
+++ b/content/browser/picture_in_picture/picture_in_picture_content_browsertest.cc
@@ -4,6 +4,7 @@
 
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/scoped_feature_list.h"
+#include "build/build_config.h"
 #include "content/browser/picture_in_picture/picture_in_picture_service_impl.h"
 #include "content/browser/picture_in_picture/picture_in_picture_window_controller_impl.h"
 #include "content/public/browser/content_browser_client.h"
@@ -285,8 +286,16 @@
   EXPECT_FALSE(web_contents_delegate()->is_in_picture_in_picture());
 }
 
+// Flaky on Linux TSAN: https://crbug.com/1210955.
+#if defined(OS_LINUX) && defined(THREAD_SANITIZER)
+#define MAYBE_EnterFullscreenThenPictureInPicture \
+  DISABLED_EnterFullscreenThenPictureInPicture
+#else
+#define MAYBE_EnterFullscreenThenPictureInPicture \
+  EnterFullscreenThenPictureInPicture
+#endif
 IN_PROC_BROWSER_TEST_F(PictureInPictureContentBrowserTest,
-                       EnterFullscreenThenPictureInPicture) {
+                       MAYBE_EnterFullscreenThenPictureInPicture) {
   ASSERT_TRUE(NavigateToURL(
       shell(), GetTestUrl("media/picture_in_picture", "one-video.html")));
 
diff --git a/content/browser/renderer_host/media/media_stream_manager.cc b/content/browser/renderer_host/media/media_stream_manager.cc
index 0254f32..ecc7ce1 100644
--- a/content/browser/renderer_host/media/media_stream_manager.cc
+++ b/content/browser/renderer_host/media/media_stream_manager.cc
@@ -2562,7 +2562,13 @@
     const std::string& salt,
     const url::Origin& security_origin,
     const std::string& raw_unique_id) {
+  // TODO(crbug.com/1215532): DCHECKs are disabled during automated testing on
+  // CrOS and this check failed when tested on an experimental builder. Revert
+  // https://crrev.com/c/2932244 to enable it. See go/chrome-dcheck-on-cros
+  // or http://crbug.com/1113456 for more details.
+#if !defined(OS_CHROMEOS)
   DCHECK(!raw_unique_id.empty());
+#endif
   if (raw_unique_id == media::AudioDeviceDescription::kDefaultDeviceId ||
       raw_unique_id == media::AudioDeviceDescription::kCommunicationsDeviceId) {
     return raw_unique_id;
diff --git a/content/browser/renderer_host/page_impl.cc b/content/browser/renderer_host/page_impl.cc
index f8f01944..b7cadd4 100644
--- a/content/browser/renderer_host/page_impl.cc
+++ b/content/browser/renderer_host/page_impl.cc
@@ -12,4 +12,8 @@
 
 PageImpl::~PageImpl() = default;
 
+const GURL& PageImpl::GetManifestURL() {
+  return manifest_url_;
+}
+
 }  // namespace content
diff --git a/content/browser/renderer_host/page_impl.h b/content/browser/renderer_host/page_impl.h
index 4fb196ea..2227fe6 100644
--- a/content/browser/renderer_host/page_impl.h
+++ b/content/browser/renderer_host/page_impl.h
@@ -8,50 +8,25 @@
 #include <memory>
 #include <vector>
 
+#include "content/public/browser/page.h"
 #include "third_party/blink/public/mojom/favicon/favicon_url.mojom.h"
-#include "url/gurl.h"
 
 namespace content {
 
 class RenderFrameHostImpl;
 
-// Page is a main document together with all of its subframes.
+// This implements the Page interface that is exposed to embedders of content,
+// and adds things only visible to content.
 
-// At the moment some navigations might create a new blink::Document in the
-// existing RenderFrameHost, which will lead to a creation of a new Page
-// associated with the same main RenderFrameHost. See the comment in
-// |RenderDocumentHostUserData| for more details and crbug.com/936696 for the
-// progress on always creating a new RenderFrameHost for each new document.
-
-// Page is created when a main document created, which can happen in the
-// following ways:
-// 1) Main RenderFrameHost is created.
-// 2) A cross-document non-bfcached navigation is committed in the same
-//    RenderFrameHost.
-// 3) Main RenderFrameHost is re-created after crash.
-
-// Page is deleted in the following cases:
-// 1) Main RenderFrameHost is deleted.
-// 2) A cross-document non-bfcached navigation is committed in the same
-//    RenderFrameHost.
-// 3) Before main RenderFrameHost is re-created after crash.
-
-// If a method can't be called on the non-main-frame RenderFrameHost or its
-// behaviour is always identical when called on the parent / child
-// RenderFrameHosts, it should be added to Page(Impl).
-
-// NOTE: Depending on the process model, the cross-origin iframes are likely to
-// be hosted in a different renderer process than the main document, so a given
-// page is hosted in multiple renderer processes at the same time.
-// RenderViewHost / RenderView / blink::Page (which are all 1:1:1) represent a
-// part of a given content::Page in a given renderer process (note, however,
-// that like RenderFrameHosts, these objects at the moment can be reused for a
-// new content::Page for a cross-document same-origin main-frame navigation).
-class PageImpl {
+// Please refer to content/public/browser/page.h for more details.
+class PageImpl : public Page {
  public:
   explicit PageImpl(RenderFrameHostImpl& rfh);
 
-  ~PageImpl();
+  ~PageImpl() override;
+
+  // Page implementation.
+  const GURL& GetManifestURL() override;
 
   RenderFrameHostImpl* main_document() { return &main_document_; }
 
@@ -61,7 +36,6 @@
   }
 
   void update_manifest_url(GURL url) { manifest_url_ = url; }
-  const GURL& manifest_url() const { return manifest_url_; }
 
   const std::vector<blink::mojom::FaviconURLPtr>& favicon_urls() const {
     return favicon_urls_;
@@ -72,7 +46,7 @@
 
  private:
   // True if we've received a notification that the onload() handler has
-  // run.
+  // run for main frame document.
   bool is_on_load_completed_ = false;
 
   // Web application manifest URL (or empty URL if none) for this page.
diff --git a/content/browser/renderer_host/page_impl_browsertest.cc b/content/browser/renderer_host/page_impl_browsertest.cc
index a2feaebc4..cb0d745 100644
--- a/content/browser/renderer_host/page_impl_browsertest.cc
+++ b/content/browser/renderer_host/page_impl_browsertest.cc
@@ -62,9 +62,9 @@
 
   // 2) Check Page for RenderFrameHosts a and b, they both should point to same
   // Page object.
-  PageImpl* page_a = rfh_a->GetPage();
-  PageImpl* page_b = rfh_b->GetPage();
-  EXPECT_EQ(page_a, page_b);
+  PageImpl& page_a = rfh_a->GetPage();
+  PageImpl& page_b = rfh_b->GetPage();
+  EXPECT_EQ(&page_a, &page_b);
 }
 
 }  // namespace content
diff --git a/content/browser/renderer_host/private_network_access_browsertest.cc b/content/browser/renderer_host/private_network_access_browsertest.cc
index f59a6c3..0465ef18 100644
--- a/content/browser/renderer_host/private_network_access_browsertest.cc
+++ b/content/browser/renderer_host/private_network_access_browsertest.cc
@@ -46,6 +46,9 @@
 constexpr char kTreatAsPublicAddressPath[] =
     "/set-header?Content-Security-Policy: treat-as-public-address";
 
+// Path to a cacheable response.
+constexpr char kCacheablePath[] = "/cachetime";
+
 // Returns a snippet of Javascript that fetch()es the given URL.
 //
 // The snippet evaluates to a boolean promise which resolves to true iff the
@@ -332,220 +335,12 @@
             }) {}
 };
 
-// This test verifies that when the right feature is enabled, iframe requests:
-//  - from an insecure page with the "treat-as-public-address" CSP directive
-//  - to a local IP address
-// are blocked.
-IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTestBlockNavigations,
-                       IframeFromInsecureTreatAsPublicToLocalIsBlocked) {
-  EXPECT_TRUE(
-      NavigateToURL(shell(), InsecureLocalURL(kTreatAsPublicAddressPath)));
-
-  GURL url = InsecureLocalURL("/empty.html");
-
-  TestNavigationManager child_navigation_manager(shell()->web_contents(), url);
-
-  EXPECT_TRUE(ExecJs(root_frame_host(), R"(
-    const iframe = document.createElement("iframe");
-    iframe.src = "/empty.html";
-    document.body.appendChild(iframe);
-  )"));
-
-  child_navigation_manager.WaitForNavigationFinished();
-
-  // Check that the child iframe failed to fetch.
-  EXPECT_FALSE(child_navigation_manager.was_successful());
-
-  ASSERT_EQ(1ul, root_frame_host()->child_count());
-  RenderFrameHostImpl* child_frame =
-      root_frame_host()->child_at(0)->current_frame_host();
-  EXPECT_EQ(GURL(kUnreachableWebDataURL),
-            EvalJs(child_frame, "document.location.href"));
-
-  // The frame committed an error page but retains the original URL so that
-  // reloading the page does the right thing. The committed origin on the other
-  // hand is opaque, which it would not be if the navigation had succeeded.
-  EXPECT_EQ(url, child_frame->GetLastCommittedURL());
-  EXPECT_TRUE(child_frame->GetLastCommittedOrigin().opaque());
-}
-
-// This test mimics the one above, only it is executed without enabling the
-// BlockInsecurePrivateNetworkRequestsForNavigations feature. It asserts that
-// the navigation is not blocked in this case.
-IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
-                       IframeFromInsecureTreatAsPublicToLocalIsNotBlocked) {
-  EXPECT_TRUE(
-      NavigateToURL(shell(), InsecureLocalURL(kTreatAsPublicAddressPath)));
-
-  GURL url = InsecureLocalURL("/empty.html");
-
-  TestNavigationManager child_navigation_manager(shell()->web_contents(), url);
-
-  EXPECT_TRUE(ExecJs(root_frame_host(), R"(
-    const iframe = document.createElement("iframe");
-    iframe.src = "/empty.html";
-    document.body.appendChild(iframe);
-  )"));
-
-  child_navigation_manager.WaitForNavigationFinished();
-
-  // Check that the child iframe navigated successfully.
-  EXPECT_TRUE(child_navigation_manager.was_successful());
-
-  ASSERT_EQ(1ul, root_frame_host()->child_count());
-  RenderFrameHostImpl* child_frame =
-      root_frame_host()->child_at(0)->current_frame_host();
-  EXPECT_EQ(url, EvalJs(child_frame, "document.location.href"));
-}
-
-// Similar to IframeFromInsecureTreatAsPublicToLocalIsBlocked, but in
-// report-only mode. As a result "treat-as-public-address" must be ignored.
-IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
-                       CspReportOnlyTreatAsPublicAddressIgnored) {
-  EXPECT_TRUE(NavigateToURL(
-      shell(),
-      InsecureLocalURL("/set-header?Content-Security-Policy-Report-Only: "
-                       "treat-as-public-address")));
-
-  GURL url = InsecureLocalURL("/empty.html");
-
-  TestNavigationManager child_navigation_manager(shell()->web_contents(), url);
-
-  EXPECT_TRUE(ExecJs(root_frame_host(), R"(
-    const iframe = document.createElement("iframe");
-    iframe.src = "/empty.html";
-    document.body.appendChild(iframe);
-  )"));
-
-  child_navigation_manager.WaitForNavigationFinished();
-
-  // Check that the child iframe was not blocked.
-  EXPECT_TRUE(child_navigation_manager.was_successful());
-
-  ASSERT_EQ(1ul, root_frame_host()->child_count());
-  RenderFrameHostImpl* child_frame =
-      root_frame_host()->child_at(0)->current_frame_host();
-  EXPECT_EQ(url, EvalJs(child_frame, "document.location.href"));
-  EXPECT_EQ(url, child_frame->GetLastCommittedURL());
-  EXPECT_FALSE(child_frame->GetLastCommittedOrigin().opaque());
-}
-
-// TODO(https://crbug.com/1129326): Revisit this when main-frame navigations are
-// subject to CORS-RFC1918 checks.
-IN_PROC_BROWSER_TEST_F(
-    PrivateNetworkAccessBrowserTestBlockNavigations,
-    FormSubmissionFromInsecurePublictoLocalIsNotBlockedInMainFrame) {
-  EXPECT_TRUE(NavigateToURL(shell(), InsecurePublicURL(kDefaultPath)));
-
-  GURL url = InsecureLocalURL(kDefaultPath);
-  TestNavigationManager navigation_manager(shell()->web_contents(), url);
-
-  base::StringPiece script_template = R"(
-    const form = document.createElement("form");
-    form.action = $1;
-    form.method = "post";
-    document.body.appendChild(form);
-    form.submit();
-  )";
-
-  EXPECT_TRUE(ExecJs(root_frame_host(), JsReplace(script_template, url)));
-
-  navigation_manager.WaitForNavigationFinished();
-
-  // Check that the child iframe was not blocked.
-  EXPECT_TRUE(navigation_manager.was_successful());
-
-  EXPECT_EQ(url, EvalJs(root_frame_host(), "document.location.href"));
-  EXPECT_EQ(url, root_frame_host()->GetLastCommittedURL());
-  EXPECT_FALSE(root_frame_host()->GetLastCommittedOrigin().opaque());
-}
-
-IN_PROC_BROWSER_TEST_F(
-    PrivateNetworkAccessBrowserTestBlockNavigations,
-    FormSubmissionFromInsecurePublictoLocalIsBlockedInChildFrame) {
-  EXPECT_TRUE(NavigateToURL(shell(), InsecurePublicURL(kDefaultPath)));
-
-  GURL url = InsecureLocalURL(kDefaultPath);
-  TestNavigationManager navigation_manager(shell()->web_contents(), url);
-
-  base::StringPiece script_template = R"(
-    const iframe = document.createElement("iframe");
-    document.body.appendChild(iframe);
-
-    const childDoc = iframe.contentDocument;
-    const form = childDoc.createElement("form");
-    form.action = $1;
-    form.method = "post";
-    childDoc.body.appendChild(form);
-    form.submit();
-  )";
-
-  EXPECT_TRUE(ExecJs(root_frame_host(), JsReplace(script_template, url)));
-
-  navigation_manager.WaitForNavigationFinished();
-
-  // Check that the child iframe was blocked.
-  EXPECT_FALSE(navigation_manager.was_successful());
-
-  ASSERT_EQ(1ul, root_frame_host()->child_count());
-  RenderFrameHostImpl* child_frame =
-      root_frame_host()->child_at(0)->current_frame_host();
-
-  // Failed navigation.
-  EXPECT_EQ(GURL(kUnreachableWebDataURL),
-            EvalJs(child_frame, "document.location.href"));
-
-  // The URL is the form target URL, to allow for reloading.
-  // The origin is opaque though, a symptom of the failed navigation.
-  EXPECT_EQ(url, child_frame->GetLastCommittedURL());
-  EXPECT_TRUE(child_frame->GetLastCommittedOrigin().opaque());
-}
-
-IN_PROC_BROWSER_TEST_F(
-    PrivateNetworkAccessBrowserTestBlockNavigations,
-    FormSubmissionGetFromInsecurePublictoLocalIsBlockedInChildFrame) {
-  EXPECT_TRUE(NavigateToURL(shell(), InsecurePublicURL(kDefaultPath)));
-
-  GURL target_url = InsecureLocalURL(kDefaultPath);
-
-  // The page navigates to `url` followed by an empty query: '?'.
-  GURL expected_url = GURL(target_url.spec() + "?");
-  TestNavigationManager navigation_manager(shell()->web_contents(),
-                                           expected_url);
-
-  base::StringPiece script_template = R"(
-    const iframe = document.createElement("iframe");
-    document.body.appendChild(iframe);
-
-    const childDoc = iframe.contentDocument;
-    const form = childDoc.createElement("form");
-    form.action = $1;
-    form.method = "get";
-    childDoc.body.appendChild(form);
-    form.submit();
-  )";
-
-  EXPECT_TRUE(
-      ExecJs(root_frame_host(), JsReplace(script_template, target_url)));
-
-  navigation_manager.WaitForNavigationFinished();
-
-  // Check that the child iframe was blocked.
-  EXPECT_FALSE(navigation_manager.was_successful());
-
-  ASSERT_EQ(1ul, root_frame_host()->child_count());
-  RenderFrameHostImpl* child_frame =
-      root_frame_host()->child_at(0)->current_frame_host();
-
-  // Failed navigation.
-  EXPECT_EQ(GURL(kUnreachableWebDataURL),
-            EvalJs(child_frame, "document.location.href"));
-
-  // The URL is the form target URL, to allow for reloading.
-  // The origin is opaque though, a symptom of the failed navigation.
-  EXPECT_EQ(expected_url, child_frame->GetLastCommittedURL());
-  EXPECT_TRUE(child_frame->GetLastCommittedOrigin().opaque());
-}
+// ===========================
+// CLIENT SECURITY STATE TESTS
+// ===========================
+//
+// These tests verify the contents of `ClientSecurityState` for top-level
+// documents in various different circumstances.
 
 // This test verifies the contents of the ClientSecurityState for the initial
 // empty document in a new main frame created by the browser.
@@ -597,7 +392,8 @@
             security_state->ip_address_space);
 }
 
-IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest, ClientSecurityStateForDataURL) {
+IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
+                       ClientSecurityStateForDataURL) {
   EXPECT_TRUE(NavigateToURL(shell(), GURL("data:text/html,foo")));
 
   const network::mojom::ClientSecurityStatePtr security_state =
@@ -608,7 +404,8 @@
             security_state->ip_address_space);
 }
 
-IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest, ClientSecurityStateForFileURL) {
+IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
+                       ClientSecurityStateForFileURL) {
   EXPECT_TRUE(NavigateToURL(shell(), GetTestUrl("", "empty.html")));
 
   const network::mojom::ClientSecurityStatePtr security_state =
@@ -708,7 +505,7 @@
                        ClientSecurityStateForCachedSecureLocalDocument) {
   // Navigate to the cacheable document in order to cache it, then navigate
   // away.
-  const GURL url = SecureLocalURL("/cachetime");
+  const GURL url = SecureLocalURL(kCacheablePath);
   EXPECT_TRUE(NavigateToURL(shell(), url));
   EXPECT_TRUE(NavigateToURL(shell(), SecureLocalURL(kDefaultPath)));
 
@@ -737,7 +534,7 @@
                        ClientSecurityStateForCachedInsecurePublicDocument) {
   // Navigate to the cacheable document in order to cache it, then navigate
   // away.
-  const GURL url = InsecurePublicURL("/cachetime");
+  const GURL url = InsecurePublicURL(kCacheablePath);
   EXPECT_TRUE(NavigateToURL(shell(), url));
   EXPECT_TRUE(NavigateToURL(shell(), SecureLocalURL(kDefaultPath)));
 
@@ -850,6 +647,10 @@
             security_state->ip_address_space);
 }
 
+// ========================
+// INHERITANCE TEST HELPERS
+// ========================
+
 namespace {
 
 // Helper for CreateBlobURL() and CreateFilesystemURL().
@@ -1135,6 +936,14 @@
 
 }  // namespace
 
+// ===============================
+// ADDRESS SPACE INHERITANCE TESTS
+// ===============================
+//
+// These tests verify that `ClientSecurityState.ip_address_space` is correctly
+// inherited by child iframes and openee documents for a variety of URLs with
+// local schemes.
+
 IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
                        IframeInheritsAddressSpaceForAboutBlankFromPublic) {
   EXPECT_TRUE(NavigateToURL(shell(), SecurePublicURL(kDefaultPath)));
@@ -1748,6 +1557,14 @@
             security_state->ip_address_space);
 }
 
+// ================================
+// SECURE CONTEXT INHERITANCE TESTS
+// ================================
+//
+// These tests verify that `ClientSecurityState.is_web_secure_context` is
+// correctly inherited by child iframes and openee documents for a variety of
+// URLs with local schemes.
+
 IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
                        IframeInheritsSecureContextForAboutBlankFromSecure) {
   EXPECT_TRUE(NavigateToURL(shell(), SecureLocalURL(kDefaultPath)));
@@ -2257,31 +2074,12 @@
   EXPECT_FALSE(security_state->is_web_secure_context);
 }
 
-// This test verifies that even with the blocking feature disabled, an insecure
-// page in the `local` address space cannot fetch a `file:` URL.
+// ====================================
+// PRIVATE NETWORK REQUEST POLICY TESTS
+// ====================================
 //
-// This is relevant to CORS-RFC1918, since `file:` URLs are considered `local`.
-IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTestNoBlocking,
-                       InsecurePageCannotRequestFile) {
-  EXPECT_TRUE(NavigateToURL(shell(), InsecureLocalURL(kDefaultPath)));
-
-  // Check that the page cannot load a `file:` URL.
-  EXPECT_EQ(false, EvalJs(root_frame_host(), FetchSubresourceScript(GetTestUrl(
-                                                 "", "empty.html"))));
-}
-
-// This test verifies that even with the blocking feature disabled, a secure
-// page in the `local` address space cannot fetch a `file:` URL.
-//
-// This is relevant to CORS-RFC1918, since `file:` URLs are considered `local`.
-IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTestNoBlocking,
-                       SecurePageCannotRequestFile) {
-  EXPECT_TRUE(NavigateToURL(shell(), SecureLocalURL(kDefaultPath)));
-
-  // Check that the page cannot load a `file:` URL.
-  EXPECT_EQ(false, EvalJs(root_frame_host(), FetchSubresourceScript(GetTestUrl(
-                                                 "", "empty.html"))));
-}
+// These tests verify the correct setting of
+// `ClientSecurityState.private_network_request_policy` in various situations.
 
 // This test verifies that with the blocking feature disabled, the private
 // network request policy used by RenderFrameHostImpl is to warn about requests
@@ -2315,21 +2113,6 @@
             network::mojom::PrivateNetworkRequestPolicy::kAllow);
 }
 
-// This test mimics the tests below, with the blocking feature disabled. It
-// verifies that by default requests:
-//  - from an insecure page with the "treat-as-public-address" CSP directive
-//  - to a local IP address
-// are not blocked.
-IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTestNoBlocking,
-                       PrivateNetworkRequestIsNotBlockedByDefault) {
-  EXPECT_TRUE(NavigateToURL(shell(), InsecureLocalURL(kDefaultPath)));
-
-  // Check that the page can load a local resource.
-  EXPECT_EQ(true,
-            EvalJs(root_frame_host(),
-                   FetchSubresourceScript(InsecureLocalURL("/image.jpg"))));
-}
-
 // This test verifies that by default, the private network request policy used
 // by RenderFrameHostImpl for requests is set to block requests from non-secure
 // contexts.
@@ -2362,38 +2145,6 @@
             network::mojom::PrivateNetworkRequestPolicy::kWarn);
 }
 
-// This test verifies that when the right feature is enabled but the content
-// browser client overrides it, requests:
-//  - from an insecure page with the "treat-as-public-address" CSP directive
-//  - to a local IP address
-// are not blocked.
-IN_PROC_BROWSER_TEST_F(
-    PrivateNetworkAccessBrowserTest,
-    FromInsecureTreatAsPublicToLocalWithPolicySetToAllowIsNotBlocked) {
-  GURL url = InsecureLocalURL(kTreatAsPublicAddressPath);
-
-  PolicyTestContentBrowserClient client;
-  client.SetAllowInsecurePrivateNetworkRequestsFrom(url::Origin::Create(url));
-
-  // Register the client before we navigate, so that the navigation commits the
-  // correct PrivateNetworkRequestPolicy.
-  ContentBrowserClientRegistration registration(&client);
-
-  EXPECT_TRUE(NavigateToURL(shell(), url));
-
-  const network::mojom::ClientSecurityStatePtr security_state =
-      root_frame_host()->BuildClientSecurityState();
-  ASSERT_FALSE(security_state.is_null());
-
-  EXPECT_EQ(security_state->private_network_request_policy,
-            network::mojom::PrivateNetworkRequestPolicy::kAllow);
-
-  // Check that the page can load a local resource.
-  EXPECT_EQ(true,
-            EvalJs(root_frame_host(),
-                   FetchSubresourceScript(InsecureLocalURL("/image.jpg"))));
-}
-
 // This test verifies that child frames with distinct origins from their parent
 // do not inherit their private network request policy, which is based on the
 // origin of the child document instead.
@@ -2503,6 +2254,29 @@
             network::mojom::PrivateNetworkRequestPolicy::kBlock);
 }
 
+// =======================
+// SUBRESOURCE FETCH TESTS
+// =======================
+//
+// These tests verify the behavior of the browser when fetching subresources
+// across IP address spaces. When the right features are enabled, private
+// network requests are blocked.
+
+// This test mimics the tests below, with the blocking feature disabled. It
+// verifies that by default requests:
+//  - from an insecure page with the "treat-as-public-address" CSP directive
+//  - to a local IP address
+// are not blocked.
+IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTestNoBlocking,
+                       PrivateNetworkRequestIsNotBlockedByDefault) {
+  EXPECT_TRUE(NavigateToURL(shell(), InsecureLocalURL(kDefaultPath)));
+
+  // Check that the page can load a local resource.
+  EXPECT_EQ(true,
+            EvalJs(root_frame_host(),
+                   FetchSubresourceScript(InsecureLocalURL("/image.jpg"))));
+}
+
 // This test verifies that when the right feature is enabled, requests:
 //  - from a secure page with the "treat-as-public-address" CSP directive
 //  - to a local IP address
@@ -2518,6 +2292,38 @@
                          FetchSubresourceScript(SecureLocalURL("/image.jpg"))));
 }
 
+// This test verifies that when the right feature is enabled but the content
+// browser client overrides it, requests:
+//  - from an insecure page with the "treat-as-public-address" CSP directive
+//  - to a local IP address
+// are not blocked.
+IN_PROC_BROWSER_TEST_F(
+    PrivateNetworkAccessBrowserTest,
+    FromInsecureTreatAsPublicToLocalWithPolicySetToAllowIsNotBlocked) {
+  GURL url = InsecureLocalURL(kTreatAsPublicAddressPath);
+
+  PolicyTestContentBrowserClient client;
+  client.SetAllowInsecurePrivateNetworkRequestsFrom(url::Origin::Create(url));
+
+  // Register the client before we navigate, so that the navigation commits the
+  // correct PrivateNetworkRequestPolicy.
+  ContentBrowserClientRegistration registration(&client);
+
+  EXPECT_TRUE(NavigateToURL(shell(), url));
+
+  const network::mojom::ClientSecurityStatePtr security_state =
+      root_frame_host()->BuildClientSecurityState();
+  ASSERT_FALSE(security_state.is_null());
+
+  EXPECT_EQ(security_state->private_network_request_policy,
+            network::mojom::PrivateNetworkRequestPolicy::kAllow);
+
+  // Check that the page can load a local resource.
+  EXPECT_EQ(true,
+            EvalJs(root_frame_host(),
+                   FetchSubresourceScript(InsecureLocalURL("/image.jpg"))));
+}
+
 // This test verifies that when the right feature is enabled, requests:
 //  - from an insecure page with the "treat-as-public-address" CSP directive
 //  - to a local IP address
@@ -2619,4 +2425,263 @@
                                            InsecureLocalURL("/image.jpg"))));
 }
 
+// This test verifies that even with the blocking feature disabled, an insecure
+// page in the `local` address space cannot fetch a `file:` URL.
+//
+// This is relevant to CORS-RFC1918, since `file:` URLs are considered `local`.
+IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTestNoBlocking,
+                       InsecurePageCannotRequestFile) {
+  EXPECT_TRUE(NavigateToURL(shell(), InsecureLocalURL(kDefaultPath)));
+
+  // Check that the page cannot load a `file:` URL.
+  EXPECT_EQ(false, EvalJs(root_frame_host(), FetchSubresourceScript(GetTestUrl(
+                                                 "", "empty.html"))));
+}
+
+// This test verifies that even with the blocking feature disabled, a secure
+// page in the `local` address space cannot fetch a `file:` URL.
+//
+// This is relevant to CORS-RFC1918, since `file:` URLs are considered `local`.
+IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTestNoBlocking,
+                       SecurePageCannotRequestFile) {
+  EXPECT_TRUE(NavigateToURL(shell(), SecureLocalURL(kDefaultPath)));
+
+  // Check that the page cannot load a `file:` URL.
+  EXPECT_EQ(false, EvalJs(root_frame_host(), FetchSubresourceScript(GetTestUrl(
+                                                 "", "empty.html"))));
+}
+
+// ======================
+// NAVIGATION FETCH TESTS
+// ======================
+//
+// These tests verify the behavior of the browser when navigating across IP
+// address spaces.
+//
+// Iframe navigations are effectively treated as subresource fetches of the
+// parent document: they are handled by checking the resource's address space
+// against the parent document's address space. This is incorrect, as the
+// initiator of the navigation is not always the parent document.
+//
+// TODO(https://crbug.com/1170335): Revisit this when the initiator's address
+// space is used instead.
+//
+// Top-level navigations are never blocked.
+//
+// TODO(https://crbug.com/1129326): Revisit this when top-level navigations are
+// subject to Private Network Access checks.
+
+// This test verifies that when the right feature is enabled, iframe requests:
+//  - from an insecure page with the "treat-as-public-address" CSP directive
+//  - to a local IP address
+// are blocked.
+IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTestBlockNavigations,
+                       IframeFromInsecureTreatAsPublicToLocalIsBlocked) {
+  EXPECT_TRUE(
+      NavigateToURL(shell(), InsecureLocalURL(kTreatAsPublicAddressPath)));
+
+  GURL url = InsecureLocalURL("/empty.html");
+
+  TestNavigationManager child_navigation_manager(shell()->web_contents(), url);
+
+  EXPECT_TRUE(ExecJs(root_frame_host(), R"(
+    const iframe = document.createElement("iframe");
+    iframe.src = "/empty.html";
+    document.body.appendChild(iframe);
+  )"));
+
+  child_navigation_manager.WaitForNavigationFinished();
+
+  // Check that the child iframe failed to fetch.
+  EXPECT_FALSE(child_navigation_manager.was_successful());
+
+  ASSERT_EQ(1ul, root_frame_host()->child_count());
+  RenderFrameHostImpl* child_frame =
+      root_frame_host()->child_at(0)->current_frame_host();
+  EXPECT_EQ(GURL(kUnreachableWebDataURL),
+            EvalJs(child_frame, "document.location.href"));
+
+  // The frame committed an error page but retains the original URL so that
+  // reloading the page does the right thing. The committed origin on the other
+  // hand is opaque, which it would not be if the navigation had succeeded.
+  EXPECT_EQ(url, child_frame->GetLastCommittedURL());
+  EXPECT_TRUE(child_frame->GetLastCommittedOrigin().opaque());
+}
+
+// This test mimics the one above, only it is executed without enabling the
+// BlockInsecurePrivateNetworkRequestsForNavigations feature. It asserts that
+// the navigation is not blocked in this case.
+IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
+                       IframeFromInsecureTreatAsPublicToLocalIsNotBlocked) {
+  EXPECT_TRUE(
+      NavigateToURL(shell(), InsecureLocalURL(kTreatAsPublicAddressPath)));
+
+  GURL url = InsecureLocalURL("/empty.html");
+
+  TestNavigationManager child_navigation_manager(shell()->web_contents(), url);
+
+  EXPECT_TRUE(ExecJs(root_frame_host(), R"(
+    const iframe = document.createElement("iframe");
+    iframe.src = "/empty.html";
+    document.body.appendChild(iframe);
+  )"));
+
+  child_navigation_manager.WaitForNavigationFinished();
+
+  // Check that the child iframe navigated successfully.
+  EXPECT_TRUE(child_navigation_manager.was_successful());
+
+  ASSERT_EQ(1ul, root_frame_host()->child_count());
+  RenderFrameHostImpl* child_frame =
+      root_frame_host()->child_at(0)->current_frame_host();
+  EXPECT_EQ(url, EvalJs(child_frame, "document.location.href"));
+}
+
+// Similar to IframeFromInsecureTreatAsPublicToLocalIsBlocked, but in
+// report-only mode. As a result "treat-as-public-address" must be ignored.
+IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessBrowserTest,
+                       CspReportOnlyTreatAsPublicAddressIgnored) {
+  EXPECT_TRUE(NavigateToURL(
+      shell(),
+      InsecureLocalURL("/set-header?Content-Security-Policy-Report-Only: "
+                       "treat-as-public-address")));
+
+  GURL url = InsecureLocalURL("/empty.html");
+
+  TestNavigationManager child_navigation_manager(shell()->web_contents(), url);
+
+  EXPECT_TRUE(ExecJs(root_frame_host(), R"(
+    const iframe = document.createElement("iframe");
+    iframe.src = "/empty.html";
+    document.body.appendChild(iframe);
+  )"));
+
+  child_navigation_manager.WaitForNavigationFinished();
+
+  // Check that the child iframe was not blocked.
+  EXPECT_TRUE(child_navigation_manager.was_successful());
+
+  ASSERT_EQ(1ul, root_frame_host()->child_count());
+  RenderFrameHostImpl* child_frame =
+      root_frame_host()->child_at(0)->current_frame_host();
+  EXPECT_EQ(url, EvalJs(child_frame, "document.location.href"));
+  EXPECT_EQ(url, child_frame->GetLastCommittedURL());
+  EXPECT_FALSE(child_frame->GetLastCommittedOrigin().opaque());
+}
+
+IN_PROC_BROWSER_TEST_F(
+    PrivateNetworkAccessBrowserTestBlockNavigations,
+    FormSubmissionFromInsecurePublictoLocalIsNotBlockedInMainFrame) {
+  EXPECT_TRUE(NavigateToURL(shell(), InsecurePublicURL(kDefaultPath)));
+
+  GURL url = InsecureLocalURL(kDefaultPath);
+  TestNavigationManager navigation_manager(shell()->web_contents(), url);
+
+  base::StringPiece script_template = R"(
+    const form = document.createElement("form");
+    form.action = $1;
+    form.method = "post";
+    document.body.appendChild(form);
+    form.submit();
+  )";
+
+  EXPECT_TRUE(ExecJs(root_frame_host(), JsReplace(script_template, url)));
+
+  navigation_manager.WaitForNavigationFinished();
+
+  // Check that the child iframe was not blocked.
+  EXPECT_TRUE(navigation_manager.was_successful());
+
+  EXPECT_EQ(url, EvalJs(root_frame_host(), "document.location.href"));
+  EXPECT_EQ(url, root_frame_host()->GetLastCommittedURL());
+  EXPECT_FALSE(root_frame_host()->GetLastCommittedOrigin().opaque());
+}
+
+IN_PROC_BROWSER_TEST_F(
+    PrivateNetworkAccessBrowserTestBlockNavigations,
+    FormSubmissionFromInsecurePublictoLocalIsBlockedInChildFrame) {
+  EXPECT_TRUE(NavigateToURL(shell(), InsecurePublicURL(kDefaultPath)));
+
+  GURL url = InsecureLocalURL(kDefaultPath);
+  TestNavigationManager navigation_manager(shell()->web_contents(), url);
+
+  base::StringPiece script_template = R"(
+    const iframe = document.createElement("iframe");
+    document.body.appendChild(iframe);
+
+    const childDoc = iframe.contentDocument;
+    const form = childDoc.createElement("form");
+    form.action = $1;
+    form.method = "post";
+    childDoc.body.appendChild(form);
+    form.submit();
+  )";
+
+  EXPECT_TRUE(ExecJs(root_frame_host(), JsReplace(script_template, url)));
+
+  navigation_manager.WaitForNavigationFinished();
+
+  // Check that the child iframe was blocked.
+  EXPECT_FALSE(navigation_manager.was_successful());
+
+  ASSERT_EQ(1ul, root_frame_host()->child_count());
+  RenderFrameHostImpl* child_frame =
+      root_frame_host()->child_at(0)->current_frame_host();
+
+  // Failed navigation.
+  EXPECT_EQ(GURL(kUnreachableWebDataURL),
+            EvalJs(child_frame, "document.location.href"));
+
+  // The URL is the form target URL, to allow for reloading.
+  // The origin is opaque though, a symptom of the failed navigation.
+  EXPECT_EQ(url, child_frame->GetLastCommittedURL());
+  EXPECT_TRUE(child_frame->GetLastCommittedOrigin().opaque());
+}
+
+IN_PROC_BROWSER_TEST_F(
+    PrivateNetworkAccessBrowserTestBlockNavigations,
+    FormSubmissionGetFromInsecurePublictoLocalIsBlockedInChildFrame) {
+  EXPECT_TRUE(NavigateToURL(shell(), InsecurePublicURL(kDefaultPath)));
+
+  GURL target_url = InsecureLocalURL(kDefaultPath);
+
+  // The page navigates to `url` followed by an empty query: '?'.
+  GURL expected_url = GURL(target_url.spec() + "?");
+  TestNavigationManager navigation_manager(shell()->web_contents(),
+                                           expected_url);
+
+  base::StringPiece script_template = R"(
+    const iframe = document.createElement("iframe");
+    document.body.appendChild(iframe);
+
+    const childDoc = iframe.contentDocument;
+    const form = childDoc.createElement("form");
+    form.action = $1;
+    form.method = "get";
+    childDoc.body.appendChild(form);
+    form.submit();
+  )";
+
+  EXPECT_TRUE(
+      ExecJs(root_frame_host(), JsReplace(script_template, target_url)));
+
+  navigation_manager.WaitForNavigationFinished();
+
+  // Check that the child iframe was blocked.
+  EXPECT_FALSE(navigation_manager.was_successful());
+
+  ASSERT_EQ(1ul, root_frame_host()->child_count());
+  RenderFrameHostImpl* child_frame =
+      root_frame_host()->child_at(0)->current_frame_host();
+
+  // Failed navigation.
+  EXPECT_EQ(GURL(kUnreachableWebDataURL),
+            EvalJs(child_frame, "document.location.href"));
+
+  // The URL is the form target URL, to allow for reloading.
+  // The origin is opaque though, a symptom of the failed navigation.
+  EXPECT_EQ(expected_url, child_frame->GetLastCommittedURL());
+  EXPECT_TRUE(child_frame->GetLastCommittedOrigin().opaque());
+}
+
 }  // namespace content
diff --git a/content/browser/renderer_host/render_frame_host_impl.cc b/content/browser/renderer_host/render_frame_host_impl.cc
index 601f43c..7549a07 100644
--- a/content/browser/renderer_host/render_frame_host_impl.cc
+++ b/content/browser/renderer_host/render_frame_host_impl.cc
@@ -43,6 +43,7 @@
 #include "base/trace_event/optional_trace_event.h"
 #include "base/trace_event/trace_conversion_helper.h"
 #include "base/trace_event/traced_value.h"
+#include "base/trace_event/typed_macros.h"
 #include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
 #include "components/download/public/common/download_url_parameters.h"
@@ -1819,8 +1820,8 @@
   return parent_;
 }
 
-PageImpl* RenderFrameHostImpl::GetPage() {
-  return GetMainFrame()->document_associated_data_->owned_page.get();
+PageImpl& RenderFrameHostImpl::GetPage() {
+  return *GetMainFrame()->document_associated_data_->owned_page.get();
 }
 
 std::vector<RenderFrameHost*> RenderFrameHostImpl::GetFramesInSubtree() {
@@ -4314,8 +4315,8 @@
 void RenderFrameHostImpl::UpdateFaviconURL(
     std::vector<blink::mojom::FaviconURLPtr> favicon_urls) {
   DCHECK(!GetParent());
-  GetPage()->set_favicon_urls(std::move(favicon_urls));
-  delegate_->UpdateFaviconURL(this, GetPage()->favicon_urls());
+  GetPage().set_favicon_urls(std::move(favicon_urls));
+  delegate_->UpdateFaviconURL(this, GetPage().favicon_urls());
 }
 
 void RenderFrameHostImpl::ScaleFactorChanged(float scale) {
@@ -4388,7 +4389,7 @@
 void RenderFrameHostImpl::UpdateManifestURL(
     const absl::optional<GURL>& manifest_url) {
   DCHECK(!GetParent());
-  GetPage()->update_manifest_url(manifest_url.value_or(GURL()));
+  GetPage().update_manifest_url(manifest_url.value_or(GURL()));
 }
 
 void RenderFrameHostImpl::DownloadURL(
@@ -5108,7 +5109,7 @@
 
 // TODO(crbug.com/1213863): Move this method to content::PageImpl.
 void RenderFrameHostImpl::DocumentOnLoadCompleted() {
-  GetPage()->set_is_on_load_completed(true);
+  GetPage().set_is_on_load_completed(true);
   // This message is only sent for top-level frames.
   //
   // TODO(avi): when frame tree mirroring works correctly, add a check here
@@ -10979,9 +10980,9 @@
   auto can_store =
       frame_tree()->controller().GetBackForwardCache().CanStorePageNow(
           top_document);
-  TRACE_EVENT1("navigation",
-               "RenderFrameHostImpl::MaybeEvictFromBackForwardCache",
-               "can_store", can_store.ToString());
+  TRACE_EVENT("navigation",
+              "RenderFrameHostImpl::MaybeEvictFromBackForwardCache",
+              "render_frame_host", this, "can_store", can_store.ToString());
 
   if (can_store)
     return;
@@ -11376,18 +11377,13 @@
 }
 
 bool RenderFrameHostImpl::IsDocumentOnLoadCompletedInMainFrame() {
-  return GetPage()->is_on_load_completed();
-}
-
-// TODO(crbug.com/1192003): Move this method to content::Page when available.
-const GURL& RenderFrameHostImpl::ManifestURL() {
-  return GetPage()->manifest_url();
+  return GetPage().is_on_load_completed();
 }
 
 // TODO(crbug.com/1192003): Move this method to content::Page when available.
 const std::vector<blink::mojom::FaviconURLPtr>&
 RenderFrameHostImpl::FaviconURLs() {
-  return GetPage()->favicon_urls();
+  return GetPage().favicon_urls();
 }
 
 mojo::PendingRemote<network::mojom::CookieAccessObserver>
diff --git a/content/browser/renderer_host/render_frame_host_impl.h b/content/browser/renderer_host/render_frame_host_impl.h
index 2d5cc6e..9fd412af 100644
--- a/content/browser/renderer_host/render_frame_host_impl.h
+++ b/content/browser/renderer_host/render_frame_host_impl.h
@@ -305,6 +305,7 @@
   RenderWidgetHostView* GetView() override;
   RenderFrameHostImpl* GetParent() override;
   RenderFrameHostImpl* GetMainFrame() override;
+  PageImpl& GetPage() override;
   std::vector<RenderFrameHost*> GetFramesInSubtree() override;
   bool IsDescendantOf(RenderFrameHost*) override;
   void ForEachRenderFrameHost(FrameIterationCallback on_frame) override;
@@ -495,14 +496,6 @@
   // shown, at which point we can service navigation requests.
   void Init();
 
-  // Returns the Page associated with this RenderFrameHost. Both GetPage() and
-  // GetMainFrame()->GetPage() will always return the same value.
-  //
-  // NOTE: For now, the associated Page object might change (when a navigation
-  // is reusing RenderFrameHost and a new document is created in this
-  // RenderFrameHost). The removal of this case is tracked in crbug.com/936696.
-  PageImpl* GetPage();
-
   // This needs to be called to make sure that the parent-child relationship
   // between frames is properly established both for cross-process iframes as
   // well as for inner web contents (i.e. right after having attached it to the
@@ -2102,7 +2095,6 @@
   void EnableWebRtcEventLogOutput(int lid, int output_period_ms) override;
   void DisableWebRtcEventLogOutput(int lid) override;
   bool IsDocumentOnLoadCompletedInMainFrame() override;
-  const GURL& ManifestURL() override;
   const std::vector<blink::mojom::FaviconURLPtr>& FaviconURLs() override;
 
 #if BUILDFLAG(ENABLE_MDNS)
diff --git a/content/browser/renderer_host/render_frame_host_manager.cc b/content/browser/renderer_host/render_frame_host_manager.cc
index 2b89bf4..dc2e636 100644
--- a/content/browser/renderer_host/render_frame_host_manager.cc
+++ b/content/browser/renderer_host/render_frame_host_manager.cc
@@ -22,6 +22,7 @@
 #include "base/notreached.h"
 #include "base/trace_event/base_tracing.h"
 #include "base/trace_event/trace_event.h"
+#include "base/trace_event/typed_macros.h"
 #include "build/build_config.h"
 #include "content/browser/child_process_security_policy_impl.h"
 #include "content/browser/devtools/render_frame_devtools_agent_host.h"
@@ -612,8 +613,9 @@
 
     auto can_store =
         back_forward_cache.CanStorePageNow(old_render_frame_host.get());
-    TRACE_EVENT1("navigation", "BackForwardCache_MaybeStorePage", "can_store",
-                 can_store.ToString());
+    TRACE_EVENT("navigation", "BackForwardCache_MaybeStorePage",
+                "old_render_frame_host", old_render_frame_host, "can_store",
+                can_store.ToString());
     if (can_store) {
       auto entry = CollectPage(std::move(old_render_frame_host));
       // Ensures RenderViewHosts are not reused while they are in the cache.
diff --git a/content/browser/renderer_host/render_process_host_impl.cc b/content/browser/renderer_host/render_process_host_impl.cc
index 67a4e821..a107b155 100644
--- a/content/browser/renderer_host/render_process_host_impl.cc
+++ b/content/browser/renderer_host/render_process_host_impl.cc
@@ -68,8 +68,6 @@
 #include "base/trace_event/memory_dump_manager.h"
 #include "base/trace_event/memory_dump_provider.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 "build/build_config.h"
 #include "build/chromeos_buildflags.h"
 #include "cc/base/switches.h"
@@ -294,8 +292,6 @@
 
 namespace {
 
-using perfetto::protos::pbzero::ChromeTrackEvent;
-
 // Stores the maximum number of renderer processes the content module can
 // create. Only applies if it is set to a non-zero value.
 size_t g_max_renderer_count_override = 0;
@@ -1716,12 +1712,11 @@
       instance_weak_factory_(absl::in_place, this),
       shutdown_exit_code_(-1) {
   CHECK(!browser_context->ShutdownStarted());
-  TRACE_EVENT("shutdown", "RenderProcessHostImpl",
-              ChromeTrackEvent::kRenderProcessHost, *this);
-  TRACE_EVENT_BEGIN("shutdown", "Browser.RenderProcessHostImpl",
-                    perfetto::Track::FromPointer(this),
-                    ChromeTrackEvent::kRenderProcessHost, *this);
-
+  TRACE_EVENT2("shutdown", "RenderProcessHostImpl", "render_process_host", this,
+               "id", GetID());
+  TRACE_EVENT_NESTABLE_ASYNC_BEGIN2("shutdown", "Browser.RenderProcessHostImpl",
+                                    this, "render_process_host", this,
+                                    "browser_context", browser_context_);
   widget_helper_ = new RenderWidgetHelper();
 
   ChildProcessSecurityPolicyImpl::GetInstance()->Add(GetID(), browser_context);
@@ -1831,8 +1826,8 @@
 }
 
 RenderProcessHostImpl::~RenderProcessHostImpl() {
-  TRACE_EVENT("shutdown", "~RenderProcessHostImpl",
-              ChromeTrackEvent::kRenderProcessHost, *this);
+  TRACE_EVENT2("shutdown", "~RenderProcessHostImpl", "render_process_host",
+               this, "id", GetID());
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
 #ifndef NDEBUG
   DCHECK(is_self_deleted_)
@@ -1863,19 +1858,16 @@
   if (cleanup_network_service_plugin_exceptions_upon_destruction_)
     RemoveNetworkServicePluginExceptions(GetID());
 
+  TRACE_EVENT_NESTABLE_ASYNC_END1("shutdown", "Cleanup in progress", this,
+                                  "render_process_host", this);
+  TRACE_EVENT_NESTABLE_ASYNC_END1("shutdown", "Browser.RenderProcessHostImpl",
+                                  this, "render_process_host", this);
 
   // Manually delete here in order to avoid DeleteOnIOThread trait when
   // kProcessHostOnUI is enabled.
   if (base::FeatureList::IsEnabled(features::kProcessHostOnUI) && gpu_client_) {
     delete gpu_client_.release();
   }
-
-  // "Cleanup in progress"
-  TRACE_EVENT_END("shutdown", perfetto::Track::FromPointer(this),
-                  ChromeTrackEvent::kRenderProcessHost, *this);
-  // "Browser.RenderProcessHostImpl"
-  TRACE_EVENT_END("shutdown", perfetto::Track::FromPointer(this),
-                  ChromeTrackEvent::kRenderProcessHost, *this);
 }
 
 bool RenderProcessHostImpl::Init() {
@@ -2386,22 +2378,6 @@
                                .ToString());
 }
 
-void RenderProcessHostImpl::WriteIntoTrace(
-    perfetto::TracedProto<perfetto::protos::pbzero::RenderProcessHost> proto) {
-  int id = GetID();
-  proto->set_id(id);
-  proto->set_process_lock(ChildProcessSecurityPolicyImpl::GetInstance()
-                              ->GetProcessLock(id)
-                              .ToString());
-  browser_context_->WriteIntoTrace(
-      proto.WriteNestedMessage<perfetto::protos::pbzero::RenderProcessHost::
-                                   FieldMetadata_BrowserContext>());
-
-  // TODO(ssid): Consider moving this to ChildProcessLauncher proto field.
-  if (child_process_launcher_)
-    proto->set_child_process_id(child_process_launcher_->GetProcess().Pid());
-}
-
 void RenderProcessHostImpl::RegisterMojoInterfaces() {
   auto registry = std::make_unique<service_manager::BinderRegistry>();
 
@@ -2839,8 +2815,9 @@
 }
 
 void RenderProcessHostImpl::DisableKeepAliveRefCount() {
-  TRACE_EVENT("shutdown", "RenderProcessHostImpl::DisableKeepAliveRefCount",
-              ChromeTrackEvent::kRenderProcessHost, *this);
+  TRACE_EVENT2("shutdown", "RenderProcessHostImpl::DisableKeepAliveRefCount",
+               "browser_context", browser_context_, "render_process_host",
+               this);
 
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
@@ -2908,26 +2885,16 @@
 
 void RenderProcessHostImpl::AddRoute(int32_t routing_id,
                                      IPC::Listener* listener) {
-  TRACE_EVENT("shutdown", "RenderProcessHostImpl::AddRoute",
-              ChromeTrackEvent::kRenderProcessHost, *this,
-              [&](perfetto::EventContext ctx) {
-                auto* proto = ctx.event<ChromeTrackEvent>()
-                                  ->set_render_process_host_listener_changed();
-                proto->set_routing_id(routing_id);
-              });
+  TRACE_EVENT2("shutdown", "RenderProcessHostImpl::AddRoute",
+               "render_process_host", this, "routing_id", routing_id);
   CHECK(!listeners_.Lookup(routing_id))
       << "Found Routing ID Conflict: " << routing_id;
   listeners_.AddWithID(listener, routing_id);
 }
 
 void RenderProcessHostImpl::RemoveRoute(int32_t routing_id) {
-  TRACE_EVENT("shutdown", "RenderProcessHostImpl::RemoveRoute",
-              ChromeTrackEvent::kRenderProcessHost, *this,
-              [&](perfetto::EventContext ctx) {
-                auto* proto = ctx.event<ChromeTrackEvent>()
-                                  ->set_render_process_host_listener_changed();
-                proto->set_routing_id(routing_id);
-              });
+  TRACE_EVENT2("shutdown", "RenderProcessHostImpl::RemoveRoute",
+               "render_process_host", this, "routing_id", routing_id);
   DCHECK(listeners_.Lookup(routing_id) != nullptr);
   listeners_.Remove(routing_id);
   Cleanup();
@@ -3803,8 +3770,8 @@
 }
 
 void RenderProcessHostImpl::Cleanup() {
-  TRACE_EVENT("shutdown", "RenderProcessHostImpl::Cleanup",
-              ChromeTrackEvent::kRenderProcessHost, *this);
+  TRACE_EVENT1("shutdown", "RenderProcessHostImpl::Cleanup",
+               "render_process_host", this);
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   // Keep the one renderer thread around forever in single process mode.
   if (run_renderer_in_process())
@@ -3816,9 +3783,10 @@
   // been made, and guarantee that the RenderProcessHostDestroyed observer
   // callback is always the last callback fired.
   if (within_process_died_observer_) {
-    TRACE_EVENT("shutdown",
-                "RenderProcessHostImpl::Cleanup : within_process_died_observer",
-                ChromeTrackEvent::kRenderProcessHost, *this);
+    TRACE_EVENT1(
+        "shutdown",
+        "RenderProcessHostImpl::Cleanup : within_process_died_observer",
+        "render_process_host", this);
     delayed_cleanup_needed_ = true;
     return;
   }
@@ -3834,32 +3802,23 @@
   // Until there are no other owners of this object, we can't delete
   // ourselves.
   if (!listeners_.IsEmpty()) {
-    TRACE_EVENT(
-        "shutdown", "RenderProcessHostImpl::Cleanup : Has listeners.",
-        ChromeTrackEvent::kRenderProcessHost, *this,
-        [&](perfetto::EventContext ctx) {
-          auto* proto =
-              ctx.event<ChromeTrackEvent>()->set_render_process_host_cleanup();
-          proto->set_listener_count(listeners_.size());
-        });
+    TRACE_EVENT2("shutdown", "RenderProcessHostImpl::Cleanup : Has listeners.",
+                 "render_process_host", this, "listener_count",
+                 listeners_.size());
     return;
   } else if (keep_alive_ref_count_ != 0) {
-    TRACE_EVENT(
-        "shutdown", "RenderProcessHostImpl::Cleanup : Have keep_alive_ref.",
-        ChromeTrackEvent::kRenderProcessHost, *this,
-        [&](perfetto::EventContext ctx) {
-          auto* proto =
-              ctx.event<ChromeTrackEvent>()->set_render_process_host_cleanup();
-          proto->set_keep_alive_ref_count(keep_alive_ref_count_);
-        });
+    TRACE_EVENT2("shutdown",
+                 "RenderProcessHostImpl::Cleanup : Have keep_alive_ref.",
+                 "render_process_host", this, "keep_alive_ref_count_",
+                 keep_alive_ref_count_);
     return;
   }
 
-  TRACE_EVENT("shutdown", "RenderProcessHostImpl::Cleanup : Starting cleanup.",
-              ChromeTrackEvent::kRenderProcessHost, *this);
-  TRACE_EVENT_BEGIN("shutdown", "Cleanup in progress",
-                    perfetto::Track::FromPointer(this),
-                    ChromeTrackEvent::kRenderProcessHost, *this);
+  TRACE_EVENT1("shutdown", "RenderProcessHostImpl::Cleanup : Starting cleanup.",
+               "render_process_host", this);
+  TRACE_EVENT_NESTABLE_ASYNC_BEGIN2("shutdown", "Cleanup in progress", this,
+                                    "render_process_host", this,
+                                    "browser_context", browser_context_);
 
   if (is_initialized_) {
     GetIOThreadTaskRunner({})->PostTask(
@@ -4034,26 +3993,19 @@
 
 // static
 void RenderProcessHostImpl::RegisterHost(int host_id, RenderProcessHost* host) {
-  TRACE_EVENT(
-      "shutdown", "RenderProcessHostImpl::RegisterHost",
-      [&](perfetto::EventContext ctx) {
-        ctx.event<ChromeTrackEvent>()->set_render_process_host()->set_id(
-            host_id);
-      });
+  TRACE_EVENT2("shutdown", "RenderProcessHostImpl::RegisterHost",
+               "render_process_host", host, "host_id", host_id);
   GetAllHosts().AddWithID(host, host_id);
 }
 
 // static
 void RenderProcessHostImpl::UnregisterHost(int host_id) {
   RenderProcessHost* host = GetAllHosts().Lookup(host_id);
+  TRACE_EVENT2("shutdown", "RenderProcessHostImpl::UnregisterHost",
+               "render_process_host", host, "host_id", host_id);
+
   if (!host)
     return;
-  TRACE_EVENT(
-      "shutdown", "RenderProcessHostImpl::UnregisterHost",
-      [&](perfetto::EventContext ctx) {
-        ctx.event<ChromeTrackEvent>()->set_render_process_host()->set_id(
-            host_id);
-      });
 
   GetAllHosts().Remove(host_id);
 
@@ -4942,9 +4894,9 @@
       priority_.is_background() != priority.is_background();
   const bool visibility_state_changed = priority_.visible != priority.visible;
 
-  TRACE_EVENT("renderer_host", "RenderProcessHostImpl::UpdateProcessPriority",
-              ChromeTrackEvent::kRenderProcessHost, *this,
-              ChromeTrackEvent::kChildProcessLauncherPriority, priority);
+  TRACE_EVENT2("renderer_host", "RenderProcessHostImpl::UpdateProcessPriority",
+               "should_background", priority.is_background(),
+               "has_pending_views", priority.boost_for_pending_views);
   priority_ = priority;
 
   // Control the background state from the browser process, otherwise the task
@@ -4955,6 +4907,13 @@
   if (!run_renderer_in_process()) {
     DCHECK(child_process_launcher_.get());
     DCHECK(!child_process_launcher_->IsStarting());
+    // Make sure to keep the pid in the trace so we can tell which process is
+    // being modified.
+    TRACE_EVENT2(
+        "renderer_host",
+        "RenderProcessHostImpl::UpdateProcessPriority.SetProcessPriority",
+        "pid", child_process_launcher_->GetProcess().Pid(),
+        "priority_is_background", priority.is_background());
     child_process_launcher_->SetProcessPriority(priority_);
   }
 
diff --git a/content/browser/renderer_host/render_process_host_impl.h b/content/browser/renderer_host/render_process_host_impl.h
index c70224e..59ee682 100644
--- a/content/browser/renderer_host/render_process_host_impl.h
+++ b/content/browser/renderer_host/render_process_host_impl.h
@@ -84,7 +84,6 @@
 #include "third_party/blink/public/mojom/plugins/plugin_registry.mojom-forward.h"
 #include "third_party/blink/public/mojom/push_messaging/push_messaging.mojom-forward.h"
 #include "third_party/blink/public/mojom/webdatabase/web_database.mojom-forward.h"
-#include "third_party/perfetto/include/perfetto/tracing/traced_proto.h"
 #include "third_party/perfetto/include/perfetto/tracing/traced_value_forward.h"
 #include "ui/gfx/gpu_memory_buffer.h"
 
@@ -105,14 +104,6 @@
 class SystemTracingService;
 }
 
-namespace perfetto {
-namespace protos {
-namespace pbzero {
-class RenderProcessHost;
-}
-}  // namespace protos
-}  // namespace perfetto
-
 namespace viz {
 class GpuClient;
 }
@@ -305,9 +296,6 @@
     child_process_activity_time_ = base::TimeTicks::Now();
   }
 
-  void WriteIntoTrace(
-      perfetto::TracedProto<perfetto::protos::pbzero::RenderProcessHost> proto);
-
   // Return the set of previously stored frame tokens for a |new_routing_id|.
   // The frame tokens were stored on the IO thread via the
   // RenderMessageFilter::GenerateFrameRoutingID mojo call. Returns false if
diff --git a/content/browser/service_worker/service_worker_host.cc b/content/browser/service_worker/service_worker_host.cc
index d03b4af4..dead583 100644
--- a/content/browser/service_worker/service_worker_host.cc
+++ b/content/browser/service_worker/service_worker_host.cc
@@ -117,6 +117,10 @@
       version_->origin());
 }
 
+const base::UnguessableToken& ServiceWorkerHost::GetReportingSource() const {
+  return version_->reporting_source();
+}
+
 base::WeakPtr<ServiceWorkerHost> ServiceWorkerHost::GetWeakPtr() {
   DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
   return weak_factory_.GetWeakPtr();
diff --git a/content/browser/service_worker/service_worker_host.h b/content/browser/service_worker/service_worker_host.h
index 7c87a9c..5f81fe5b 100644
--- a/content/browser/service_worker/service_worker_host.h
+++ b/content/browser/service_worker/service_worker_host.h
@@ -75,6 +75,7 @@
   }
 
   net::NetworkIsolationKey GetNetworkIsolationKey() const;
+  const base::UnguessableToken& GetReportingSource() const;
 
   base::WeakPtr<ServiceWorkerHost> GetWeakPtr();
 
diff --git a/content/browser/service_worker/service_worker_version.cc b/content/browser/service_worker/service_worker_version.cc
index f75206fd..561ca59 100644
--- a/content/browser/service_worker/service_worker_version.cc
+++ b/content/browser/service_worker/service_worker_version.cc
@@ -258,7 +258,8 @@
       validator_(std::make_unique<blink::TrialTokenValidator>()),
       remote_reference_(std::move(remote_reference)),
       ukm_source_id_(ukm::ConvertToSourceId(ukm::AssignNewSourceId(),
-                                            ukm::SourceIdType::WORKER_ID)) {
+                                            ukm::SourceIdType::WORKER_ID)),
+      reporting_source_(base::UnguessableToken::Create()) {
   DCHECK_NE(blink::mojom::kInvalidServiceWorkerVersionId, version_id);
   DCHECK(context_);
   DCHECK(registration);
diff --git a/content/browser/service_worker/service_worker_version.h b/content/browser/service_worker/service_worker_version.h
index 58c5352..3804f76e 100644
--- a/content/browser/service_worker/service_worker_version.h
+++ b/content/browser/service_worker/service_worker_version.h
@@ -213,6 +213,9 @@
   ServiceWorkerVersionInfo GetInfo();
   Status status() const { return status_; }
   ukm::SourceId ukm_source_id() const { return ukm_source_id_; }
+  const base::UnguessableToken& reporting_source() const {
+    return reporting_source_;
+  }
 
   // This status is set to EXISTS or DOES_NOT_EXIST when the install event has
   // been executed in a new version or when an installed version is loaded from
@@ -1143,6 +1146,8 @@
   // it can be associated with clients' source IDs.
   const ukm::SourceId ukm_source_id_;
 
+  base::UnguessableToken reporting_source_;
+
   base::WeakPtrFactory<ServiceWorkerVersion> weak_factory_{this};
 
   DISALLOW_COPY_AND_ASSIGN(ServiceWorkerVersion);
diff --git a/content/browser/tracing/startup_tracing_browsertest.cc b/content/browser/tracing/startup_tracing_browsertest.cc
index 20a45fd..44c9db4 100644
--- a/content/browser/tracing/startup_tracing_browsertest.cc
+++ b/content/browser/tracing/startup_tracing_browsertest.cc
@@ -326,12 +326,6 @@
   CheckOutput(GetExpectedPath(), GetOutputType());
 }
 
-IN_PROC_BROWSER_TEST_P(StartupTracingTest, ContinueAtShutdown) {
-  EXPECT_TRUE(NavigateToURL(shell(), GetTestUrl("", "title1.html")));
-  StartupTracingController::GetInstance()
-      .set_continue_on_shutdown_for_testing();
-}
-
 class EmergencyStopTracingTest : public StartupTracingTest {};
 
 INSTANTIATE_TEST_SUITE_P(
diff --git a/content/browser/tracing/startup_tracing_controller.cc b/content/browser/tracing/startup_tracing_controller.cc
index 5957d39..7cc95b6 100644
--- a/content/browser/tracing/startup_tracing_controller.cc
+++ b/content/browser/tracing/startup_tracing_controller.cc
@@ -511,15 +511,6 @@
   run_loop.Run();
 }
 
-void StartupTracingController::ShutdownAndWaitForStopIfNeeded() {
-  DCHECK_CURRENTLY_ON(BrowserThread::UI);
-
-  if (should_continue_on_shutdown_)
-    return;
-
-  WaitUntilStopped();
-}
-
 // static
 void StartupTracingController::EmergencyStop() {
   if (GetIOThreadTaskRunner({})->RunsTasksInCurrentSequence()) {
diff --git a/content/browser/tracing/startup_tracing_controller.h b/content/browser/tracing/startup_tracing_controller.h
index 492a036c..fcbbc4d28 100644
--- a/content/browser/tracing/startup_tracing_controller.h
+++ b/content/browser/tracing/startup_tracing_controller.h
@@ -31,7 +31,6 @@
 
   void StartIfNeeded();
   void WaitUntilStopped();
-  void ShutdownAndWaitForStopIfNeeded();
 
   // By default, a trace is written into a temporary file which then is renamed,
   // however this can lead to data loss when the browser process crashes.
@@ -69,10 +68,6 @@
 
   bool is_finished_for_testing() const { return state_ == State::kStopped; }
 
-  void set_continue_on_shutdown_for_testing() {
-    should_continue_on_shutdown_ = true;
-  }
-
  private:
   void Stop(base::OnceClosure on_finished_callback);
 
@@ -97,8 +92,6 @@
 
   std::string default_basename_;
   bool basename_for_test_set_ = false;
-  // Used for testing only
-  bool should_continue_on_shutdown_ = false;
 
   TempFilePolicy temp_file_policy_ = TempFilePolicy::kUseTemporaryFile;
 };
diff --git a/content/browser/url_loader_factory_params_helper.cc b/content/browser/url_loader_factory_params_helper.cc
index 8d0ad93..0be0015 100644
--- a/content/browser/url_loader_factory_params_helper.cc
+++ b/content/browser/url_loader_factory_params_helper.cc
@@ -12,7 +12,6 @@
 #include "content/public/browser/content_browser_client.h"
 #include "content/public/browser/render_process_host.h"
 #include "content/public/browser/render_view_host.h"
-#include "content/public/browser/web_contents.h"
 #include "content/public/common/content_client.h"
 #include "content/public/common/content_switches.h"
 #include "content/public/common/url_constants.h"
@@ -132,9 +131,7 @@
       false,         // is_trusted
       frame->GetTopFrameToken(), isolation_info,
       std::move(client_security_state), std::move(coep_reporter),
-      WebContents::FromRenderFrameHost(frame)
-          ->GetOrCreateWebPreferences()
-          .allow_universal_access_from_file_urls,
+      frame->GetOrCreateWebPreferences().allow_universal_access_from_file_urls,
       false,  // is_for_isolated_world
       frame->CreateCookieAccessObserver(),
       frame->CreateURLLoaderNetworkObserver(),
@@ -159,9 +156,7 @@
       frame->GetTopFrameToken(), isolation_info,
       std::move(client_security_state),
       mojo::NullRemote(),  // coep_reporter
-      WebContents::FromRenderFrameHost(frame)
-          ->GetOrCreateWebPreferences()
-          .allow_universal_access_from_file_urls,
+      frame->GetOrCreateWebPreferences().allow_universal_access_from_file_urls,
       true,  // is_for_isolated_world
       frame->CreateCookieAccessObserver(),
       frame->CreateURLLoaderNetworkObserver(),
@@ -186,9 +181,7 @@
       net::IsolationInfo(),  // isolation_info
       std::move(client_security_state),
       mojo::NullRemote(),  // coep_reporter
-      WebContents::FromRenderFrameHost(frame)
-          ->GetOrCreateWebPreferences()
-          .allow_universal_access_from_file_urls,
+      frame->GetOrCreateWebPreferences().allow_universal_access_from_file_urls,
       false,  // is_for_isolated_world
       frame->CreateCookieAccessObserver(),
       frame->CreateURLLoaderNetworkObserver(),
diff --git a/content/browser/web_contents/color_chooser_unittest.cc b/content/browser/web_contents/color_chooser_unittest.cc
index abd4ee30..02861b9 100644
--- a/content/browser/web_contents/color_chooser_unittest.cc
+++ b/content/browser/web_contents/color_chooser_unittest.cc
@@ -41,12 +41,12 @@
   ~OpenColorChooserDelegate() override = default;
 
   // WebContentsDelegate:
-  ColorChooser* OpenColorChooser(
+  std::unique_ptr<ColorChooser> OpenColorChooser(
       WebContents* web_contents,
       SkColor color,
       const std::vector<blink::mojom::ColorSuggestionPtr>& suggestions)
       override {
-    return std::move(mock_color_chooser_).release();
+    return std::move(mock_color_chooser_);
   }
 
   bool IsBackForwardCacheSupported() override { return true; }
diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc
index 15621d2..ca25b37 100644
--- a/content/browser/web_contents/web_contents_impl.cc
+++ b/content/browser/web_contents/web_contents_impl.cc
@@ -697,16 +697,17 @@
   DISALLOW_COPY_AND_ASSIGN(WebContentsDestructionObserver);
 };
 
-// TODO(sreejakshetty): Make |WebContentsImpl::ColorChooser| per-frame instead
-// of WebContents-owned.
-// WebContentsImpl::ColorChooser ----------------------------------------------
-class WebContentsImpl::ColorChooser : public blink::mojom::ColorChooser {
+// TODO(sreejakshetty): Make |WebContentsImpl::ColorChooserHolder| per-frame
+// instead of WebContents-owned.
+// WebContentsImpl::ColorChooserHolder -----------------------------------------
+class WebContentsImpl::ColorChooserHolder : public blink::mojom::ColorChooser {
  public:
-  ColorChooser(mojo::PendingReceiver<blink::mojom::ColorChooser> receiver,
-               mojo::PendingRemote<blink::mojom::ColorChooserClient> client)
+  ColorChooserHolder(
+      mojo::PendingReceiver<blink::mojom::ColorChooser> receiver,
+      mojo::PendingRemote<blink::mojom::ColorChooserClient> client)
       : receiver_(this, std::move(receiver)), client_(std::move(client)) {}
 
-  ~ColorChooser() override {
+  ~ColorChooserHolder() override {
     if (chooser_)
       chooser_->End();
   }
@@ -721,8 +722,8 @@
   }
 
   void SetSelectedColor(SkColor color) override {
-    OPTIONAL_TRACE_EVENT0("content",
-                          "WebContentsImpl::ColorChooser::SetSelectedColor");
+    OPTIONAL_TRACE_EVENT0(
+        "content", "WebContentsImpl::ColorChooserHolder::SetSelectedColor");
     if (chooser_)
       chooser_->SetSelectedColor(color);
   }
@@ -730,7 +731,7 @@
   void DidChooseColorInColorChooser(SkColor color) {
     OPTIONAL_TRACE_EVENT0(
         "content",
-        "WebContentsImpl::ColorChooser::DidChooseColorInColorChooser");
+        "WebContentsImpl::ColorChooserHolder::DidChooseColorInColorChooser");
     client_->DidChooseColor(color);
   }
 
@@ -1000,7 +1001,7 @@
     dialog_manager_->CancelDialogs(this, /*reset_state=*/true);
   }
 
-  color_chooser_.reset();
+  color_chooser_holder_.reset();
   find_request_manager_.reset();
 
   // Shutdown the primary FrameTree.
@@ -5101,13 +5102,13 @@
   OPTIONAL_TRACE_EVENT1("content",
                         "WebContentsImpl::DidChooseColorInColorChooser",
                         "color", color);
-  if (color_chooser_)
-    color_chooser_->DidChooseColorInColorChooser(color);
+  if (color_chooser_holder_)
+    color_chooser_holder_->DidChooseColorInColorChooser(color);
 }
 
 void WebContentsImpl::DidEndColorChooser() {
   OPTIONAL_TRACE_EVENT0("content", "WebContentsImpl::DidEndColorChooser");
-  color_chooser_.reset();
+  color_chooser_holder_.reset();
 }
 
 int WebContentsImpl::DownloadImage(
@@ -6131,24 +6132,24 @@
     SkColor color,
     std::vector<blink::mojom::ColorSuggestionPtr> suggestions) {
   OPTIONAL_TRACE_EVENT0("content", "WebContentsImpl::OpenColorChooser");
-  // Create `color_chooser_` before calling OpenColorChooser since
+  // Create `color_chooser_holder_` before calling OpenColorChooser since
   // OpenColorChooser may callback with results.
-  color_chooser_.reset();
-  color_chooser_ = std::make_unique<ColorChooser>(std::move(chooser_receiver),
-                                                  std::move(client));
+  color_chooser_holder_.reset();
+  color_chooser_holder_ = std::make_unique<ColorChooserHolder>(
+      std::move(chooser_receiver), std::move(client));
 
-  auto new_color_chooser = base::WrapUnique(
+  auto new_color_chooser =
       delegate_ ? delegate_->OpenColorChooser(this, color, suggestions)
-                : nullptr);
-  if (color_chooser_ && new_color_chooser) {
-    color_chooser_->SetChooser(std::move(new_color_chooser));
+                : nullptr;
+  if (color_chooser_holder_ && new_color_chooser) {
+    color_chooser_holder_->SetChooser(std::move(new_color_chooser));
   } else if (new_color_chooser) {
     // OpenColorChooser synchronously called back to DidEndColorChooser.
-    DCHECK(!color_chooser_);
+    DCHECK(!color_chooser_holder_);
     new_color_chooser->End();
-  } else if (color_chooser_) {
+  } else if (color_chooser_holder_) {
     DCHECK(!new_color_chooser);
-    color_chooser_.reset();
+    color_chooser_holder_.reset();
   }
 }
 
@@ -8898,11 +8899,11 @@
                         });
 
   if (old_state == LifecycleState::kActive && !render_frame_host->GetParent()) {
-    // TODO(sreejakshetty): Remove this reset when ColorChooser becomes
+    // TODO(sreejakshetty): Remove this reset when ColorChooserHolder becomes
     // per-frame.
     // Close the color chooser popup when RenderFrameHost changes state from
     // kActive.
-    color_chooser_.reset();
+    color_chooser_holder_.reset();
   }
 
   observers_.NotifyObservers(&WebContentsObserver::RenderFrameHostStateChanged,
diff --git a/content/browser/web_contents/web_contents_impl.h b/content/browser/web_contents/web_contents_impl.h
index 9152a4d..cd007f4 100644
--- a/content/browser/web_contents/web_contents_impl.h
+++ b/content/browser/web_contents/web_contents_impl.h
@@ -1983,8 +1983,8 @@
   gfx::Size view_size_before_emulation_;
 
   // Holds information about a current color chooser dialog, if one is visible.
-  class ColorChooser;
-  std::unique_ptr<ColorChooser> color_chooser_;
+  class ColorChooserHolder;
+  std::unique_ptr<ColorChooserHolder> color_chooser_holder_;
 
   // Manages the embedder state for browser plugins, if this WebContents is an
   // embedder; NULL otherwise.
diff --git a/content/browser/worker_host/dedicated_worker_host.cc b/content/browser/worker_host/dedicated_worker_host.cc
index f11021f..0657643 100644
--- a/content/browser/worker_host/dedicated_worker_host.cc
+++ b/content/browser/worker_host/dedicated_worker_host.cc
@@ -67,6 +67,7 @@
       // the worker script URL.
       worker_origin_(creator_origin),
       isolation_info_(isolation_info),
+      reporting_source_(base::UnguessableToken::Create()),
       creator_cross_origin_embedder_policy_(cross_origin_embedder_policy),
       host_receiver_(this, std::move(host)),
       creator_coep_reporter_(std::move(creator_coep_reporter)),
diff --git a/content/browser/worker_host/dedicated_worker_host.h b/content/browser/worker_host/dedicated_worker_host.h
index 7d9994d..062b2b5 100644
--- a/content/browser/worker_host/dedicated_worker_host.h
+++ b/content/browser/worker_host/dedicated_worker_host.h
@@ -135,6 +135,10 @@
     return isolation_info_.network_isolation_key();
   }
 
+  const base::UnguessableToken& GetReportingSource() const {
+    return reporting_source_;
+  }
+
   const network::CrossOriginEmbedderPolicy& cross_origin_embedder_policy()
       const {
     DCHECK(worker_cross_origin_embedder_policy_.has_value());
@@ -239,6 +243,8 @@
   // frame or the worker that created this worker.
   const net::IsolationInfo isolation_info_;
 
+  const base::UnguessableToken reporting_source_;
+
   // The frame/worker's Cross-Origin-Embedder-Policy (COEP) that directly starts
   // this worker.
   const network::CrossOriginEmbedderPolicy
diff --git a/content/browser/worker_host/shared_worker_host.cc b/content/browser/worker_host/shared_worker_host.cc
index 208cd46..6b2c9a62 100644
--- a/content/browser/worker_host/shared_worker_host.cc
+++ b/content/browser/worker_host/shared_worker_host.cc
@@ -120,7 +120,8 @@
       next_connection_request_id_(1),
       devtools_handle_(std::make_unique<ScopedDevToolsHandle>(this)),
       ukm_source_id_(ukm::ConvertToSourceId(ukm::AssignNewSourceId(),
-                                            ukm::SourceIdType::WORKER_ID)) {
+                                            ukm::SourceIdType::WORKER_ID)),
+      reporting_source_(base::UnguessableToken::Create()) {
   DCHECK(GetProcessHost());
   DCHECK(GetProcessHost()->IsInitializedAndNotDead());
 
diff --git a/content/browser/worker_host/shared_worker_host.h b/content/browser/worker_host/shared_worker_host.h
index 312bc60..ce401ab 100644
--- a/content/browser/worker_host/shared_worker_host.h
+++ b/content/browser/worker_host/shared_worker_host.h
@@ -165,6 +165,10 @@
 
   net::NetworkIsolationKey GetNetworkIsolationKey() const;
 
+  const base::UnguessableToken& GetReportingSource() const {
+    return reporting_source_;
+  }
+
   void ReportNoBinderForInterface(const std::string& error);
 
   // Creates a network factory params for subresource requests from this worker.
@@ -274,6 +278,8 @@
 
   const ukm::SourceId ukm_source_id_;
 
+  const base::UnguessableToken reporting_source_;
+
   base::WeakPtrFactory<SharedWorkerHost> weak_factory_{this};
 
   DISALLOW_COPY_AND_ASSIGN(SharedWorkerHost);
diff --git a/content/public/browser/BUILD.gn b/content/public/browser/BUILD.gn
index c605ea0..1048061 100644
--- a/content/public/browser/BUILD.gn
+++ b/content/public/browser/BUILD.gn
@@ -256,6 +256,7 @@
     "origin_policy_commands.h",
     "overlay_window.h",
     "overscroll_configuration.h",
+    "page.h",
     "page_navigator.cc",
     "page_navigator.h",
     "payment_app_provider.h",
diff --git a/content/public/browser/browser_context.h b/content/public/browser/browser_context.h
index 2d9cc0c..e42b740 100644
--- a/content/public/browser/browser_context.h
+++ b/content/public/browser/browser_context.h
@@ -25,7 +25,6 @@
 #include "third_party/blink/public/mojom/blob/blob.mojom-forward.h"
 #include "third_party/blink/public/mojom/push_messaging/push_messaging.mojom-forward.h"
 #include "third_party/blink/public/mojom/push_messaging/push_messaging_status.mojom-forward.h"
-#include "third_party/perfetto/include/perfetto/tracing/traced_proto.h"
 #include "third_party/perfetto/include/perfetto/tracing/traced_value_forward.h"
 
 #if !defined(OS_ANDROID)
@@ -62,14 +61,6 @@
 class VariationsClient;
 }  // namespace variations
 
-namespace perfetto {
-namespace protos {
-namespace pbzero {
-class ChromeBrowserContext;
-}
-}  // namespace protos
-}  // namespace perfetto
-
 namespace content {
 
 class BackgroundFetchDelegate;
@@ -279,11 +270,6 @@
   // Write a representation of this object into a trace.
   void WriteIntoTrace(perfetto::TracedValue context);
 
-  // Write a representation of this object into tracing proto.
-  void WriteIntoTrace(
-      perfetto::TracedProto<perfetto::protos::pbzero::ChromeBrowserContext>
-          context);
-
   //////////////////////////////////////////////////////////////////////////////
   // The //content embedder can override the methods below to change or extend
   // how the //content layer interacts with a BrowserContext.
diff --git a/content/public/browser/page.h b/content/public/browser/page.h
new file mode 100644
index 0000000..d4638c87
--- /dev/null
+++ b/content/public/browser/page.h
@@ -0,0 +1,66 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CONTENT_PUBLIC_BROWSER_PAGE_H_
+#define CONTENT_PUBLIC_BROWSER_PAGE_H_
+
+#include "content/common/content_export.h"
+#include "url/gurl.h"
+
+namespace content {
+
+// Page represents a collection of documents with the same main document.
+
+// At the moment some navigations might create a new blink::Document in the
+// existing RenderFrameHost, which will lead to a creation of a new Page
+// associated with the same main RenderFrameHost. See the comment in
+// |RenderDocumentHostUserData| for more details and crbug.com/936696 for the
+// progress on always creating a new RenderFrameHost for each new document.
+
+// Page is created when a main document is created, which can happen in the
+// following ways:
+// 1) Main RenderFrameHost is created.
+// 2) A cross-document non-bfcached navigation is committed in the same
+//    RenderFrameHost.
+// 3) Main RenderFrameHost is re-created after crash.
+
+// Page is deleted in the following cases:
+// 1) Main RenderFrameHost is deleted.
+// 2) A cross-document non-bfcached navigation is committed in the same
+//    RenderFrameHost.
+// 3) Before main RenderFrameHost is re-created after crash.
+
+// If a method can be called only for main RenderFrameHosts or if its behaviour
+// is identical when called on the parent / child RenderFrameHosts, it should
+// be added to Page(Impl).
+
+// With Multiple Page Architecture (MPArch), each WebContents may have
+// additional FrameTrees which will have their own associated Page. Please take
+// into consideration when assuming that Page is appropriate for storing
+// something that's common for all frames you see on a tab.
+
+// NOTE: Depending on the process model, the cross-origin iframes are likely to
+// be hosted in a different renderer process than the main document, so a given
+// page is hosted in multiple renderer processes at the same time.
+// RenderViewHost / RenderView / blink::Page (which are all 1:1:1) represent a
+// part of a given content::Page in a given renderer process (note, however,
+// that like RenderFrameHosts, these objects at the moment can be reused for a
+// new content::Page for a cross-document same-site main-frame navigation).
+class CONTENT_EXPORT Page {
+ public:
+  virtual ~Page() {}
+
+  // The GURL for the page's web application manifest.
+  // See https://w3c.github.io/manifest/#web-application-manifest
+  virtual const GURL& GetManifestURL() = 0;
+
+ private:
+  // This interface should only be implemented inside content.
+  friend class PageImpl;
+  Page() {}
+};
+
+}  // namespace content
+
+#endif  // CONTENT_PUBLIC_BROWSER_PAGE_H_
diff --git a/content/public/browser/render_frame_host.h b/content/public/browser/render_frame_host.h
index 9cb4c16..6072d59 100644
--- a/content/public/browser/render_frame_host.h
+++ b/content/public/browser/render_frame_host.h
@@ -106,6 +106,7 @@
 class SiteInstance;
 class StoragePartition;
 class WebUI;
+class Page;
 
 // The interface provides a communication conduit with a frame in the renderer.
 // The preferred way to keep a reference to a RenderFrameHost is storing a
@@ -671,6 +672,14 @@
   // use by resource metrics.
   virtual size_t GetProxyCount() = 0;
 
+  // Returns the Page associated with this RenderFrameHost. Both GetPage() and
+  // GetMainFrame()->GetPage() will always return the same value.
+  //
+  // NOTE: For now, the associated Page object might change (when a navigation
+  // is reusing RenderFrameHost and a new document is created in this
+  // RenderFrameHost). The removal of this case is tracked in crbug.com/936696.
+  virtual Page& GetPage() = 0;
+
   // Returns true if the frame has a selection.
   virtual bool HasSelection() = 0;
 
@@ -889,11 +898,6 @@
   // Return true if onload has been executed in the renderer in the main frame.
   virtual bool IsDocumentOnLoadCompletedInMainFrame() = 0;
 
-  // The GURL for the document's web application manifest. If called on a
-  // subframe, returns the value from the corresponding main frame.  See
-  // https://w3c.github.io/manifest/#web-application-manifest
-  virtual const GURL& ManifestURL() = 0;
-
   // Returns the raw list of favicon candidates as reported to observers via
   // since the last navigation start. If called on a subframe, returns the
   // value from the corresponding main frame.
diff --git a/content/public/browser/render_view_host.h b/content/public/browser/render_view_host.h
index 9f1acca..740d1c32 100644
--- a/content/public/browser/render_view_host.h
+++ b/content/public/browser/render_view_host.h
@@ -40,7 +40,7 @@
 // DEPRECATED: RenderViewHost is being removed as part of the SiteIsolation
 // project. New code should not be added here, but to RenderWidgetHost (if it's
 // about drawing or events), RenderFrameHost (if it's frame specific), or
-// WebContents (if it's page specific).
+// Page (if it's page specific).
 //
 // For context, please see https://crbug.com/467770 and
 // https://www.chromium.org/developers/design-documents/site-isolation.
diff --git a/content/public/browser/web_contents_delegate.cc b/content/public/browser/web_contents_delegate.cc
index bf70b55..8ba5d82 100644
--- a/content/public/browser/web_contents_delegate.cc
+++ b/content/public/browser/web_contents_delegate.cc
@@ -12,6 +12,7 @@
 #include "base/memory/singleton.h"
 #include "build/build_config.h"
 #include "components/viz/common/surfaces/surface_id.h"
+#include "content/public/browser/color_chooser.h"
 #include "content/public/browser/file_select_listener.h"
 #include "content/public/browser/keyboard_event_processing_result.h"
 #include "content/public/browser/render_view_host.h"
@@ -180,7 +181,7 @@
   web_contents->GotResponseToKeyboardLockRequest(false);
 }
 
-ColorChooser* WebContentsDelegate::OpenColorChooser(
+std::unique_ptr<ColorChooser> WebContentsDelegate::OpenColorChooser(
     WebContents* web_contents,
     SkColor color,
     const std::vector<blink::mojom::ColorSuggestionPtr>& suggestions) {
diff --git a/content/public/browser/web_contents_delegate.h b/content/public/browser/web_contents_delegate.h
index b1cfa65..8a3c4c2 100644
--- a/content/public/browser/web_contents_delegate.h
+++ b/content/public/browser/web_contents_delegate.h
@@ -400,9 +400,8 @@
 
   // Called when color chooser should open. Returns the opened color chooser.
   // Returns nullptr if we failed to open the color chooser (e.g. when there is
-  // a ColorChooserDialog already open on Windows). Ownership of the returned
-  // pointer is transferred to the caller.
-  virtual ColorChooser* OpenColorChooser(
+  // a ColorChooserDialog already open on Windows).
+  virtual std::unique_ptr<ColorChooser> OpenColorChooser(
       WebContents* web_contents,
       SkColor color,
       const std::vector<blink::mojom::ColorSuggestionPtr>& suggestions);
diff --git a/device/fido/cable/fido_ble_connection.cc b/device/fido/cable/fido_ble_connection.cc
index 4637826f..64256de 100644
--- a/device/fido/cable/fido_ble_connection.cc
+++ b/device/fido/cable/fido_ble_connection.cc
@@ -191,7 +191,7 @@
                      weak_factory_.GetWeakPtr()),
       base::BindOnce(&FidoBleConnection::OnCreateGattConnectionError,
                      weak_factory_.GetWeakPtr()),
-      BluetoothUUID(kCableAdvertisementUUID128));
+      BluetoothUUID(kGoogleCableUUID128));
 }
 
 void FidoBleConnection::ReadControlPointLength(
diff --git a/device/fido/cable/fido_ble_uuids.cc b/device/fido/cable/fido_ble_uuids.cc
index cbe7258c2..f01930e 100644
--- a/device/fido/cable/fido_ble_uuids.cc
+++ b/device/fido/cable/fido_ble_uuids.cc
@@ -17,11 +17,9 @@
 const char kFidoServiceRevisionBitfieldUUID[] =
     "f1d0fff4-deaa-ecee-b42f-c9ba7ed623bb";
 
-const char kCableAdvertisementUUID16[] = "fde2";
-const char kCableAdvertisementUUID128[] =
-    "0000fde2-0000-1000-8000-00805f9b34fb";
-
-const uint8_t kCableAdvertisementUUID[16] = {
+const char kGoogleCableUUID128[] = "0000fde2-0000-1000-8000-00805f9b34fb";
+const char kGoogleCableUUID16[] = "fde2";
+const uint8_t kGoogleCableUUID[16] = {
     0x00, 0x00, 0xfd, 0xe2, 0x00, 0x00, 0x10, 0x00,
     0x80, 0x00, 0x00, 0x80, 0x5f, 0x9b, 0x34, 0xfb,
 };
diff --git a/device/fido/cable/fido_ble_uuids.h b/device/fido/cable/fido_ble_uuids.h
index f24e452..856ccff6 100644
--- a/device/fido/cable/fido_ble_uuids.h
+++ b/device/fido/cable/fido_ble_uuids.h
@@ -24,13 +24,15 @@
 COMPONENT_EXPORT(DEVICE_FIDO) extern const char kFidoServiceRevisionUUID[];
 COMPONENT_EXPORT(DEVICE_FIDO)
 extern const char kFidoServiceRevisionBitfieldUUID[];
-// TODO(hongjunchoi): Add URL to the specification once CaBLE protocol is
-// standardized.
-COMPONENT_EXPORT(DEVICE_FIDO) extern const char kCableAdvertisementUUID16[];
-COMPONENT_EXPORT(DEVICE_FIDO) extern const char kCableAdvertisementUUID128[];
-// kCableAdvertisementUUID is the binary form of
-// |kCableAdvertisementUUID128|, the UUID allocated for caBLE adverts.
-COMPONENT_EXPORT(DEVICE_FIDO) extern const uint8_t kCableAdvertisementUUID[16];
+
+// kGoogleCableUUID128 is a 16-bit UUID assigned to Google that we use for
+// caBLE.
+COMPONENT_EXPORT(DEVICE_FIDO) extern const char kGoogleCableUUID128[];
+// kGoogleCableUUID16 is the 16-bit version of |kGoogleCableUUID128|.
+COMPONENT_EXPORT(DEVICE_FIDO) extern const char kGoogleCableUUID16[];
+// kGoogleCableUUID is the binary form of
+// |kGoogleCableUUID128|, the UUID allocated to Google for caBLE adverts.
+COMPONENT_EXPORT(DEVICE_FIDO) extern const uint8_t kGoogleCableUUID[16];
 
 }  // namespace device
 
diff --git a/device/fido/cable/fido_cable_device.cc b/device/fido/cable/fido_cable_device.cc
index 2f71ae4..88d47096 100644
--- a/device/fido/cable/fido_cable_device.cc
+++ b/device/fido/cable/fido_cable_device.cc
@@ -50,7 +50,7 @@
 FidoCableDevice::FidoCableDevice(BluetoothAdapter* adapter,
                                  std::string address) {
   connection_ = std::make_unique<FidoBleConnection>(
-      adapter, std::move(address), BluetoothUUID(kCableAdvertisementUUID128),
+      adapter, std::move(address), BluetoothUUID(kGoogleCableUUID128),
       base::BindRepeating(&FidoCableDevice::OnStatusMessage,
                           weak_factory_.GetWeakPtr()));
 }
diff --git a/device/fido/cable/fido_cable_discovery.cc b/device/fido/cable/fido_cable_discovery.cc
index 3147400..7cb1290 100644
--- a/device/fido/cable/fido_cable_discovery.cc
+++ b/device/fido/cable/fido_cable_discovery.cc
@@ -46,7 +46,7 @@
 
 #if defined(OS_MAC)
   auto list = std::make_unique<BluetoothAdvertisement::UUIDList>();
-  list->emplace_back(kCableAdvertisementUUID16);
+  list->emplace_back(kGoogleCableUUID16);
   list->emplace_back(fido_parsing_utils::ConvertBytesToUuid(client_eid));
   advertisement_data->set_service_uuids(std::move(list));
 
@@ -93,8 +93,7 @@
   service_data_value[1] = 1 /* version */;
   std::copy(client_eid.begin(), client_eid.end(),
             service_data_value.begin() + 2);
-  service_data->emplace(kCableAdvertisementUUID128,
-                        std::move(service_data_value));
+  service_data->emplace(kGoogleCableUUID128, std::move(service_data_value));
   advertisement_data->set_service_data(std::move(service_data));
 #endif
 
@@ -102,17 +101,16 @@
 }
 
 static bool Is128BitUUID(const CableEidArray& eid) {
-  // Abbreviated UUIDs have a fixed, 12-byte suffix. kCableAdvertisementUUID
+  // Abbreviated UUIDs have a fixed, 12-byte suffix. kGoogleCableUUID
   // is one such abbeviated UUID.
-  static_assert(sizeof(kCableAdvertisementUUID) == EXTENT(eid), "");
-  return memcmp(eid.data() + 4, kCableAdvertisementUUID + 4,
-                sizeof(kCableAdvertisementUUID) - 4) != 0;
+  static_assert(sizeof(kGoogleCableUUID) == EXTENT(eid), "");
+  return memcmp(eid.data() + 4, kGoogleCableUUID + 4,
+                sizeof(kGoogleCableUUID) - 4) != 0;
 }
 
 static bool IsCableUUID(const CableEidArray& eid) {
-  static_assert(sizeof(kCableAdvertisementUUID) == EXTENT(eid), "");
-  return memcmp(eid.data(), kCableAdvertisementUUID,
-                sizeof(kCableAdvertisementUUID)) == 0;
+  static_assert(sizeof(kGoogleCableUUID) == EXTENT(eid), "");
+  return memcmp(eid.data(), kGoogleCableUUID, sizeof(kGoogleCableUUID)) == 0;
 }
 
 }  // namespace
@@ -218,15 +216,15 @@
 }
 
 // static
-const BluetoothUUID& FidoCableDiscovery::CableAdvertisementUUID() {
+const BluetoothUUID& FidoCableDiscovery::GoogleCableUUID() {
   static const base::NoDestructor<BluetoothUUID> service_uuid(
-      kCableAdvertisementUUID128);
+      kGoogleCableUUID128);
   return *service_uuid;
 }
 
 // static
 bool FidoCableDiscovery::IsCableDevice(const BluetoothDevice* device) {
-  const auto& uuid = CableAdvertisementUUID();
+  const auto& uuid = GoogleCableUUID();
   return base::Contains(device->GetServiceData(), uuid) ||
          base::Contains(device->GetUUIDs(), uuid);
 }
@@ -563,7 +561,7 @@
 absl::optional<FidoCableDiscovery::V1DiscoveryDataAndEID>
 FidoCableDiscovery::GetCableDiscoveryData(const BluetoothDevice* device) {
   const std::vector<uint8_t>* const service_data =
-      device->GetServiceDataForUUID(CableAdvertisementUUID());
+      device->GetServiceDataForUUID(GoogleCableUUID());
   absl::optional<CableEidArray> maybe_eid_from_service_data =
       MaybeGetEidFromServiceData(device);
   std::vector<CableEidArray> uuids = GetUUIDs(device);
@@ -661,7 +659,7 @@
 absl::optional<CableEidArray> FidoCableDiscovery::MaybeGetEidFromServiceData(
     const BluetoothDevice* device) {
   const std::vector<uint8_t>* service_data =
-      device->GetServiceDataForUUID(CableAdvertisementUUID());
+      device->GetServiceDataForUUID(GoogleCableUUID());
   if (!service_data) {
     return absl::nullopt;
   }
diff --git a/device/fido/cable/fido_cable_discovery.h b/device/fido/cable/fido_cable_discovery.h
index 4bd5acb..43696722 100644
--- a/device/fido/cable/fido_cable_discovery.h
+++ b/device/fido/cable/fido_cable_discovery.h
@@ -77,7 +77,7 @@
     std::vector<CableEidArray> uuids;
   };
 
-  static const BluetoothUUID& CableAdvertisementUUID();
+  static const BluetoothUUID& GoogleCableUUID();
   static bool IsCableDevice(const BluetoothDevice* device);
 
   // ResultDebugString returns a string containing a hex dump of |eid| and a
diff --git a/device/fido/cable/fido_cable_discovery_unittest.cc b/device/fido/cable/fido_cable_discovery_unittest.cc
index c65dd661..9565d15 100644
--- a/device/fido/cable/fido_cable_discovery_unittest.cc
+++ b/device/fido/cable/fido_cable_discovery_unittest.cc
@@ -126,8 +126,7 @@
 
 #elif defined(OS_LINUX) || defined(OS_CHROMEOS)
   const auto service_data = arg->service_data();
-  const auto service_data_with_uuid =
-      service_data->find(kCableAdvertisementUUID128);
+  const auto service_data_with_uuid = service_data->find(kGoogleCableUUID128);
 
   if (service_data_with_uuid == service_data->end())
     return false;
@@ -211,8 +210,7 @@
     std::copy(authenticator_eid.begin(), authenticator_eid.end(),
               service_data.begin() + 2);
     BluetoothDevice::ServiceDataMap service_data_map;
-    service_data_map.emplace(kCableAdvertisementUUID128,
-                             std::move(service_data));
+    service_data_map.emplace(kGoogleCableUUID128, std::move(service_data));
 
     mock_device->UpdateAdvertisementData(
         1 /* rssi */, absl::nullopt /* flags */, BluetoothDevice::UUIDList(),
diff --git a/extensions/browser/api/device_permissions_manager.h b/extensions/browser/api/device_permissions_manager.h
index c60d9009..08e64b5 100644
--- a/extensions/browser/api/device_permissions_manager.h
+++ b/extensions/browser/api/device_permissions_manager.h
@@ -16,7 +16,6 @@
 #include "base/gtest_prod_util.h"
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
-#include "base/scoped_observer.h"
 #include "base/threading/thread_checker.h"
 #include "components/keyed_service/content/browser_context_keyed_service_factory.h"
 #include "components/keyed_service/core/keyed_service.h"
diff --git a/extensions/browser/api/hid/hid_device_manager.h b/extensions/browser/api/hid/hid_device_manager.h
index 0980249..0371c210 100644
--- a/extensions/browser/api/hid/hid_device_manager.h
+++ b/extensions/browser/api/hid/hid_device_manager.h
@@ -13,7 +13,6 @@
 #include "base/callback.h"
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
-#include "base/scoped_observer.h"
 #include "base/threading/thread_checker.h"
 #include "base/values.h"
 #include "extensions/browser/browser_context_keyed_api_factory.h"
diff --git a/extensions/browser/api/webcam_private/webcam_private_api.h b/extensions/browser/api/webcam_private/webcam_private_api.h
index 8e949c0..3d020ea 100644
--- a/extensions/browser/api/webcam_private/webcam_private_api.h
+++ b/extensions/browser/api/webcam_private/webcam_private_api.h
@@ -9,7 +9,6 @@
 #include <memory>
 
 #include "base/macros.h"
-#include "base/scoped_observer.h"
 #include "extensions/browser/api/api_resource_manager.h"
 #include "extensions/browser/api/webcam_private/webcam.h"
 #include "extensions/browser/browser_context_keyed_api_factory.h"
diff --git a/extensions/browser/app_window/app_delegate.h b/extensions/browser/app_window/app_delegate.h
index 2685e9d1..a1d5fa2 100644
--- a/extensions/browser/app_window/app_delegate.h
+++ b/extensions/browser/app_window/app_delegate.h
@@ -5,6 +5,8 @@
 #ifndef EXTENSIONS_BROWSER_APP_WINDOW_APP_DELEGATE_H_
 #define EXTENSIONS_BROWSER_APP_WINDOW_APP_DELEGATE_H_
 
+#include <memory>
+
 #include "base/callback_forward.h"
 #include "content/public/browser/media_stream_request.h"
 #include "third_party/blink/public/common/mediastream/media_stream_request.h"
@@ -68,7 +70,7 @@
       bool user_gesture) = 0;
 
   // Feature support.
-  virtual content::ColorChooser* ShowColorChooser(
+  virtual std::unique_ptr<content::ColorChooser> ShowColorChooser(
       content::WebContents* web_contents,
       SkColor initial_color) = 0;
   virtual void RunFileChooser(
diff --git a/extensions/browser/app_window/app_window.cc b/extensions/browser/app_window/app_window.cc
index fcb2c6c5..7acdfdb 100644
--- a/extensions/browser/app_window/app_window.cc
+++ b/extensions/browser/app_window/app_window.cc
@@ -23,6 +23,7 @@
 #include "build/chromeos_buildflags.h"
 #include "components/web_modal/web_contents_modal_dialog_manager.h"
 #include "content/public/browser/browser_context.h"
+#include "content/public/browser/color_chooser.h"
 #include "content/public/browser/file_select_listener.h"
 #include "content/public/browser/invalidate_type.h"
 #include "content/public/browser/keyboard_event_processing_result.h"
@@ -884,7 +885,7 @@
   return true;
 }
 
-content::ColorChooser* AppWindow::OpenColorChooser(
+std::unique_ptr<content::ColorChooser> AppWindow::OpenColorChooser(
     WebContents* web_contents,
     SkColor initial_color,
     const std::vector<blink::mojom::ColorSuggestionPtr>& suggestions) {
diff --git a/extensions/browser/app_window/app_window.h b/extensions/browser/app_window/app_window.h
index 556fdbb..5316d83 100644
--- a/extensions/browser/app_window/app_window.h
+++ b/extensions/browser/app_window/app_window.h
@@ -395,7 +395,7 @@
   void ActivateContents(content::WebContents* contents) override;
   void CloseContents(content::WebContents* contents) override;
   bool ShouldSuppressDialogs(content::WebContents* source) override;
-  content::ColorChooser* OpenColorChooser(
+  std::unique_ptr<content::ColorChooser> OpenColorChooser(
       content::WebContents* web_contents,
       SkColor color,
       const std::vector<blink::mojom::ColorSuggestionPtr>& suggestions)
diff --git a/extensions/common/api/_api_features.json b/extensions/common/api/_api_features.json
index aa91cd5..725c27e6 100644
--- a/extensions/common/api/_api_features.json
+++ b/extensions/common/api/_api_features.json
@@ -557,8 +557,8 @@
     "contexts": ["blessed_extension", "unblessed_extension", "content_script"]
   },
   "storage.session": {
-    "channel": "canary"
-    // TODO(crbug.com/1185226): Restrict to Manifest V3.
+    "channel": "canary",
+    "min_manifest_version": 3
   },
   "system.cpu": {
     "dependencies": ["permission:system.cpu"],
diff --git a/extensions/renderer/storage_area_unittest.cc b/extensions/renderer/storage_area_unittest.cc
index 5af2add..bbcbd9d 100644
--- a/extensions/renderer/storage_area_unittest.cc
+++ b/extensions/renderer/storage_area_unittest.cc
@@ -132,7 +132,10 @@
 
 TEST_F(StorageAreaTrunkTest, HasOnChanged) {
   scoped_refptr<const Extension> extension =
-      ExtensionBuilder("foo").AddPermission("storage").Build();
+      ExtensionBuilder("foo")
+          .SetManifestKey("manifest_version", 3)
+          .AddPermission("storage")
+          .Build();
   RegisterExtension(extension);
 
   v8::HandleScope handle_scope(isolate());
diff --git a/extensions/shell/browser/shell_app_delegate.cc b/extensions/shell/browser/shell_app_delegate.cc
index 40a49e7..1f9278c 100644
--- a/extensions/shell/browser/shell_app_delegate.cc
+++ b/extensions/shell/browser/shell_app_delegate.cc
@@ -4,6 +4,7 @@
 
 #include "extensions/shell/browser/shell_app_delegate.h"
 
+#include "content/public/browser/color_chooser.h"
 #include "content/public/browser/file_select_listener.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/browser/web_contents_delegate.h"
@@ -58,7 +59,7 @@
   NOTIMPLEMENTED();
 }
 
-content::ColorChooser* ShellAppDelegate::ShowColorChooser(
+std::unique_ptr<content::ColorChooser> ShellAppDelegate::ShowColorChooser(
     content::WebContents* web_contents,
     SkColor initial_color) {
   NOTIMPLEMENTED();
diff --git a/extensions/shell/browser/shell_app_delegate.h b/extensions/shell/browser/shell_app_delegate.h
index db2032f3..5a6d7fdf 100644
--- a/extensions/shell/browser/shell_app_delegate.h
+++ b/extensions/shell/browser/shell_app_delegate.h
@@ -33,8 +33,9 @@
                       WindowOpenDisposition disposition,
                       const gfx::Rect& initial_rect,
                       bool user_gesture) override;
-  content::ColorChooser* ShowColorChooser(content::WebContents* web_contents,
-                                          SkColor initial_color) override;
+  std::unique_ptr<content::ColorChooser> ShowColorChooser(
+      content::WebContents* web_contents,
+      SkColor initial_color) override;
   void RunFileChooser(content::RenderFrameHost* render_frame_host,
                       scoped_refptr<content::FileSelectListener> listener,
                       const blink::mojom::FileChooserParams& params) override;
diff --git a/gpu/command_buffer/service/external_vk_image_backing.cc b/gpu/command_buffer/service/external_vk_image_backing.cc
index 35fbaab5..ae998ca 100644
--- a/gpu/command_buffer/service/external_vk_image_backing.cc
+++ b/gpu/command_buffer/service/external_vk_image_backing.cc
@@ -191,7 +191,6 @@
     base::span<const uint8_t> pixel_data,
     bool using_gmb) {
   bool is_external = context_state->support_vulkan_external_object();
-  bool is_transfer_dst = using_gmb || !pixel_data.empty() || !is_external;
 
   auto* device_queue = context_state->vk_context_provider()->GetDeviceQueue();
   VkFormat vk_format = ToVkFormat(format);
@@ -199,7 +198,9 @@
       SHARED_IMAGE_USAGE_GLES2 | SHARED_IMAGE_USAGE_GLES2_FRAMEBUFFER_HINT |
       SHARED_IMAGE_USAGE_RASTER | SHARED_IMAGE_USAGE_OOP_RASTERIZATION |
       SHARED_IMAGE_USAGE_WEBGPU;
-  VkImageUsageFlags vk_usage = VK_IMAGE_USAGE_SAMPLED_BIT;
+  VkImageUsageFlags vk_usage = VK_IMAGE_USAGE_SAMPLED_BIT |
+                               VK_IMAGE_USAGE_TRANSFER_SRC_BIT |
+                               VK_IMAGE_USAGE_TRANSFER_DST_BIT;
   if (usage & kUsageNeedsColorAttachment) {
     vk_usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
     if (format == viz::ETC1) {
@@ -208,42 +209,16 @@
     }
   }
 
-  if (is_transfer_dst)
-    vk_usage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT;
-
   // Requested usage flags must be supported.
   DCHECK_EQ(vk_usage & image_usage_cache->optimal_tiling_usage[format],
             vk_usage);
 
-  if (is_external && (usage & SHARED_IMAGE_USAGE_GLES2)) {
-    // Must request all available image usage flags if aliasing GL texture. This
-    // is a spec requirement per EXT_memory_object. However, if
-    // ANGLE_memory_object_flags is supported, usage flags can be arbitrary.
-    if (UseMinimalUsageFlags(context_state.get())) {
-      // The following additional usage flags are provided for ANGLE:
-      //
-      // - TRANSFER_SRC: Used for copies from this image.
-      // - TRANSFER_DST: Used for copies to this image or clears.
-      vk_usage |=
-          VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
-    } else {
-      vk_usage |= image_usage_cache->optimal_tiling_usage[format];
-    }
-  }
-
-  if (is_external && (usage & SHARED_IMAGE_USAGE_WEBGPU)) {
-    // The following additional usage flags are provided for Dawn:
-    //
-    // - TRANSFER_SRC: Used for copies from this image.
-    // - TRANSFER_DST: Used for copies to this image or clears.
-    vk_usage |=
-        VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
-  }
-
-  if (usage & SHARED_IMAGE_USAGE_DISPLAY) {
-    // Skia currently requires all VkImages it uses to support transfers
-    vk_usage |=
-        VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
+  // Must request all available image usage flags if aliasing GL texture. This
+  // is a spec requirement per EXT_memory_object. However, if
+  // ANGLE_memory_object_flags is supported, usage flags can be arbitrary.
+  if (is_external && (usage & SHARED_IMAGE_USAGE_GLES2) &&
+      !UseMinimalUsageFlags(context_state.get())) {
+    vk_usage |= image_usage_cache->optimal_tiling_usage[format];
   }
 
   VkImageCreateFlags vk_flags = 0;
diff --git a/gpu/command_buffer/service/external_vk_image_factory.cc b/gpu/command_buffer/service/external_vk_image_factory.cc
index ef135955..ea8954b8 100644
--- a/gpu/command_buffer/service/external_vk_image_factory.cc
+++ b/gpu/command_buffer/service/external_vk_image_factory.cc
@@ -24,19 +24,31 @@
 VkImageUsageFlags GetMaximalImageUsageFlags(
     VkFormatFeatureFlags feature_flags) {
   VkImageUsageFlags usage_flags = 0;
+  // The TRANSFER_SRC/DST format features were added in Vulkan 1.1 and their
+  // support is required when SAMPLED_IMAGE is supported. In Vulkan 1.0 all
+  // formats support these features implicitly. See discussion in
+  // https://github.com/KhronosGroup/Vulkan-Docs/issues/1223
   if (feature_flags & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT)
-    usage_flags |= VK_IMAGE_USAGE_SAMPLED_BIT;
+    usage_flags |= VK_IMAGE_USAGE_SAMPLED_BIT |
+                   VK_IMAGE_USAGE_TRANSFER_SRC_BIT |
+                   VK_IMAGE_USAGE_TRANSFER_DST_BIT;
+
+  // VUID-VkImageViewCreateInfo-usage-02652: support for INPUT_ATTACHMENT is
+  // implied by both of COLOR_ATTACHNENT and DEPTH_STENCIL_ATTACHMENT
+  if (feature_flags & VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT)
+    usage_flags |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT |
+                   VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT;
+  if (feature_flags & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT)
+    usage_flags |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT |
+                   VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT;
+
   if (feature_flags & VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT)
     usage_flags |= VK_IMAGE_USAGE_STORAGE_BIT;
-  if (feature_flags & VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT)
-    usage_flags |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
-  if (feature_flags & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT)
-    usage_flags |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
   if (feature_flags & VK_FORMAT_FEATURE_TRANSFER_SRC_BIT)
     usage_flags |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
   if (feature_flags & VK_FORMAT_FEATURE_TRANSFER_DST_BIT)
     usage_flags |= VK_IMAGE_USAGE_TRANSFER_DST_BIT;
-  usage_flags |= VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT;
+
   return usage_flags;
 }
 
diff --git a/infra/config/generated/cr-buildbucket.cfg b/infra/config/generated/cr-buildbucket.cfg
index 471fe03..51731fc 100644
--- a/infra/config/generated/cr-buildbucket.cfg
+++ b/infra/config/generated/cr-buildbucket.cfg
@@ -25758,12 +25758,12 @@
         properties_j: "$recipe_engine/isolated:{\"server\":\"https://isolateserver.appspot.com\"}"
         properties_j: "$recipe_engine/resultdb/test_presentation:{\"column_keys\":[],\"grouping_keys\":[\"status\",\"v.test_suite\"]}"
         properties_j: "builder_group:\"chromium.fyi\""
-        properties_j: "xcode_build_version:\"11e608cwk\""
+        properties_j: "xcode_build_version:\"12e262wk\""
       }
       execution_timeout_secs: 36000
       caches {
-        name: "xcode_ios_11e608cwk"
-        path: "xcode_ios_11e608cwk.app"
+        name: "xcode_ios_12e262wk"
+        path: "xcode_ios_12e262wk.app"
       }
       build_numbers: YES
       service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
@@ -35253,6 +35253,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -35316,6 +35320,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -35379,6 +35387,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -35442,6 +35454,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -35505,6 +35521,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -35572,6 +35592,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -37473,6 +37497,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -37546,6 +37574,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -37619,6 +37651,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -37692,6 +37728,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -37765,6 +37805,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -37838,6 +37882,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -37911,6 +37959,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -37984,6 +38036,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -38057,6 +38113,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -38130,6 +38190,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -38203,6 +38267,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -38275,6 +38343,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -38349,6 +38421,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -38423,6 +38499,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -38496,6 +38576,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -38569,6 +38653,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -38642,6 +38730,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -38715,6 +38807,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -38788,6 +38884,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -38861,6 +38961,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -38934,6 +39038,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -39008,6 +39116,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -39082,6 +39194,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -39154,6 +39270,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -39227,6 +39347,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -39300,6 +39424,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -39373,6 +39501,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -39446,6 +39578,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -39519,6 +39655,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -39592,6 +39732,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -39665,6 +39809,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -39738,6 +39886,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -39811,6 +39963,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -39884,6 +40040,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -39957,6 +40117,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -40030,6 +40194,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -40103,6 +40271,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -40176,6 +40348,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -40249,6 +40425,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -40321,6 +40501,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -40393,6 +40577,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -40465,6 +40653,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -40537,6 +40729,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -40609,6 +40805,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -40681,6 +40881,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -40754,6 +40958,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -40827,6 +41035,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -40900,6 +41112,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -40973,6 +41189,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -41046,6 +41266,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -41118,6 +41342,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -41191,6 +41419,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -41264,6 +41496,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -41336,6 +41572,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -41409,6 +41649,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -41482,6 +41726,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -41554,6 +41802,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -41627,6 +41879,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -41696,6 +41952,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -41770,6 +42030,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -41842,6 +42106,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -41915,6 +42183,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -41987,6 +42259,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -42060,6 +42336,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -42133,6 +42413,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -42206,6 +42490,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -42278,6 +42566,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -42351,6 +42643,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -42423,6 +42719,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -42496,6 +42796,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -42568,6 +42872,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -42641,6 +42949,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -42714,6 +43026,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -42789,6 +43105,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -42860,6 +43180,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -42935,6 +43259,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -43010,6 +43338,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -43085,6 +43417,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -43160,6 +43496,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -43235,6 +43575,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -43307,6 +43651,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -43379,6 +43727,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -43452,6 +43804,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -43525,6 +43881,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -43598,6 +43958,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -43671,6 +44035,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -43744,6 +44112,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -43817,6 +44189,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -43890,6 +44266,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -43963,6 +44343,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -44036,6 +44420,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -44108,6 +44496,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -44180,6 +44572,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -44252,6 +44648,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -44324,6 +44724,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -44393,6 +44797,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -44462,6 +44870,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -44531,6 +44943,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -44600,6 +45016,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -44669,6 +45089,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -44738,6 +45162,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -44807,6 +45235,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -44876,6 +45308,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -44945,6 +45381,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -45014,6 +45454,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -45083,6 +45527,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -45152,6 +45600,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -45221,6 +45673,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -45290,6 +45746,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -45359,6 +45819,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -45428,6 +45892,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -45497,6 +45965,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -45566,6 +46038,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -45635,6 +46111,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -45704,6 +46184,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -45773,6 +46257,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -45842,6 +46330,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -45911,6 +46403,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -45980,6 +46476,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -46049,6 +46549,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -46118,6 +46622,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -46187,6 +46695,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -46256,6 +46768,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -46325,6 +46841,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -46394,6 +46914,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -46466,6 +46990,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -46538,6 +47066,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -46610,6 +47142,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -46682,6 +47218,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -46754,6 +47294,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -46826,6 +47370,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -46898,6 +47446,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -46970,6 +47522,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -47042,6 +47598,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -47114,6 +47674,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -47186,6 +47750,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -47258,6 +47826,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -47330,6 +47902,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -47403,6 +47979,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -47476,6 +48056,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -47549,6 +48133,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -47622,6 +48210,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -47695,6 +48287,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -47768,6 +48364,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -47841,6 +48441,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -47914,6 +48518,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -47987,6 +48595,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -48060,6 +48672,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -48133,6 +48749,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -48206,6 +48826,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -48279,6 +48903,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -48348,6 +48976,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -48417,6 +49049,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -48486,6 +49122,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -48558,6 +49198,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -48630,6 +49274,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -48703,6 +49351,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -48780,6 +49432,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -48860,6 +49516,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -48941,6 +49601,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -49021,6 +49685,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -49102,6 +49770,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -49182,6 +49854,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -49262,6 +49938,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -49342,6 +50022,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -49423,6 +50107,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -49503,6 +50191,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -49583,6 +50275,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -49663,6 +50359,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -49743,6 +50443,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -49815,6 +50519,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -49888,6 +50596,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -49961,6 +50673,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -50035,6 +50751,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -50107,6 +50827,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -50179,6 +50903,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -50251,6 +50979,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -50324,6 +51056,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -50397,6 +51133,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -50470,6 +51210,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -50543,6 +51287,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -50616,6 +51364,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -50689,6 +51441,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -50762,6 +51518,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -50835,6 +51595,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -50908,6 +51672,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -50981,6 +51749,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -51054,6 +51826,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -51126,6 +51902,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -51199,6 +51979,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -51272,6 +52056,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -51346,6 +52134,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -51419,6 +52211,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -51492,6 +52288,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -51565,6 +52365,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -51636,6 +52440,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -51709,6 +52517,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -51782,6 +52594,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -51855,6 +52671,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -51927,6 +52747,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -52000,6 +52824,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -52073,6 +52901,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -52146,6 +52978,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -52219,6 +53055,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -52292,6 +53132,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -52364,6 +53208,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -52437,6 +53285,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -52510,6 +53362,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -52583,6 +53439,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -52655,6 +53515,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -52728,6 +53592,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -52801,6 +53669,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -52875,6 +53747,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -52949,6 +53825,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -53023,6 +53903,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -53096,6 +53980,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -53166,6 +54054,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -53236,6 +54128,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -53306,6 +54202,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -53376,6 +54276,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -53449,6 +54353,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -53522,6 +54430,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -53595,6 +54507,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -53668,6 +54584,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -53741,6 +54661,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -53814,6 +54738,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -53887,6 +54815,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -53960,6 +54892,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -54032,6 +54968,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -54105,6 +55045,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -54178,6 +55122,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -54251,6 +55199,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -54324,6 +55276,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -54397,6 +55353,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -54470,6 +55430,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -54543,6 +55507,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -54616,6 +55584,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -54689,6 +55661,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -54765,6 +55741,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -54838,6 +55818,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -54915,6 +55899,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -54988,6 +55976,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -55060,6 +56052,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -55132,6 +56128,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -55205,6 +56205,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -55278,6 +56282,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -55351,6 +56359,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -55424,6 +56436,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -55497,6 +56513,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -55569,6 +56589,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -55641,6 +56665,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -55714,6 +56742,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -55789,6 +56821,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -55864,6 +56900,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -55940,6 +56980,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -56015,6 +57059,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -56091,6 +57139,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -56166,6 +57218,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -56242,6 +57298,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -56319,6 +57379,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -56396,6 +57460,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -56468,6 +57536,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -56543,6 +57615,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -56618,6 +57694,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -56694,6 +57774,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -56770,6 +57854,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -56846,6 +57934,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -56922,6 +58014,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -56998,6 +58094,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -57074,6 +58174,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -57150,6 +58254,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -57226,6 +58334,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -57302,6 +58414,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -57378,6 +58494,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -57454,6 +58574,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -57530,6 +58654,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -57606,6 +58734,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -57682,6 +58814,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -57758,6 +58894,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -57834,6 +58974,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -57909,6 +59053,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -57984,6 +59132,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -58059,6 +59211,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -58132,6 +59288,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -58205,6 +59365,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -58278,6 +59442,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -58351,6 +59519,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -58424,6 +59596,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -58500,6 +59676,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -58576,6 +59756,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -58652,6 +59836,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -58728,6 +59916,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -58805,6 +59997,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -58882,6 +60078,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -58963,6 +60163,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -59038,6 +60242,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -59114,6 +60322,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -59191,6 +60403,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -59265,6 +60481,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -59339,6 +60559,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -59413,6 +60637,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -59487,6 +60715,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -59561,6 +60793,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -59635,6 +60871,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -59709,6 +60949,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -59786,6 +61030,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -59863,6 +61111,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -59940,6 +61192,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -60017,6 +61273,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -60093,6 +61353,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -60170,6 +61434,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -60248,6 +61516,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -60324,6 +61596,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -60401,6 +61677,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -60478,6 +61758,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -60555,6 +61839,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -60632,6 +61920,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -60709,6 +62001,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -60786,6 +62082,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -60863,6 +62163,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -60940,6 +62244,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -61017,6 +62325,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -61094,6 +62406,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -61171,6 +62487,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -61246,6 +62566,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
@@ -61323,6 +62647,10 @@
         key: "luci.use_realms"
         value: 100
       }
+      experiments {
+        key: "use_rbe_cas"
+        value: 5
+      }
       resultdb {
         enable: true
         bq_exports {
diff --git a/infra/config/lib/builders.star b/infra/config/lib/builders.star
index 55592f2..50bfe168 100644
--- a/infra/config/lib/builders.star
+++ b/infra/config/lib/builders.star
@@ -165,14 +165,14 @@
 xcode = struct(
     # in use by webrtc mac builders
     x11c29 = xcode_enum("11c29"),
-    # in use by ios-webkit-tot
-    x11e608cwk = xcode_enum("11e608cwk"),
     # (current default for other projects) xc12.0 gm seed
     x12a7209 = xcode_enum("12a7209"),
     # (current default for iOS) xc12.4 gm seed
     x12d4e = xcode_enum("12d4e"),
     # Xcode 12.5. Requires Mac11+ OS.
     x12e262 = xcode_enum("12e262"),
+    # in use by ios-webkit-tot
+    x12e262wk = xcode_enum("12e262wk"),
 )
 
 ################################################################################
diff --git a/infra/config/lib/try.star b/infra/config/lib/try.star
index 6d6684b..46e9810 100644
--- a/infra/config/lib/try.star
+++ b/infra/config/lib/try.star
@@ -163,6 +163,9 @@
         if kwargs["goma_enable_ats"] != False:
             fail("Try Windows builder {} must disable ATS".format(name))
 
+    # TODO(crbug.com/1143122): remove this after migration.
+    experiments["use_rbe_cas"] = 5
+
     # Define the builder first so that any validation of luci.builder arguments
     # (e.g. bucket) occurs before we try to use it
     builders.builder(
diff --git a/infra/config/subprojects/chromium/ci.star b/infra/config/subprojects/chromium/ci.star
index 834e6faf..07cde02 100644
--- a/infra/config/subprojects/chromium/ci.star
+++ b/infra/config/subprojects/chromium/ci.star
@@ -4167,7 +4167,7 @@
     ),
     schedule = "0 1-23/6 * * *",
     triggered_by = [],
-    xcode = xcode.x11e608cwk,
+    xcode = xcode.x12e262wk,
 )
 
 ci.fyi_ios_builder(
diff --git a/ios/chrome/browser/app_launcher/app_launcher_browser_agent.h b/ios/chrome/browser/app_launcher/app_launcher_browser_agent.h
index 093b6cb4..61285fc 100644
--- a/ios/chrome/browser/app_launcher/app_launcher_browser_agent.h
+++ b/ios/chrome/browser/app_launcher/app_launcher_browser_agent.h
@@ -5,7 +5,6 @@
 #ifndef IOS_CHROME_BROWSER_APP_LAUNCHER_APP_LAUNCHER_BROWSER_AGENT_H_
 #define IOS_CHROME_BROWSER_APP_LAUNCHER_APP_LAUNCHER_BROWSER_AGENT_H_
 
-#include "base/scoped_observer.h"
 #import "ios/chrome/browser/app_launcher/app_launcher_tab_helper.h"
 #import "ios/chrome/browser/app_launcher/app_launcher_tab_helper_delegate.h"
 #import "ios/chrome/browser/main/browser_user_data.h"
diff --git a/ios/chrome/browser/bookmarks/bookmark_remover_helper.h b/ios/chrome/browser/bookmarks/bookmark_remover_helper.h
index a9a855dd..41776b6 100644
--- a/ios/chrome/browser/bookmarks/bookmark_remover_helper.h
+++ b/ios/chrome/browser/bookmarks/bookmark_remover_helper.h
@@ -6,7 +6,6 @@
 #define IOS_CHROME_BROWSER_BOOKMARKS_BOOKMARK_REMOVER_HELPER_H_
 
 #include "base/callback.h"
-#include "base/scoped_observer.h"
 #include "base/sequence_checker.h"
 #include "components/bookmarks/browser/base_bookmark_model_observer.h"
 
diff --git a/ios/chrome/browser/credential_provider/credential_provider_service.mm b/ios/chrome/browser/credential_provider/credential_provider_service.mm
index 82cffb3..f7ff0d9 100644
--- a/ios/chrome/browser/credential_provider/credential_provider_service.mm
+++ b/ios/chrome/browser/credential_provider/credential_provider_service.mm
@@ -9,7 +9,6 @@
 #include "base/check.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/notreached.h"
-#include "base/scoped_observer.h"
 #include "base/strings/sys_string_conversions.h"
 #include "base/threading/sequenced_task_runner_handle.h"
 #include "build/build_config.h"
diff --git a/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/translate/translate_infobar_banner_interaction_handler.h b/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/translate/translate_infobar_banner_interaction_handler.h
index 091e610..d002252 100644
--- a/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/translate/translate_infobar_banner_interaction_handler.h
+++ b/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/translate/translate_infobar_banner_interaction_handler.h
@@ -7,7 +7,6 @@
 
 #import "ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/common/infobar_banner_interaction_handler.h"
 
-#include "base/scoped_observer.h"
 #include "components/translate/core/browser/translate_infobar_delegate.h"
 
 // Helper object that updates the model layer for interaction events with the
diff --git a/ios/chrome/browser/infobars/overlays/infobar_modal_completion_notifier.h b/ios/chrome/browser/infobars/overlays/infobar_modal_completion_notifier.h
index b691aad..9c8a4d6 100644
--- a/ios/chrome/browser/infobars/overlays/infobar_modal_completion_notifier.h
+++ b/ios/chrome/browser/infobars/overlays/infobar_modal_completion_notifier.h
@@ -10,7 +10,6 @@
 #include "base/memory/weak_ptr.h"
 #include "base/observer_list.h"
 #include "base/observer_list_types.h"
-#include "base/scoped_observer.h"
 #include "ios/chrome/browser/overlays/public/overlay_request_callback_installer.h"
 
 class InfoBarIOS;
diff --git a/ios/chrome/browser/metrics/incognito_web_state_observer.h b/ios/chrome/browser/metrics/incognito_web_state_observer.h
index afb8b490..f3df03b 100644
--- a/ios/chrome/browser/metrics/incognito_web_state_observer.h
+++ b/ios/chrome/browser/metrics/incognito_web_state_observer.h
@@ -8,7 +8,6 @@
 #include <set>
 
 #include "base/macros.h"
-#include "base/scoped_observer.h"
 #import "ios/chrome/browser/web_state_list/web_state_list_observer.h"
 
 class AllWebStateListObservationRegistrar;
diff --git a/ios/chrome/browser/net/ios_chrome_network_delegate.cc b/ios/chrome/browser/net/ios_chrome_network_delegate.cc
index 815d8e2..15c76a7 100644
--- a/ios/chrome/browser/net/ios_chrome_network_delegate.cc
+++ b/ios/chrome/browser/net/ios_chrome_network_delegate.cc
@@ -76,7 +76,7 @@
     return allowed_from_caller;
 
   return allowed_from_caller &&
-         cookie_settings_->IsCookieAccessAllowed(
+         cookie_settings_->IsFullCookieAccessAllowed(
              request.url(), request.site_for_cookies().RepresentativeUrl());
 }
 
@@ -90,7 +90,7 @@
     return allowed_from_caller;
 
   return allowed_from_caller &&
-         cookie_settings_->IsCookieAccessAllowed(
+         cookie_settings_->IsFullCookieAccessAllowed(
              request.url(), request.site_for_cookies().RepresentativeUrl());
 }
 
@@ -102,7 +102,7 @@
   if (!cookie_settings_.get())
     return false;
 
-  return !cookie_settings_->IsCookieAccessAllowed(
+  return !cookie_settings_->IsFullCookieAccessAllowed(
       url, site_for_cookies.RepresentativeUrl(), top_frame_origin);
 }
 
diff --git a/ios/chrome/browser/omaha/omaha_service.h b/ios/chrome/browser/omaha/omaha_service.h
index a89a81238..71035b8 100644
--- a/ios/chrome/browser/omaha/omaha_service.h
+++ b/ios/chrome/browser/omaha/omaha_service.h
@@ -12,7 +12,6 @@
 #include "base/gtest_prod_util.h"
 #include "base/macros.h"
 #include "base/no_destructor.h"
-#include "base/scoped_observer.h"
 #include "base/timer/timer.h"
 #include "base/version.h"
 
diff --git a/ios/chrome/browser/passwords/password_store_observer_bridge.h b/ios/chrome/browser/passwords/password_store_observer_bridge.h
index 6990776..f69ca9d 100644
--- a/ios/chrome/browser/passwords/password_store_observer_bridge.h
+++ b/ios/chrome/browser/passwords/password_store_observer_bridge.h
@@ -8,7 +8,6 @@
 #import <Foundation/Foundation.h>
 
 
-#include "base/scoped_observer.h"
 #include "components/password_manager/core/browser/password_store.h"
 
 // Protocol to observe changes on the Password Store.
diff --git a/ios/chrome/browser/policy/BUILD.gn b/ios/chrome/browser/policy/BUILD.gn
index 2d8fa03..4953037 100644
--- a/ios/chrome/browser/policy/BUILD.gn
+++ b/ios/chrome/browser/policy/BUILD.gn
@@ -69,6 +69,7 @@
     "//ios/chrome/browser/browser_state",
     "//ios/chrome/browser/main:public",
     "//ios/chrome/browser/signin",
+    "//ios/chrome/browser/ui/authentication/signin:signin_headers",
     "//ios/chrome/browser/ui/commands",
     "//ios/chrome/browser/ui/main:scene_state_header",
     "//ios/chrome/common",
diff --git a/ios/chrome/browser/policy/browser_signin_policy_handler.cc b/ios/chrome/browser/policy/browser_signin_policy_handler.cc
index 7dbcd48..2edb18d 100644
--- a/ios/chrome/browser/policy/browser_signin_policy_handler.cc
+++ b/ios/chrome/browser/policy/browser_signin_policy_handler.cc
@@ -73,10 +73,10 @@
       // sign-in enabled.
       FALLTHROUGH;
     case BrowserSigninMode::kEnabled:
-      prefs->SetBoolean(prefs::kSigninAllowed, true);
+      prefs->SetBoolean(prefs::kSigninAllowedByPolicy, true);
       break;
     case BrowserSigninMode::kDisabled:
-      prefs->SetBoolean(prefs::kSigninAllowed, false);
+      prefs->SetBoolean(prefs::kSigninAllowedByPolicy, false);
       break;
   }
 }
diff --git a/ios/chrome/browser/policy/browser_signin_policy_handler_unittest.cc b/ios/chrome/browser/policy/browser_signin_policy_handler_unittest.cc
index d4c7028f..1d4ddcf 100644
--- a/ios/chrome/browser/policy/browser_signin_policy_handler_unittest.cc
+++ b/ios/chrome/browser/policy/browser_signin_policy_handler_unittest.cc
@@ -72,7 +72,7 @@
     bool value = false;
     PrefValueMap prefs;
     handler.ApplyPolicySettings(policies, &prefs);
-    EXPECT_TRUE(prefs.GetBoolean(prefs::kSigninAllowed, &value));
+    EXPECT_TRUE(prefs.GetBoolean(prefs::kSigninAllowedByPolicy, &value));
     EXPECT_EQ(test_case.expected_pref_value, value)
         << "For test case: mode = "
         << BrowserSigninModeToString(test_case.mode);
diff --git a/ios/chrome/browser/policy/policy_watcher_browser_agent.mm b/ios/chrome/browser/policy/policy_watcher_browser_agent.mm
index 1fde103..1ff1cb6 100644
--- a/ios/chrome/browser/policy/policy_watcher_browser_agent.mm
+++ b/ios/chrome/browser/policy/policy_watcher_browser_agent.mm
@@ -15,6 +15,7 @@
 #include "ios/chrome/browser/policy/policy_watcher_browser_agent_observer.h"
 #import "ios/chrome/browser/signin/authentication_service.h"
 #import "ios/chrome/browser/signin/authentication_service_factory.h"
+#import "ios/chrome/browser/ui/authentication/signin/signin_utils.h"
 #import "ios/chrome/browser/ui/commands/policy_signout_commands.h"
 #import "ios/chrome/browser/ui/main/scene_state.h"
 #import "ios/chrome/browser/ui/main/scene_state_browser_agent.h"
@@ -54,7 +55,7 @@
     return;
   }
   prefs_change_observer_->Add(
-      prefs::kSigninAllowed,
+      prefs::kSigninAllowedByPolicy,
       base::BindRepeating(
           &PolicyWatcherBrowserAgent::ForceSignOutIfSigninDisabled,
           base::Unretained(this)));
@@ -66,8 +67,8 @@
 
 void PolicyWatcherBrowserAgent::ForceSignOutIfSigninDisabled() {
   DCHECK(handler_);
-  if (!browser_->GetBrowserState()->GetPrefs()->GetBoolean(
-          prefs::kSigninAllowed)) {
+  if (!signin::IsSigninAllowedByPolicy(
+          browser_->GetBrowserState()->GetPrefs())) {
     AuthenticationService* service =
         AuthenticationServiceFactory::GetForBrowserState(
             browser_->GetBrowserState());
diff --git a/ios/chrome/browser/policy/policy_watcher_browser_agent_unittest.mm b/ios/chrome/browser/policy/policy_watcher_browser_agent_unittest.mm
index 9a03751..b9dbacb 100644
--- a/ios/chrome/browser/policy/policy_watcher_browser_agent_unittest.mm
+++ b/ios/chrome/browser/policy/policy_watcher_browser_agent_unittest.mm
@@ -51,7 +51,8 @@
     chrome_browser_state_ = builder.Build();
 
     // Set the initial pref value.
-    chrome_browser_state_->GetPrefs()->SetBoolean(prefs::kSigninAllowed, true);
+    chrome_browser_state_->GetPrefs()->SetBoolean(prefs::kSigninAllowedByPolicy,
+                                                  true);
 
     // Set up the test browser and attach the browser agents.
     browser_ = std::make_unique<TestBrowser>(chrome_browser_state_.get());
@@ -104,7 +105,8 @@
 // been called.
 TEST_F(PolicyWatcherBrowserAgentTest, NoObservationIfNoInitialize) {
   // Set the initial pref value.
-  chrome_browser_state_->GetPrefs()->SetBoolean(prefs::kSigninAllowed, true);
+  chrome_browser_state_->GetPrefs()->SetBoolean(prefs::kSigninAllowedByPolicy,
+                                                true);
 
   // Set up the test browser and attach the browser agent under test.
   std::unique_ptr<Browser> browser =
@@ -119,16 +121,18 @@
   agent_->AddObserver(&bridge);
 
   // Action: disable browser sign-in.
-  chrome_browser_state_->GetPrefs()->SetBoolean(prefs::kSigninAllowed, false);
+  chrome_browser_state_->GetPrefs()->SetBoolean(prefs::kSigninAllowedByPolicy,
+                                                false);
 
   agent_->RemoveObserver(&bridge);
 }
 
-// Tests that the browser agent monitors the kSigninAllowed pref and notifies
-// its observers when it changes.
-TEST_F(PolicyWatcherBrowserAgentTest, ObservesSigninAllowed) {
+// Tests that the browser agent monitors the kSigninAllowedByPolicy pref and
+// notifies its observers when it changes.
+TEST_F(PolicyWatcherBrowserAgentTest, ObservesSigninAllowedByPolicy) {
   // Set the initial pref value.
-  chrome_browser_state_->GetPrefs()->SetBoolean(prefs::kSigninAllowed, true);
+  chrome_browser_state_->GetPrefs()->SetBoolean(prefs::kSigninAllowedByPolicy,
+                                                true);
   // Set up the mock observer handler.
   id mockObserver =
       OCMStrictProtocolMock(@protocol(PolicyWatcherBrowserAgentObserving));
@@ -143,7 +147,8 @@
       [mockObserver policyWatcherBrowserAgentNotifySignInDisabled:agent_]);
 
   // Action: disable browser sign-in.
-  chrome_browser_state_->GetPrefs()->SetBoolean(prefs::kSigninAllowed, false);
+  chrome_browser_state_->GetPrefs()->SetBoolean(prefs::kSigninAllowedByPolicy,
+                                                false);
 
   // Verify the forceSignOut command was dispatched by the browser agent.
   EXPECT_OCMOCK_VERIFY(mockObserver);
@@ -166,7 +171,8 @@
   agent_->Initialize(mockHandler);
 
   // Action: disable browser sign-in.
-  chrome_browser_state_->GetPrefs()->SetBoolean(prefs::kSigninAllowed, false);
+  chrome_browser_state_->GetPrefs()->SetBoolean(prefs::kSigninAllowedByPolicy,
+                                                false);
 }
 
 // Tests that the pref change triggers a command if the user is signed
@@ -186,7 +192,8 @@
   OCMExpect([mockHandler showPolicySignoutPrompt]);
 
   // Action: disable browser sign-in.
-  chrome_browser_state_->GetPrefs()->SetBoolean(prefs::kSigninAllowed, false);
+  chrome_browser_state_->GetPrefs()->SetBoolean(prefs::kSigninAllowedByPolicy,
+                                                false);
 
   // Verify the forceSignOut command was dispatched by the browser agent.
   EXPECT_OCMOCK_VERIFY(mockHandler);
@@ -212,7 +219,8 @@
   agent_->Initialize(mockHandler);
 
   // Action: disable browser sign-in.
-  chrome_browser_state_->GetPrefs()->SetBoolean(prefs::kSigninAllowed, false);
+  chrome_browser_state_->GetPrefs()->SetBoolean(prefs::kSigninAllowedByPolicy,
+                                                false);
 
   EXPECT_TRUE(scene_state_.appState.shouldShowPolicySignoutPrompt);
   EXPECT_FALSE(authentication_service->IsAuthenticated());
@@ -225,7 +233,8 @@
   // pref changed in background.
 
   // Update the pref and Sign in.
-  chrome_browser_state_->GetPrefs()->SetBoolean(prefs::kSigninAllowed, false);
+  chrome_browser_state_->GetPrefs()->SetBoolean(prefs::kSigninAllowedByPolicy,
+                                                false);
   AuthenticationService* authentication_service =
       AuthenticationServiceFactory::GetForBrowserState(
           chrome_browser_state_.get());
@@ -263,7 +272,8 @@
 // Tests that the command to show the UI isn't sent if the authentication
 // service is still signing out the user.
 TEST_F(PolicyWatcherBrowserAgentTest, UINotShownWhileSignOut) {
-  chrome_browser_state_->GetPrefs()->SetBoolean(prefs::kSigninAllowed, false);
+  chrome_browser_state_->GetPrefs()->SetBoolean(prefs::kSigninAllowedByPolicy,
+                                                false);
 
   AuthenticationService* authentication_service =
       static_cast<AuthenticationServiceFake*>(
@@ -300,7 +310,8 @@
 // Tests that the command to show the UI is sent when the Browser Agent is
 // notified of the UI being dismissed.
 TEST_F(PolicyWatcherBrowserAgentTest, CommandSentWhenUIIsDismissed) {
-  chrome_browser_state_->GetPrefs()->SetBoolean(prefs::kSigninAllowed, false);
+  chrome_browser_state_->GetPrefs()->SetBoolean(prefs::kSigninAllowedByPolicy,
+                                                false);
   SignIn();
 
   // Strict protocol: method calls will fail until the method is stubbed.
diff --git a/ios/chrome/browser/safe_browsing/safe_browsing_blocking_page.h b/ios/chrome/browser/safe_browsing/safe_browsing_blocking_page.h
index 2fd8092..defa8dd 100644
--- a/ios/chrome/browser/safe_browsing/safe_browsing_blocking_page.h
+++ b/ios/chrome/browser/safe_browsing/safe_browsing_blocking_page.h
@@ -60,10 +60,11 @@
 
   // security_interstitials::IOSSecurityInterstitialPage:
   std::string GetHtmlContents() const override;
-  void HandleScriptCommand(const base::DictionaryValue& message,
-                           const GURL& origin_url,
-                           bool user_is_interacting,
-                           web::WebFrame* sender_frame) override;
+  void HandleCommand(
+      security_interstitials::SecurityInterstitialCommand command,
+      const GURL& origin_url,
+      bool user_is_interacting,
+      web::WebFrame* sender_frame) override;
   bool ShouldCreateNewNavigation() const override;
   void PopulateInterstitialStrings(
       base::DictionaryValue* load_time_data) const override;
diff --git a/ios/chrome/browser/safe_browsing/safe_browsing_blocking_page.mm b/ios/chrome/browser/safe_browsing/safe_browsing_blocking_page.mm
index 3e283cc..e930bb8 100644
--- a/ios/chrome/browser/safe_browsing/safe_browsing_blocking_page.mm
+++ b/ios/chrome/browser/safe_browsing/safe_browsing_blocking_page.mm
@@ -121,32 +121,12 @@
   return webui::GetI18nTemplateHtml(html, &load_time_data);
 }
 
-void SafeBrowsingBlockingPage::HandleScriptCommand(
-    const base::DictionaryValue& message,
+void SafeBrowsingBlockingPage::HandleCommand(
+    SecurityInterstitialCommand command,
     const GURL& origin_url,
     bool user_is_interacting,
     web::WebFrame* sender_frame) {
-  // Fetch the command string from the message.
-  std::string command_string;
-  if (!message.GetString("command", &command_string)) {
-    LOG(ERROR) << "JS message parameter not found: command";
-    return;
-  }
-
-  // Remove the command prefix so that the string value can be converted to a
-  // SecurityInterstitialCommand enum value.
-  std::size_t delimiter = command_string.find(".");
-  if (delimiter == std::string::npos)
-    return;
-
-  // Parse the command int value from the text after the delimiter.
-  int command = 0;
-  if (!base::StringToInt(command_string.substr(delimiter + 1), &command)) {
-    NOTREACHED() << "Command cannot be parsed to an int : " << command_string;
-    return;
-  }
-
-  error_ui_->HandleCommand(static_cast<SecurityInterstitialCommand>(command));
+  error_ui_->HandleCommand(command);
   if (command == security_interstitials::CMD_DONT_PROCEED) {
     // |error_ui_| handles recording PROCEED decisions.
     client_->metrics_helper()->RecordUserDecision(
diff --git a/ios/chrome/browser/safe_browsing/safe_browsing_blocking_page_unittest.mm b/ios/chrome/browser/safe_browsing/safe_browsing_blocking_page_unittest.mm
index 8068210..4389f84 100644
--- a/ios/chrome/browser/safe_browsing/safe_browsing_blocking_page_unittest.mm
+++ b/ios/chrome/browser/safe_browsing/safe_browsing_blocking_page_unittest.mm
@@ -63,11 +63,9 @@
   }
 
   void SendCommand(SecurityInterstitialCommand command) {
-    base::DictionaryValue dict;
-    dict.SetKey("command", base::Value("." + base::NumberToString(command)));
-    page_->HandleScriptCommand(dict, url_,
-                               /*user_is_interacting=*/true,
-                               /*sender_frame=*/nullptr);
+    page_->HandleCommand(command, url_,
+                         /*user_is_interacting=*/true,
+                         /*sender_frame=*/nullptr);
   }
 
  protected:
diff --git a/ios/chrome/browser/safe_browsing/safe_browsing_egtest.mm b/ios/chrome/browser/safe_browsing/safe_browsing_egtest.mm
index aa93408..d4827d1 100644
--- a/ios/chrome/browser/safe_browsing/safe_browsing_egtest.mm
+++ b/ios/chrome/browser/safe_browsing/safe_browsing_egtest.mm
@@ -314,6 +314,15 @@
   [ChromeEarlGrey tapWebStateElementWithID:@"details-button"];
   [ChromeEarlGrey waitForWebStateContainingText:kMalwareWarningDetails];
 
+  if (@available(iOS 15.1, *)) {
+  } else {
+    if (@available(iOS 14.5, *)) {
+      // Workaround https://bugs.webkit.org/show_bug.cgi?id=226323, which can
+      // break loading the unsafe page below.
+      return;
+    }
+  }
+
   // Tap on the link to proceed to the unsafe page, and verify that this page is
   // loaded.
   [ChromeEarlGrey tapWebStateElementWithID:@"proceed-link"];
@@ -358,6 +367,15 @@
   [ChromeEarlGrey tapWebStateElementWithID:@"details-button"];
   [ChromeEarlGrey waitForWebStateContainingText:kMalwareWarningDetails];
 
+  if (@available(iOS 15.1, *)) {
+  } else {
+    if (@available(iOS 14.5, *)) {
+      // Workaround https://bugs.webkit.org/show_bug.cgi?id=226323, which can
+      // break loading the unsafe page below.
+      return;
+    }
+  }
+
   // Tap on the link to proceed to the unsafe page, and verify that this page is
   // loaded.
   [ChromeEarlGrey tapWebStateElementWithID:@"proceed-link"];
diff --git a/ios/chrome/browser/search_engines/search_engine_js_unittest.mm b/ios/chrome/browser/search_engines/search_engine_js_unittest.mm
index 29750d5..4395636 100644
--- a/ios/chrome/browser/search_engines/search_engine_js_unittest.mm
+++ b/ios/chrome/browser/search_engines/search_engine_js_unittest.mm
@@ -71,7 +71,7 @@
     WebTestWithWebState::TearDown();
   }
 
-  void OnMessageFromJavaScript(const base::DictionaryValue& message,
+  void OnMessageFromJavaScript(const base::Value& message,
                                const GURL& page_url,
                                bool user_is_interacting,
                                web::WebFrame* sender_frame) {
diff --git a/ios/chrome/browser/search_engines/search_engine_tab_helper.h b/ios/chrome/browser/search_engines/search_engine_tab_helper.h
index 34f14fa..b9f5a2b 100644
--- a/ios/chrome/browser/search_engines/search_engine_tab_helper.h
+++ b/ios/chrome/browser/search_engines/search_engine_tab_helper.h
@@ -58,7 +58,7 @@
   // Handles messages from JavaScript. Messages can be:
   //   1. A OSDD <link> is found;
   //   2. A searchable URL is generated from <form> submission.
-  void OnJsMessage(const base::DictionaryValue& message,
+  void OnJsMessage(const base::Value& message,
                    const GURL& page_url,
                    bool user_is_interacting,
                    web::WebFrame* sender_frame);
diff --git a/ios/chrome/browser/search_engines/search_engine_tab_helper.mm b/ios/chrome/browser/search_engines/search_engine_tab_helper.mm
index d28363c..65293ab 100644
--- a/ios/chrome/browser/search_engines/search_engine_tab_helper.mm
+++ b/ios/chrome/browser/search_engines/search_engine_tab_helper.mm
@@ -124,7 +124,7 @@
   }
 }
 
-void SearchEngineTabHelper::OnJsMessage(const base::DictionaryValue& message,
+void SearchEngineTabHelper::OnJsMessage(const base::Value& message,
                                         const GURL& page_url,
                                         bool user_is_interacting,
                                         web::WebFrame* sender_frame) {
diff --git a/ios/chrome/browser/signin/authentication_service.mm b/ios/chrome/browser/signin/authentication_service.mm
index 03eaa86..610c8a8 100644
--- a/ios/chrome/browser/signin/authentication_service.mm
+++ b/ios/chrome/browser/signin/authentication_service.mm
@@ -531,7 +531,8 @@
       !user_approved_account_list_manager_.GetApprovedAccountIDList().empty());
   identity_manager_->GetDeviceAccountsSynchronizer()
       ->ReloadAllAccountsFromSystemWithPrimaryAccount(
-          identity_manager_->GetPrimaryAccountId(signin::ConsentLevel::kSync));
+          identity_manager_->GetPrimaryAccountId(
+              signin::ConsentLevel::kSignin));
   if (!keychain_reload) {
     // The changes come from Chrome, so we can approve this new account list,
     // since this change comes from the user.
diff --git a/ios/chrome/browser/ssl/ios_captive_portal_blocking_page.h b/ios/chrome/browser/ssl/ios_captive_portal_blocking_page.h
index 8533908..9b2b8384 100644
--- a/ios/chrome/browser/ssl/ios_captive_portal_blocking_page.h
+++ b/ios/chrome/browser/ssl/ios_captive_portal_blocking_page.h
@@ -34,10 +34,11 @@
   // IOSSecurityInterstitialPage overrides:
   bool ShouldCreateNewNavigation() const override;
   void PopulateInterstitialStrings(base::DictionaryValue*) const override;
-  void HandleScriptCommand(const base::DictionaryValue& message,
-                           const GURL& origin_url,
-                           bool user_is_interacting,
-                           web::WebFrame* sender_frame) override;
+  void HandleCommand(
+      security_interstitials::SecurityInterstitialCommand command,
+      const GURL& origin_url,
+      bool user_is_interacting,
+      web::WebFrame* sender_frame) override;
 
   // The landing page url for the captive portal network.
   const GURL landing_url_;
diff --git a/ios/chrome/browser/ssl/ios_captive_portal_blocking_page.mm b/ios/chrome/browser/ssl/ios_captive_portal_blocking_page.mm
index a03f0f5..aeb4a38 100644
--- a/ios/chrome/browser/ssl/ios_captive_portal_blocking_page.mm
+++ b/ios/chrome/browser/ssl/ios_captive_portal_blocking_page.mm
@@ -84,29 +84,13 @@
   load_time_data->SetBoolean("show_recurrent_error_paragraph", false);
 }
 
-void IOSCaptivePortalBlockingPage::HandleScriptCommand(
-    const base::DictionaryValue& message,
+void IOSCaptivePortalBlockingPage::HandleCommand(
+    security_interstitials::SecurityInterstitialCommand command,
     const GURL& origin_url,
     bool user_is_interacting,
     web::WebFrame* sender_frame) {
-  std::string command;
-  if (!message.GetString("command", &command)) {
-    LOG(ERROR) << "JS message parameter not found: command";
-    return;
-  }
-
-  // Remove the command prefix since it is ignored when converting the value
-  // to a SecurityInterstitialCommand.
-  std::size_t delimiter = command.find(".");
-  if (delimiter == std::string::npos)
-    return;
-
-  std::string command_suffix = command.substr(delimiter + 1);
-  int command_num = 0;
-  bool command_is_num = base::StringToInt(command_suffix, &command_num);
-  DCHECK(command_is_num) << command_suffix;
   // Any command other than "open the login page" is ignored.
-  if (command_num == security_interstitials::CMD_OPEN_LOGIN) {
+  if (command == security_interstitials::CMD_OPEN_LOGIN) {
     captive_portal::CaptivePortalMetrics::LogCaptivePortalBlockingPageEvent(
         captive_portal::CaptivePortalMetrics::OPEN_LOGIN_PAGE);
 
diff --git a/ios/chrome/browser/ssl/ios_ssl_blocking_page.h b/ios/chrome/browser/ssl/ios_ssl_blocking_page.h
index a1650a4..8989fbe7 100644
--- a/ios/chrome/browser/ssl/ios_ssl_blocking_page.h
+++ b/ios/chrome/browser/ssl/ios_ssl_blocking_page.h
@@ -50,10 +50,11 @@
       base::DictionaryValue* load_time_data) const override;
 
  private:
-  void HandleScriptCommand(const base::DictionaryValue& message,
-                           const GURL& origin_url,
-                           bool user_is_interacting,
-                           web::WebFrame* sender_frame) override;
+  void HandleCommand(
+      security_interstitials::SecurityInterstitialCommand command,
+      const GURL& origin_url,
+      bool user_is_interacting,
+      web::WebFrame* sender_frame) override;
 
   // Returns true if |options_mask| refers to a soft-overridable SSL error.
   static bool IsOverridable(int options_mask);
diff --git a/ios/chrome/browser/ssl/ios_ssl_blocking_page.mm b/ios/chrome/browser/ssl/ios_ssl_blocking_page.mm
index 4f9bf39..109ee71 100644
--- a/ios/chrome/browser/ssl/ios_ssl_blocking_page.mm
+++ b/ios/chrome/browser/ssl/ios_ssl_blocking_page.mm
@@ -81,31 +81,14 @@
          !(options_mask & SSLErrorOptionsMask::STRICT_ENFORCEMENT);
 }
 
-void IOSSSLBlockingPage::HandleScriptCommand(
-    const base::DictionaryValue& message,
+void IOSSSLBlockingPage::HandleCommand(
+    security_interstitials::SecurityInterstitialCommand command,
     const GURL& origin_url,
     bool user_is_interacting,
     web::WebFrame* sender_frame) {
-  std::string command;
-  if (!message.GetString("command", &command)) {
-    LOG(ERROR) << "JS message parameter not found: command";
-    return;
-  }
-
-  // Remove the command prefix so that the string value can be converted to a
-  // SecurityInterstitialCommand enum value.
-  std::size_t delimiter = command.find(".");
-  if (delimiter == std::string::npos) {
-    return;
-  }
-  std::string command_str = command.substr(delimiter + 1);
-  int command_num = 0;
-  bool command_is_num = base::StringToInt(command_str, &command_num);
-  DCHECK(command_is_num) << command_str;
-
   // If a proceed command is received, allowlist the certificate and reload
   // the page to re-initiate the original navigation.
-  if (command_num == security_interstitials::CMD_PROCEED) {
+  if (command == security_interstitials::CMD_PROCEED) {
     web_state_->GetSessionCertificatePolicyCache()->RegisterAllowedCertificate(
         ssl_info_.cert, request_url().host(), ssl_info_.cert_status);
     web_state_->GetNavigationManager()->Reload(web::ReloadType::NORMAL,
@@ -113,7 +96,5 @@
     return;
   }
 
-  ssl_error_ui_->HandleCommand(
-      static_cast<security_interstitials::SecurityInterstitialCommand>(
-          command_num));
+  ssl_error_ui_->HandleCommand(command);
 }
diff --git a/ios/chrome/browser/translate/language_detection_javascript_unittest.mm b/ios/chrome/browser/translate/language_detection_javascript_unittest.mm
index ad02860e0..ed91175 100644
--- a/ios/chrome/browser/translate/language_detection_javascript_unittest.mm
+++ b/ios/chrome/browser/translate/language_detection_javascript_unittest.mm
@@ -246,16 +246,16 @@
         web_state()->AddScriptCommandCallback(callback, "languageDetection");
   }
   // Called when "languageDetection" command is received.
-  void CommandReceived(const base::DictionaryValue& command,
+  void CommandReceived(const base::Value& command,
                        const GURL& url,
                        bool user_is_interacting,
                        web::WebFrame* sender_frame) {
-    commands_received_.push_back(command.CreateDeepCopy());
+    commands_received_.push_back(command.Clone());
   }
 
  protected:
   // Received "languageDetection" commands.
-  std::vector<std::unique_ptr<base::DictionaryValue>> commands_received_;
+  std::vector<base::Value> commands_received_;
 
   // Subscription for JS message.
   base::CallbackListSubscription subscription_;
@@ -287,11 +287,11 @@
   InjectJSAndWaitUntilCondition(@"__gCrWeb.languageDetection.detectLanguage()",
                                 commands_recieved_block);
   ASSERT_EQ(1U, commands_received_.size());
-  base::DictionaryValue* value = commands_received_[0].get();
-  EXPECT_TRUE(value->HasKey("translationAllowed"));
-  bool translation_allowed = true;
-  value->GetBoolean("translationAllowed", &translation_allowed);
-  EXPECT_FALSE(translation_allowed);
+  const base::Value& value = commands_received_[0];
+  absl::optional<bool> translation_allowed =
+      value.FindBoolKey("translationAllowed");
+  ASSERT_TRUE(translation_allowed);
+  EXPECT_FALSE(*translation_allowed);
 }
 
 // Tests if |__gCrWeb.languageDetection.detectLanguage| correctly informs the
@@ -317,14 +317,14 @@
     return !commands_received_.empty();
   });
   ASSERT_EQ(1U, commands_received_.size());
-  base::DictionaryValue* value = commands_received_[0].get();
+  const base::Value& value = commands_received_[0];
 
-  EXPECT_TRUE(value->HasKey("translationAllowed"));
-  EXPECT_TRUE(value->HasKey("captureTextTime"));
-  EXPECT_TRUE(value->HasKey("htmlLang"));
-  EXPECT_TRUE(value->HasKey("httpContentLanguage"));
+  absl::optional<bool> translation_allowed =
+      value.FindBoolKey("translationAllowed");
 
-  bool translation_allowed = false;
-  value->GetBoolean("translationAllowed", &translation_allowed);
-  EXPECT_TRUE(translation_allowed);
+  ASSERT_TRUE(translation_allowed);
+  EXPECT_TRUE(value.FindKey("captureTextTime"));
+  EXPECT_TRUE(value.FindKey("htmlLang"));
+  EXPECT_TRUE(value.FindKey("httpContentLanguage"));
+  EXPECT_TRUE(*translation_allowed);
 }
diff --git a/ios/chrome/browser/ui/authentication/signed_in_accounts_view_controller_unittest.mm b/ios/chrome/browser/ui/authentication/signed_in_accounts_view_controller_unittest.mm
index 4b53483..4126708 100644
--- a/ios/chrome/browser/ui/authentication/signed_in_accounts_view_controller_unittest.mm
+++ b/ios/chrome/browser/ui/authentication/signed_in_accounts_view_controller_unittest.mm
@@ -54,7 +54,8 @@
   }
 
  protected:
-  web::WebTaskEnvironment task_environment_;
+  web::WebTaskEnvironment task_environment_{
+      web::WebTaskEnvironment::IO_MAINLOOP};
   signin::IdentityTestEnvironment identity_test_env_;
   std::unique_ptr<TestChromeBrowserState> browser_state_;
 };
diff --git a/ios/chrome/browser/ui/authentication/signin/signin_utils.h b/ios/chrome/browser/ui/authentication/signin/signin_utils.h
index 7e7a130..a4944f4 100644
--- a/ios/chrome/browser/ui/authentication/signin/signin_utils.h
+++ b/ios/chrome/browser/ui/authentication/signin/signin_utils.h
@@ -33,7 +33,7 @@
 bool IsSigninAllowed(const PrefService* prefs);
 
 // Returns a boolean indicating whether policy allows browser sign-in.
-bool IsSigninAllowedByPolicy();
+bool IsSigninAllowedByPolicy(const PrefService* prefs);
 
 }  // namespace signin
 
diff --git a/ios/chrome/browser/ui/authentication/signin/signin_utils.mm b/ios/chrome/browser/ui/authentication/signin/signin_utils.mm
index a4ba192..f749c6c 100644
--- a/ios/chrome/browser/ui/authentication/signin/signin_utils.mm
+++ b/ios/chrome/browser/ui/authentication/signin/signin_utils.mm
@@ -80,7 +80,7 @@
   if (net::NetworkChangeNotifier::IsOffline())
     return false;
 
-  // Sign-in can be disabled by policy.
+  // Sign-in can be disabled by policy or through user Settings.
   if (!signin::IsSigninAllowed(browser_state->GetPrefs()))
     return false;
 
@@ -157,22 +157,12 @@
 }
 
 bool IsSigninAllowed(const PrefService* prefs) {
-  return prefs->GetBoolean(prefs::kSigninAllowed);
+  return prefs->GetBoolean(prefs::kSigninAllowed) &&
+         prefs->GetBoolean(prefs::kSigninAllowedByPolicy);
 }
 
-bool IsSigninAllowedByPolicy() {
-  NSDictionary* configuration = [[NSUserDefaults standardUserDefaults]
-      dictionaryForKey:kPolicyLoaderIOSConfigurationKey];
-
-  NSValue* value = [configuration
-      valueForKey:base::SysUTF8ToNSString(policy::key::kBrowserSignin)];
-  if (!value) {
-    return true;
-  }
-
-  BrowserSigninMode signin_mode;
-  [value getValue:&signin_mode];
-  return signin_mode == BrowserSigninMode::kEnabled;
+bool IsSigninAllowedByPolicy(const PrefService* prefs) {
+  return prefs->GetBoolean(prefs::kSigninAllowedByPolicy);
 }
 
 }  // namespace signin
diff --git a/ios/chrome/browser/ui/authentication/signin/signin_utils_unittest.mm b/ios/chrome/browser/ui/authentication/signin/signin_utils_unittest.mm
index e030a34..38d3da8 100644
--- a/ios/chrome/browser/ui/authentication/signin/signin_utils_unittest.mm
+++ b/ios/chrome/browser/ui/authentication/signin/signin_utils_unittest.mm
@@ -235,7 +235,8 @@
   const base::Version version_1_0("1.0");
   ios::FakeChromeIdentityService::GetInstanceFromChromeProvider()
       ->AddIdentities(@[ @"foo1" ]);
-  chrome_browser_state_->GetPrefs()->SetBoolean(prefs::kSigninAllowed, false);
+  chrome_browser_state_->GetPrefs()->SetBoolean(prefs::kSigninAllowedByPolicy,
+                                                false);
 
   EXPECT_FALSE(signin::ShouldPresentUserSigninUpgrade(
       chrome_browser_state_.get(), version_1_0));
@@ -248,12 +249,39 @@
   // Sign-in is allowed by default.
   EXPECT_TRUE(signin::IsSigninAllowed(chrome_browser_state_.get()->GetPrefs()));
 
-  // When sign-in is disabled by policy, the accessor should return false.
+  // When sign-in is disabled, the accessor should return false.
   chrome_browser_state_->GetPrefs()->SetBoolean(prefs::kSigninAllowed, false);
   EXPECT_FALSE(
       signin::IsSigninAllowed(chrome_browser_state_.get()->GetPrefs()));
 }
 
+// signin::IsSigninAllowedByPolicy should respect the kSigninAllowedByPolicy
+// pref.
+TEST_F(SigninUtilsTest, TestSigninAllowedByPolicyPref) {
+  ios::FakeChromeIdentityService::GetInstanceFromChromeProvider()
+      ->AddIdentities(@[ @"foo", @"bar" ]);
+  // Sign-in is allowed by default.
+  EXPECT_TRUE(
+      signin::IsSigninAllowedByPolicy(chrome_browser_state_.get()->GetPrefs()));
+
+  // When sign-in is disabled by policy, the accessor should return false.
+  chrome_browser_state_->GetPrefs()->SetBoolean(prefs::kSigninAllowedByPolicy,
+                                                false);
+  EXPECT_FALSE(
+      signin::IsSigninAllowedByPolicy(chrome_browser_state_.get()->GetPrefs()));
+  EXPECT_FALSE(
+      signin::IsSigninAllowed(chrome_browser_state_.get()->GetPrefs()));
+
+  // When sign-in is explicitly enabled by the user, but the policy has not
+  // changed the accessor should return false.
+  chrome_browser_state_->GetPrefs()->SetBoolean(prefs::kSigninAllowed, true);
+
+  EXPECT_FALSE(
+      signin::IsSigninAllowedByPolicy(chrome_browser_state_.get()->GetPrefs()));
+  EXPECT_FALSE(
+      signin::IsSigninAllowed(chrome_browser_state_.get()->GetPrefs()));
+}
+
 // Show the sign-in upgrade on version 1.0.
 // Move to version 3.0.
 // Add an account subject to minor mode restrictions.
diff --git a/ios/chrome/browser/ui/content_suggestions/content_suggestions_coordinator.mm b/ios/chrome/browser/ui/content_suggestions/content_suggestions_coordinator.mm
index 29a3b63f..2679dd80 100644
--- a/ios/chrome/browser/ui/content_suggestions/content_suggestions_coordinator.mm
+++ b/ios/chrome/browser/ui/content_suggestions/content_suggestions_coordinator.mm
@@ -8,7 +8,6 @@
 #include "base/mac/foundation_util.h"
 #include "base/metrics/user_metrics.h"
 #include "base/metrics/user_metrics_action.h"
-#include "base/scoped_observer.h"
 #include "base/strings/sys_string_conversions.h"
 #import "components/feature_engagement/public/event_constants.h"
 #import "components/feature_engagement/public/tracker.h"
diff --git a/ios/chrome/browser/ui/content_suggestions/ntp_home_mediator.mm b/ios/chrome/browser/ui/content_suggestions/ntp_home_mediator.mm
index bd4ce056..f553756 100644
--- a/ios/chrome/browser/ui/content_suggestions/ntp_home_mediator.mm
+++ b/ios/chrome/browser/ui/content_suggestions/ntp_home_mediator.mm
@@ -11,7 +11,6 @@
 #include "base/mac/foundation_util.h"
 #include "base/metrics/user_metrics.h"
 #include "base/metrics/user_metrics_action.h"
-#include "base/scoped_observer.h"
 #include "components/ntp_snippets/content_suggestions_service.h"
 #include "components/ntp_snippets/features.h"
 #import "components/signin/public/identity_manager/objc/identity_manager_observer_bridge.h"
diff --git a/ios/chrome/browser/ui/first_run/signin/signin_screen_coordinator.mm b/ios/chrome/browser/ui/first_run/signin/signin_screen_coordinator.mm
index 7bad73f..e2b2d982 100644
--- a/ios/chrome/browser/ui/first_run/signin/signin_screen_coordinator.mm
+++ b/ios/chrome/browser/ui/first_run/signin/signin_screen_coordinator.mm
@@ -71,10 +71,11 @@
 }
 
 - (void)start {
-  // TODO(crbug.com/1189836): The kSigninAllowed pref should be observed in case
-  // the policy is applied while this screen is presented.
+  // TODO(crbug.com/1189836): The kSigninAllowedByPolicy pref should be observed
+  // in case the policy is applied while this screen is presented.
 
-  if (!signin::IsSigninAllowed(self.browser->GetBrowserState()->GetPrefs())) {
+  if (!signin::IsSigninAllowedByPolicy(
+          self.browser->GetBrowserState()->GetPrefs())) {
     self.attemptStatus = first_run::SignInAttemptStatus::SKIPPED_BY_POLICY;
     [self finishPresentingAndSkipRemainingScreens:NO];
     return;
diff --git a/ios/chrome/browser/ui/first_run/welcome_to_chrome_view_controller.mm b/ios/chrome/browser/ui/first_run/welcome_to_chrome_view_controller.mm
index a89b5fc..7798a06 100644
--- a/ios/chrome/browser/ui/first_run/welcome_to_chrome_view_controller.mm
+++ b/ios/chrome/browser/ui/first_run/welcome_to_chrome_view_controller.mm
@@ -221,7 +221,8 @@
       ios::GetChromeBrowserProvider()->GetChromeIdentityService();
   self.firstRunConfig.hasSSOAccount = identityService->HasIdentities();
 
-  if (!signin::IsSigninAllowed(_browser->GetBrowserState()->GetPrefs())) {
+  if (!signin::IsSigninAllowedByPolicy(
+          _browser->GetBrowserState()->GetPrefs())) {
     // Sign-in is disabled by policy. Skip the sign-in flow.
     self.firstRunConfig.signInAttemptStatus =
         first_run::SignInAttemptStatus::SKIPPED_BY_POLICY;
diff --git a/ios/chrome/browser/ui/main/scene_controller.mm b/ios/chrome/browser/ui/main/scene_controller.mm
index bf350d8..1e5ab32 100644
--- a/ios/chrome/browser/ui/main/scene_controller.mm
+++ b/ios/chrome/browser/ui/main/scene_controller.mm
@@ -977,7 +977,7 @@
   }
   if (postOpeningAction == NO_ACTION &&
       !self.sceneState.appState.postCrashLaunch &&
-      !IsChromeLikelyDefaultBrowser() && !UserInPromoCooldown()) {
+      !IsChromeLikelyDefaultBrowser()) {
     // Show the Default Browser promo UI if the user's past behavior fits
     // the categorization of potentially interested users or if the user is
     // signed in. Do not show if it is determined that Chrome is already the
@@ -1006,7 +1006,7 @@
         !HasUserInteractedWithTailoredFullscreenPromoBefore() &&
         (isMadeForIOSPromoEligible || isAllTabsPromoEligible ||
          isStaySafePromoEligible);
-    if (isTailoredPromoEligibleUser) {
+    if (isTailoredPromoEligibleUser && !UserInPromoCooldown()) {
       self.sceneState.appState.shouldShowDefaultBrowserPromo = YES;
       self.sceneState.appState.defaultBrowserPromoTypeToShow =
           MostRecentInterestDefaultPromoType(!isSignedIn);
@@ -1018,7 +1018,8 @@
     BOOL isGeneralPromoEligibleUser =
         !HasUserInteractedWithFullscreenPromoBefore() &&
         (IsLikelyInterestedDefaultBrowserUser(DefaultPromoTypeGeneral) ||
-         isSignedIn);
+         isSignedIn) &&
+        !UserInPromoCooldown();
     if (isGeneralPromoEligibleUser ||
         ShouldShowRemindMeLaterDefaultBrowserFullscreenPromo()) {
       self.sceneState.appState.shouldShowDefaultBrowserPromo = YES;
@@ -2713,7 +2714,7 @@
 - (void)startSigninCoordinatorWithCompletion:
     (signin_ui::CompletionCallback)completion {
   DCHECK(self.signinCoordinator);
-  if (!signin::IsSigninAllowed(
+  if (!signin::IsSigninAllowedByPolicy(
           self.signinCoordinator.browser->GetBrowserState()->GetPrefs())) {
     completion(/*success=*/NO);
     [self.signinCoordinator stop];
diff --git a/ios/chrome/browser/ui/overlays/overlay_container_coordinator.mm b/ios/chrome/browser/ui/overlays/overlay_container_coordinator.mm
index 3d579b8f..b0699c5 100644
--- a/ios/chrome/browser/ui/overlays/overlay_container_coordinator.mm
+++ b/ios/chrome/browser/ui/overlays/overlay_container_coordinator.mm
@@ -8,7 +8,6 @@
 #include <memory>
 
 #include "base/check_op.h"
-#include "base/scoped_observer.h"
 #import "ios/chrome/browser/main/browser.h"
 #import "ios/chrome/browser/ui/overlays/overlay_container_view_controller.h"
 #import "ios/chrome/browser/ui/overlays/overlay_presentation_context_coordinator.h"
diff --git a/ios/chrome/browser/ui/overlays/overlay_presentation_context_coordinator.mm b/ios/chrome/browser/ui/overlays/overlay_presentation_context_coordinator.mm
index f56e59d3..a128c63 100644
--- a/ios/chrome/browser/ui/overlays/overlay_presentation_context_coordinator.mm
+++ b/ios/chrome/browser/ui/overlays/overlay_presentation_context_coordinator.mm
@@ -8,7 +8,6 @@
 
 #include "base/check.h"
 #import "base/ios/block_types.h"
-#include "base/scoped_observer.h"
 #import "ios/chrome/browser/ui/overlays/overlay_presentation_context_impl.h"
 #import "ios/chrome/browser/ui/overlays/overlay_presentation_context_view_controller.h"
 
diff --git a/ios/chrome/browser/ui/recent_tabs/recent_tabs_table_view_controller.mm b/ios/chrome/browser/ui/recent_tabs/recent_tabs_table_view_controller.mm
index 7e40bc9..acda4fad 100644
--- a/ios/chrome/browser/ui/recent_tabs/recent_tabs_table_view_controller.mm
+++ b/ios/chrome/browser/ui/recent_tabs/recent_tabs_table_view_controller.mm
@@ -464,9 +464,10 @@
 - (void)addOtherDevicesSectionForState:(SessionsSyncUserState)state {
   // If sign-in is disabled through user Settings, do not show Other Devices
   // section. However, if sign-in is disabled by policy Chrome will
-  // continue to show the Other Devices section with a specialized mesage.
-  if (!signin::IsSigninAllowed(self.browserState->GetPrefs()) &&
-      signin::IsSigninAllowedByPolicy()) {
+  // continue to show the Other Devices section with a specialized message.
+  const PrefService* prefs = self.browserState->GetPrefs();
+  if (!signin::IsSigninAllowed(prefs) &&
+      signin::IsSigninAllowedByPolicy(prefs)) {
     return;
   }
 
@@ -1331,6 +1332,17 @@
             (SigninPromoViewConfigurator*)configurator
                              identityChanged:(BOOL)identityChanged {
   DCHECK(self.signinPromoViewMediator);
+  if (![self.tableViewModel
+          hasSectionForSectionIdentifier:SectionIdentifierOtherDevices]) {
+    // Need to remove the sign-in promo view mediator when the section doesn't
+    // exist anymore. The mediator should not be removed each time the section
+    // is removed since the section is replaced at each reload.
+    // Metrics would be recorded too often.
+    [self.signinPromoViewMediator signinPromoViewIsRemoved];
+    self.signinPromoViewMediator.consumer = nil;
+    self.signinPromoViewMediator = nil;
+    return;
+  }
   // Update the TableViewSigninPromoItem configurator. It will be used by the
   // item to configure the cell once |self.tableView| requests a cell on
   // cellForRowAtIndexPath.
diff --git a/ios/chrome/browser/ui/settings/google_services/accounts_table_view_controller.mm b/ios/chrome/browser/ui/settings/google_services/accounts_table_view_controller.mm
index 9ba0747..56ef58b 100644
--- a/ios/chrome/browser/ui/settings/google_services/accounts_table_view_controller.mm
+++ b/ios/chrome/browser/ui/settings/google_services/accounts_table_view_controller.mm
@@ -445,12 +445,14 @@
                       "-stopBrowserStateServiceObservers";
 
   [self reloadData];
-  [self popViewIfSignedOut];
   if (![self authService] -> IsAuthenticated() &&
                                  _dimissAccountDetailsViewControllerBlock) {
     _dimissAccountDetailsViewControllerBlock(/*animated=*/YES);
     _dimissAccountDetailsViewControllerBlock = nil;
   }
+  // Only attempt to pop the top-most view controller once the account list
+  // has been dismissed.
+  [self popViewIfSignedOut];
 }
 
 #pragma mark - Authentication operations
diff --git a/ios/chrome/browser/ui/settings/google_services/google_services_settings_mediator.mm b/ios/chrome/browser/ui/settings/google_services/google_services_settings_mediator.mm
index 126c3bb..f83d94b 100644
--- a/ios/chrome/browser/ui/settings/google_services/google_services_settings_mediator.mm
+++ b/ios/chrome/browser/ui/settings/google_services/google_services_settings_mediator.mm
@@ -583,7 +583,7 @@
       case AllowChromeSigninItemType: {
         SyncSwitchItem* signinDisabledItem =
             base::mac::ObjCCast<SyncSwitchItem>(item);
-        if (signin::IsSigninAllowedByPolicy()) {
+        if (signin::IsSigninAllowedByPolicy(self.userPrefService)) {
           signinDisabledItem.on = self.allowChromeSigninPreference.value;
         } else {
           signinDisabledItem.on = NO;
@@ -692,7 +692,7 @@
     NSMutableArray* items = [NSMutableArray array];
     if (signin::IsMobileIdentityConsistencyEnabled()) {
       int detailTextID =
-          signin::IsSigninAllowedByPolicy()
+          signin::IsSigninAllowedByPolicy(self.userPrefService)
               ? IDS_IOS_GOOGLE_SERVICES_SETTINGS_ALLOW_SIGNIN_DETAIL
               : IDS_IOS_GOOGLE_SERVICES_SETTINGS_SIGNIN_DISABLED_BY_ADMINISTRATOR;
       SyncSwitchItem* allowSigninItem =
diff --git a/ios/chrome/browser/ui/settings/settings_table_view_controller.mm b/ios/chrome/browser/ui/settings/settings_table_view_controller.mm
index e448d753..0cb1c43 100644
--- a/ios/chrome/browser/ui/settings/settings_table_view_controller.mm
+++ b/ios/chrome/browser/ui/settings/settings_table_view_controller.mm
@@ -542,7 +542,7 @@
       AuthenticationServiceFactory::GetForBrowserState(_browserState);
   // If sign-in is disabled there should not be a sign-in promo.
   if (!signin::IsSigninAllowed(_browserState->GetPrefs())) {
-    item = signin::IsSigninAllowedByPolicy()
+    item = signin::IsSigninAllowedByPolicy(_browserState->GetPrefs())
                ? [self signinDisabledTextItem]
                : [self signinDisabledByPolicyTextItem];
   } else if (self.shouldDisplaySyncPromo || self.shouldDisplaySigninPromo) {
@@ -1137,7 +1137,7 @@
     case SettingsItemTypeSigninDisabled: {
       // Adds a trailing button with more information when the sign-in policy
       // has been enabled by the organization.
-      if (!signin::IsSigninAllowedByPolicy()) {
+      if (!signin::IsSigninAllowedByPolicy(_browserState->GetPrefs())) {
         TableViewInfoButtonCell* managedCell =
             base::mac::ObjCCastStrict<TableViewInfoButtonCell>(cell);
         [managedCell.trailingButton
diff --git a/ios/chrome/browser/ui/settings/settings_table_view_controller_unittest.mm b/ios/chrome/browser/ui/settings/settings_table_view_controller_unittest.mm
index 8a813e8..fbb72f5 100644
--- a/ios/chrome/browser/ui/settings/settings_table_view_controller_unittest.mm
+++ b/ios/chrome/browser/ui/settings/settings_table_view_controller_unittest.mm
@@ -212,7 +212,8 @@
 // item if sign-in is disabled by policy.
 TEST_F(SettingsTableViewControllerTest, SigninDisabledByPolicy) {
   AddSigninDisabledEnterprisePolicy();
-  chrome_browser_state_->GetPrefs()->SetBoolean(prefs::kSigninAllowed, false);
+  chrome_browser_state_->GetPrefs()->SetBoolean(prefs::kSigninAllowedByPolicy,
+                                                false);
   CreateController();
   CheckController();
 
diff --git a/ios/chrome/browser/ui/toolbar/toolbar_mediator.mm b/ios/chrome/browser/ui/toolbar/toolbar_mediator.mm
index 247bd1f..e801385a 100644
--- a/ios/chrome/browser/ui/toolbar/toolbar_mediator.mm
+++ b/ios/chrome/browser/ui/toolbar/toolbar_mediator.mm
@@ -5,7 +5,6 @@
 #import "ios/chrome/browser/ui/toolbar/toolbar_mediator.h"
 
 #include "base/memory/ptr_util.h"
-#include "base/scoped_observer.h"
 #include "base/strings/sys_string_conversions.h"
 #include "components/bookmarks/browser/bookmark_model.h"
 #include "components/bookmarks/common/bookmark_pref_names.h"
diff --git a/ios/chrome/browser/voice/voice_search_availability_unittest.mm b/ios/chrome/browser/voice/voice_search_availability_unittest.mm
index 0099a94..93a6ec7 100644
--- a/ios/chrome/browser/voice/voice_search_availability_unittest.mm
+++ b/ios/chrome/browser/voice/voice_search_availability_unittest.mm
@@ -4,7 +4,6 @@
 
 #import "ios/chrome/browser/voice/voice_search_availability.h"
 
-#include "base/scoped_observer.h"
 #import "ios/chrome/browser/voice/fake_voice_search_availability.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/platform_test.h"
diff --git a/ios/chrome/browser/web/error_page_controller_bridge.h b/ios/chrome/browser/web/error_page_controller_bridge.h
index 11eb1a2..163cf9e8 100644
--- a/ios/chrome/browser/web/error_page_controller_bridge.h
+++ b/ios/chrome/browser/web/error_page_controller_bridge.h
@@ -36,7 +36,7 @@
   ErrorPageControllerBridge(web::WebState* web_state);
 
   // Handler for "errorPageController.*" JavaScript command.
-  void OnErrorPageCommand(const base::DictionaryValue& message,
+  void OnErrorPageCommand(const base::Value& message,
                           const GURL& url,
                           bool user_is_interacting,
                           web::WebFrame* sender_frame);
diff --git a/ios/chrome/browser/web/error_page_controller_bridge.mm b/ios/chrome/browser/web/error_page_controller_bridge.mm
index a7b1db66..8034531 100644
--- a/ios/chrome/browser/web/error_page_controller_bridge.mm
+++ b/ios/chrome/browser/web/error_page_controller_bridge.mm
@@ -38,37 +38,40 @@
 }
 
 void ErrorPageControllerBridge::OnErrorPageCommand(
-    const base::DictionaryValue& message,
+    const base::Value& message,
     const GURL& url,
     bool user_is_interacting,
     web::WebFrame* sender_frame) {
-  std::string command;
-  if (!message.GetString("command", &command)) {
+  const std::string* command = message.FindStringKey("command");
+  if (!command) {
     return;
   }
-  if (command == "errorPageController.updateEasterEggHighScore") {
-    std::string high_score_string;
-    if (!message.GetString("highScore", &high_score_string)) {
+  if (*command == "errorPageController.updateEasterEggHighScore") {
+    const std::string* high_score_string = message.FindStringKey("highScore");
+    if (!high_score_string) {
       return;
     }
     int high_score;
-    if (!base::StringToInt(high_score_string, &high_score)) {
+    if (!base::StringToInt(*high_score_string, &high_score)) {
       return;
     }
     [[NSUserDefaults standardUserDefaults] setInteger:high_score
                                                forKey:kEasterEggHighScore];
+    return;
   }
-  if (command == "errorPageController.resetEasterEggHighScore") {
+  if (*command == "errorPageController.resetEasterEggHighScore") {
     [[NSUserDefaults standardUserDefaults]
         removeObjectForKey:kEasterEggHighScore];
+    return;
   }
-  if (command == "errorPageController.trackEasterEgg") {
+  if (*command == "errorPageController.trackEasterEgg") {
     int high_score = [[NSUserDefaults standardUserDefaults]
         integerForKey:kEasterEggHighScore];
     std::vector<base::Value> parameters;
     parameters.push_back(base::Value(high_score));
     sender_frame->CallJavaScriptFunction(
         "errorPageController.initializeEasterEggHighScore", parameters);
+    return;
   }
 }
 
diff --git a/ios/chrome/browser/web/font_size/BUILD.gn b/ios/chrome/browser/web/font_size/BUILD.gn
index ca2430a..5b63c49 100644
--- a/ios/chrome/browser/web/font_size/BUILD.gn
+++ b/ios/chrome/browser/web/font_size/BUILD.gn
@@ -29,7 +29,10 @@
 }
 
 js_compile_bundle("font_size_js") {
-  visibility = [ ":font_size" ]
+  visibility = [
+    ":font_size",
+    ":unit_tests",
+  ]
   closure_entry_point = "__crWeb.font_size"
 
   sources = [ "resources/font_size.js" ]
@@ -38,9 +41,13 @@
 source_set("unit_tests") {
   configs += [ "//build/config/compiler:enable_arc" ]
   testonly = true
-  sources = [ "font_size_tab_helper_unittest.mm" ]
+  sources = [
+    "font_size_js_unittest.mm",
+    "font_size_tab_helper_unittest.mm",
+  ]
   deps = [
     ":font_size",
+    ":font_size_js",
     "//base",
     "//base/test:test_support",
     "//components/sync_preferences",
@@ -57,5 +64,6 @@
     "//ios/web/public/test",
     "//testing/gtest",
     "//third_party/ocmock",
+    "//ui/base",
   ]
 }
diff --git a/ios/chrome/browser/web/font_size/font_size_js_unittest.mm b/ios/chrome/browser/web/font_size/font_size_js_unittest.mm
index 90ebb58..f946d127 100644
--- a/ios/chrome/browser/web/font_size/font_size_js_unittest.mm
+++ b/ios/chrome/browser/web/font_size/font_size_js_unittest.mm
@@ -21,7 +21,7 @@
 class FontSizeJsTest : public web::WebJsTest<web::WebTestWithWebState> {
  public:
   FontSizeJsTest()
-      : web::WebJsTest<web::WebTestWithWebState>(@[ @"accessibility" ]) {}
+      : web::WebJsTest<web::WebTestWithWebState>(@[ @"font_size_js" ]) {}
 
   // Find DOM element by |element_id| and get computed font size in px.
   float GetElementFontSize(NSString* element_id) {
@@ -47,23 +47,23 @@
                                    html]);
   }
 
-  // Executes JavaScript "__gCrWeb.accessibility.adjustFontSize(|scale|)" to
+  // Executes JavaScript "__gCrWeb.font_size.adjustFontSize(|scale|)" to
   // adjust font size to |scale|% and return if it is executed without
   // exception.
   bool AdjustFontSize(int scale) WARN_UNUSED_RESULT {
     id script_result = ExecuteJavaScriptWithFormat(
-        @"__gCrWeb.accessibility.adjustFontSize(%d); true;", scale);
+        @"__gCrWeb.font_size.adjustFontSize(%d); true;", scale);
     return [script_result isEqual:@YES];
   }
 
   DISALLOW_COPY_AND_ASSIGN(FontSizeJsTest);
 };
 
-// Tests that __gCrWeb.accessibility.adjustFontSize works for any scale.
+// Tests that __gCrWeb.font_size.adjustFontSize works for any scale.
 TEST_F(FontSizeJsTest, TestAdjustFontSizeForScale) {
-  // TODO(crbug.com/983776): This test fails on ipad since beta5 due to a
-  // simulator bug. Re-enable this once the bug is fixed.
-  if (base::ios::IsRunningOnIOS13OrLater() &&
+  // TODO(crbug.com/983776): This test fails on iOS 13 ipad due to a
+  // simulator bug. Re-enable once iOS 13 support is dropped.
+  if (!base::ios::IsRunningOnIOS14OrLater() &&
       ui::GetDeviceFormFactor() == ui::DEVICE_FORM_FACTOR_TABLET) {
     return;
   }
@@ -180,11 +180,11 @@
   EXPECT_FLOAT_EQ(current_size, original_size * 200 / 100);
 }
 
-// Tests that __gCrWeb.accessibility.adjustFontSize works for any CSS unit.
+// Tests that __gCrWeb.font_size.adjustFontSize works for any CSS unit.
 TEST_F(FontSizeJsTest, TestAdjustFontSizeForUnit) {
-  // TODO(crbug.com/983776): This test fails on ipad since beta5 due to a
-  // simulator bug. Re-enable this once the bug is fixed.
-  if (base::ios::IsRunningOnIOS13OrLater() &&
+  // TODO(crbug.com/983776): This test fails on iOS 13 ipad due to a
+  // simulator bug. Re-enable once iOS 13 support is dropped.
+  if (!base::ios::IsRunningOnIOS14OrLater() &&
       ui::GetDeviceFormFactor() == ui::DEVICE_FORM_FACTOR_TABLET) {
     return;
   }
@@ -253,11 +253,11 @@
   EXPECT_FLOAT_EQ(current_size, original_size * 110 / 100);
 }
 
-// Tests that __gCrWeb.accessibility.adjustFontSize works for nested elements.
+// Tests that __gCrWeb.font_size.adjustFontSize works for nested elements.
 TEST_F(FontSizeJsTest, TestAdjustFontSizeForNestedElements) {
-  // TODO(crbug.com/983776): This test fails on ipad since beta5 due to a
-  // simulator bug. Re-enable this once the bug is fixed.
-  if (base::ios::IsRunningOnIOS13OrLater() &&
+  // TODO(crbug.com/983776): This test fails on iOS 13 ipad due to a
+  // simulator bug. Re-enable once iOS 13 support is dropped.
+  if (!base::ios::IsRunningOnIOS14OrLater() &&
       ui::GetDeviceFormFactor() == ui::DEVICE_FORM_FACTOR_TABLET) {
     return;
   }
diff --git a/ios/chrome/test/data/policy/policy_test_cases.json b/ios/chrome/test/data/policy/policy_test_cases.json
index 57c9f004..48a58e2a 100644
--- a/ios/chrome/test/data/policy/policy_test_cases.json
+++ b/ios/chrome/test/data/policy/policy_test_cases.json
@@ -55,7 +55,7 @@
     "policy_pref_mapping_tests": [
       {
         "policies": { "BrowserSignin": 0 },
-        "prefs": { "signin.allowed": {} }
+        "prefs": { "signin.allowed_by_policy": {} }
       }
     ]
   },
diff --git a/ios/chrome/test/wpt/cwt_webdriver_app_interface.mm b/ios/chrome/test/wpt/cwt_webdriver_app_interface.mm
index b3e6f90..77f5992 100644
--- a/ios/chrome/test/wpt/cwt_webdriver_app_interface.mm
+++ b/ios/chrome/test/wpt/cwt_webdriver_app_interface.mm
@@ -215,7 +215,7 @@
 
   __block absl::optional<base::Value> messageValue;
   const web::WebState::ScriptCommandCallback callback =
-      base::BindRepeating(^(const base::DictionaryValue& value, const GURL&,
+      base::BindRepeating(^(const base::Value& value, const GURL&,
                             /*interacted*/ bool,
                             /*sender_frame*/ web::WebFrame*) {
         const base::Value* result = value.FindKey(kMessageResultKey);
diff --git a/ios/components/security_interstitials/ios_blocking_page_tab_helper.h b/ios/components/security_interstitials/ios_blocking_page_tab_helper.h
index 9a5a160d..49e5e29 100644
--- a/ios/components/security_interstitials/ios_blocking_page_tab_helper.h
+++ b/ios/components/security_interstitials/ios_blocking_page_tab_helper.h
@@ -46,7 +46,7 @@
 
   // Handler for "blockingPage.*" JavaScript command. Dispatch to more specific
   // handler.
-  void OnBlockingPageCommand(const base::DictionaryValue& message,
+  void OnBlockingPageCommand(const base::Value& message,
                              const GURL& url,
                              bool user_is_interacting,
                              web::WebFrame* sender_frame);
diff --git a/ios/components/security_interstitials/ios_blocking_page_tab_helper.mm b/ios/components/security_interstitials/ios_blocking_page_tab_helper.mm
index 71ae88b0b..3f627d56 100644
--- a/ios/components/security_interstitials/ios_blocking_page_tab_helper.mm
+++ b/ios/components/security_interstitials/ios_blocking_page_tab_helper.mm
@@ -5,6 +5,7 @@
 #import "ios/components/security_interstitials/ios_blocking_page_tab_helper.h"
 
 #include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
 #include "base/values.h"
 #include "ios/components/security_interstitials/ios_security_interstitial_page.h"
 #import "ios/web/public/navigation/navigation_context.h"
@@ -58,19 +59,35 @@
 }
 
 void IOSBlockingPageTabHelper::OnBlockingPageCommand(
-    const base::DictionaryValue& message,
+    const base::Value& message,
     const GURL& url,
     bool user_is_interacting,
     web::WebFrame* sender_frame) {
-  std::string command;
-  if (!message.GetString("command", &command)) {
-    DLOG(WARNING) << "JS message parameter not found: command";
-  } else {
-    if (blocking_page_for_currently_committed_navigation_) {
-      blocking_page_for_currently_committed_navigation_->HandleScriptCommand(
-          message, url, user_is_interacting, sender_frame);
-    }
-  }
+  if (!blocking_page_for_currently_committed_navigation_)
+    return;
+
+  const std::string* command = message.FindStringKey("command");
+  if (!command)
+    return;
+
+  // Remove the command prefix since it is ignored when converting the value
+  // to a SecurityInterstitialCommand.
+  const std::size_t pos = command->find('.');
+  if (pos == std::string::npos || pos + 1 == command->size())
+    return;
+
+  // Use a string piece to avoid creating a copy of the suffix (as calling
+  // std::string::substr would do). This is safe as `*command` is owned by
+  // the base::Value which stay in scope for the whole method.
+  const base::StringPiece suffix = base::StringPiece(*command).substr(pos + 1);
+
+  int command_id;
+  if (!base::StringToInt(suffix, &command_id))
+    return;
+
+  blocking_page_for_currently_committed_navigation_->HandleCommand(
+      static_cast<SecurityInterstitialCommand>(command_id), url,
+      user_is_interacting, sender_frame);
 }
 
 void IOSBlockingPageTabHelper::UpdateForFinishedNavigation(
diff --git a/ios/components/security_interstitials/ios_blocking_page_tab_helper_unittest.mm b/ios/components/security_interstitials/ios_blocking_page_tab_helper_unittest.mm
index 80c0be47..3a883bbd 100644
--- a/ios/components/security_interstitials/ios_blocking_page_tab_helper_unittest.mm
+++ b/ios/components/security_interstitials/ios_blocking_page_tab_helper_unittest.mm
@@ -33,10 +33,10 @@
   }
 
  private:
-  void HandleScriptCommand(const base::DictionaryValue& message,
-                           const GURL& origin_url,
-                           bool user_is_interacting,
-                           web::WebFrame* sender_frame) override {}
+  void HandleCommand(SecurityInterstitialCommand command,
+                     const GURL& origin_url,
+                     bool user_is_interacting,
+                     web::WebFrame* sender_frame) override {}
   bool ShouldCreateNewNavigation() const override { return false; }
   void PopulateInterstitialStrings(
       base::DictionaryValue* load_time_data) const override {}
diff --git a/ios/components/security_interstitials/ios_security_interstitial_page.h b/ios/components/security_interstitials/ios_security_interstitial_page.h
index 0ee03b9e..e97db16 100644
--- a/ios/components/security_interstitials/ios_security_interstitial_page.h
+++ b/ios/components/security_interstitials/ios_security_interstitial_page.h
@@ -36,12 +36,12 @@
   // respected by committed interstitials only.
   virtual bool ShouldDisplayURL() const;
 
-  // Handles JS commands from the interstitial page. Overridden in subclasses
+  // Handles `command` from the interstitial page. Overridden in subclasses
   // to handle actions specific to the type of interstitial.
-  virtual void HandleScriptCommand(const base::DictionaryValue& message,
-                                   const GURL& origin_url,
-                                   bool user_is_interacting,
-                                   web::WebFrame* sender_frame) = 0;
+  virtual void HandleCommand(SecurityInterstitialCommand command,
+                             const GURL& origin_url,
+                             bool user_is_interacting,
+                             web::WebFrame* sender_frame) = 0;
 
  protected:
   // Returns true if the interstitial should create a new navigation item.
diff --git a/ios/components/security_interstitials/legacy_tls/legacy_tls_blocking_page.h b/ios/components/security_interstitials/legacy_tls/legacy_tls_blocking_page.h
index a30d343d..cf761d8 100644
--- a/ios/components/security_interstitials/legacy_tls/legacy_tls_blocking_page.h
+++ b/ios/components/security_interstitials/legacy_tls/legacy_tls_blocking_page.h
@@ -29,10 +29,11 @@
       base::DictionaryValue* load_time_data) const override;
 
  private:
-  void HandleScriptCommand(const base::DictionaryValue& message,
-                           const GURL& origin_url,
-                           bool user_is_interacting,
-                           web::WebFrame* sender_frame) override;
+  void HandleCommand(
+      security_interstitials::SecurityInterstitialCommand command,
+      const GURL& origin_url,
+      bool user_is_interacting,
+      web::WebFrame* sender_frame) override;
 
   web::WebState* web_state_ = nullptr;
   const GURL request_url_;
diff --git a/ios/components/security_interstitials/legacy_tls/legacy_tls_blocking_page.mm b/ios/components/security_interstitials/legacy_tls/legacy_tls_blocking_page.mm
index 15d13cb..fe04726 100644
--- a/ios/components/security_interstitials/legacy_tls/legacy_tls_blocking_page.mm
+++ b/ios/components/security_interstitials/legacy_tls/legacy_tls_blocking_page.mm
@@ -61,31 +61,11 @@
       load_time_data, hostname);
 }
 
-void LegacyTLSBlockingPage::HandleScriptCommand(
-    const base::DictionaryValue& message,
+void LegacyTLSBlockingPage::HandleCommand(
+    security_interstitials::SecurityInterstitialCommand command,
     const GURL& origin_url,
     bool user_is_interacting,
     web::WebFrame* sender_frame) {
-  std::string command_string;
-  if (!message.GetString("command", &command_string)) {
-    LOG(ERROR) << "JS message parameter not found: command";
-    return;
-  }
-
-  // Remove the command prefix so that the string value can be converted to a
-  // SecurityInterstitialCommand enum value.
-  std::size_t delimiter = command_string.find(".");
-  if (delimiter == std::string::npos) {
-    return;
-  }
-
-  // Parse the command int value from the text after the delimiter.
-  int command = 0;
-  if (!base::StringToInt(command_string.substr(delimiter + 1), &command)) {
-    NOTREACHED() << "Command cannot be parsed to an int : " << command_string;
-    return;
-  }
-
   if (command == security_interstitials::CMD_DONT_PROCEED) {
     controller_->metrics_helper()->RecordUserDecision(
         security_interstitials::MetricsHelper::DONT_PROCEED);
diff --git a/ios/components/security_interstitials/lookalikes/lookalike_url_blocking_page.h b/ios/components/security_interstitials/lookalikes/lookalike_url_blocking_page.h
index ed99a07..10c84bd0 100644
--- a/ios/components/security_interstitials/lookalikes/lookalike_url_blocking_page.h
+++ b/ios/components/security_interstitials/lookalikes/lookalike_url_blocking_page.h
@@ -36,10 +36,11 @@
   bool ShouldDisplayURL() const override;
 
  private:
-  void HandleScriptCommand(const base::DictionaryValue& message,
-                           const GURL& origin_url,
-                           bool user_is_interacting,
-                           web::WebFrame* sender_frame) override;
+  void HandleCommand(
+      security_interstitials::SecurityInterstitialCommand command,
+      const GURL& origin_url,
+      bool user_is_interacting,
+      web::WebFrame* sender_frame) override;
 
   web::WebState* web_state_ = nullptr;
   std::unique_ptr<LookalikeUrlControllerClient> controller_;
diff --git a/ios/components/security_interstitials/lookalikes/lookalike_url_blocking_page.mm b/ios/components/security_interstitials/lookalikes/lookalike_url_blocking_page.mm
index 8f52e60..b6c4261 100644
--- a/ios/components/security_interstitials/lookalikes/lookalike_url_blocking_page.mm
+++ b/ios/components/security_interstitials/lookalikes/lookalike_url_blocking_page.mm
@@ -74,31 +74,11 @@
   return false;
 }
 
-void LookalikeUrlBlockingPage::HandleScriptCommand(
-    const base::DictionaryValue& message,
+void LookalikeUrlBlockingPage::HandleCommand(
+    security_interstitials::SecurityInterstitialCommand command,
     const GURL& origin_url,
     bool user_is_interacting,
     web::WebFrame* sender_frame) {
-  std::string command_string;
-  if (!message.GetString("command", &command_string)) {
-    LOG(ERROR) << "JS message parameter not found: command";
-    return;
-  }
-
-  // Remove the command prefix so that the string value can be converted to a
-  // SecurityInterstitialCommand enum value.
-  std::size_t delimiter = command_string.find(".");
-  if (delimiter == std::string::npos) {
-    return;
-  }
-
-  // Parse the command int value from the text after the delimiter.
-  int command = 0;
-  if (!base::StringToInt(command_string.substr(delimiter + 1), &command)) {
-    NOTREACHED() << "Command cannot be parsed to an int : " << command_string;
-    return;
-  }
-
   if (command == security_interstitials::CMD_DONT_PROCEED) {
     controller_->metrics_helper()->RecordUserDecision(
         security_interstitials::MetricsHelper::DONT_PROCEED);
diff --git a/ios/components/security_interstitials/lookalikes/lookalike_url_blocking_page_unittest.mm b/ios/components/security_interstitials/lookalikes/lookalike_url_blocking_page_unittest.mm
index 8de82f9..02f1740 100644
--- a/ios/components/security_interstitials/lookalikes/lookalike_url_blocking_page_unittest.mm
+++ b/ios/components/security_interstitials/lookalikes/lookalike_url_blocking_page_unittest.mm
@@ -78,11 +78,9 @@
   }
 
   void SendCommand(SecurityInterstitialCommand command) {
-    base::DictionaryValue dict;
-    dict.SetKey("command", base::Value("." + base::NumberToString(command)));
-    page_->HandleScriptCommand(dict, url_,
-                               /*user_is_interacting=*/true,
-                               /*sender_frame=*/nullptr);
+    page_->HandleCommand(command, url_,
+                         /*user_is_interacting=*/true,
+                         /*sender_frame=*/nullptr);
   }
 
   // Checks that UKM recorded an event with the given metric name and value.
diff --git a/ios/web/js_messaging/web_frame_impl.h b/ios/web/js_messaging/web_frame_impl.h
index 05fc363..87b0e927 100644
--- a/ios/web/js_messaging/web_frame_impl.h
+++ b/ios/web/js_messaging/web_frame_impl.h
@@ -155,7 +155,7 @@
   // Handles message from JavaScript with result of executing the function
   // specified in CallJavaScriptFunction.
   void OnJavaScriptReply(web::WebState* web_state,
-                         const base::DictionaryValue& command,
+                         const base::Value& command,
                          const GURL& page_url,
                          bool interacting,
                          WebFrame* sender_frame);
diff --git a/ios/web/js_messaging/web_frame_impl.mm b/ios/web/js_messaging/web_frame_impl.mm
index 737b39c..5ca67a9 100644
--- a/ios/web/js_messaging/web_frame_impl.mm
+++ b/ios/web/js_messaging/web_frame_impl.mm
@@ -358,30 +358,23 @@
 }
 
 void WebFrameImpl::OnJavaScriptReply(web::WebState* web_state,
-                                     const base::DictionaryValue& command_json,
+                                     const base::Value& command_json,
                                      const GURL& page_url,
                                      bool interacting,
                                      WebFrame* sender_frame) {
-  auto* command = command_json.FindKey("command");
-  if (!command || !command->is_string() || !command_json.HasKey("messageId")) {
-    NOTREACHED();
+  const std::string* command_string = command_json.FindStringKey("command");
+  if (!command_string ||
+      *command_string != (GetScriptCommandPrefix() + ".reply")) {
     return;
   }
 
-  const std::string command_string = command->GetString();
-  if (command_string != (GetScriptCommandPrefix() + ".reply")) {
-    NOTREACHED();
+  absl::optional<double> message_id = command_json.FindDoubleKey("messageId");
+  if (!message_id) {
     return;
   }
 
-  auto* message_id_value = command_json.FindKey("messageId");
-  if (!message_id_value->is_double()) {
-    NOTREACHED();
-    return;
-  }
-
-  int message_id = static_cast<int>(message_id_value->GetDouble());
-  CompleteRequest(message_id, command_json.FindKey("result"));
+  CompleteRequest(static_cast<int>(*message_id),
+                  command_json.FindKey("result"));
 }
 
 void WebFrameImpl::DetachFromWebState() {
diff --git a/ios/web/js_messaging/web_frame_impl_inttest.mm b/ios/web/js_messaging/web_frame_impl_inttest.mm
index 6edebe6c..255a1520 100644
--- a/ios/web/js_messaging/web_frame_impl_inttest.mm
+++ b/ios/web/js_messaging/web_frame_impl_inttest.mm
@@ -205,7 +205,7 @@
   // The callback doesn't care about any of the parameters not related to
   // frames.
   auto callback = base::BindRepeating(
-      ^(const base::DictionaryValue& /* json */, const GURL& /* origin_url */,
+      ^(const base::Value& /* json */, const GURL& /* origin_url */,
         bool /* user_is_interacting */, WebFrame* sender_frame) {
         command_received = true;
         EXPECT_TRUE(sender_frame->IsMainFrame());
@@ -233,7 +233,7 @@
   // The callback doesn't care about any of the parameters not related to
   // frames.
   auto callback = base::BindRepeating(
-      ^(const base::DictionaryValue& /* json */, const GURL& /* origin_url */,
+      ^(const base::Value& /* json */, const GURL& /* origin_url */,
         bool /* user_is_interacting */, WebFrame* sender_frame) {
         command_received = true;
         EXPECT_FALSE(sender_frame->IsMainFrame());
diff --git a/ios/web/navigation/crw_js_navigation_handler.mm b/ios/web/navigation/crw_js_navigation_handler.mm
index 1e7af68b..95d8dd4 100644
--- a/ios/web/navigation/crw_js_navigation_handler.mm
+++ b/ios/web/navigation/crw_js_navigation_handler.mm
@@ -68,14 +68,16 @@
     _delegate = delegate;
 
     __weak CRWJSNavigationHandler* weakSelf = self;
-    auto navigationStateCallback = ^(
-        const base::DictionaryValue& message, const GURL&,
-        bool /* user_is_interacting */, web::WebFrame* senderFrame) {
+    auto navigationStateCallback = ^(const base::Value& message, const GURL&,
+                                     bool /* user_is_interacting */,
+                                     web::WebFrame* senderFrame) {
       if (!senderFrame->IsMainFrame())
         return;
 
       const std::string* command = message.FindStringKey("command");
-      DCHECK(command);
+      if (!command)
+        return;
+
       if (*command == "navigation.hashchange") {
         [weakSelf handleNavigationHashChangeInFrame:senderFrame];
       } else if (*command == "navigation.willChangeState") {
@@ -129,8 +131,7 @@
 }
 
 // Handles the navigation.didChangeState message sent from |senderFrame|.
-- (void)handleNavigationDidPushStateMessage:
-            (const base::DictionaryValue&)message
+- (void)handleNavigationDidPushStateMessage:(const base::Value&)message
                                     inFrame:(web::WebFrame*)senderFrame {
   DCHECK(self.changingHistoryState);
   self.changingHistoryState = NO;
@@ -204,8 +205,7 @@
 }
 
 // Handles the navigation.didReplaceState message sent from |senderFrame|.
-- (void)handleNavigationDidReplaceStateMessage:
-            (const base::DictionaryValue&)message
+- (void)handleNavigationDidReplaceStateMessage:(const base::Value&)message
                                        inFrame:(web::WebFrame*)senderFrame {
   DCHECK(self.changingHistoryState);
   self.changingHistoryState = NO;
diff --git a/ios/web/navigation/crw_wk_navigation_handler.mm b/ios/web/navigation/crw_wk_navigation_handler.mm
index b863c718..079fdec 100644
--- a/ios/web/navigation/crw_wk_navigation_handler.mm
+++ b/ios/web/navigation/crw_wk_navigation_handler.mm
@@ -162,12 +162,8 @@
     web::NavigationItem* item = [[CRWNavigationItemHolder
         holderForBackForwardListItem:webView.backForwardList.currentItem]
         navigationItem];
-    if (item) {
+    if (item)
       item->SetUserAgentType(userAgentType);
-      if (web::wk_navigation_util::IsRestoreSessionUrl(item->GetURL())) {
-        self.webStateImpl->SetUserAgent(userAgentType);
-      }
-    }
   }
 
   if (userAgentType != web::UserAgentType::NONE) {
@@ -1918,7 +1914,9 @@
   // Create pending item.
   self.navigationManagerImpl->AddPendingItem(
       blockedURL, web::Referrer(), transition,
-      web::NavigationInitiationType::BROWSER_INITIATED);
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
 
   // Create context.
   std::unique_ptr<web::NavigationContextImpl> context =
diff --git a/ios/web/navigation/navigation_manager_impl.h b/ios/web/navigation/navigation_manager_impl.h
index f551d2b6..f330fdf7 100644
--- a/ios/web/navigation/navigation_manager_impl.h
+++ b/ios/web/navigation/navigation_manager_impl.h
@@ -143,14 +143,17 @@
   // type and user agent override option, making it the pending item. If pending
   // item is the same as the current item, this does nothing. |referrer| may be
   // nil if there isn't one. The item starts out as pending, and will be lost
-  // unless |-commitPendingItem| is called. |is_using_https_as_default_scheme|
-  // must be true for navigations that use https:// as the default scheme
+  // unless |-commitPendingItem| is called.
+  // |is_post_navigation| is true if the navigation is using a POST HTTP method.
+  //|is_using_https_as_default_scheme| must be true for navigations that use
+  // https:// as the default scheme
   // in their URL, if the user typed the URL without a scheme.
   void AddPendingItem(const GURL& url,
                       const web::Referrer& referrer,
                       ui::PageTransition navigation_type,
                       NavigationInitiationType initiation_type,
-                      bool is_using_https_as_default_scheme = false);
+                      bool is_post_navigation,
+                      bool is_using_https_as_default_scheme);
 
   // Commits the pending item, if any.
   // TODO(crbug.com/936933): Remove this method.
diff --git a/ios/web/navigation/navigation_manager_impl.mm b/ios/web/navigation/navigation_manager_impl.mm
index dd5419f..458117f4 100644
--- a/ios/web/navigation/navigation_manager_impl.mm
+++ b/ios/web/navigation/navigation_manager_impl.mm
@@ -185,6 +185,7 @@
     const web::Referrer& referrer,
     ui::PageTransition navigation_type,
     NavigationInitiationType initiation_type,
+    bool is_post_navigation,
     bool is_using_https_as_default_scheme) {
   DiscardNonCommittedItems();
 
@@ -260,7 +261,11 @@
         IsSameOrPlaceholderOf(current_item_url, net::GURLWithNSURL(proxy.URL));
   }
 
-  if (proxy.backForwardList.currentItem && isCurrentURLSameAsPending) {
+  bool is_form_post =
+      is_post_navigation &&
+      (navigation_type & ui::PageTransition::PAGE_TRANSITION_FORM_SUBMIT);
+  if (proxy.backForwardList.currentItem && isCurrentURLSameAsPending &&
+      !is_form_post) {
     pending_item_index_ = web_view_cache_.GetCurrentItemIndex();
 
     // If |currentItem| is not already associated with a NavigationItemImpl,
@@ -268,15 +273,10 @@
     // since it will be a duplicate.
     NavigationItemImpl* current_item =
         GetNavigationItemFromWKItem(current_wk_item);
-    ui::PageTransition transition = pending_item_->GetTransitionType();
     if (!current_item) {
       current_item = pending_item_.get();
       SetNavigationItemInWKItem(current_wk_item, std::move(pending_item_));
     }
-    // Updating the transition type of the item is needed, for example when
-    // doing a FormSubmit with a GET method on the same URL. See
-    // crbug.com/1211879.
-    current_item->SetTransitionType(transition);
 
     pending_item_.reset();
   }
@@ -700,7 +700,8 @@
           ? NavigationInitiationType::RENDERER_INITIATED
           : NavigationInitiationType::BROWSER_INITIATED;
   AddPendingItem(params.url, params.referrer, params.transition_type,
-                 initiation_type, params.is_using_https_as_default_scheme);
+                 initiation_type, /*is_post_navigation=*/false,
+                 params.is_using_https_as_default_scheme);
 
   // Mark pending item as created from hash change if necessary. This is needed
   // because window.hashchange message may not arrive on time.
diff --git a/ios/web/navigation/navigation_manager_impl_unittest.mm b/ios/web/navigation/navigation_manager_impl_unittest.mm
index 12fdb97..6c0aa8d 100644
--- a/ios/web/navigation/navigation_manager_impl_unittest.mm
+++ b/ios/web/navigation/navigation_manager_impl_unittest.mm
@@ -191,7 +191,9 @@
 TEST_F(NavigationManagerTest, GetPendingItemIndexWithoutPendingEntry) {
   navigation_manager()->AddPendingItem(
       GURL("http://www.url.com"), Referrer(), ui::PAGE_TRANSITION_TYPED,
-      web::NavigationInitiationType::BROWSER_INITIATED);
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
 
   [mock_wk_list_ setCurrentURL:@"http://www.url.com"];
   navigation_manager()->CommitPendingItem();
@@ -203,14 +205,18 @@
 TEST_F(NavigationManagerTest, GetPendingItemIndexWithPendingEntry) {
   navigation_manager()->AddPendingItem(
       GURL("http://www.url.com"), Referrer(), ui::PAGE_TRANSITION_TYPED,
-      web::NavigationInitiationType::BROWSER_INITIATED);
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
 
   [mock_wk_list_ setCurrentURL:@"http://www.url.com"];
   navigation_manager()->CommitPendingItem();
 
   navigation_manager()->AddPendingItem(
       GURL("http://www.url.com/0"), Referrer(), ui::PAGE_TRANSITION_TYPED,
-      web::NavigationInitiationType::BROWSER_INITIATED);
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
   EXPECT_EQ(-1, navigation_manager()->GetPendingItemIndex());
 }
 
@@ -218,7 +224,9 @@
 TEST_F(NavigationManagerTest, SetAndGetPendingItemIndex) {
   navigation_manager()->AddPendingItem(
       GURL("http://www.url.test"), Referrer(), ui::PAGE_TRANSITION_TYPED,
-      web::NavigationInitiationType::BROWSER_INITIATED);
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
   [mock_wk_list_ setCurrentURL:@"http://www.url.test"];
   navigation_manager()->CommitPendingItem();
 
@@ -230,14 +238,18 @@
 TEST_F(NavigationManagerTest, GetPendingItemIndexWithIndexedPendingEntry) {
   navigation_manager()->AddPendingItem(
       GURL("http://www.url.com"), Referrer(), ui::PAGE_TRANSITION_TYPED,
-      web::NavigationInitiationType::BROWSER_INITIATED);
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
 
   [mock_wk_list_ setCurrentURL:@"http://www.url.com"];
   navigation_manager()->CommitPendingItem();
 
   navigation_manager()->AddPendingItem(
       GURL("http://www.url.com/0"), Referrer(), ui::PAGE_TRANSITION_TYPED,
-      web::NavigationInitiationType::BROWSER_INITIATED);
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
 
   [mock_wk_list_ setCurrentURL:@"http://www.url.com/0"
                   backListURLs:@[ @"http://www.url.com" ]
@@ -266,7 +278,9 @@
   GURL url("http://www.url.test");
   navigation_manager()->AddPendingItem(
       url, Referrer(), ui::PAGE_TRANSITION_TYPED,
-      web::NavigationInitiationType::BROWSER_INITIATED);
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
 
   EXPECT_NE(item.get(), navigation_manager()->GetPendingItem());
   ASSERT_TRUE(navigation_manager()->GetPendingItem());
@@ -278,7 +292,9 @@
   GURL url("http://www.url.test");
   navigation_manager()->AddPendingItem(
       url, Referrer(), ui::PAGE_TRANSITION_TYPED,
-      web::NavigationInitiationType::BROWSER_INITIATED);
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
   [mock_wk_list_ setCurrentURL:@"http://www.url.test"];
   navigation_manager()->CommitPendingItem();
   navigation_manager()->SetPendingItemIndex(0);
@@ -303,7 +319,9 @@
 TEST_F(NavigationManagerTest, CanGoBackWithSingleCommitedItem) {
   navigation_manager()->AddPendingItem(
       GURL("http://www.url.com"), Referrer(), ui::PAGE_TRANSITION_TYPED,
-      web::NavigationInitiationType::BROWSER_INITIATED);
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
 
   [mock_wk_list_ setCurrentURL:@"http://www.url.com"];
   navigation_manager()->CommitPendingItem();
@@ -316,13 +334,17 @@
 TEST_F(NavigationManagerTest, CanGoBackWithMultipleCommitedItems) {
   navigation_manager()->AddPendingItem(
       GURL("http://www.url.com"), Referrer(), ui::PAGE_TRANSITION_TYPED,
-      web::NavigationInitiationType::BROWSER_INITIATED);
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
   [mock_wk_list_ setCurrentURL:@"http://www.url.com"];
   navigation_manager()->CommitPendingItem();
 
   navigation_manager()->AddPendingItem(
       GURL("http://www.url.com/0"), Referrer(), ui::PAGE_TRANSITION_TYPED,
-      web::NavigationInitiationType::BROWSER_INITIATED);
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
 
   [mock_wk_list_ setCurrentURL:@"http://www.url.com/0"
                   backListURLs:@[ @"http://www.url.com" ]
@@ -331,7 +353,9 @@
 
   navigation_manager()->AddPendingItem(
       GURL("http://www.url.com/1"), Referrer(), ui::PAGE_TRANSITION_TYPED,
-      web::NavigationInitiationType::BROWSER_INITIATED);
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
 
   [mock_wk_list_
         setCurrentURL:@"http://www.url.com/1"
@@ -367,7 +391,9 @@
 TEST_F(NavigationManagerTest, CanGoForwardWithSingleCommitedItem) {
   navigation_manager()->AddPendingItem(
       GURL("http://www.url.com"), Referrer(), ui::PAGE_TRANSITION_TYPED,
-      web::NavigationInitiationType::BROWSER_INITIATED);
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
   [mock_wk_list_ setCurrentURL:@"http://www.url.com/"];
   navigation_manager()->CommitPendingItem();
 
@@ -379,14 +405,18 @@
 TEST_F(NavigationManagerTest, CanGoForwardWithMultipleCommitedEntries) {
   navigation_manager()->AddPendingItem(
       GURL("http://www.url.com"), Referrer(), ui::PAGE_TRANSITION_TYPED,
-      web::NavigationInitiationType::BROWSER_INITIATED);
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
 
   [mock_wk_list_ setCurrentURL:@"http://www.url.com"];
   navigation_manager()->CommitPendingItem();
 
   navigation_manager()->AddPendingItem(
       GURL("http://www.url.com/0"), Referrer(), ui::PAGE_TRANSITION_TYPED,
-      web::NavigationInitiationType::BROWSER_INITIATED);
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
 
   [mock_wk_list_ setCurrentURL:@"http://www.url.com/0"
                   backListURLs:@[ @"http://www.url.com" ]
@@ -395,7 +425,9 @@
 
   navigation_manager()->AddPendingItem(
       GURL("http://www.url.com/1"), Referrer(), ui::PAGE_TRANSITION_TYPED,
-      web::NavigationInitiationType::BROWSER_INITIATED);
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
 
   [mock_wk_list_
         setCurrentURL:@"http://www.url.com/1"
@@ -429,7 +461,9 @@
 TEST_F(NavigationManagerTest, OffsetsWithoutPendingIndex) {
   navigation_manager()->AddPendingItem(
       GURL("http://www.url.com/0"), Referrer(), ui::PAGE_TRANSITION_LINK,
-      web::NavigationInitiationType::BROWSER_INITIATED);
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
 
   [mock_wk_list_ setCurrentURL:@"http://www.url.com/0"];
   navigation_manager()->CommitPendingItem();
@@ -437,7 +471,9 @@
   navigation_manager()->AddPendingItem(
       GURL("http://www.url.com/redirect"), Referrer(),
       ui::PAGE_TRANSITION_CLIENT_REDIRECT,
-      web::NavigationInitiationType::BROWSER_INITIATED);
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
 
   [mock_wk_list_ setCurrentURL:@"http://www.url.com/redirect"
                   backListURLs:@[ @"http://www.url.com/0" ]
@@ -446,7 +482,9 @@
 
   navigation_manager()->AddPendingItem(
       GURL("http://www.url.com/1"), Referrer(), ui::PAGE_TRANSITION_LINK,
-      web::NavigationInitiationType::BROWSER_INITIATED);
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
 
   [mock_wk_list_ setCurrentURL:@"http://www.url.com/1"
                   backListURLs:@[
@@ -457,7 +495,9 @@
 
   navigation_manager()->AddPendingItem(
       GURL("http://www.url.com/2"), Referrer(), ui::PAGE_TRANSITION_LINK,
-      web::NavigationInitiationType::BROWSER_INITIATED);
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
 
   [mock_wk_list_ setCurrentURL:@"http://www.url.com/2"
                   backListURLs:@[
@@ -470,7 +510,9 @@
   navigation_manager()->AddPendingItem(
       GURL("http://www.url.com/redirect"), Referrer(),
       ui::PAGE_TRANSITION_CLIENT_REDIRECT,
-      web::NavigationInitiationType::BROWSER_INITIATED);
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
 
   [mock_wk_list_ setCurrentURL:@"http://www.url.com/redirect"
                   backListURLs:@[
@@ -490,7 +532,9 @@
   GURL existing_url = GURL("http://www.existing.com");
   navigation_manager()->AddPendingItem(
       existing_url, Referrer(), ui::PAGE_TRANSITION_TYPED,
-      web::NavigationInitiationType::BROWSER_INITIATED);
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
   ASSERT_TRUE(navigation_manager()->GetPendingItem());
   EXPECT_EQ(existing_url, navigation_manager()->GetPendingItem()->GetURL());
   EXPECT_FALSE(navigation_manager()->GetPendingItem()->IsUpgradedToHttps());
@@ -499,7 +543,9 @@
   GURL new_url1 = GURL("http://www.new1.com");
   navigation_manager()->AddPendingItem(
       new_url1, Referrer(), ui::PAGE_TRANSITION_TYPED,
-      web::NavigationInitiationType::BROWSER_INITIATED);
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
   ASSERT_TRUE(navigation_manager()->GetPendingItem());
   EXPECT_EQ(new_url1, navigation_manager()->GetPendingItem()->GetURL());
   EXPECT_FALSE(navigation_manager()->GetPendingItem()->IsUpgradedToHttps());
@@ -509,7 +555,8 @@
   navigation_manager()->AddPendingItem(
       new_url2, Referrer(), ui::PAGE_TRANSITION_TYPED,
       web::NavigationInitiationType::BROWSER_INITIATED,
-      /*is_upgraded_to_https=*/true);
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/true);
   ASSERT_TRUE(navigation_manager()->GetPendingItem());
   EXPECT_EQ(new_url2, navigation_manager()->GetPendingItem()->GetURL());
   EXPECT_TRUE(navigation_manager()->GetPendingItem()->IsUpgradedToHttps());
@@ -523,7 +570,9 @@
   GURL existing_url = GURL("http://www.existing.com");
   navigation_manager()->AddPendingItem(
       existing_url, Referrer(), ui::PAGE_TRANSITION_TYPED,
-      web::NavigationInitiationType::BROWSER_INITIATED);
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
   ASSERT_TRUE(navigation_manager()->GetPendingItem());
   EXPECT_TRUE(ui::PageTransitionCoreTypeIs(
       navigation_manager()->GetPendingItem()->GetTransitionType(),
@@ -533,7 +582,9 @@
 
   navigation_manager()->AddPendingItem(
       existing_url, Referrer(), ui::PAGE_TRANSITION_LINK,
-      web::NavigationInitiationType::BROWSER_INITIATED);
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
   ASSERT_TRUE(navigation_manager()->GetPendingItem());
 
   // NavigationManagerImpl assumes that AddPendingItem() is only called for
@@ -547,7 +598,8 @@
   navigation_manager()->AddPendingItem(
       existing_url, Referrer(), ui::PAGE_TRANSITION_LINK,
       web::NavigationInitiationType::BROWSER_INITIATED,
-      /*is_upgraded_to_https=*/true);
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/true);
   ASSERT_TRUE(navigation_manager()->GetPendingItem());
   EXPECT_TRUE(navigation_manager()->GetPendingItem()->IsUpgradedToHttps());
 }
@@ -559,7 +611,9 @@
   GURL existing_url = GURL("http://www.existing.com");
   navigation_manager()->AddPendingItem(
       existing_url, Referrer(), ui::PAGE_TRANSITION_TYPED,
-      web::NavigationInitiationType::BROWSER_INITIATED);
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
   ASSERT_TRUE(navigation_manager()->GetPendingItem());
   EXPECT_TRUE(ui::PageTransitionCoreTypeIs(
       navigation_manager()->GetPendingItem()->GetTransitionType(),
@@ -569,7 +623,9 @@
 
   navigation_manager()->AddPendingItem(
       existing_url, Referrer(), ui::PAGE_TRANSITION_FORM_SUBMIT,
-      web::NavigationInitiationType::BROWSER_INITIATED);
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
   ASSERT_TRUE(navigation_manager()->GetPendingItem());
   EXPECT_TRUE(ui::PageTransitionCoreTypeIs(
       navigation_manager()->GetPendingItem()->GetTransitionType(),
@@ -581,7 +637,8 @@
   navigation_manager()->AddPendingItem(
       existing_url, Referrer(), ui::PAGE_TRANSITION_FORM_SUBMIT,
       web::NavigationInitiationType::BROWSER_INITIATED,
-      /*is_upgraded_to_https=*/true);
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/true);
   ASSERT_TRUE(navigation_manager()->GetPendingItem());
   EXPECT_TRUE(ui::PageTransitionCoreTypeIs(
       navigation_manager()->GetPendingItem()->GetTransitionType(),
@@ -597,7 +654,9 @@
   GURL existing_url = GURL("http://www.existing.com");
   navigation_manager()->AddPendingItem(
       existing_url, Referrer(), ui::PAGE_TRANSITION_TYPED,
-      web::NavigationInitiationType::BROWSER_INITIATED);
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
 
   ASSERT_TRUE(navigation_manager()->GetPendingItem());
   EXPECT_TRUE(ui::PageTransitionCoreTypeIs(
@@ -607,7 +666,9 @@
 
   navigation_manager()->AddPendingItem(
       existing_url, Referrer(), ui::PAGE_TRANSITION_LINK,
-      web::NavigationInitiationType::BROWSER_INITIATED);
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
 
   ASSERT_TRUE(navigation_manager()->GetPendingItem());
 
@@ -625,7 +686,9 @@
   GURL existing_url = GURL("http://www.existing.com");
   navigation_manager()->AddPendingItem(
       existing_url, Referrer(), ui::PAGE_TRANSITION_TYPED,
-      web::NavigationInitiationType::BROWSER_INITIATED);
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
   ASSERT_TRUE(navigation_manager()->GetPendingItem());
   EXPECT_TRUE(ui::PageTransitionCoreTypeIs(
       navigation_manager()->GetPendingItem()->GetTransitionType(),
@@ -635,7 +698,9 @@
 
   navigation_manager()->AddPendingItem(
       existing_url, Referrer(), ui::PAGE_TRANSITION_RELOAD,
-      web::NavigationInitiationType::BROWSER_INITIATED);
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
   ASSERT_TRUE(navigation_manager()->GetPendingItem());
   EXPECT_TRUE(ui::PageTransitionCoreTypeIs(
       navigation_manager()->GetPendingItem()->GetTransitionType(),
@@ -646,7 +711,8 @@
   navigation_manager()->AddPendingItem(
       existing_url, Referrer(), ui::PAGE_TRANSITION_RELOAD,
       web::NavigationInitiationType::BROWSER_INITIATED,
-      /*is_upgraded_to_https=*/true);
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/true);
   ASSERT_TRUE(navigation_manager()->GetPendingItem());
   EXPECT_TRUE(ui::PageTransitionCoreTypeIs(
       navigation_manager()->GetPendingItem()->GetTransitionType(),
@@ -661,7 +727,9 @@
   GURL existing_url = GURL("http://www.existing.com");
   navigation_manager()->AddPendingItem(
       existing_url, Referrer(), ui::PAGE_TRANSITION_TYPED,
-      web::NavigationInitiationType::BROWSER_INITIATED);
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
   ASSERT_TRUE(navigation_manager()->GetPendingItem());
   navigation_manager()->GetPendingItem()->SetUserAgentType(
       UserAgentType::DESKTOP);
@@ -675,7 +743,9 @@
 
   navigation_manager()->AddPendingItem(
       existing_url, Referrer(), ui::PAGE_TRANSITION_RELOAD,
-      web::NavigationInitiationType::BROWSER_INITIATED);
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
   ASSERT_TRUE(navigation_manager()->GetPendingItem());
   EXPECT_TRUE(ui::PageTransitionCoreTypeIs(
       navigation_manager()->GetPendingItem()->GetTransitionType(),
@@ -686,7 +756,8 @@
   navigation_manager()->AddPendingItem(
       existing_url, Referrer(), ui::PAGE_TRANSITION_RELOAD,
       web::NavigationInitiationType::BROWSER_INITIATED,
-      /*is_upgraded_to_https=*/true);
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/true);
   ASSERT_TRUE(navigation_manager()->GetPendingItem());
   EXPECT_TRUE(ui::PageTransitionCoreTypeIs(
       navigation_manager()->GetPendingItem()->GetTransitionType(),
@@ -701,11 +772,11 @@
   GURL existing_url = GURL("http://www.existing.com");
   navigation_manager()->AddPendingItem(
       existing_url, Referrer(), ui::PAGE_TRANSITION_TYPED,
-      web::NavigationInitiationType::BROWSER_INITIATED);
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
 
   [mock_wk_list_ setCurrentURL:@"http://www.existing.com"];
-  OCMStub([mock_web_view_ URL])
-      .andReturn([NSURL URLWithString:@"http://www.existing.com"]);
   navigation_manager()->CommitPendingItem();
 
   ASSERT_TRUE(navigation_manager()->GetLastCommittedItem());
@@ -716,7 +787,9 @@
   GURL new_url = GURL("http://www.new.com");
   navigation_manager()->AddPendingItem(
       new_url, Referrer(), ui::PAGE_TRANSITION_TYPED,
-      web::NavigationInitiationType::BROWSER_INITIATED);
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
   ASSERT_TRUE(navigation_manager()->GetPendingItem());
   EXPECT_EQ(new_url, navigation_manager()->GetPendingItem()->GetURL());
   EXPECT_FALSE(navigation_manager()->GetPendingItem()->IsUpgradedToHttps());
@@ -729,11 +802,11 @@
   GURL existing_url = GURL("http://www.existing.com");
   navigation_manager()->AddPendingItem(
       existing_url, Referrer(), ui::PAGE_TRANSITION_TYPED,
-      web::NavigationInitiationType::BROWSER_INITIATED);
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
 
   [mock_wk_list_ setCurrentURL:@"http://www.existing.com"];
-  OCMStub([mock_web_view_ URL])
-      .andReturn([NSURL URLWithString:@"http://www.existing.com"]);
   navigation_manager()->CommitPendingItem();
 
   ASSERT_TRUE(navigation_manager()->GetLastCommittedItem());
@@ -745,13 +818,13 @@
 
   navigation_manager()->AddPendingItem(
       existing_url, Referrer(), ui::PAGE_TRANSITION_LINK,
-      web::NavigationInitiationType::BROWSER_INITIATED);
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
 
   // NavigationManagerImpl assumes that AddPendingItem() is only called for
   // new navigation, so it always creates a new pending item.
   ASSERT_TRUE(navigation_manager()->GetPendingItem());
-  EXPECT_EQ(navigation_manager()->GetPendingItem(),
-            navigation_manager()->GetLastCommittedItem());
   EXPECT_TRUE(ui::PageTransitionCoreTypeIs(
       navigation_manager()->GetPendingItem()->GetTransitionType(),
       ui::PAGE_TRANSITION_LINK));
@@ -759,13 +832,48 @@
 }
 
 // Tests that when the last committed item exists, adding a pending item with
-// the same URL succeeds if the new item is a form submission while the last
-// committed item is not.
-TEST_F(NavigationManagerTest, AddSameUrlPendingItemIfFormSubmission) {
+// the same URL updates the existing committed item if the form submission isn't
+// using POST.
+TEST_F(NavigationManagerTest, NotAddSameUrlPendingItemIfGETFormSubmission) {
   GURL existing_url = GURL("http://www.existing.com");
   navigation_manager()->AddPendingItem(
       existing_url, Referrer(), ui::PAGE_TRANSITION_TYPED,
-      web::NavigationInitiationType::BROWSER_INITIATED);
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
+
+  [mock_wk_list_ setCurrentURL:@"http://www.existing.com"];
+  navigation_manager()->CommitPendingItem();
+
+  ASSERT_TRUE(navigation_manager()->GetLastCommittedItem());
+  EXPECT_TRUE(ui::PageTransitionCoreTypeIs(
+      navigation_manager()->GetLastCommittedItem()->GetTransitionType(),
+      ui::PAGE_TRANSITION_TYPED));
+  EXPECT_FALSE(navigation_manager()->GetPendingItem());
+  EXPECT_EQ(1, navigation_manager()->GetItemCount());
+
+  // Add if new transition is a form submission.
+  navigation_manager()->AddPendingItem(
+      existing_url, Referrer(), ui::PAGE_TRANSITION_FORM_SUBMIT,
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
+  ASSERT_TRUE(navigation_manager()->GetPendingItem());
+  EXPECT_TRUE(ui::PageTransitionCoreTypeIs(
+      navigation_manager()->GetPendingItem()->GetTransitionType(),
+      ui::PAGE_TRANSITION_FORM_SUBMIT));
+  EXPECT_EQ(1, navigation_manager()->GetItemCount());
+}
+
+// Tests that when the last committed item exists, adding a pending item with
+// the same URL creates a new pending item if the form submission is using POST.
+TEST_F(NavigationManagerTest, AddSameUrlPendingItemIfPOSTFormSubmission) {
+  GURL existing_url = GURL("http://www.existing.com");
+  navigation_manager()->AddPendingItem(
+      existing_url, Referrer(), ui::PAGE_TRANSITION_TYPED,
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
 
   [mock_wk_list_ setCurrentURL:@"http://www.existing.com"];
   OCMStub([mock_web_view_ URL])
@@ -782,14 +890,15 @@
   // Add if new transition is a form submission.
   navigation_manager()->AddPendingItem(
       existing_url, Referrer(), ui::PAGE_TRANSITION_FORM_SUBMIT,
-      web::NavigationInitiationType::BROWSER_INITIATED);
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/true,
+      /*is_using_https_as_default_scheme=*/false);
   ASSERT_TRUE(navigation_manager()->GetPendingItem());
-  EXPECT_EQ(navigation_manager()->GetPendingItem(),
+  EXPECT_NE(navigation_manager()->GetPendingItem(),
             navigation_manager()->GetLastCommittedItem());
   EXPECT_TRUE(ui::PageTransitionCoreTypeIs(
       navigation_manager()->GetPendingItem()->GetTransitionType(),
       ui::PAGE_TRANSITION_FORM_SUBMIT));
-  EXPECT_EQ(1, navigation_manager()->GetItemCount());
 }
 
 // Tests that when the last committed item exists, adding a pending item with
@@ -798,11 +907,11 @@
   GURL existing_url = GURL("http://www.existing.com");
   navigation_manager()->AddPendingItem(
       existing_url, Referrer(), ui::PAGE_TRANSITION_TYPED,
-      web::NavigationInitiationType::BROWSER_INITIATED);
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
 
   [mock_wk_list_ setCurrentURL:@"http://www.existing.com"];
-  OCMStub([mock_web_view_ URL])
-      .andReturn([NSURL URLWithString:@"http://www.existing.com"]);
   navigation_manager()->CommitPendingItem();
 
   ASSERT_TRUE(navigation_manager()->GetLastCommittedItem());
@@ -813,13 +922,13 @@
 
   navigation_manager()->AddPendingItem(
       existing_url, Referrer(), ui::PAGE_TRANSITION_LINK,
-      web::NavigationInitiationType::BROWSER_INITIATED);
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
 
   // NavigationManagerImpl assumes that AddPendingItem() is only called for
   // new navigation, so it always creates a new pending item.
   ASSERT_TRUE(navigation_manager()->GetPendingItem());
-  EXPECT_EQ(navigation_manager()->GetPendingItem(),
-            navigation_manager()->GetLastCommittedItem());
   EXPECT_TRUE(ui::PageTransitionCoreTypeIs(
       navigation_manager()->GetPendingItem()->GetTransitionType(),
       ui::PAGE_TRANSITION_LINK));
@@ -832,11 +941,11 @@
   GURL existing_url = GURL("http://www.existing.com");
   navigation_manager()->AddPendingItem(
       existing_url, Referrer(), ui::PAGE_TRANSITION_TYPED,
-      web::NavigationInitiationType::BROWSER_INITIATED);
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
 
   [mock_wk_list_ setCurrentURL:@"http://www.existing.com"];
-  OCMStub([mock_web_view_ URL])
-      .andReturn([NSURL URLWithString:@"http://www.existing.com"]);
   navigation_manager()->CommitPendingItem();
 
   ASSERT_TRUE(navigation_manager()->GetLastCommittedItem());
@@ -847,11 +956,11 @@
 
   navigation_manager()->AddPendingItem(
       existing_url, Referrer(), ui::PAGE_TRANSITION_RELOAD,
-      web::NavigationInitiationType::BROWSER_INITIATED);
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
 
   ASSERT_TRUE(navigation_manager()->GetPendingItem());
-  EXPECT_EQ(navigation_manager()->GetPendingItem(),
-            navigation_manager()->GetLastCommittedItem());
   EXPECT_TRUE(ui::PageTransitionCoreTypeIs(
       navigation_manager()->GetPendingItem()->GetTransitionType(),
       ui::PAGE_TRANSITION_RELOAD));
@@ -878,7 +987,9 @@
   GURL url_before_reload = GURL("http://www.url.com");
   navigation_manager()->AddPendingItem(
       url_before_reload, Referrer(), ui::PAGE_TRANSITION_TYPED,
-      web::NavigationInitiationType::RENDERER_INITIATED);
+      web::NavigationInitiationType::RENDERER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
 
   EXPECT_CALL(navigation_manager_delegate(), Reload()).Times(1);
   navigation_manager()->Reload(web::ReloadType::NORMAL,
@@ -895,7 +1006,9 @@
   GURL url_before_reload = GURL("http://www.url.com");
   navigation_manager()->AddPendingItem(
       url_before_reload, Referrer(), ui::PAGE_TRANSITION_TYPED,
-      web::NavigationInitiationType::BROWSER_INITIATED);
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
 
   EXPECT_CALL(navigation_manager_delegate(), Reload()).Times(1);
   navigation_manager()->Reload(web::ReloadType::NORMAL,
@@ -911,7 +1024,9 @@
 TEST_F(NavigationManagerTest, ReloadLastCommittedItemWithNormalType) {
   navigation_manager()->AddPendingItem(
       GURL("http://www.url.com/0"), Referrer(), ui::PAGE_TRANSITION_TYPED,
-      web::NavigationInitiationType::BROWSER_INITIATED);
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
 
   [mock_wk_list_ setCurrentURL:@"http://www.url.com/0"];
   navigation_manager()->CommitPendingItem();
@@ -919,7 +1034,9 @@
   GURL url_before_reload = GURL("http://www.url.com/1");
   navigation_manager()->AddPendingItem(
       url_before_reload, Referrer(), ui::PAGE_TRANSITION_TYPED,
-      web::NavigationInitiationType::BROWSER_INITIATED);
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
 
   [mock_wk_list_ setCurrentURL:@"http://www.url.com/1"
                   backListURLs:@[ @"http://www.url.com/0" ]
@@ -942,7 +1059,9 @@
        ReloadLastCommittedItemWithNormalTypeWithForwardItems) {
   navigation_manager()->AddPendingItem(
       GURL("http://www.url.com/0"), Referrer(), ui::PAGE_TRANSITION_TYPED,
-      web::NavigationInitiationType::BROWSER_INITIATED);
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
 
   [mock_wk_list_ setCurrentURL:@"http://www.url.com/0"];
   navigation_manager()->CommitPendingItem();
@@ -950,7 +1069,9 @@
   GURL url_before_reload = GURL("http://www.url.com/1");
   navigation_manager()->AddPendingItem(
       url_before_reload, Referrer(), ui::PAGE_TRANSITION_TYPED,
-      web::NavigationInitiationType::BROWSER_INITIATED);
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
 
   [mock_wk_list_ setCurrentURL:@"http://www.url.com/1"
                   backListURLs:@[ @"http://www.url.com/0" ]
@@ -959,7 +1080,9 @@
 
   navigation_manager()->AddPendingItem(
       GURL("http://www.url.com/2"), Referrer(), ui::PAGE_TRANSITION_TYPED,
-      web::NavigationInitiationType::BROWSER_INITIATED);
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
 
   [mock_wk_list_
         setCurrentURL:@"http://www.url.com/2"
@@ -999,7 +1122,9 @@
 TEST_F(NavigationManagerTest, ReloadRendererPendingItemWithOriginalType) {
   navigation_manager()->AddPendingItem(
       GURL("http://www.url.com"), Referrer(), ui::PAGE_TRANSITION_TYPED,
-      web::NavigationInitiationType::RENDERER_INITIATED);
+      web::NavigationInitiationType::RENDERER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
   ASSERT_TRUE(navigation_manager()->GetPendingItem());
   GURL expected_original_url = GURL("http://www.url.com/original");
   navigation_manager()->GetPendingItem()->SetOriginalRequestURL(
@@ -1020,7 +1145,9 @@
 TEST_F(NavigationManagerTest, ReloadUserPendingItemWithOriginalType) {
   navigation_manager()->AddPendingItem(
       GURL("http://www.url.com"), Referrer(), ui::PAGE_TRANSITION_TYPED,
-      web::NavigationInitiationType::BROWSER_INITIATED);
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
   ASSERT_TRUE(navigation_manager()->GetPendingItem());
   GURL expected_original_url = GURL("http://www.url.com/original");
   navigation_manager()->GetPendingItem()->SetOriginalRequestURL(
@@ -1041,14 +1168,18 @@
 TEST_F(NavigationManagerTest, ReloadLastCommittedItemWithOriginalType) {
   navigation_manager()->AddPendingItem(
       GURL("http://www.url.com/0"), Referrer(), ui::PAGE_TRANSITION_TYPED,
-      web::NavigationInitiationType::BROWSER_INITIATED);
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
 
   [mock_wk_list_ setCurrentURL:@"http://www.url.com/0"];
   navigation_manager()->CommitPendingItem();
 
   navigation_manager()->AddPendingItem(
       GURL("http://www.url.com/1"), Referrer(), ui::PAGE_TRANSITION_TYPED,
-      web::NavigationInitiationType::BROWSER_INITIATED);
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
   GURL expected_original_url = GURL("http://www.url.com/1/original");
   ASSERT_TRUE(navigation_manager()->GetPendingItem());
   navigation_manager()->GetPendingItem()->SetOriginalRequestURL(
@@ -1075,14 +1206,18 @@
        ReloadLastCommittedItemWithOriginalTypeWithForwardItems) {
   navigation_manager()->AddPendingItem(
       GURL("http://www.url.com/0"), Referrer(), ui::PAGE_TRANSITION_TYPED,
-      web::NavigationInitiationType::BROWSER_INITIATED);
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
 
   [mock_wk_list_ setCurrentURL:@"http://www.url.com/0"];
   navigation_manager()->CommitPendingItem();
 
   navigation_manager()->AddPendingItem(
       GURL("http://www.url.com/1"), Referrer(), ui::PAGE_TRANSITION_TYPED,
-      web::NavigationInitiationType::BROWSER_INITIATED);
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
   GURL expected_original_url = GURL("http://www.url.com/1/original");
   ASSERT_TRUE(navigation_manager()->GetPendingItem());
   navigation_manager()->GetPendingItem()->SetOriginalRequestURL(
@@ -1095,7 +1230,9 @@
 
   navigation_manager()->AddPendingItem(
       GURL("http://www.url.com/2"), Referrer(), ui::PAGE_TRANSITION_TYPED,
-      web::NavigationInitiationType::BROWSER_INITIATED);
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
 
   [mock_wk_list_ setCurrentURL:@"http://www.url.com/2"
                   backListURLs:@[
@@ -1122,7 +1259,9 @@
   GURL url("http://www.1.com");
   navigation_manager()->AddPendingItem(
       url, Referrer(), ui::PAGE_TRANSITION_TYPED,
-      NavigationInitiationType::BROWSER_INITIATED);
+      NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
   GURL virtual_url("http://www.1.com/virtual");
   navigation_manager()->GetPendingItem()->SetVirtualURL(virtual_url);
   [mock_wk_list_ setCurrentURL:@"http://www.1.com"];
@@ -1150,14 +1289,18 @@
   GURL url("http://www.1.com");
   navigation_manager()->AddPendingItem(
       url, Referrer(), ui::PAGE_TRANSITION_TYPED,
-      NavigationInitiationType::BROWSER_INITIATED);
+      NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
   [mock_wk_list_ setCurrentURL:@"http://www.1.com"];
   navigation_manager()->CommitPendingItem();
 
   navigation_manager()->AddPendingItem(
       GURL("http://www.redirect.com"), Referrer(),
       ui::PAGE_TRANSITION_CLIENT_REDIRECT,
-      NavigationInitiationType::BROWSER_INITIATED);
+      NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
   [mock_wk_list_ setCurrentURL:@"http://www.url.com/redirect"
                   backListURLs:@[ @"http://www.1.com" ]
                forwardListURLs:nil];
@@ -1177,7 +1320,9 @@
   GURL url("http://www.1.com");
   navigation_manager()->AddPendingItem(
       url, Referrer(), ui::PAGE_TRANSITION_CLIENT_REDIRECT,
-      NavigationInitiationType::BROWSER_INITIATED);
+      NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
   [mock_wk_list_ setCurrentURL:@"http://www.1.com"];
   navigation_manager()->CommitPendingItem();
 
@@ -1196,7 +1341,9 @@
   GURL url = wk_navigation_util::CreateRedirectUrl(GURL("http://www.1.com"));
   navigation_manager()->AddPendingItem(
       url, Referrer(), ui::PAGE_TRANSITION_TYPED,
-      NavigationInitiationType::BROWSER_INITIATED);
+      NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
   GURL virtual_url("http://www.1.com/virtual");
   navigation_manager()
       ->GetPendingItemInCurrentOrRestoredSession()
@@ -1219,7 +1366,9 @@
   GURL url1(url::SchemeHostPort(kSchemeToRewrite, "test", 0).Serialize());
   navigation_manager()->AddPendingItem(
       url1, Referrer(), ui::PAGE_TRANSITION_LINK,
-      web::NavigationInitiationType::RENDERER_INITIATED);
+      web::NavigationInitiationType::RENDERER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
   EXPECT_EQ(url1, navigation_manager()->GetPendingItem()->GetURL());
 
   // URL should not be rewritten because last committed URL is not app-specific.
@@ -1229,7 +1378,9 @@
   GURL url2(url::SchemeHostPort(kSchemeToRewrite, "test2", 0).Serialize());
   navigation_manager()->AddPendingItem(
       url2, Referrer(), ui::PAGE_TRANSITION_LINK,
-      web::NavigationInitiationType::RENDERER_INITIATED);
+      web::NavigationInitiationType::RENDERER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
   EXPECT_EQ(url2, navigation_manager()->GetPendingItem()->GetURL());
 
   // URL should not be rewritten for user initiated reload navigations.
@@ -1237,14 +1388,18 @@
       url::SchemeHostPort(kSchemeToRewrite, "test-reload", 0).Serialize());
   navigation_manager()->AddPendingItem(
       url_reload, Referrer(), ui::PAGE_TRANSITION_RELOAD,
-      web::NavigationInitiationType::BROWSER_INITIATED);
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
   EXPECT_EQ(url_reload, navigation_manager()->GetPendingItem()->GetURL());
 
   // URL should be rewritten for user initiated navigations.
   GURL url3(url::SchemeHostPort(kSchemeToRewrite, "test3", 0).Serialize());
   navigation_manager()->AddPendingItem(
       url3, Referrer(), ui::PAGE_TRANSITION_LINK,
-      web::NavigationInitiationType::BROWSER_INITIATED);
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
   GURL rewritten_url3(
       url::SchemeHostPort(kTestWebUIScheme, "test3", 0).Serialize());
   EXPECT_EQ(rewritten_url3, navigation_manager()->GetPendingItem()->GetURL());
@@ -1258,7 +1413,9 @@
   GURL url4(url::SchemeHostPort(kSchemeToRewrite, "test4", 0).Serialize());
   navigation_manager()->AddPendingItem(
       url4, Referrer(), ui::PAGE_TRANSITION_LINK,
-      web::NavigationInitiationType::RENDERER_INITIATED);
+      web::NavigationInitiationType::RENDERER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
   GURL rewritten_url4(
       url::SchemeHostPort(kTestWebUIScheme, "test4", 0).Serialize());
   EXPECT_EQ(rewritten_url4, navigation_manager()->GetPendingItem()->GetURL());
@@ -1269,7 +1426,9 @@
   navigation_manager()->AddTransientURLRewriter(&AppendingUrlRewriter);
   navigation_manager()->AddPendingItem(
       GURL("http://www.0.com"), Referrer(), ui::PAGE_TRANSITION_LINK,
-      web::NavigationInitiationType::BROWSER_INITIATED);
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
 
   NavigationItem* pending_item = navigation_manager()->GetPendingItem();
   EXPECT_EQ(kRewrittenQueryParam, pending_item->GetURL().query());
@@ -1279,7 +1438,9 @@
   GURL url("http://www.1.com");
   navigation_manager()->AddPendingItem(
       url, Referrer(), ui::PAGE_TRANSITION_LINK,
-      web::NavigationInitiationType::BROWSER_INITIATED);
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
 
   EXPECT_EQ(url, navigation_manager()->GetPendingItem()->GetURL());
 }
@@ -1296,7 +1457,9 @@
   // Create two items and add them to the NavigationManagerImpl.
   navigation_manager()->AddPendingItem(
       GURL("http://www.url.com/0"), Referrer(), ui::PAGE_TRANSITION_TYPED,
-      web::NavigationInitiationType::BROWSER_INITIATED);
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
 
   mock_wk_list_.currentItem = wk_item0;
   navigation_manager()->CommitPendingItem();
@@ -1304,7 +1467,9 @@
   NavigationItem* item0 = navigation_manager()->GetLastCommittedItem();
   navigation_manager()->AddPendingItem(
       GURL("http://www.url.com/1"), Referrer(), ui::PAGE_TRANSITION_TYPED,
-      web::NavigationInitiationType::BROWSER_INITIATED);
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
 
   mock_wk_list_.currentItem = wk_item1;
   mock_wk_list_.backList = @[ wk_item0 ];
@@ -1324,14 +1489,18 @@
 TEST_F(NavigationManagerTest, TestBackwardForwardItems) {
   navigation_manager()->AddPendingItem(
       GURL("http://www.url.com/0"), Referrer(), ui::PAGE_TRANSITION_TYPED,
-      web::NavigationInitiationType::BROWSER_INITIATED);
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
 
   [mock_wk_list_ setCurrentURL:@"http://www.url.com/0"];
   navigation_manager()->CommitPendingItem();
 
   navigation_manager()->AddPendingItem(
       GURL("http://www.url.com/1"), Referrer(), ui::PAGE_TRANSITION_TYPED,
-      web::NavigationInitiationType::BROWSER_INITIATED);
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
 
   [mock_wk_list_ setCurrentURL:@"http://www.url.com/1"
                   backListURLs:@[ @"http://www.url.com/0" ]
@@ -1340,7 +1509,9 @@
 
   navigation_manager()->AddPendingItem(
       GURL("http://www.url.com/2"), Referrer(), ui::PAGE_TRANSITION_TYPED,
-      web::NavigationInitiationType::BROWSER_INITIATED);
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
 
   [mock_wk_list_
         setCurrentURL:@"http://www.url.com/2"
@@ -1458,14 +1629,18 @@
 TEST_F(NavigationManagerTest, NewPendingItemIsHiddenFromHistory) {
   navigation_manager()->AddPendingItem(
       GURL("http://www.url.com/0"), Referrer(), ui::PAGE_TRANSITION_TYPED,
-      web::NavigationInitiationType::BROWSER_INITIATED);
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
 
   [mock_wk_list_ setCurrentURL:@"http://www.url.com/0"];
   navigation_manager()->CommitPendingItem();
 
   navigation_manager()->AddPendingItem(
       GURL("http://www.url.com/1"), Referrer(), ui::PAGE_TRANSITION_TYPED,
-      web::NavigationInitiationType::BROWSER_INITIATED);
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
 
   [mock_wk_list_ setCurrentURL:@"http://www.url.com/1"
                   backListURLs:@[ @"http://www.url.com/0" ]
@@ -1474,7 +1649,9 @@
 
   navigation_manager()->AddPendingItem(
       GURL("http://www.url.com/2"), Referrer(), ui::PAGE_TRANSITION_TYPED,
-      web::NavigationInitiationType::BROWSER_INITIATED);
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
 
   EXPECT_EQ(1, navigation_manager()->GetLastCommittedItemIndex());
   EXPECT_TRUE(navigation_manager()->GetPendingItem());
@@ -1490,7 +1667,9 @@
 
   navigation_manager()->AddPendingItem(
       GURL("http://www.url.com/0"), Referrer(), ui::PAGE_TRANSITION_TYPED,
-      web::NavigationInitiationType::BROWSER_INITIATED);
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
 
   ASSERT_TRUE(navigation_manager()->GetVisibleItem());
   EXPECT_EQ("http://www.url.com/0",
@@ -1503,7 +1682,9 @@
 
   navigation_manager()->AddPendingItem(
       GURL("http://www.url.com/1"), Referrer(), ui::PAGE_TRANSITION_TYPED,
-      web::NavigationInitiationType::BROWSER_INITIATED);
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
 
   ASSERT_TRUE(navigation_manager()->GetVisibleItem());
   EXPECT_EQ("http://www.url.com/1",
@@ -1516,7 +1697,9 @@
 
   navigation_manager()->AddPendingItem(
       GURL("http://www.url.com/0"), Referrer(), ui::PAGE_TRANSITION_TYPED,
-      web::NavigationInitiationType::RENDERER_INITIATED);
+      web::NavigationInitiationType::RENDERER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
 
   EXPECT_EQ(nullptr, navigation_manager()->GetVisibleItem());
 }
@@ -1524,14 +1707,18 @@
 TEST_F(NavigationManagerTest, PendingItemIsNotVisibleIfNotNewNavigation) {
   navigation_manager()->AddPendingItem(
       GURL("http://www.url.com/0"), Referrer(), ui::PAGE_TRANSITION_TYPED,
-      web::NavigationInitiationType::RENDERER_INITIATED);
+      web::NavigationInitiationType::RENDERER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
 
   [mock_wk_list_ setCurrentURL:@"http://www.url.com/0"];
   navigation_manager()->CommitPendingItem();
 
   navigation_manager()->AddPendingItem(
       GURL("http://www.url.com/1"), Referrer(), ui::PAGE_TRANSITION_TYPED,
-      web::NavigationInitiationType::RENDERER_INITIATED);
+      web::NavigationInitiationType::RENDERER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
 
   [mock_wk_list_ setCurrentURL:@"http://www.url.com/1"
                   backListURLs:@[ @"http://www.url.com/0" ]
@@ -1547,7 +1734,9 @@
   navigation_manager()->AddPendingItem(
       GURL("http://www.url.com/0"), Referrer(),
       ui::PAGE_TRANSITION_FORWARD_BACK,
-      web::NavigationInitiationType::BROWSER_INITIATED);
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
   ASSERT_EQ(0, navigation_manager()->GetPendingItemIndex());
 
   delegate_.SetWebState(&web_state_);
@@ -1567,14 +1756,18 @@
 
   navigation_manager()->AddPendingItem(
       GURL("http://www.url.com/0"), Referrer(), ui::PAGE_TRANSITION_TYPED,
-      web::NavigationInitiationType::RENDERER_INITIATED);
+      web::NavigationInitiationType::RENDERER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
 
   [mock_wk_list_ setCurrentURL:@"http://www.url.com/0"];
   navigation_manager()->CommitPendingItem();
 
   navigation_manager()->AddPendingItem(
       GURL("http://www.url.com/1"), Referrer(), ui::PAGE_TRANSITION_TYPED,
-      web::NavigationInitiationType::RENDERER_INITIATED);
+      web::NavigationInitiationType::RENDERER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
 
   OCMStub([mock_web_view_ URL])
       .andReturn([[NSURL alloc] initWithString:@"http://www.url.com/0"]);
@@ -1617,7 +1810,9 @@
 TEST_F(NavigationManagerTest, LoadURLWithParamsSavesStateOnCurrentItem) {
   navigation_manager()->AddPendingItem(
       GURL("http://www.url.com/0"), Referrer(), ui::PAGE_TRANSITION_TYPED,
-      web::NavigationInitiationType::BROWSER_INITIATED);
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
 
   [mock_wk_list_ setCurrentURL:@"http://www.url.com/0"];
   navigation_manager()->CommitPendingItem();
@@ -1655,7 +1850,9 @@
 TEST_F(NavigationManagerTest, UpdatePendingItemWithPendingItem) {
   navigation_manager()->AddPendingItem(
       GURL("http://www.url.com"), Referrer(), ui::PAGE_TRANSITION_TYPED,
-      web::NavigationInitiationType::BROWSER_INITIATED);
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
   navigation_manager()->UpdatePendingItemUrl(GURL("http://another.url.com"));
 
   ASSERT_TRUE(navigation_manager()->GetPendingItem());
@@ -1667,7 +1864,9 @@
        UpdatePendingItemWithPendingItemAlreadyCommitted) {
   navigation_manager()->AddPendingItem(
       GURL("http://www.url.com"), Referrer(), ui::PAGE_TRANSITION_TYPED,
-      web::NavigationInitiationType::BROWSER_INITIATED);
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
 
   [mock_wk_list_ setCurrentURL:@"http://www.url.com"];
   navigation_manager()->CommitPendingItem();
@@ -1682,13 +1881,17 @@
 TEST_F(NavigationManagerTest, GoToIndexDifferentDocument) {
   navigation_manager()->AddPendingItem(
       GURL("http://www.url.com/0"), Referrer(), ui::PAGE_TRANSITION_TYPED,
-      web::NavigationInitiationType::BROWSER_INITIATED);
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
   [mock_wk_list_ setCurrentURL:@"http://www.url.com/0"];
   navigation_manager()->CommitPendingItem();
 
   navigation_manager()->AddPendingItem(
       GURL("http://www.url.com/1"), Referrer(), ui::PAGE_TRANSITION_TYPED,
-      web::NavigationInitiationType::BROWSER_INITIATED);
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
   [mock_wk_list_ setCurrentURL:@"http://www.url.com/1"
                   backListURLs:@[ @"http://www.url.com/0" ]
                forwardListURLs:nil];
@@ -1711,13 +1914,17 @@
 TEST_F(NavigationManagerTest, GoToIndexSameDocument) {
   navigation_manager()->AddPendingItem(
       GURL("http://www.url.com/0"), Referrer(), ui::PAGE_TRANSITION_TYPED,
-      web::NavigationInitiationType::BROWSER_INITIATED);
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
   [mock_wk_list_ setCurrentURL:@"http://www.url.com/0"];
   navigation_manager()->CommitPendingItem();
 
   navigation_manager()->AddPendingItem(
       GURL("http://www.url.com/0#hash"), Referrer(), ui::PAGE_TRANSITION_TYPED,
-      web::NavigationInitiationType::BROWSER_INITIATED);
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
   static_cast<NavigationItemImpl*>(navigation_manager()->GetPendingItem())
       ->SetIsCreatedFromHashChange(true);
   [mock_wk_list_ setCurrentURL:@"http://www.url.com/0#hash"
@@ -1744,7 +1951,9 @@
   ASSERT_EQ(0, navigation_manager()->GetItemCount());
   navigation_manager()->AddPendingItem(
       GURL("http://www.url.com/0"), Referrer(), ui::PAGE_TRANSITION_TYPED,
-      web::NavigationInitiationType::BROWSER_INITIATED);
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
 
   [mock_wk_list_ setCurrentURL:@"http://www.url.com/1"
                   backListURLs:nil
@@ -1780,11 +1989,15 @@
                forwardListURLs:nil];
   navigation_manager()->AddPendingItem(
       GURL("http://www.url.test/0"), Referrer(), ui::PAGE_TRANSITION_TYPED,
-      web::NavigationInitiationType::BROWSER_INITIATED);
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
   navigation_manager()->CommitPendingItem();
   navigation_manager()->AddPendingItem(
       GURL("http://www.url.test/1"), Referrer(), ui::PAGE_TRANSITION_TYPED,
-      web::NavigationInitiationType::BROWSER_INITIATED);
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
   navigation_manager()->CommitPendingItem();
   SimulateGoToIndex(0);
   mock_wk_list_.backList = @[ mock_wk_list_.currentItem ];
@@ -1832,7 +2045,9 @@
 
   navigation_manager()->AddPendingItem(
       GURL("http://www.url.com/0"), Referrer(), ui::PAGE_TRANSITION_TYPED,
-      web::NavigationInitiationType::BROWSER_INITIATED);
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
   [mock_wk_list_ setCurrentURL:@"http://www.url.com/0"];
   navigation_manager()->CommitPendingItem();
   NavigationItem* last_committed_item =
@@ -1842,7 +2057,9 @@
 
   navigation_manager()->AddPendingItem(
       GURL("http://www.url.com/1"), Referrer(), ui::PAGE_TRANSITION_TYPED,
-      web::NavigationInitiationType::BROWSER_INITIATED);
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
   NavigationItem* pending_item = navigation_manager()->GetPendingItem();
   ASSERT_NE(pending_item, nullptr);
   EXPECT_EQ(pending_item, navigation_manager()->GetCurrentItemImpl());
@@ -1853,7 +2070,9 @@
       GURL("http://www.url.com/0"),
       Referrer(GURL("http://referrer.com"), ReferrerPolicyDefault),
       ui::PAGE_TRANSITION_TYPED,
-      web::NavigationInitiationType::BROWSER_INITIATED);
+      web::NavigationInitiationType::BROWSER_INITIATED,
+      /*is_post_navigation=*/false,
+      /*is_using_https_as_default_scheme=*/false);
 
   // Tests that pending item can be replaced.
   GURL replace_page_url("http://www.url.com/replace");
@@ -1980,7 +2199,9 @@
 
   // Verifies that the test URL is rewritten into an app-specific URL.
   manager_->AddPendingItem(url, Referrer(), ui::PAGE_TRANSITION_TYPED,
-                           web::NavigationInitiationType::BROWSER_INITIATED);
+                           web::NavigationInitiationType::BROWSER_INITIATED,
+                           /*is_post_navigation=*/false,
+                           /*is_using_https_as_default_scheme=*/false);
   NavigationItem* pending_item = manager_->GetPendingItem();
   ASSERT_TRUE(pending_item);
   ASSERT_TRUE(web::GetWebClient()->IsAppSpecificURL(pending_item->GetURL()));
@@ -1999,7 +2220,9 @@
   // Simulate a main frame navigation.
   manager_->AddPendingItem(GURL("http://www.0.com"), Referrer(),
                            ui::PAGE_TRANSITION_TYPED,
-                           web::NavigationInitiationType::BROWSER_INITIATED);
+                           web::NavigationInitiationType::BROWSER_INITIATED,
+                           /*is_post_navigation=*/false,
+                           /*is_using_https_as_default_scheme=*/false);
   NavigationItem* pending_item0 = manager_->GetPendingItem();
 
   [mock_wk_list_ setCurrentURL:@"http://www.0.com"];
@@ -2015,7 +2238,9 @@
   // Simulate a second main frame navigation.
   manager_->AddPendingItem(GURL("http://www.2.com"), Referrer(),
                            ui::PAGE_TRANSITION_TYPED,
-                           web::NavigationInitiationType::BROWSER_INITIATED);
+                           web::NavigationInitiationType::BROWSER_INITIATED,
+                           /*is_post_navigation=*/false,
+                           /*is_using_https_as_default_scheme=*/false);
   NavigationItem* pending_item2 = manager_->GetPendingItem();
 
   // Simulate an iframe navigation between the two main frame navigations.
@@ -2067,7 +2292,9 @@
       .andReturn([[NSURL alloc] initWithString:@"http://www.0.com"]);
   manager_->AddPendingItem(GURL("http://www.0.com"), Referrer(),
                            ui::PAGE_TRANSITION_TYPED,
-                           web::NavigationInitiationType::BROWSER_INITIATED);
+                           web::NavigationInitiationType::BROWSER_INITIATED,
+                           /*is_post_navigation=*/false,
+                           /*is_using_https_as_default_scheme=*/false);
 
   EXPECT_EQ(original_item0, manager_->GetPendingItem());
 
@@ -2081,7 +2308,9 @@
   original_item0 = manager_->GetItemAtIndex(0);
   manager_->AddPendingItem(GURL("http://www.0.com"), Referrer(),
                            ui::PAGE_TRANSITION_RELOAD,
-                           web::NavigationInitiationType::BROWSER_INITIATED);
+                           web::NavigationInitiationType::BROWSER_INITIATED,
+                           /*is_post_navigation=*/false,
+                           /*is_using_https_as_default_scheme=*/false);
   EXPECT_EQ(original_item0, manager_->GetPendingItem());
 }
 
@@ -2100,7 +2329,9 @@
 
   manager_->AddPendingItem(GURL("chrome://newtab"), Referrer(),
                            ui::PAGE_TRANSITION_RELOAD,
-                           web::NavigationInitiationType::BROWSER_INITIATED);
+                           web::NavigationInitiationType::BROWSER_INITIATED,
+                           /*is_post_navigation=*/false,
+                           /*is_using_https_as_default_scheme=*/false);
 
   EXPECT_EQ(original_item, manager_->GetPendingItem());
 }
@@ -2109,7 +2340,9 @@
 TEST_F(NavigationManagerTest, TransientURLRewritersOnlyUsedForPendingItem) {
   manager_->AddPendingItem(GURL("http://www.0.com"), Referrer(),
                            ui::PAGE_TRANSITION_TYPED,
-                           NavigationInitiationType::BROWSER_INITIATED);
+                           NavigationInitiationType::BROWSER_INITIATED,
+                           /*is_post_navigation=*/false,
+                           /*is_using_https_as_default_scheme=*/false);
 
   // Install transient URL rewriters.
   manager_->AddTransientURLRewriter(&AppendingUrlRewriter);
@@ -2122,7 +2355,9 @@
   // Transient URL rewriters are applied to a new pending item.
   manager_->AddPendingItem(GURL("http://www.2.com"), Referrer(),
                            ui::PAGE_TRANSITION_TYPED,
-                           NavigationInitiationType::BROWSER_INITIATED);
+                           NavigationInitiationType::BROWSER_INITIATED,
+                           /*is_post_navigation=*/false,
+                           /*is_using_https_as_default_scheme=*/false);
   EXPECT_EQ(kRewrittenQueryParam, manager_->GetPendingItem()->GetURL().query());
 }
 
@@ -2130,7 +2365,9 @@
 TEST_F(NavigationManagerTest, DiscardNonCommittedItems) {
   manager_->AddPendingItem(GURL("http://www.0.com"), Referrer(),
                            ui::PAGE_TRANSITION_TYPED,
-                           web::NavigationInitiationType::BROWSER_INITIATED);
+                           web::NavigationInitiationType::BROWSER_INITIATED,
+                           /*is_post_navigation=*/false,
+                           /*is_using_https_as_default_scheme=*/false);
 
   EXPECT_NE(nullptr, manager_->GetPendingItem());
 
@@ -2144,12 +2381,16 @@
 
   manager_->AddPendingItem(GURL("http://www.0.com"), Referrer(),
                            ui::PAGE_TRANSITION_TYPED,
-                           web::NavigationInitiationType::BROWSER_INITIATED);
+                           web::NavigationInitiationType::BROWSER_INITIATED,
+                           /*is_post_navigation=*/false,
+                           /*is_using_https_as_default_scheme=*/false);
   [mock_wk_list_ setCurrentURL:@"http://www.0.com"];
 
   manager_->AddPendingItem(GURL("http://www.1.com"), Referrer(),
                            ui::PAGE_TRANSITION_TYPED,
-                           web::NavigationInitiationType::BROWSER_INITIATED);
+                           web::NavigationInitiationType::BROWSER_INITIATED,
+                           /*is_post_navigation=*/false,
+                           /*is_using_https_as_default_scheme=*/false);
   [mock_wk_list_ setCurrentURL:@"http://www.1.com"
                   backListURLs:@[ @"http://www.0.com" ]
                forwardListURLs:nil];
@@ -2170,13 +2411,17 @@
 TEST_F(NavigationManagerTest, GoForward) {
   manager_->AddPendingItem(GURL("http://www.0.com"), Referrer(),
                            ui::PAGE_TRANSITION_TYPED,
-                           web::NavigationInitiationType::BROWSER_INITIATED);
+                           web::NavigationInitiationType::BROWSER_INITIATED,
+                           /*is_post_navigation=*/false,
+                           /*is_using_https_as_default_scheme=*/false);
   [mock_wk_list_ setCurrentURL:@"http://www.0.com"];
   manager_->CommitPendingItem();
 
   manager_->AddPendingItem(GURL("http://www.1.com"), Referrer(),
                            ui::PAGE_TRANSITION_TYPED,
-                           web::NavigationInitiationType::BROWSER_INITIATED);
+                           web::NavigationInitiationType::BROWSER_INITIATED,
+                           /*is_post_navigation=*/false,
+                           /*is_using_https_as_default_scheme=*/false);
   [mock_wk_list_ setCurrentURL:@"http://www.1.com"
                   backListURLs:@[ @"http://www.0.com" ]
                forwardListURLs:nil];
@@ -2197,13 +2442,17 @@
 TEST_F(NavigationManagerTest, GoForwardShouldDiscardsUncommittedItems) {
   manager_->AddPendingItem(GURL("http://www.0.com"), Referrer(),
                            ui::PAGE_TRANSITION_TYPED,
-                           web::NavigationInitiationType::BROWSER_INITIATED);
+                           web::NavigationInitiationType::BROWSER_INITIATED,
+                           /*is_post_navigation=*/false,
+                           /*is_using_https_as_default_scheme=*/false);
   [mock_wk_list_ setCurrentURL:@"http://www.0.com"];
   manager_->CommitPendingItem();
 
   manager_->AddPendingItem(GURL("http://www.1.com"), Referrer(),
                            ui::PAGE_TRANSITION_TYPED,
-                           web::NavigationInitiationType::BROWSER_INITIATED);
+                           web::NavigationInitiationType::BROWSER_INITIATED,
+                           /*is_post_navigation=*/false,
+                           /*is_using_https_as_default_scheme=*/false);
   [mock_wk_list_ setCurrentURL:@"http://www.1.com"
                   backListURLs:@[ @"http://www.0.com" ]
                forwardListURLs:nil];
@@ -2213,7 +2462,9 @@
 
   manager_->AddPendingItem(GURL("http://www.0.com"), Referrer(),
                            ui::PAGE_TRANSITION_TYPED,
-                           web::NavigationInitiationType::BROWSER_INITIATED);
+                           web::NavigationInitiationType::BROWSER_INITIATED,
+                           /*is_post_navigation=*/false,
+                           /*is_using_https_as_default_scheme=*/false);
 
   EXPECT_NE(nullptr, manager_->GetPendingItem());
 
@@ -2232,14 +2483,18 @@
 TEST_F(NavigationManagerTest, CanGoToOffset) {
   manager_->AddPendingItem(GURL("http://www.url.com/0"), Referrer(),
                            ui::PAGE_TRANSITION_LINK,
-                           web::NavigationInitiationType::BROWSER_INITIATED);
+                           web::NavigationInitiationType::BROWSER_INITIATED,
+                           /*is_post_navigation=*/false,
+                           /*is_using_https_as_default_scheme=*/false);
 
   [mock_wk_list_ setCurrentURL:@"http://www.url.com/0"];
   manager_->CommitPendingItem();
 
   manager_->AddPendingItem(GURL("http://www.url.com/1"), Referrer(),
                            ui::PAGE_TRANSITION_LINK,
-                           web::NavigationInitiationType::BROWSER_INITIATED);
+                           web::NavigationInitiationType::BROWSER_INITIATED,
+                           /*is_post_navigation=*/false,
+                           /*is_using_https_as_default_scheme=*/false);
 
   [mock_wk_list_ setCurrentURL:@"http://www.url.com/1"
                   backListURLs:@[ @"http://www.url.com/0" ]
@@ -2248,7 +2503,9 @@
 
   manager_->AddPendingItem(GURL("http://www.url.com/2"), Referrer(),
                            ui::PAGE_TRANSITION_LINK,
-                           web::NavigationInitiationType::BROWSER_INITIATED);
+                           web::NavigationInitiationType::BROWSER_INITIATED,
+                           /*is_post_navigation=*/false,
+                           /*is_using_https_as_default_scheme=*/false);
 
   [mock_wk_list_
         setCurrentURL:@"http://www.url.com/2"
@@ -2306,7 +2563,9 @@
       .andReturn([[NSURL alloc] initWithString:@"http://www.url.com/1"]);
   manager_->AddPendingItem(GURL("http://www.url.com/1"), Referrer(),
                            ui::PAGE_TRANSITION_LINK,
-                           web::NavigationInitiationType::BROWSER_INITIATED);
+                           web::NavigationInitiationType::BROWSER_INITIATED,
+                           /*is_post_navigation=*/false,
+                           /*is_using_https_as_default_scheme=*/false);
 
   EXPECT_EQ(3, manager_->GetItemCount());
   EXPECT_EQ(2, manager_->GetLastCommittedItemIndex());
@@ -2370,13 +2629,17 @@
   // and previous_item_index is 0. Basically, none of them is -1.
   manager_->AddPendingItem(GURL("http://www.url.com/0"), Referrer(),
                            ui::PAGE_TRANSITION_TYPED,
-                           web::NavigationInitiationType::BROWSER_INITIATED);
+                           web::NavigationInitiationType::BROWSER_INITIATED,
+                           /*is_post_navigation=*/false,
+                           /*is_using_https_as_default_scheme=*/false);
   [mock_wk_list_ setCurrentURL:@"http://www.url.com/0"];
   manager_->CommitPendingItem();
 
   manager_->AddPendingItem(GURL("http://www.url.com/1"), Referrer(),
                            ui::PAGE_TRANSITION_TYPED,
-                           web::NavigationInitiationType::BROWSER_INITIATED);
+                           web::NavigationInitiationType::BROWSER_INITIATED,
+                           /*is_post_navigation=*/false,
+                           /*is_using_https_as_default_scheme=*/false);
   [mock_wk_list_ setCurrentURL:@"http://www.url.com/1"
                   backListURLs:@[ @"http://www.url.com/0" ]
                forwardListURLs:nil];
@@ -2387,7 +2650,9 @@
       .andReturn([NSURL URLWithString:@"http://www.url.com/0"]);
   manager_->AddPendingItem(GURL("http://www.url.com/0"), Referrer(),
                            ui::PAGE_TRANSITION_TYPED,
-                           web::NavigationInitiationType::BROWSER_INITIATED);
+                           web::NavigationInitiationType::BROWSER_INITIATED,
+                           /*is_post_navigation=*/false,
+                           /*is_using_https_as_default_scheme=*/false);
 
   EXPECT_EQ(1, manager_->GetLastCommittedItemIndex());
   EXPECT_EQ(0, manager_->GetPendingItemIndex());
@@ -2470,7 +2735,9 @@
 
   manager_->AddPendingItem(GURL(url::kAboutBlankURL), Referrer(),
                            ui::PAGE_TRANSITION_LINK,
-                           web::NavigationInitiationType::RENDERER_INITIATED);
+                           web::NavigationInitiationType::RENDERER_INITIATED,
+                           /*is_post_navigation=*/false,
+                           /*is_using_https_as_default_scheme=*/false);
 
   const NavigationItem* pending_item = manager_->GetPendingItem();
   ASSERT_TRUE(pending_item);
@@ -2508,7 +2775,9 @@
   // item.
   manager_->AddPendingItem(GURL("http://www.2.com"), Referrer(),
                            ui::PAGE_TRANSITION_TYPED,
-                           web::NavigationInitiationType::BROWSER_INITIATED);
+                           web::NavigationInitiationType::BROWSER_INITIATED,
+                           /*is_post_navigation=*/false,
+                           /*is_using_https_as_default_scheme=*/false);
 
   const NavigationItem* pending_item_2 = manager_->GetPendingItem();
   ASSERT_TRUE(pending_item_2);
@@ -2731,20 +3000,26 @@
 TEST_F(NavigationManagerDetachedModeTest, NotSerializable) {
   manager_->AddPendingItem(GURL("http://www.0.com"), Referrer(),
                            ui::PAGE_TRANSITION_TYPED,
-                           web::NavigationInitiationType::BROWSER_INITIATED);
+                           web::NavigationInitiationType::BROWSER_INITIATED,
+                           /*is_post_navigation=*/false,
+                           /*is_using_https_as_default_scheme=*/false);
   EXPECT_FALSE(manager_->GetPendingItemInCurrentOrRestoredSession()
                    ->ShouldSkipSerialization());
 
   manager_->SetWKWebViewNextPendingUrlNotSerializable(GURL("http://www.1.com"));
   manager_->AddPendingItem(GURL("http://www.1.com"), Referrer(),
                            ui::PAGE_TRANSITION_TYPED,
-                           web::NavigationInitiationType::BROWSER_INITIATED);
+                           web::NavigationInitiationType::BROWSER_INITIATED,
+                           /*is_post_navigation=*/false,
+                           /*is_using_https_as_default_scheme=*/false);
   EXPECT_TRUE(manager_->GetPendingItemInCurrentOrRestoredSession()
                   ->ShouldSkipSerialization());
 
   manager_->AddPendingItem(GURL("http://www.1.com"), Referrer(),
                            ui::PAGE_TRANSITION_TYPED,
-                           web::NavigationInitiationType::BROWSER_INITIATED);
+                           web::NavigationInitiationType::BROWSER_INITIATED,
+                           /*is_post_navigation=*/false,
+                           /*is_using_https_as_default_scheme=*/false);
   EXPECT_FALSE(manager_->GetPendingItemInCurrentOrRestoredSession()
                    ->ShouldSkipSerialization());
 }
diff --git a/ios/web/navigation/navigation_manager_util_unittest.mm b/ios/web/navigation/navigation_manager_util_unittest.mm
index 56e4661..1e63a4e 100644
--- a/ios/web/navigation/navigation_manager_util_unittest.mm
+++ b/ios/web/navigation/navigation_manager_util_unittest.mm
@@ -54,7 +54,9 @@
           /*is_renderer_initiated=*/false);
   manager_->AddPendingItem(GURL("http://chromium.org"), Referrer(),
                            ui::PAGE_TRANSITION_TYPED,
-                           web::NavigationInitiationType::BROWSER_INITIATED);
+                           web::NavigationInitiationType::BROWSER_INITIATED,
+                           /*is_post_navigation=*/false,
+                           /*is_using_https_as_default_scheme=*/false);
   NavigationItem* item = manager_->GetPendingItem();
   int unique_id = item->GetUniqueID();
   context->SetNavigationItemUniqueID(item->GetUniqueID());
diff --git a/ios/web/public/test/earl_grey/web_view_actions.mm b/ios/web/public/test/earl_grey/web_view_actions.mm
index a3530375..2559059 100644
--- a/ios/web/public/test/earl_grey/web_view_actions.mm
+++ b/ios/web/public/test/earl_grey/web_view_actions.mm
@@ -95,7 +95,7 @@
   // The callback doesn't care about any of the parameters, just whether it is
   // called or not.
   auto callback = base::BindRepeating(
-      ^(const base::DictionaryValue& /* json */, const GURL& /* origin_url */,
+      ^(const base::Value& /* json */, const GURL& /* origin_url */,
         bool /* user_is_interacting */, web::WebFrame* /* sender_frame */) {
         *verified = true;
       });
diff --git a/ios/web/public/test/js_test_storage_util.mm b/ios/web/public/test/js_test_storage_util.mm
index fa907446a..1bb9b624 100644
--- a/ios/web/public/test/js_test_storage_util.mm
+++ b/ios/web/public/test/js_test_storage_util.mm
@@ -149,20 +149,26 @@
   __block NSString* block_error_message;
   base::CallbackListSubscription subscription_ =
       web_state->AddScriptCommandCallback(
-          base::BindRepeating(^(const base::DictionaryValue& message,
+          base::BindRepeating(^(const base::Value& message,
                                 const GURL& page_url, bool user_is_interacting,
                                 web::WebFrame* sender_frame) {
-            const base::Value* result = message.FindPath("result");
+            const base::Value* result = message.FindKey("result");
             if (!result) {
               return;
             }
             if (result->is_bool()) {
               async_success = result->GetBool();
-            } else {
-              block_error_message = base::SysUTF8ToNSString(
-                  result->FindPath("message")->GetString());
-              async_success = true;
+              return;
             }
+            if (result->is_dict()) {
+              const std::string* message = result->FindStringKey("message");
+              if (message) {
+                block_error_message = base::SysUTF8ToNSString(*message);
+                async_success = true;
+                return;
+              }
+            }
+            async_success = false;
           }),
           "cookieTest");
 
@@ -199,24 +205,28 @@
   __block NSString* block_error_message;
   base::CallbackListSubscription subscription_ =
       web_state->AddScriptCommandCallback(
-          base::BindRepeating(^(const base::DictionaryValue& message,
+          base::BindRepeating(^(const base::Value& message,
                                 const GURL& page_url, bool user_is_interacting,
                                 web::WebFrame* sender_frame) {
-            const base::Value* javascript_result = message.FindPath("result");
-            if (!javascript_result) {
+            const base::Value* result = message.FindPath("result");
+            if (!result) {
               return;
             }
-            if (javascript_result->is_string()) {
-              block_result =
-                  base::SysUTF8ToNSString(javascript_result->GetString());
+            if (result->is_string()) {
+              block_result = base::SysUTF8ToNSString(result->GetString());
               async_success = true;
-            } else if (javascript_result->is_dict()) {
-              block_error_message = base::SysUTF8ToNSString(
-                  javascript_result->FindPath("message")->GetString());
-              async_success = true;
-            } else {
-              async_success = false;
+              return;
             }
+            if (result->is_dict()) {
+              const std::string* message = result->FindStringKey("message");
+              if (message) {
+                block_error_message = base::SysUTF8ToNSString(*message);
+                async_success = true;
+                return;
+              }
+            }
+
+            async_success = false;
           }),
           "cookieTest");
 
diff --git a/ios/web/public/test/web_test_with_web_state.mm b/ios/web/public/test/web_test_with_web_state.mm
index 69eb749..801b2870 100644
--- a/ios/web/public/test/web_test_with_web_state.mm
+++ b/ios/web/public/test/web_test_with_web_state.mm
@@ -6,7 +6,6 @@
 
 #include "base/ios/ios_util.h"
 #include "base/run_loop.h"
-#include "base/scoped_observer.h"
 #include "base/strings/sys_string_conversions.h"
 #include "base/task/current_thread.h"
 #import "base/test/ios/wait_util.h"
@@ -75,7 +74,9 @@
   GetWebController(web_state())
       .webStateImpl->GetNavigationManagerImpl()
       .AddPendingItem(url, Referrer(), transition,
-                      web::NavigationInitiationType::BROWSER_INITIATED);
+                      web::NavigationInitiationType::BROWSER_INITIATED,
+                      /*is_post_navigation=*/false,
+                      /*is_using_https_as_default_scheme=*/false);
 }
 
 bool WebTestWithWebState::LoadHtmlWithoutSubresources(const std::string& html) {
diff --git a/ios/web/public/web_state.h b/ios/web/public/web_state.h
index f0d84af..26a5a627 100644
--- a/ios/web/public/web_state.h
+++ b/ios/web/public/web_state.h
@@ -37,7 +37,6 @@
 typedef UIView<CRWScrollableContent> CRWContentView;
 
 namespace base {
-class DictionaryValue;
 class Value;
 }
 
@@ -315,11 +314,10 @@
   // frame, |user_is_interacting| indicates if the user is interacting with the
   // page.
   // TODO(crbug.com/881813): remove |page_url|.
-  using ScriptCommandCallbackSignature =
-      void(const base::DictionaryValue& message,
-           const GURL& page_url,
-           bool user_is_interacting,
-           web::WebFrame* sender_frame);
+  using ScriptCommandCallbackSignature = void(const base::Value& message,
+                                              const GURL& page_url,
+                                              bool user_is_interacting,
+                                              web::WebFrame* sender_frame);
   using ScriptCommandCallback =
       base::RepeatingCallback<ScriptCommandCallbackSignature>;
   // Registers |callback| for JS message whose 'command' matches
diff --git a/ios/web/security/crw_ssl_status_updater_unittest.mm b/ios/web/security/crw_ssl_status_updater_unittest.mm
index 344375d..e804b515 100644
--- a/ios/web/security/crw_ssl_status_updater_unittest.mm
+++ b/ios/web/security/crw_ssl_status_updater_unittest.mm
@@ -122,7 +122,9 @@
     [fake_wk_list_ setCurrentURL:base::SysUTF8ToNSString(item_url_spec)];
     nav_manager_.AddPendingItem(
         GURL(item_url_spec), Referrer(), ui::PAGE_TRANSITION_LINK,
-        web::NavigationInitiationType::BROWSER_INITIATED);
+        web::NavigationInitiationType::BROWSER_INITIATED,
+        /*is_post_navigation=*/false,
+        /*is_using_https_as_default_scheme=*/false);
     nav_manager_.CommitPendingItem();
   }
 
diff --git a/ios/web/text_fragments/text_fragments_manager_impl.h b/ios/web/text_fragments/text_fragments_manager_impl.h
index 068532b..b4c8025 100644
--- a/ios/web/text_fragments/text_fragments_manager_impl.h
+++ b/ios/web/text_fragments/text_fragments_manager_impl.h
@@ -48,7 +48,10 @@
 
   bool AreTextFragmentsAllowed(const web::NavigationContext* context);
 
-  void DidReceiveJavaScriptResponse(const base::DictionaryValue& response);
+  void DidReceiveJavaScriptResponse(const base::Value& response,
+                                    const GURL& page_url,
+                                    bool interacted,
+                                    web::WebFrame* sender_frame);
 
   web::WebState* web_state_ = nullptr;
   base::CallbackListSubscription subscription_;
diff --git a/ios/web/text_fragments/text_fragments_manager_impl.mm b/ios/web/text_fragments/text_fragments_manager_impl.mm
index 2ec0499..1a380dd 100644
--- a/ios/web/text_fragments/text_fragments_manager_impl.mm
+++ b/ios/web/text_fragments/text_fragments_manager_impl.mm
@@ -49,16 +49,11 @@
   DCHECK(web_state_);
   web_state_->AddObserver(this);
 
-  const web::WebState::ScriptCommandCallback callback = base::BindRepeating(
-      ^(const base::DictionaryValue& message, const GURL& page_url,
-        bool interacted, web::WebFrame* sender_frame) {
-        if (web_state_ && sender_frame && sender_frame->IsMainFrame()) {
-          DidReceiveJavaScriptResponse(message);
-        }
-      });
-
-  subscription_ =
-      web_state_->AddScriptCommandCallback(callback, kScriptCommandPrefix);
+  subscription_ = web_state_->AddScriptCommandCallback(
+      base::BindRepeating(
+          &TextFragmentsManagerImpl::DidReceiveJavaScriptResponse,
+          base::Unretained(this)),
+      kScriptCommandPrefix);
 }
 
 TextFragmentsManagerImpl::~TextFragmentsManagerImpl() {
@@ -164,7 +159,13 @@
 }
 
 void TextFragmentsManagerImpl::DidReceiveJavaScriptResponse(
-    const base::DictionaryValue& response) {
+    const base::Value& response,
+    const GURL& page_url,
+    bool interacted,
+    web::WebFrame* sender_frame) {
+  if (!web_state_ || !sender_frame || !sender_frame->IsMainFrame())
+    return;
+
   const std::string* command = response.FindStringKey("command");
   if (!command || *command != kScriptResponseCommand) {
     return;
diff --git a/ios/web/web_state/ui/cookie_blocking_error_logger.h b/ios/web/web_state/ui/cookie_blocking_error_logger.h
index 0369593..2412a78 100644
--- a/ios/web/web_state/ui/cookie_blocking_error_logger.h
+++ b/ios/web/web_state/ui/cookie_blocking_error_logger.h
@@ -8,9 +8,6 @@
 #include "base/macros.h"
 #import "ios/web/public/web_state.h"
 
-namespace base {
-class DictionaryValue;
-}
 class GURL;
 namespace web {
 class WebFrame;
@@ -29,7 +26,7 @@
 
   // Callback called when this class receives a Javascript message from its
   // corresponding web state.
-  void OnJavascriptMessageReceived(const base::DictionaryValue& message,
+  void OnJavascriptMessageReceived(const base::Value& message,
                                    const GURL& page_url,
                                    bool user_is_interacting,
                                    WebFrame* sender_frame);
diff --git a/ios/web/web_state/ui/cookie_blocking_error_logger.mm b/ios/web/web_state/ui/cookie_blocking_error_logger.mm
index 4d41ad76..c50eebc 100644
--- a/ios/web/web_state/ui/cookie_blocking_error_logger.mm
+++ b/ios/web/web_state/ui/cookie_blocking_error_logger.mm
@@ -47,12 +47,12 @@
 CookieBlockingErrorLogger::~CookieBlockingErrorLogger() {}
 
 void CookieBlockingErrorLogger::OnJavascriptMessageReceived(
-    const base::DictionaryValue& message,
+    const base::Value& message,
     const GURL& page_url,
     bool user_is_interacting,
     WebFrame* sender_frame) {
-  const base::Value* broken_overrides = message.FindListPath("brokenOverrides");
-  if (!broken_overrides || !broken_overrides->is_list()) {
+  const base::Value* broken_overrides = message.FindListKey("brokenOverrides");
+  if (!broken_overrides) {
     DLOG(WARNING) << "Broken overrides parameter not found: brokenOverrides";
     return;
   }
diff --git a/ios/web/web_state/ui/crw_web_controller_unittest.mm b/ios/web/web_state/ui/crw_web_controller_unittest.mm
index 7a3a559..d4e8ac4 100644
--- a/ios/web/web_state/ui/crw_web_controller_unittest.mm
+++ b/ios/web/web_state/ui/crw_web_controller_unittest.mm
@@ -1233,7 +1233,9 @@
       [web_controller() webStateImpl]->GetNavigationManagerImpl();
   nav_manager.AddPendingItem(GURL(kTestAppSpecificURL), Referrer(),
                              ui::PAGE_TRANSITION_TYPED,
-                             NavigationInitiationType::BROWSER_INITIATED);
+                             NavigationInitiationType::BROWSER_INITIATED,
+                             /*is_post_navigation=*/false,
+                             /*is_using_https_as_default_scheme=*/false);
   nav_manager.CommitPendingItem();
 
   NSError* error = nil;
diff --git a/ios/web/web_state/ui/crw_web_request_controller.mm b/ios/web/web_state/ui/crw_web_request_controller.mm
index 5befd83..c06b2e7 100644
--- a/ios/web/web_state/ui/crw_web_request_controller.mm
+++ b/ios/web/web_state/ui/crw_web_request_controller.mm
@@ -319,10 +319,14 @@
       self.navigationManagerImpl->UpdatePendingItemUrl(requestURL);
     }
   } else {
+    BOOL isPostNavigation =
+        [self.navigationHandler.pendingNavigationInfo.HTTPMethod
+            isEqual:@"POST"];
     self.navigationManagerImpl->AddPendingItem(
         requestURL, referrer, transition,
         rendererInitiated ? web::NavigationInitiationType::RENDERER_INITIATED
-                          : web::NavigationInitiationType::BROWSER_INITIATED);
+                          : web::NavigationInitiationType::BROWSER_INITIATED,
+        isPostNavigation, /*is_using_https_as_default_scheme=*/false);
     item =
         self.navigationManagerImpl->GetPendingItemInCurrentOrRestoredSession();
   }
diff --git a/ios/web/web_state/web_state_impl.h b/ios/web/web_state/web_state_impl.h
index 2770512..5803d09 100644
--- a/ios/web/web_state/web_state_impl.h
+++ b/ios/web/web_state/web_state_impl.h
@@ -95,7 +95,7 @@
 
   // Called when a script command is received.
   void OnScriptCommandReceived(const std::string& command,
-                               const base::DictionaryValue& value,
+                               const base::Value& value,
                                const GURL& page_url,
                                bool user_is_interacting,
                                web::WebFrame* sender_frame);
diff --git a/ios/web/web_state/web_state_impl.mm b/ios/web/web_state/web_state_impl.mm
index 41af1e1..dd320c6b 100644
--- a/ios/web/web_state/web_state_impl.mm
+++ b/ios/web/web_state/web_state_impl.mm
@@ -204,7 +204,7 @@
 }
 
 void WebStateImpl::OnScriptCommandReceived(const std::string& command,
-                                           const base::DictionaryValue& value,
+                                           const base::Value& value,
                                            const GURL& page_url,
                                            bool user_is_interacting,
                                            web::WebFrame* sender_frame) {
diff --git a/ios/web/web_state/web_state_impl_unittest.mm b/ios/web/web_state/web_state_impl_unittest.mm
index e6edce30..d42e4ea 100644
--- a/ios/web/web_state/web_state_impl_unittest.mm
+++ b/ios/web/web_state/web_state_impl_unittest.mm
@@ -138,11 +138,11 @@
 // Sets |is_called| to true if it is called, and checks that the parameters
 // match their expected values.
 void HandleScriptCommand(bool* is_called,
-                         base::DictionaryValue* expected_value,
+                         base::Value* expected_value,
                          const GURL& expected_url,
                          bool expected_user_is_interacting,
                          web::WebFrame* expected_sender_frame,
-                         const base::DictionaryValue& value,
+                         const base::Value& value,
                          const GURL& url,
                          bool user_is_interacting,
                          web::WebFrame* sender_frame) {
@@ -170,7 +170,9 @@
   void AddCommittedNavigationItem() {
     web_state_->GetNavigationManagerImpl().AddPendingItem(
         GURL::EmptyGURL(), web::Referrer(), ui::PAGE_TRANSITION_LINK,
-        NavigationInitiationType::RENDERER_INITIATED);
+        NavigationInitiationType::RENDERER_INITIATED,
+        /*is_post_navigation=*/false,
+        /*is_using_https_as_default_scheme=*/false);
     web_state_->GetNavigationManagerImpl().CommitPendingItem();
   }
 
@@ -838,8 +840,8 @@
   // Set up three script command callbacks.
   const std::string kPrefix1("prefix1");
   const std::string kCommand1("prefix1.command1");
-  base::DictionaryValue value_1;
-  value_1.SetString("a", "b");
+  base::Value value_1(base::Value::Type::DICTIONARY);
+  value_1.SetStringKey("a", "b");
   const GURL kUrl1("http://foo");
   bool is_called_1 = false;
   auto main_frame = FakeWebFrame::CreateMainWebFrame(GURL::EmptyGURL());
@@ -852,8 +854,8 @@
 
   const std::string kPrefix2("prefix2");
   const std::string kCommand2("prefix2.command2");
-  base::DictionaryValue value_2;
-  value_2.SetString("c", "d");
+  base::Value value_2(base::Value::Type::DICTIONARY);
+  value_2.SetStringKey("c", "d");
   const GURL kUrl2("http://bar");
   bool is_called_2 = false;
   base::CallbackListSubscription subscription_2 =
@@ -865,8 +867,8 @@
 
   const std::string kPrefix3("prefix3");
   const std::string kCommand3("prefix3.command3");
-  base::DictionaryValue value_3;
-  value_3.SetString("e", "f");
+  base::Value value_3(base::Value::Type::DICTIONARY);
+  value_3.SetStringKey("e", "f");
   const GURL kUrl3("http://iframe");
   bool is_called_3 = false;
   auto subframe = FakeWebFrame::CreateChildWebFrame(GURL::EmptyGURL());
diff --git a/ios/web/web_state/web_state_unittest.mm b/ios/web/web_state/web_state_unittest.mm
index 2b71e64..09142d8 100644
--- a/ios/web/web_state/web_state_unittest.mm
+++ b/ios/web/web_state/web_state_unittest.mm
@@ -130,7 +130,7 @@
   // Add a script command handler.
   __block bool message_received = false;
   const web::WebState::ScriptCommandCallback callback = base::BindRepeating(
-      ^(const base::DictionaryValue&, const GURL&,
+      ^(const base::Value&, const GURL&,
         /*interacted*/ bool, /*is_main_frame*/ web::WebFrame*) {
         message_received = true;
       });
@@ -392,7 +392,7 @@
   __block bool message_from_main_frame = false;
   __block base::Value message_value;
   const web::WebState::ScriptCommandCallback callback =
-      base::BindRepeating(^(const base::DictionaryValue& value, const GURL&,
+      base::BindRepeating(^(const base::Value& value, const GURL&,
                             bool user_interacted, WebFrame* sender_frame) {
         message_received = true;
         message_from_main_frame = sender_frame->IsMainFrame();
@@ -425,7 +425,7 @@
   __block bool message_from_main_frame = false;
   __block base::Value message_value;
   const web::WebState::ScriptCommandCallback callback =
-      base::BindRepeating(^(const base::DictionaryValue& value, const GURL&,
+      base::BindRepeating(^(const base::Value& value, const GURL&,
                             bool user_interacted, WebFrame* sender_frame) {
         message_received = true;
         message_from_main_frame = sender_frame->IsMainFrame();
diff --git a/ios/web/webui/web_ui_ios_impl.h b/ios/web/webui/web_ui_ios_impl.h
index 95a57614..5d4aed29 100644
--- a/ios/web/webui/web_ui_ios_impl.h
+++ b/ios/web/webui/web_ui_ios_impl.h
@@ -51,7 +51,7 @@
                          const std::vector<const base::Value*>& args) override;
 
  private:
-  void OnJsMessage(const base::DictionaryValue& message,
+  void OnJsMessage(const base::Value& message,
                    const GURL& page_url,
                    bool user_is_interacting,
                    web::WebFrame* sender_frame);
diff --git a/ios/web/webui/web_ui_ios_impl.mm b/ios/web/webui/web_ui_ios_impl.mm
index 7c3d4785..404c5aee 100644
--- a/ios/web/webui/web_ui_ios_impl.mm
+++ b/ios/web/webui/web_ui_ios_impl.mm
@@ -113,7 +113,7 @@
   message_callbacks_.insert(std::make_pair(message, callback));
 }
 
-void WebUIIOSImpl::OnJsMessage(const base::DictionaryValue& message,
+void WebUIIOSImpl::OnJsMessage(const base::Value& message,
                                const GURL& page_url,
                                bool user_is_interacting,
                                web::WebFrame* sender_frame) {
@@ -125,17 +125,18 @@
       web::URLVerificationTrustLevel::kNone;
   const GURL current_url = web_state_->GetCurrentURL(&trust_level);
   if (web::GetWebClient()->IsAppSpecificURL(current_url)) {
-    std::string message_content;
-    const base::ListValue* arguments = nullptr;
-    if (!message.GetString("message", &message_content)) {
+    const std::string* message_content = message.FindStringKey("message");
+    if (!message_content) {
       DLOG(WARNING) << "JS message parameter not found: message";
       return;
     }
-    if (!message.GetList("arguments", &arguments)) {
+    const base::Value* arguments = message.FindListKey("arguments");
+    if (!arguments) {
       DLOG(WARNING) << "JS message parameter not found: arguments";
       return;
     }
-    ProcessWebUIIOSMessage(current_url, message_content, *arguments);
+    ProcessWebUIIOSMessage(current_url, *message_content,
+                           base::Value::AsListValue(*arguments));
   }
 }
 
diff --git a/ios/web_view/internal/cwv_web_view.mm b/ios/web_view/internal/cwv_web_view.mm
index 6056584..28b53fa6 100644
--- a/ios/web_view/internal/cwv_web_view.mm
+++ b/ios/web_view/internal/cwv_web_view.mm
@@ -71,20 +71,19 @@
 // A key used in NSCoder to store the session storage object.
 NSString* const kSessionStorageKey = @"sessionStorage";
 
-// Converts base::DictionaryValue to NSDictionary.
-NSDictionary* NSDictionaryFromDictionaryValue(
-    const base::DictionaryValue& value) {
+// Converts base::Value expected to be a dictionary to NSDictionary.
+NSDictionary* NSDictionaryFromDictionaryValue(const base::Value& value) {
+  DCHECK(value.is_dict()) << "Incorrect value type: " << value.type();
+
   std::string json;
-  if (!base::JSONWriter::Write(value, &json)) {
-    NOTREACHED() << "Failed to convert base::DictionaryValue to JSON";
-    return nil;
-  }
+  const bool success = base::JSONWriter::Write(value, &json);
+  DCHECK(success) << "Failed to convert base::Value to JSON";
 
   NSData* json_data = [NSData dataWithBytes:json.c_str() length:json.length()];
-  NSDictionary* ns_dictionary =
+  NSDictionary* ns_dictionary = base::mac::ObjCCastStrict<NSDictionary>(
       [NSJSONSerialization JSONObjectWithData:json_data
                                       options:kNilOptions
-                                        error:nil];
+                                        error:nil]);
   DCHECK(ns_dictionary) << "Failed to convert JSON to NSDictionary";
   return ns_dictionary;
 }
@@ -635,7 +634,7 @@
                   commandPrefix:(NSString*)commandPrefix {
   CWVWebView* __weak weakSelf = self;
   const web::WebState::ScriptCommandCallback callback = base::BindRepeating(
-      ^(const base::DictionaryValue& content, const GURL& mainDocumentURL,
+      ^(const base::Value& content, const GURL& mainDocumentURL,
         bool userInteracting, web::WebFrame* senderFrame) {
         NSDictionary* nsContent = NSDictionaryFromDictionaryValue(content);
         CWVScriptCommand* command = [[CWVScriptCommand alloc]
diff --git a/media/BUILD.gn b/media/BUILD.gn
index d09e862..b61a15c 100644
--- a/media/BUILD.gn
+++ b/media/BUILD.gn
@@ -29,6 +29,7 @@
     "ENABLE_FFMPEG=$media_use_ffmpeg",
     "ENABLE_FFMPEG_VIDEO_DECODERS=$enable_ffmpeg_video_decoders",
     "ENABLE_PLATFORM_HEVC=$enable_platform_hevc",
+    "ENABLE_PLATFORM_ENCRYPTED_HEVC=$enable_platform_encrypted_hevc",
     "ENABLE_HLS_SAMPLE_AES=$enable_hls_sample_aes",
     "ENABLE_LIBGAV1_DECODER=$enable_libgav1_decoder",
     "ENABLE_LIBRARY_CDMS=$enable_library_cdms",
diff --git a/media/base/supported_types.cc b/media/base/supported_types.cc
index f55859e..0756462 100644
--- a/media/base/supported_types.cc
+++ b/media/base/supported_types.cc
@@ -52,8 +52,7 @@
   return false;
 }
 
-#if BUILDFLAG(ENABLE_PLATFORM_HEVC) && \
-    (BUILDFLAG(USE_CHROMEOS_PROTECTED_MEDIA) || defined(OS_WIN))
+#if BUILDFLAG(ENABLE_PLATFORM_ENCRYPTED_HEVC)
 bool IsHevcProfileSupported(VideoCodecProfile profile) {
   // Only encrypted HEVC content is supported, and normally MSE.isTypeSupported
   // returns false for HEVC. The kEnableClearHevcForTesting flag allows it to
@@ -74,7 +73,7 @@
   }
   return false;
 }
-#endif  // ENABLE_PLATFORM_HEVC && (USE_CHROMEOS_PROTECTED_MEDIA || OS_WIN)
+#endif  // BUILDFLAG(ENABLE_PLATFORM_ENCRYPTED_HEVC)
 
 }  // namespace
 
@@ -344,13 +343,12 @@
       return true;
 
     case kCodecHEVC:
-#if BUILDFLAG(ENABLE_PLATFORM_HEVC) && \
-    (BUILDFLAG(USE_CHROMEOS_PROTECTED_MEDIA) || defined(OS_WIN))
+#if BUILDFLAG(ENABLE_PLATFORM_ENCRYPTED_HEVC)
       return IsColorSpaceSupported(type.color_space) &&
              IsHevcProfileSupported(type.profile);
 #else
       return false;
-#endif
+#endif  // BUILDFLAG(ENABLE_PLATFORM_ENCRYPTED_HEVC)
     case kUnknownVideoCodec:
     case kCodecVC1:
     case kCodecMPEG2:
diff --git a/media/blink/key_system_config_selector.cc b/media/blink/key_system_config_selector.cc
index 1175e7e4..462550a 100644
--- a/media/blink/key_system_config_selector.cc
+++ b/media/blink/key_system_config_selector.cc
@@ -160,12 +160,11 @@
   std::vector<std::string> codec_vector;
   SplitCodecs(codecs, &codec_vector);
 
-#if BUILDFLAG(ENABLE_PLATFORM_HEVC) && \
-    (BUILDFLAG(USE_CHROMEOS_PROTECTED_MEDIA) || defined(OS_WIN))
-  // EME HEVC is supported on CrOS and Windows under these build flags, but it
-  // is not supported for clear playback. Remove the HEVC codec strings to avoid
-  // asking IsSupported*MediaFormat() about HEVC. EME support for HEVC profiles
-  // is described via KeySystemProperties::GetSupportedCodecs().
+#if BUILDFLAG(ENABLE_PLATFORM_ENCRYPTED_HEVC)
+  // EME HEVC is supported on under this build flag, but it is not supported for
+  // clear playback. Remove the HEVC codec strings to avoid asking
+  // IsSupported*MediaFormat() about HEVC. EME support for HEVC profiles is
+  // described via KeySystemProperties::GetSupportedCodecs().
   // TODO(1156282): Decouple the rest of clear vs EME codec support.
   if (base::ToLowerASCII(container_mime_type) == "video/mp4" &&
       !codec_vector.empty()) {
@@ -185,7 +184,7 @@
     if (codec_vector.empty())
       return true;
   }
-#endif  // ENABLE_PLATFORM_HEVC && (USE_CHROMEOS_PROTECTED_MEDIA || OS_WIN)
+#endif  // BUILDFLAG(ENABLE_PLATFORM_ENCRYPTED_HEVC)
 
   // AesDecryptor decrypts the stream in the demuxer before it reaches the
   // decoder so check whether the media format is supported when clear.
diff --git a/media/filters/source_buffer_state.cc b/media/filters/source_buffer_state.cc
index 0dad6903e..1ea91df 100644
--- a/media/filters/source_buffer_state.cc
+++ b/media/filters/source_buffer_state.cc
@@ -717,28 +717,25 @@
       DCHECK(video_config.IsValidConfig());
 
       if (video_config.codec() == kCodecHEVC) {
-#if BUILDFLAG(ENABLE_PLATFORM_HEVC)
-#if BUILDFLAG(USE_CHROMEOS_PROTECTED_MEDIA) || defined(OS_WIN)
-        // On ChromeOS and Windows, HEVC is only supported through EME, so
-        // require the config to be for an encrypted track if on ChromeOS or
-        // Windows. Even so, conditionally allow clear HEVC on ChromeOS or
-        // Windows if cmdline has test override.
+#if BUILDFLAG(ENABLE_PLATFORM_ENCRYPTED_HEVC)
+        // HEVC is only supported through EME under this build flag, so
+        // require the config to be for an encrypted track. Even so,
+        // conditionally allow clear HEVC if cmdline has test override.
         if (video_config.encryption_scheme() ==
                 EncryptionScheme::kUnencrypted &&
             !base::CommandLine::ForCurrentProcess()->HasSwitch(
                 switches::kEnableClearHevcForTesting)) {
           MEDIA_LOG(ERROR, media_log_)
-              << "MSE playback of HEVC on ChromeOS and Windows is only "
-                 "supported via platform decryptor, but the provided HEVC "
+              << "MSE playback of HEVC on is only supported via platform "
+                 "decryptor, but the provided HEVC "
                  "track is not encrypted.";
           return false;
         }
-#endif  // BUILDFLAG(USE_CHROMEOS_PROTECTED_MEDIA) || defined(OS_WIN)
-#else
+#elif !BUILDFLAG(ENABLE_PLATFORM_HEVC)
         NOTREACHED()
             << "MSE parser must not emit HEVC tracks on build configurations "
                "that do not support HEVC playback via platform.";
-#endif  // BUILDFLAG(ENABLE_PLATFORM_HEVC)
+#endif  // BUILDFLAG(ENABLE_PLATFORM_ENCRYPTED_HEVC)
       }
 
       const auto& it = std::find(expected_vcodecs.begin(),
diff --git a/media/media_options.gni b/media/media_options.gni
index b9376be..e58384f 100644
--- a/media/media_options.gni
+++ b/media/media_options.gni
@@ -52,12 +52,8 @@
   enable_mse_mpeg2ts_stream_parser =
       proprietary_codecs && (is_chromecast || is_fuchsia || use_fuzzing_engine)
 
-  # Enable HEVC/H265 demuxing. Actual decoding must be provided by the
-  # platform. Enabled by default for Chromecast, Chrome OS protected media,
-  # Windows with Chrome branding and fuzzer builds.
-  enable_platform_hevc = proprietary_codecs &&
-                         (is_chromecast || use_chromeos_protected_media ||
-                          use_fuzzing_engine || (is_win && is_chrome_branded))
+  enable_platform_encrypted_hevc =
+      use_chromeos_protected_media || (is_win && is_chrome_branded)
 
   # Enable Dolby Vision demuxing. Enable by default for Chromecast. Actual
   # decoding must be provided by the platform. Note some Dolby Vision profiles
@@ -81,10 +77,25 @@
 
 declare_args() {
   enable_av1_decoder = enable_dav1d_decoder || enable_libgav1_decoder
+
+  # Enable HEVC/H265 demuxing. Actual decoding must be provided by the
+  # platform. Enabled by default for Chromecast, fuzzer builds and protected
+  # video on ChromeOS and Windows.
+  enable_platform_hevc =
+      proprietary_codecs &&
+      (is_chromecast || use_fuzzing_engine || enable_platform_encrypted_hevc)
 }
 
-# enable_hls_sample_aes can only be true if enable_mse_mpeg2ts_stream_parser is.
-assert(enable_mse_mpeg2ts_stream_parser || !enable_hls_sample_aes)
+assert(
+    !enable_hls_sample_aes || enable_mse_mpeg2ts_stream_parser,
+    "enable_mse_mpeg2ts_stream_parser=true is required for enable_hls_sample_aes=true.")
+
+assert(!enable_platform_hevc || proprietary_codecs,
+       "proprietary_codecs=true is required for enable_platform_hevc=true.")
+
+assert(
+    !enable_platform_encrypted_hevc || enable_platform_hevc,
+    "enable_platform_hevc=true is required for enable_platform_encrypted_hevc=true.")
 
 # Use a second declare_args() to pick up possible overrides of |use_cras|.
 declare_args() {
@@ -234,8 +245,8 @@
   enable_cast_streaming_renderer = false
 }
 
-# Currently, libcast mirroring is only supported for chromecast.
-assert(!enable_cast_streaming_renderer || is_chromecast)
+assert(!enable_cast_streaming_renderer || is_chromecast,
+       "Currently, libcast mirroring is only supported for chromecast.")
 
 # Do not expand this list without double-checking with OWNERS, this is a list of
 # all targets which roll up into the //media component. It controls visibility
diff --git a/media/test/pipeline_integration_test.cc b/media/test/pipeline_integration_test.cc
index 1d64dca..220d3e0a 100644
--- a/media/test/pipeline_integration_test.cc
+++ b/media/test/pipeline_integration_test.cc
@@ -2561,9 +2561,9 @@
   TestMediaSource source("bear-320x240-v_frag-hevc.mp4", kMp4HevcVideoOnly,
                          kAppendWholeFile);
 #if BUILDFLAG(ENABLE_PLATFORM_HEVC)
-#if BUILDFLAG(USE_CHROMEOS_PROTECTED_MEDIA) || defined(OS_WIN)
-  // On ChromeOS and Windows, HEVC is only supported through EME.
-  // So this unencrypted track cannot be demuxed.
+#if BUILDFLAG(ENABLE_PLATFORM_ENCRYPTED_HEVC)
+  // HEVC is only supported through EME under this build flag. So this
+  // unencrypted track cannot be demuxed.
   source.set_expected_append_result(
       TestMediaSource::ExpectedAppendResult::kFailure);
   EXPECT_EQ(
@@ -2572,12 +2572,12 @@
 #else
   PipelineStatus status = StartPipelineWithMediaSource(&source);
   EXPECT_TRUE(status == PIPELINE_OK || status == DECODER_ERROR_NOT_SUPPORTED);
-#endif  // BUILDFLAG(USE_CHROMEOS_PROTECTED_MEDIA) || defined(OS_WIN)
+#endif  // BUILDFLAG(ENABLE_PLATFORM_ENCRYPTED_HEVC)
 #else
   EXPECT_EQ(
       DEMUXER_ERROR_COULD_NOT_OPEN,
       StartPipelineWithMediaSource(&source, kExpectDemuxerFailure, nullptr));
-#endif
+#endif  // BUILDFLAG(ENABLE_PLATFORM_HEVC)
 }
 
 // Same test as above but using a different mime type.
@@ -2586,9 +2586,9 @@
   TestMediaSource source("bear-320x240-v_frag-hevc.mp4", kMp4Hev1VideoOnly,
                          kAppendWholeFile);
 #if BUILDFLAG(ENABLE_PLATFORM_HEVC)
-#if BUILDFLAG(USE_CHROMEOS_PROTECTED_MEDIA) || defined(OS_WIN)
-  // On ChromeOS and Windows, HEVC is only supported through EME.
-  // So this unencrypted track cannot be demuxed.
+#if BUILDFLAG(ENABLE_PLATFORM_ENCRYPTED_HEVC)
+  // HEVC is only supported through EME under this build flag. So this
+  // unencrypted track cannot be demuxed.
   source.set_expected_append_result(
       TestMediaSource::ExpectedAppendResult::kFailure);
   EXPECT_EQ(
@@ -2597,12 +2597,12 @@
 #else
   PipelineStatus status = StartPipelineWithMediaSource(&source);
   EXPECT_TRUE(status == PIPELINE_OK || status == DECODER_ERROR_NOT_SUPPORTED);
-#endif  // BUILDFLAG(USE_CHROMEOS_PROTECTED_MEDIA) || defined(OS_WIN)
+#endif  // BUILDFLAG(ENABLE_PLATFORM_ENCRYPTED_HEVC)
 #else
   EXPECT_EQ(
       DEMUXER_ERROR_COULD_NOT_OPEN,
       StartPipelineWithMediaSource(&source, kExpectDemuxerFailure, nullptr));
-#endif
+#endif  // BUILDFLAG(ENABLE_PLATFORM_HEVC)
 }
 
 #endif  // BUILDFLAG(USE_PROPRIETARY_CODECS)
diff --git a/media/webrtc/webrtc_switches.cc b/media/webrtc/webrtc_switches.cc
index 7157986..051228d7 100644
--- a/media/webrtc/webrtc_switches.cc
+++ b/media/webrtc/webrtc_switches.cc
@@ -41,4 +41,8 @@
 const base::Feature kWebRtcHybridAgc{"WebRtcHybridAgc",
                                      base::FEATURE_DISABLED_BY_DEFAULT};
 
+// Enables and configures the clipping control in the WebRTC analog AGC.
+const base::Feature kWebRtcAnalogAgcClippingControl{
+    "WebRtcAnalogAgcClippingControl", base::FEATURE_DISABLED_BY_DEFAULT};
+
 }  // namespace features
diff --git a/media/webrtc/webrtc_switches.h b/media/webrtc/webrtc_switches.h
index e81aa89..c7675917 100644
--- a/media/webrtc/webrtc_switches.h
+++ b/media/webrtc/webrtc_switches.h
@@ -30,6 +30,9 @@
 COMPONENT_EXPORT(MEDIA_WEBRTC)
 extern const base::Feature kWebRtcHybridAgc;
 
+COMPONENT_EXPORT(MEDIA_WEBRTC)
+extern const base::Feature kWebRtcAnalogAgcClippingControl;
+
 }  // namespace features
 
 #endif  // MEDIA_WEBRTC_WEBRTC_SWITCHES_H_
diff --git a/printing/backend/cups_connection.cc b/printing/backend/cups_connection.cc
index ac31022..c76a791b 100644
--- a/printing/backend/cups_connection.cc
+++ b/printing/backend/cups_connection.cc
@@ -80,10 +80,11 @@
 
   ~CupsConnectionImpl() override {}
 
-  std::vector<std::unique_ptr<CupsPrinter>> GetDests() override {
+  bool GetDests(std::vector<std::unique_ptr<CupsPrinter>>& printers) override {
+    printers.clear();
     if (!Connect()) {
-      LOG(WARNING) << "CUPS connection failed";
-      return std::vector<std::unique_ptr<CupsPrinter>>();
+      LOG(WARNING) << "CUPS connection failed: ";
+      return false;
     }
 
     // On macOS, AirPrint destinations show up even if they're not added to the
@@ -101,17 +102,16 @@
 
     if (!success) {
       LOG(WARNING) << "Enumerating printers failed";
-      return std::vector<std::unique_ptr<CupsPrinter>>();
+      return false;
     }
 
     auto dests = std::move(enumerator.get_dests());
-    std::vector<std::unique_ptr<CupsPrinter>> printers;
     for (auto& dest : dests) {
       printers.push_back(
           CupsPrinter::Create(cups_http_.get(), std::move(dest)));
     }
 
-    return printers;
+    return true;
   }
 
   std::unique_ptr<CupsPrinter> GetPrinter(const std::string& name) override {
diff --git a/printing/backend/cups_connection.h b/printing/backend/cups_connection.h
index eb45523..fc0960a7 100644
--- a/printing/backend/cups_connection.h
+++ b/printing/backend/cups_connection.h
@@ -40,8 +40,11 @@
                                                 http_encryption_t encryption,
                                                 bool blocking);
 
-  // Returns a vector of all the printers configure on the CUPS server.
-  virtual std::vector<std::unique_ptr<CupsPrinter>> GetDests() = 0;
+  // Obtain a vector of all the printers configure on the CUPS server.  Returns
+  // true if the list of printers was obtained, and false if an error was
+  // encountered during the query.
+  virtual bool GetDests(
+      std::vector<std::unique_ptr<CupsPrinter>>& printers) = 0;
 
   // Returns a printer for `printer_name` from the connected server.
   virtual std::unique_ptr<CupsPrinter> GetPrinter(
diff --git a/printing/backend/print_backend.h b/printing/backend/print_backend.h
index 52662be..1565c60b 100644
--- a/printing/backend/print_backend.h
+++ b/printing/backend/print_backend.h
@@ -181,8 +181,12 @@
   // failure in generating the list.
   virtual mojom::ResultCode EnumeratePrinters(PrinterList* printer_list) = 0;
 
-  // Gets the default printer name. Empty string if no default printer.
-  virtual std::string GetDefaultPrinterName() = 0;
+  // Gets the default printer name.  If there is no default printer then it
+  // will still return success and `default_printer` will be empty.  The result
+  // code will return one of the error result codes when there is a failure in
+  // trying to get the default printer.
+  virtual mojom::ResultCode GetDefaultPrinterName(
+      std::string& default_printer) = 0;
 
   // Gets the basic printer info for a specific printer. Implementations must
   // check `printer_name` validity in the same way as IsValidPrinter().
diff --git a/printing/backend/print_backend_chromeos.cc b/printing/backend/print_backend_chromeos.cc
index e122459..974fc20 100644
--- a/printing/backend/print_backend_chromeos.cc
+++ b/printing/backend/print_backend_chromeos.cc
@@ -24,7 +24,8 @@
 
   // PrintBackend implementation.
   mojom::ResultCode EnumeratePrinters(PrinterList* printer_list) override;
-  std::string GetDefaultPrinterName() override;
+  mojom::ResultCode GetDefaultPrinterName(
+      std::string& default_printer) override;
   mojom::ResultCode GetPrinterBasicInfo(
       const std::string& printer_name,
       PrinterBasicInfo* printer_info) override;
@@ -75,8 +76,10 @@
   return std::string();
 }
 
-std::string PrintBackendChromeOS::GetDefaultPrinterName() {
-  return std::string();
+mojom::ResultCode PrintBackendChromeOS::GetDefaultPrinterName(
+    std::string& default_printer) {
+  default_printer = std::string();
+  return mojom::ResultCode::kSuccess;
 }
 
 bool PrintBackendChromeOS::IsValidPrinter(const std::string& printer_name) {
diff --git a/printing/backend/print_backend_cups.cc b/printing/backend/print_backend_cups.cc
index dc0baf895..b828629 100644
--- a/printing/backend/print_backend_cups.cc
+++ b/printing/backend/print_backend_cups.cc
@@ -162,14 +162,21 @@
   return mojom::ResultCode::kSuccess;
 }
 
-std::string PrintBackendCUPS::GetDefaultPrinterName() {
+mojom::ResultCode PrintBackendCUPS::GetDefaultPrinterName(
+    std::string& default_printer) {
   // Not using cupsGetDefault() because it lies about the default printer.
   cups_dest_t* dests;
   int num_dests = GetDests(&dests);
   cups_dest_t* dest = cupsGetDest(nullptr, nullptr, num_dests, dests);
-  std::string name = dest ? std::string(dest->name) : std::string();
+  if (!dest) {
+    LOG(ERROR) << "CUPS: Error getting default printer: "
+               << cupsLastErrorString();
+    return mojom::ResultCode::kFailed;
+  }
+
+  default_printer = std::string(dest->name);
   cupsFreeDests(num_dests, dests);
-  return name;
+  return mojom::ResultCode::kSuccess;
 }
 
 mojom::ResultCode PrintBackendCUPS::GetPrinterBasicInfo(
diff --git a/printing/backend/print_backend_cups.h b/printing/backend/print_backend_cups.h
index 3d60170..71395cfa 100644
--- a/printing/backend/print_backend_cups.h
+++ b/printing/backend/print_backend_cups.h
@@ -43,7 +43,8 @@
 
   // PrintBackend implementation.
   mojom::ResultCode EnumeratePrinters(PrinterList* printer_list) override;
-  std::string GetDefaultPrinterName() override;
+  mojom::ResultCode GetDefaultPrinterName(
+      std::string& default_printer) override;
   mojom::ResultCode GetPrinterBasicInfo(
       const std::string& printer_name,
       PrinterBasicInfo* printer_info) override;
diff --git a/printing/backend/print_backend_cups_ipp.cc b/printing/backend/print_backend_cups_ipp.cc
index 4f7a219..10d8ccb7 100644
--- a/printing/backend/print_backend_cups_ipp.cc
+++ b/printing/backend/print_backend_cups_ipp.cc
@@ -34,23 +34,13 @@
   DCHECK(printer_list);
   printer_list->clear();
 
-  std::vector<std::unique_ptr<CupsPrinter>> printers =
-      cups_connection_->GetDests();
-  if (printers.empty()) {
-    // No destinations could mean the operation failed or that there are simply
-    // no printer drivers installed.  Rely upon CUPS error code to distinguish
-    // between these.
-    const int last_error = cups_connection_->last_error();
-    if (last_error != IPP_STATUS_ERROR_NOT_FOUND) {
-      LOG(WARNING) << "CUPS: Error getting printers from CUPS server"
-                   << ", server: " << cups_connection_->server_name()
-                   << ", error: " << last_error << " - "
-                   << cups_connection_->last_error_message();
-      return mojom::ResultCode::kFailed;
-    }
-    VLOG(1) << "CUPS: No printers found for CUPS server: "
-            << cups_connection_->server_name();
-    return mojom::ResultCode::kSuccess;
+  std::vector<std::unique_ptr<CupsPrinter>> printers;
+  if (!cups_connection_->GetDests(printers)) {
+    LOG(WARNING) << "CUPS: Error getting printers from CUPS server"
+                 << ", server: " << cups_connection_->server_name()
+                 << ", error: " << cups_connection_->last_error() << " - "
+                 << cups_connection_->last_error_message();
+    return mojom::ResultCode::kFailed;
   }
 
   VLOG(1) << "CUPS: found " << printers.size()
@@ -65,16 +55,24 @@
   return mojom::ResultCode::kSuccess;
 }
 
-std::string PrintBackendCupsIpp::GetDefaultPrinterName() {
-  std::vector<std::unique_ptr<CupsPrinter>> printers =
-      cups_connection_->GetDests();
+mojom::ResultCode PrintBackendCupsIpp::GetDefaultPrinterName(
+    std::string& default_printer) {
+  std::vector<std::unique_ptr<CupsPrinter>> printers;
+  if (!cups_connection_->GetDests(printers)) {
+    LOG(ERROR) << "CUPS: unable to get default printer: "
+               << cupsLastErrorString();
+    return mojom::ResultCode::kFailed;
+  }
+
   for (const auto& printer : printers) {
     if (printer->is_default()) {
-      return printer->GetName();
+      default_printer = printer->GetName();
+      return mojom::ResultCode::kSuccess;
     }
   }
 
-  return std::string();
+  default_printer = std::string();
+  return mojom::ResultCode::kSuccess;
 }
 
 mojom::ResultCode PrintBackendCupsIpp::GetPrinterBasicInfo(
diff --git a/printing/backend/print_backend_cups_ipp.h b/printing/backend/print_backend_cups_ipp.h
index 7da7a88..ceb021a 100644
--- a/printing/backend/print_backend_cups_ipp.h
+++ b/printing/backend/print_backend_cups_ipp.h
@@ -24,7 +24,8 @@
 
   // PrintBackend implementation.
   mojom::ResultCode EnumeratePrinters(PrinterList* printer_list) override;
-  std::string GetDefaultPrinterName() override;
+  mojom::ResultCode GetDefaultPrinterName(
+      std::string& default_printer) override;
   mojom::ResultCode GetPrinterBasicInfo(
       const std::string& printer_name,
       PrinterBasicInfo* printer_info) override;
diff --git a/printing/backend/print_backend_dummy.cc b/printing/backend/print_backend_dummy.cc
index fefe3bf..f888d92 100644
--- a/printing/backend/print_backend_dummy.cc
+++ b/printing/backend/print_backend_dummy.cc
@@ -24,7 +24,11 @@
     return mojom::ResultCode::kFailed;
   }
 
-  std::string GetDefaultPrinterName() override { return std::string(); }
+  mojom::ResultCode GetDefaultPrinterName(
+      std::string& default_printer) override {
+    default_printer = std::string();
+    return mojom::ResultCode::kSuccess;
+  }
 
   mojom::ResultCode GetPrinterBasicInfo(
       const std::string& printer_name,
diff --git a/printing/backend/print_backend_win.cc b/printing/backend/print_backend_win.cc
index f87312d..92844c6 100644
--- a/printing/backend/print_backend_win.cc
+++ b/printing/backend/print_backend_win.cc
@@ -186,7 +186,8 @@
 
   // PrintBackend implementation.
   mojom::ResultCode EnumeratePrinters(PrinterList* printer_list) override;
-  std::string GetDefaultPrinterName() override;
+  mojom::ResultCode GetDefaultPrinterName(
+      std::string& default_printer) override;
   mojom::ResultCode GetPrinterBasicInfo(
       const std::string& printer_name,
       PrinterBasicInfo* printer_info) override;
@@ -231,7 +232,11 @@
     return GetResultCodeFromSystemErrorCode(logging::GetLastSystemErrorCode());
   }
 
-  std::string default_printer = GetDefaultPrinterName();
+  // No need to worry about a query failure for `GetDefaultPrinterName()` here,
+  // that would mean we can just treat it as there being no default printer.
+  std::string default_printer;
+  GetDefaultPrinterName(default_printer);
+
   PRINTER_INFO_4* printer_info =
       reinterpret_cast<PRINTER_INFO_4*>(printer_info_buffer.get());
   for (DWORD index = 0; index < count_returned; index++) {
@@ -246,7 +251,8 @@
   return mojom::ResultCode::kSuccess;
 }
 
-std::string PrintBackendWin::GetDefaultPrinterName() {
+mojom::ResultCode PrintBackendWin::GetDefaultPrinterName(
+    std::string& default_printer) {
   DWORD size = MAX_PATH;
   TCHAR default_printer_name[MAX_PATH];
   base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
@@ -255,9 +261,10 @@
     LOG(ERROR) << "Error getting default printer: "
                << logging::SystemErrorCodeToString(
                       logging::GetLastSystemErrorCode());
-    return std::string();
+    return mojom::ResultCode::kFailed;
   }
-  return base::WideToUTF8(default_printer_name);
+  default_printer = base::WideToUTF8(default_printer_name);
+  return mojom::ResultCode::kSuccess;
 }
 
 mojom::ResultCode PrintBackendWin::GetPrinterBasicInfo(
@@ -273,8 +280,14 @@
     return mojom::ResultCode::kFailed;
   }
 
-  std::string default_printer = GetDefaultPrinterName();
-  printer_info->is_default = (printer_info->printer_name == default_printer);
+  std::string default_printer;
+  mojom::ResultCode result = GetDefaultPrinterName(default_printer);
+  if (result != mojom::ResultCode::kSuccess) {
+    // Query failure means we can treat this printer as not the default.
+    printer_info->is_default = false;
+  } else {
+    printer_info->is_default = (printer_info->printer_name == default_printer);
+  }
   return mojom::ResultCode::kSuccess;
 }
 
diff --git a/printing/backend/test_print_backend.cc b/printing/backend/test_print_backend.cc
index 61c582d5..23f84c3 100644
--- a/printing/backend/test_print_backend.cc
+++ b/printing/backend/test_print_backend.cc
@@ -61,8 +61,10 @@
   return mojom::ResultCode::kSuccess;
 }
 
-std::string TestPrintBackend::GetDefaultPrinterName() {
-  return default_printer_name_;
+mojom::ResultCode TestPrintBackend::GetDefaultPrinterName(
+    std::string& default_printer) {
+  default_printer = default_printer_name_;
+  return mojom::ResultCode::kSuccess;
 }
 
 mojom::ResultCode TestPrintBackend::GetPrinterBasicInfo(
diff --git a/printing/backend/test_print_backend.h b/printing/backend/test_print_backend.h
index b8b910d..140e8d9 100644
--- a/printing/backend/test_print_backend.h
+++ b/printing/backend/test_print_backend.h
@@ -22,7 +22,8 @@
 
   // PrintBackend overrides
   mojom::ResultCode EnumeratePrinters(PrinterList* printer_list) override;
-  std::string GetDefaultPrinterName() override;
+  mojom::ResultCode GetDefaultPrinterName(
+      std::string& default_printer) override;
   mojom::ResultCode GetPrinterBasicInfo(
       const std::string& printer_name,
       PrinterBasicInfo* printer_info) override;
diff --git a/printing/backend/test_print_backend_unittest.cc b/printing/backend/test_print_backend_unittest.cc
index bb365bf..c9303c6 100644
--- a/printing/backend/test_print_backend_unittest.cc
+++ b/printing/backend/test_print_backend_unittest.cc
@@ -110,16 +110,24 @@
 }
 
 TEST_F(TestPrintBackendTest, DefaultPrinterName) {
+  std::string default_printer;
+
   // If no printers added then no default.
-  EXPECT_TRUE(GetPrintBackend()->GetDefaultPrinterName().empty());
+  ASSERT_EQ(GetPrintBackend()->GetDefaultPrinterName(default_printer),
+            mojom::ResultCode::kSuccess);
+  EXPECT_TRUE(default_printer.empty());
 
   // Once printers are available, should be a default.
   AddPrinters();
-  EXPECT_EQ(GetPrintBackend()->GetDefaultPrinterName(), kDefaultPrinterName);
+  ASSERT_EQ(GetPrintBackend()->GetDefaultPrinterName(default_printer),
+            mojom::ResultCode::kSuccess);
+  EXPECT_EQ(default_printer, kDefaultPrinterName);
 
   // Changing default should be reflected on next query.
   GetPrintBackend()->SetDefaultPrinterName(kAlternatePrinterName);
-  EXPECT_EQ(GetPrintBackend()->GetDefaultPrinterName(), kAlternatePrinterName);
+  ASSERT_EQ(GetPrintBackend()->GetDefaultPrinterName(default_printer),
+            mojom::ResultCode::kSuccess);
+  EXPECT_EQ(default_printer, kAlternatePrinterName);
 
   // Adding a new printer to environment which is marked as default should
   // automatically make it the new default.
@@ -130,17 +138,23 @@
   printer_info->is_default = true;
   GetPrintBackend()->AddValidPrinter(kNewDefaultPrinterName, std::move(caps),
                                      std::move(printer_info));
-  EXPECT_EQ(GetPrintBackend()->GetDefaultPrinterName(), kNewDefaultPrinterName);
+  ASSERT_EQ(GetPrintBackend()->GetDefaultPrinterName(default_printer),
+            mojom::ResultCode::kSuccess);
+  EXPECT_EQ(default_printer, kNewDefaultPrinterName);
 
   // Requesting an invalid printer name to be a default should have no effect.
   GetPrintBackend()->SetDefaultPrinterName(kInvalidPrinterName);
-  EXPECT_EQ(GetPrintBackend()->GetDefaultPrinterName(), kNewDefaultPrinterName);
+  ASSERT_EQ(GetPrintBackend()->GetDefaultPrinterName(default_printer),
+            mojom::ResultCode::kSuccess);
+  EXPECT_EQ(default_printer, kNewDefaultPrinterName);
 
   // Verify that re-adding a printer that was previously the default with null
   // basic info results in no default printer anymore.
   GetPrintBackend()->AddValidPrinter(kNewDefaultPrinterName, /*caps=*/nullptr,
                                      /*info=*/nullptr);
-  EXPECT_TRUE(GetPrintBackend()->GetDefaultPrinterName().empty());
+  ASSERT_EQ(GetPrintBackend()->GetDefaultPrinterName(default_printer),
+            mojom::ResultCode::kSuccess);
+  EXPECT_TRUE(default_printer.empty());
 }
 
 TEST_F(TestPrintBackendTest, PrinterBasicInfo) {
diff --git a/printing/printing_context_chromeos_unittest.cc b/printing/printing_context_chromeos_unittest.cc
index fed56d39..88cc280 100644
--- a/printing/printing_context_chromeos_unittest.cc
+++ b/printing/printing_context_chromeos_unittest.cc
@@ -84,7 +84,7 @@
 
 class MockCupsConnection : public CupsConnection {
  public:
-  MOCK_METHOD0(GetDests, std::vector<std::unique_ptr<CupsPrinter>>());
+  MOCK_METHOD1(GetDests, bool(std::vector<std::unique_ptr<CupsPrinter>>&));
   MOCK_METHOD2(GetJobs,
                bool(const std::vector<std::string>& printer_ids,
                     std::vector<QueueStatus>* jobs));
diff --git a/printing/printing_context_win.cc b/printing/printing_context_win.cc
index 4519103..5be3a8f 100644
--- a/printing/printing_context_win.cc
+++ b/printing/printing_context_win.cc
@@ -78,8 +78,13 @@
 
   scoped_refptr<PrintBackend> backend =
       PrintBackend::CreateInstance(delegate_->GetAppLocale());
-  std::wstring default_printer =
-      base::UTF8ToWide(backend->GetDefaultPrinterName());
+  std::string default_printer_name;
+  mojom::ResultCode result =
+      backend->GetDefaultPrinterName(default_printer_name);
+  if (result != mojom::ResultCode::kSuccess)
+    return FAILED;
+
+  std::wstring default_printer = base::UTF8ToWide(default_printer_name);
   if (!default_printer.empty()) {
     ScopedPrinterHandle printer;
     if (printer.OpenPrinterWithName(default_printer.c_str())) {
diff --git a/services/device/serial/serial_device_enumerator_win.h b/services/device/serial/serial_device_enumerator_win.h
index a41d9b7..83d6667 100644
--- a/services/device/serial/serial_device_enumerator_win.h
+++ b/services/device/serial/serial_device_enumerator_win.h
@@ -6,7 +6,6 @@
 #define SERVICES_DEVICE_SERIAL_SERIAL_DEVICE_ENUMERATOR_WIN_H_
 
 #include "base/macros.h"
-#include "base/scoped_observer.h"
 #include "base/win/windows_types.h"
 #include "device/base/device_monitor_win.h"
 #include "services/device/serial/serial_device_enumerator.h"
diff --git a/services/device/usb/usb_service_linux.cc b/services/device/usb/usb_service_linux.cc
index b8b8577..c4aa4e3 100644
--- a/services/device/usb/usb_service_linux.cc
+++ b/services/device/usb/usb_service_linux.cc
@@ -18,7 +18,6 @@
 #include "base/logging.h"
 #include "base/memory/ptr_util.h"
 #include "base/memory/weak_ptr.h"
-#include "base/scoped_observer.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/threading/scoped_blocking_call.h"
diff --git a/services/network/cookie_manager_unittest.cc b/services/network/cookie_manager_unittest.cc
index 6d69fe9..d56496a 100644
--- a/services/network/cookie_manager_unittest.cc
+++ b/services/network/cookie_manager_unittest.cc
@@ -2522,25 +2522,25 @@
 TEST_F(CookieManagerTest, BlockThirdPartyCookies) {
   const GURL kThisURL = GURL("http://www.this.com");
   const GURL kThatURL = GURL("http://www.that.com");
-  EXPECT_TRUE(
-      service()->cookie_settings().IsCookieAccessAllowed(kThisURL, kThatURL));
+  EXPECT_TRUE(service()->cookie_settings().IsFullCookieAccessAllowed(kThisURL,
+                                                                     kThatURL));
 
   // Set block third party cookies to true, cookie should now be blocked.
   cookie_service_client()->BlockThirdPartyCookies(true);
   base::RunLoop().RunUntilIdle();
 
-  EXPECT_FALSE(
-      service()->cookie_settings().IsCookieAccessAllowed(kThisURL, kThatURL));
-  EXPECT_TRUE(
-      service()->cookie_settings().IsCookieAccessAllowed(kThisURL, kThisURL));
+  EXPECT_FALSE(service()->cookie_settings().IsFullCookieAccessAllowed(
+      kThisURL, kThatURL));
+  EXPECT_TRUE(service()->cookie_settings().IsFullCookieAccessAllowed(kThisURL,
+                                                                     kThisURL));
 
   // Set block third party cookies back to false, cookie should no longer be
   // blocked.
   cookie_service_client()->BlockThirdPartyCookies(false);
   base::RunLoop().RunUntilIdle();
 
-  EXPECT_TRUE(
-      service()->cookie_settings().IsCookieAccessAllowed(kThisURL, kThatURL));
+  EXPECT_TRUE(service()->cookie_settings().IsFullCookieAccessAllowed(kThisURL,
+                                                                     kThatURL));
 }
 
 // A test class having cookie store with a persistent backing store.
diff --git a/services/network/restricted_cookie_manager.cc b/services/network/restricted_cookie_manager.cc
index b1ab2b4..a921e988 100644
--- a/services/network/restricted_cookie_manager.cc
+++ b/services/network/restricted_cookie_manager.cc
@@ -586,7 +586,7 @@
     return;
   }
 
-  std::move(callback).Run(cookie_settings_->IsCookieAccessAllowed(
+  std::move(callback).Run(cookie_settings_->IsFullCookieAccessAllowed(
       url, site_for_cookies.RepresentativeUrl(), top_frame_origin));
 }
 
diff --git a/services/preferences/public/cpp/dictionary_value_update.cc b/services/preferences/public/cpp/dictionary_value_update.cc
index 9db22ff..c268f60 100644
--- a/services/preferences/public/cpp/dictionary_value_update.cc
+++ b/services/preferences/public/cpp/dictionary_value_update.cc
@@ -189,7 +189,12 @@
 bool DictionaryValueUpdate::GetBooleanWithoutPathExpansion(
     base::StringPiece key,
     bool* out_value) const {
-  return value_->GetBooleanWithoutPathExpansion(key, out_value);
+  absl::optional<bool> flag = value_->FindBoolKey(key);
+  if (!flag)
+    return false;
+
+  *out_value = flag.value();
+  return true;
 }
 
 bool DictionaryValueUpdate::GetIntegerWithoutPathExpansion(
diff --git a/testing/buildbot/chromium.fyi.json b/testing/buildbot/chromium.fyi.json
index 4662287..685ffbd 100644
--- a/testing/buildbot/chromium.fyi.json
+++ b/testing/buildbot/chromium.fyi.json
@@ -35439,13 +35439,13 @@
           "--platform",
           "iPad Air 2",
           "--version",
-          "13.5",
+          "14.5",
           "--args-json",
           "{\"test_args\": [\"--run-with-custom-webkit\"]}",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "11e608c",
+          "12e262",
           "--xctest"
         ],
         "isolate_name": "absl_hardening_tests",
@@ -35453,7 +35453,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "absl_hardening_tests_iPad Air 2 13.5",
+        "name": "absl_hardening_tests_iPad Air 2 14.5",
         "resultdb": {
           "enable": true
         },
@@ -35474,12 +35474,12 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_11e608c",
+              "name": "xcode_ios_12e262",
               "path": "Xcode.app"
             },
             {
-              "name": "runtime_ios_13_5",
-              "path": "Runtime-ios-13.5"
+              "name": "runtime_ios_14_5",
+              "path": "Runtime-ios-14.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -35491,13 +35491,13 @@
           "--platform",
           "iPhone X",
           "--version",
-          "13.5",
+          "14.5",
           "--args-json",
           "{\"test_args\": [\"--run-with-custom-webkit\"]}",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "11e608c",
+          "12e262",
           "--xctest"
         ],
         "isolate_name": "absl_hardening_tests",
@@ -35505,7 +35505,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "absl_hardening_tests_iPhone X 13.5",
+        "name": "absl_hardening_tests_iPhone X 14.5",
         "resultdb": {
           "enable": true
         },
@@ -35526,12 +35526,12 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_11e608c",
+              "name": "xcode_ios_12e262",
               "path": "Xcode.app"
             },
             {
-              "name": "runtime_ios_13_5",
-              "path": "Runtime-ios-13.5"
+              "name": "runtime_ios_14_5",
+              "path": "Runtime-ios-14.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -35543,13 +35543,13 @@
           "--platform",
           "iPad Air 2",
           "--version",
-          "13.5",
+          "14.5",
           "--args-json",
           "{\"test_args\": [\"--run-with-custom-webkit\"]}",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "11e608c",
+          "12e262",
           "--xctest"
         ],
         "isolate_name": "base_unittests",
@@ -35557,7 +35557,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "base_unittests_iPad Air 2 13.5",
+        "name": "base_unittests_iPad Air 2 14.5",
         "resultdb": {
           "enable": true
         },
@@ -35578,12 +35578,12 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_11e608c",
+              "name": "xcode_ios_12e262",
               "path": "Xcode.app"
             },
             {
-              "name": "runtime_ios_13_5",
-              "path": "Runtime-ios-13.5"
+              "name": "runtime_ios_14_5",
+              "path": "Runtime-ios-14.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -35595,13 +35595,13 @@
           "--platform",
           "iPhone X",
           "--version",
-          "13.5",
+          "14.5",
           "--args-json",
           "{\"test_args\": [\"--run-with-custom-webkit\"]}",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "11e608c",
+          "12e262",
           "--xctest"
         ],
         "isolate_name": "base_unittests",
@@ -35609,7 +35609,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "base_unittests_iPhone X 13.5",
+        "name": "base_unittests_iPhone X 14.5",
         "resultdb": {
           "enable": true
         },
@@ -35630,12 +35630,12 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_11e608c",
+              "name": "xcode_ios_12e262",
               "path": "Xcode.app"
             },
             {
-              "name": "runtime_ios_13_5",
-              "path": "Runtime-ios-13.5"
+              "name": "runtime_ios_14_5",
+              "path": "Runtime-ios-14.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -35647,13 +35647,13 @@
           "--platform",
           "iPad Air 2",
           "--version",
-          "13.5",
+          "14.5",
           "--args-json",
           "{\"test_args\": [\"--run-with-custom-webkit\"]}",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "11e608c",
+          "12e262",
           "--xctest"
         ],
         "isolate_name": "boringssl_crypto_tests",
@@ -35661,7 +35661,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "boringssl_crypto_tests_iPad Air 2 13.5",
+        "name": "boringssl_crypto_tests_iPad Air 2 14.5",
         "resultdb": {
           "enable": true
         },
@@ -35682,12 +35682,12 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_11e608c",
+              "name": "xcode_ios_12e262",
               "path": "Xcode.app"
             },
             {
-              "name": "runtime_ios_13_5",
-              "path": "Runtime-ios-13.5"
+              "name": "runtime_ios_14_5",
+              "path": "Runtime-ios-14.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -35699,13 +35699,13 @@
           "--platform",
           "iPhone X",
           "--version",
-          "13.5",
+          "14.5",
           "--args-json",
           "{\"test_args\": [\"--run-with-custom-webkit\"]}",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "11e608c",
+          "12e262",
           "--xctest"
         ],
         "isolate_name": "boringssl_crypto_tests",
@@ -35713,7 +35713,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "boringssl_crypto_tests_iPhone X 13.5",
+        "name": "boringssl_crypto_tests_iPhone X 14.5",
         "resultdb": {
           "enable": true
         },
@@ -35734,12 +35734,12 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_11e608c",
+              "name": "xcode_ios_12e262",
               "path": "Xcode.app"
             },
             {
-              "name": "runtime_ios_13_5",
-              "path": "Runtime-ios-13.5"
+              "name": "runtime_ios_14_5",
+              "path": "Runtime-ios-14.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -35751,13 +35751,13 @@
           "--platform",
           "iPad Air 2",
           "--version",
-          "13.5",
+          "14.5",
           "--args-json",
           "{\"test_args\": [\"--run-with-custom-webkit\"]}",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "11e608c",
+          "12e262",
           "--xctest"
         ],
         "isolate_name": "boringssl_ssl_tests",
@@ -35765,7 +35765,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "boringssl_ssl_tests_iPad Air 2 13.5",
+        "name": "boringssl_ssl_tests_iPad Air 2 14.5",
         "resultdb": {
           "enable": true
         },
@@ -35786,12 +35786,12 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_11e608c",
+              "name": "xcode_ios_12e262",
               "path": "Xcode.app"
             },
             {
-              "name": "runtime_ios_13_5",
-              "path": "Runtime-ios-13.5"
+              "name": "runtime_ios_14_5",
+              "path": "Runtime-ios-14.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -35803,13 +35803,13 @@
           "--platform",
           "iPhone X",
           "--version",
-          "13.5",
+          "14.5",
           "--args-json",
           "{\"test_args\": [\"--run-with-custom-webkit\"]}",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "11e608c",
+          "12e262",
           "--xctest"
         ],
         "isolate_name": "boringssl_ssl_tests",
@@ -35817,7 +35817,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "boringssl_ssl_tests_iPhone X 13.5",
+        "name": "boringssl_ssl_tests_iPhone X 14.5",
         "resultdb": {
           "enable": true
         },
@@ -35838,12 +35838,12 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_11e608c",
+              "name": "xcode_ios_12e262",
               "path": "Xcode.app"
             },
             {
-              "name": "runtime_ios_13_5",
-              "path": "Runtime-ios-13.5"
+              "name": "runtime_ios_14_5",
+              "path": "Runtime-ios-14.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -35855,13 +35855,13 @@
           "--platform",
           "iPad Air 2",
           "--version",
-          "13.5",
+          "14.5",
           "--args-json",
           "{\"test_args\": [\"--run-with-custom-webkit\"]}",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "11e608c",
+          "12e262",
           "--xctest"
         ],
         "isolate_name": "components_unittests",
@@ -35869,7 +35869,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "components_unittests_iPad Air 2 13.5",
+        "name": "components_unittests_iPad Air 2 14.5",
         "resultdb": {
           "enable": true
         },
@@ -35890,12 +35890,12 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_11e608c",
+              "name": "xcode_ios_12e262",
               "path": "Xcode.app"
             },
             {
-              "name": "runtime_ios_13_5",
-              "path": "Runtime-ios-13.5"
+              "name": "runtime_ios_14_5",
+              "path": "Runtime-ios-14.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -35907,13 +35907,13 @@
           "--platform",
           "iPhone X",
           "--version",
-          "13.5",
+          "14.5",
           "--args-json",
           "{\"test_args\": [\"--run-with-custom-webkit\"]}",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "11e608c",
+          "12e262",
           "--xctest"
         ],
         "isolate_name": "components_unittests",
@@ -35921,7 +35921,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "components_unittests_iPhone X 13.5",
+        "name": "components_unittests_iPhone X 14.5",
         "resultdb": {
           "enable": true
         },
@@ -35942,12 +35942,12 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_11e608c",
+              "name": "xcode_ios_12e262",
               "path": "Xcode.app"
             },
             {
-              "name": "runtime_ios_13_5",
-              "path": "Runtime-ios-13.5"
+              "name": "runtime_ios_14_5",
+              "path": "Runtime-ios-14.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -35959,13 +35959,13 @@
           "--platform",
           "iPad Air 2",
           "--version",
-          "13.5",
+          "14.5",
           "--args-json",
           "{\"test_args\": [\"--run-with-custom-webkit\"]}",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "11e608c",
+          "12e262",
           "--xctest"
         ],
         "isolate_name": "crypto_unittests",
@@ -35973,7 +35973,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "crypto_unittests_iPad Air 2 13.5",
+        "name": "crypto_unittests_iPad Air 2 14.5",
         "resultdb": {
           "enable": true
         },
@@ -35994,12 +35994,12 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_11e608c",
+              "name": "xcode_ios_12e262",
               "path": "Xcode.app"
             },
             {
-              "name": "runtime_ios_13_5",
-              "path": "Runtime-ios-13.5"
+              "name": "runtime_ios_14_5",
+              "path": "Runtime-ios-14.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -36011,13 +36011,13 @@
           "--platform",
           "iPhone X",
           "--version",
-          "13.5",
+          "14.5",
           "--args-json",
           "{\"test_args\": [\"--run-with-custom-webkit\"]}",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "11e608c",
+          "12e262",
           "--xctest"
         ],
         "isolate_name": "crypto_unittests",
@@ -36025,7 +36025,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "crypto_unittests_iPhone X 13.5",
+        "name": "crypto_unittests_iPhone X 14.5",
         "resultdb": {
           "enable": true
         },
@@ -36046,12 +36046,12 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_11e608c",
+              "name": "xcode_ios_12e262",
               "path": "Xcode.app"
             },
             {
-              "name": "runtime_ios_13_5",
-              "path": "Runtime-ios-13.5"
+              "name": "runtime_ios_14_5",
+              "path": "Runtime-ios-14.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -36063,13 +36063,13 @@
           "--platform",
           "iPad Air 2",
           "--version",
-          "13.5",
+          "14.5",
           "--args-json",
           "{\"test_args\": [\"--run-with-custom-webkit\"]}",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "11e608c",
+          "12e262",
           "--xctest"
         ],
         "isolate_name": "gfx_unittests",
@@ -36077,7 +36077,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "gfx_unittests_iPad Air 2 13.5",
+        "name": "gfx_unittests_iPad Air 2 14.5",
         "resultdb": {
           "enable": true
         },
@@ -36098,12 +36098,12 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_11e608c",
+              "name": "xcode_ios_12e262",
               "path": "Xcode.app"
             },
             {
-              "name": "runtime_ios_13_5",
-              "path": "Runtime-ios-13.5"
+              "name": "runtime_ios_14_5",
+              "path": "Runtime-ios-14.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -36115,13 +36115,13 @@
           "--platform",
           "iPhone X",
           "--version",
-          "13.5",
+          "14.5",
           "--args-json",
           "{\"test_args\": [\"--run-with-custom-webkit\"]}",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "11e608c",
+          "12e262",
           "--xctest"
         ],
         "isolate_name": "gfx_unittests",
@@ -36129,7 +36129,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "gfx_unittests_iPhone X 13.5",
+        "name": "gfx_unittests_iPhone X 14.5",
         "resultdb": {
           "enable": true
         },
@@ -36150,12 +36150,12 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_11e608c",
+              "name": "xcode_ios_12e262",
               "path": "Xcode.app"
             },
             {
-              "name": "runtime_ios_13_5",
-              "path": "Runtime-ios-13.5"
+              "name": "runtime_ios_14_5",
+              "path": "Runtime-ios-14.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -36167,13 +36167,13 @@
           "--platform",
           "iPad Air 2",
           "--version",
-          "13.5",
+          "14.5",
           "--args-json",
           "{\"test_args\": [\"--run-with-custom-webkit\"]}",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "11e608c",
+          "12e262",
           "--xctest"
         ],
         "isolate_name": "google_apis_unittests",
@@ -36181,7 +36181,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "google_apis_unittests_iPad Air 2 13.5",
+        "name": "google_apis_unittests_iPad Air 2 14.5",
         "resultdb": {
           "enable": true
         },
@@ -36202,12 +36202,12 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_11e608c",
+              "name": "xcode_ios_12e262",
               "path": "Xcode.app"
             },
             {
-              "name": "runtime_ios_13_5",
-              "path": "Runtime-ios-13.5"
+              "name": "runtime_ios_14_5",
+              "path": "Runtime-ios-14.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -36219,13 +36219,13 @@
           "--platform",
           "iPhone X",
           "--version",
-          "13.5",
+          "14.5",
           "--args-json",
           "{\"test_args\": [\"--run-with-custom-webkit\"]}",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "11e608c",
+          "12e262",
           "--xctest"
         ],
         "isolate_name": "google_apis_unittests",
@@ -36233,7 +36233,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "google_apis_unittests_iPhone X 13.5",
+        "name": "google_apis_unittests_iPhone X 14.5",
         "resultdb": {
           "enable": true
         },
@@ -36254,12 +36254,12 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_11e608c",
+              "name": "xcode_ios_12e262",
               "path": "Xcode.app"
             },
             {
-              "name": "runtime_ios_13_5",
-              "path": "Runtime-ios-13.5"
+              "name": "runtime_ios_14_5",
+              "path": "Runtime-ios-14.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -36271,13 +36271,13 @@
           "--platform",
           "iPad Air 2",
           "--version",
-          "13.5",
+          "14.5",
           "--args-json",
           "{\"test_args\": [\"--run-with-custom-webkit\"]}",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "11e608c",
+          "12e262",
           "--xctest",
           "--xcode-parallelization"
         ],
@@ -36286,7 +36286,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_chrome_bookmarks_eg2tests_module_iPad Air 2 13.5",
+        "name": "ios_chrome_bookmarks_eg2tests_module_iPad Air 2 14.5",
         "resultdb": {
           "enable": true
         },
@@ -36307,12 +36307,12 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_11e608c",
+              "name": "xcode_ios_12e262",
               "path": "Xcode.app"
             },
             {
-              "name": "runtime_ios_13_5",
-              "path": "Runtime-ios-13.5"
+              "name": "runtime_ios_14_5",
+              "path": "Runtime-ios-14.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -36324,13 +36324,13 @@
           "--platform",
           "iPhone X",
           "--version",
-          "13.5",
+          "14.5",
           "--args-json",
           "{\"test_args\": [\"--run-with-custom-webkit\"]}",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "11e608c",
+          "12e262",
           "--xctest",
           "--xcode-parallelization"
         ],
@@ -36339,7 +36339,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_chrome_bookmarks_eg2tests_module_iPhone X 13.5",
+        "name": "ios_chrome_bookmarks_eg2tests_module_iPhone X 14.5",
         "resultdb": {
           "enable": true
         },
@@ -36360,12 +36360,12 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_11e608c",
+              "name": "xcode_ios_12e262",
               "path": "Xcode.app"
             },
             {
-              "name": "runtime_ios_13_5",
-              "path": "Runtime-ios-13.5"
+              "name": "runtime_ios_14_5",
+              "path": "Runtime-ios-14.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -36377,13 +36377,13 @@
           "--platform",
           "iPad Air 2",
           "--version",
-          "13.5",
+          "14.5",
           "--args-json",
           "{\"test_args\": [\"--run-with-custom-webkit\"]}",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "11e608c",
+          "12e262",
           "--xctest",
           "--xcode-parallelization"
         ],
@@ -36392,7 +36392,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_chrome_integration_eg2tests_module_iPad Air 2 13.5",
+        "name": "ios_chrome_integration_eg2tests_module_iPad Air 2 14.5",
         "resultdb": {
           "enable": true
         },
@@ -36413,12 +36413,12 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_11e608c",
+              "name": "xcode_ios_12e262",
               "path": "Xcode.app"
             },
             {
-              "name": "runtime_ios_13_5",
-              "path": "Runtime-ios-13.5"
+              "name": "runtime_ios_14_5",
+              "path": "Runtime-ios-14.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
@@ -36431,13 +36431,13 @@
           "--platform",
           "iPhone X",
           "--version",
-          "13.5",
+          "14.5",
           "--args-json",
           "{\"test_args\": [\"--run-with-custom-webkit\"]}",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "11e608c",
+          "12e262",
           "--xctest",
           "--xcode-parallelization"
         ],
@@ -36446,7 +36446,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_chrome_integration_eg2tests_module_iPhone X 13.5",
+        "name": "ios_chrome_integration_eg2tests_module_iPhone X 14.5",
         "resultdb": {
           "enable": true
         },
@@ -36467,12 +36467,12 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_11e608c",
+              "name": "xcode_ios_12e262",
               "path": "Xcode.app"
             },
             {
-              "name": "runtime_ios_13_5",
-              "path": "Runtime-ios-13.5"
+              "name": "runtime_ios_14_5",
+              "path": "Runtime-ios-14.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
@@ -36485,13 +36485,13 @@
           "--platform",
           "iPad Air 2",
           "--version",
-          "13.5",
+          "14.5",
           "--args-json",
           "{\"test_args\": [\"--run-with-custom-webkit\"]}",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "11e608c",
+          "12e262",
           "--xctest",
           "--xcode-parallelization"
         ],
@@ -36500,7 +36500,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_chrome_settings_eg2tests_module_iPad Air 2 13.5",
+        "name": "ios_chrome_settings_eg2tests_module_iPad Air 2 14.5",
         "resultdb": {
           "enable": true
         },
@@ -36521,12 +36521,12 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_11e608c",
+              "name": "xcode_ios_12e262",
               "path": "Xcode.app"
             },
             {
-              "name": "runtime_ios_13_5",
-              "path": "Runtime-ios-13.5"
+              "name": "runtime_ios_14_5",
+              "path": "Runtime-ios-14.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
@@ -36539,13 +36539,13 @@
           "--platform",
           "iPhone X",
           "--version",
-          "13.5",
+          "14.5",
           "--args-json",
           "{\"test_args\": [\"--run-with-custom-webkit\"]}",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "11e608c",
+          "12e262",
           "--xctest",
           "--xcode-parallelization"
         ],
@@ -36554,7 +36554,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_chrome_settings_eg2tests_module_iPhone X 13.5",
+        "name": "ios_chrome_settings_eg2tests_module_iPhone X 14.5",
         "resultdb": {
           "enable": true
         },
@@ -36575,12 +36575,12 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_11e608c",
+              "name": "xcode_ios_12e262",
               "path": "Xcode.app"
             },
             {
-              "name": "runtime_ios_13_5",
-              "path": "Runtime-ios-13.5"
+              "name": "runtime_ios_14_5",
+              "path": "Runtime-ios-14.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
@@ -36593,13 +36593,13 @@
           "--platform",
           "iPad Air 2",
           "--version",
-          "13.5",
+          "14.5",
           "--args-json",
           "{\"test_args\": [\"--run-with-custom-webkit\"]}",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "11e608c",
+          "12e262",
           "--xctest",
           "--xcode-parallelization"
         ],
@@ -36608,7 +36608,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_chrome_signin_eg2tests_module_iPad Air 2 13.5",
+        "name": "ios_chrome_signin_eg2tests_module_iPad Air 2 14.5",
         "resultdb": {
           "enable": true
         },
@@ -36629,12 +36629,12 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_11e608c",
+              "name": "xcode_ios_12e262",
               "path": "Xcode.app"
             },
             {
-              "name": "runtime_ios_13_5",
-              "path": "Runtime-ios-13.5"
+              "name": "runtime_ios_14_5",
+              "path": "Runtime-ios-14.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -36646,13 +36646,13 @@
           "--platform",
           "iPhone X",
           "--version",
-          "13.5",
+          "14.5",
           "--args-json",
           "{\"test_args\": [\"--run-with-custom-webkit\"]}",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "11e608c",
+          "12e262",
           "--xctest",
           "--xcode-parallelization"
         ],
@@ -36661,7 +36661,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_chrome_signin_eg2tests_module_iPhone X 13.5",
+        "name": "ios_chrome_signin_eg2tests_module_iPhone X 14.5",
         "resultdb": {
           "enable": true
         },
@@ -36682,12 +36682,12 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_11e608c",
+              "name": "xcode_ios_12e262",
               "path": "Xcode.app"
             },
             {
-              "name": "runtime_ios_13_5",
-              "path": "Runtime-ios-13.5"
+              "name": "runtime_ios_14_5",
+              "path": "Runtime-ios-14.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -36699,13 +36699,13 @@
           "--platform",
           "iPad Air 2",
           "--version",
-          "13.5",
+          "14.5",
           "--args-json",
           "{\"test_args\": [\"--run-with-custom-webkit\"]}",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "11e608c",
+          "12e262",
           "--xctest",
           "--xcode-parallelization"
         ],
@@ -36714,7 +36714,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_chrome_smoke_eg2tests_module_iPad Air 2 13.5",
+        "name": "ios_chrome_smoke_eg2tests_module_iPad Air 2 14.5",
         "resultdb": {
           "enable": true
         },
@@ -36735,12 +36735,12 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_11e608c",
+              "name": "xcode_ios_12e262",
               "path": "Xcode.app"
             },
             {
-              "name": "runtime_ios_13_5",
-              "path": "Runtime-ios-13.5"
+              "name": "runtime_ios_14_5",
+              "path": "Runtime-ios-14.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -36752,13 +36752,13 @@
           "--platform",
           "iPhone X",
           "--version",
-          "13.5",
+          "14.5",
           "--args-json",
           "{\"test_args\": [\"--run-with-custom-webkit\"]}",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "11e608c",
+          "12e262",
           "--xctest",
           "--xcode-parallelization"
         ],
@@ -36767,7 +36767,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_chrome_smoke_eg2tests_module_iPhone X 13.5",
+        "name": "ios_chrome_smoke_eg2tests_module_iPhone X 14.5",
         "resultdb": {
           "enable": true
         },
@@ -36788,12 +36788,12 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_11e608c",
+              "name": "xcode_ios_12e262",
               "path": "Xcode.app"
             },
             {
-              "name": "runtime_ios_13_5",
-              "path": "Runtime-ios-13.5"
+              "name": "runtime_ios_14_5",
+              "path": "Runtime-ios-14.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -36805,13 +36805,13 @@
           "--platform",
           "iPad Air 2",
           "--version",
-          "13.5",
+          "14.5",
           "--args-json",
           "{\"test_args\": [\"--run-with-custom-webkit\"]}",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "11e608c",
+          "12e262",
           "--xctest",
           "--xcode-parallelization"
         ],
@@ -36820,7 +36820,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_chrome_ui_eg2tests_module_iPad Air 2 13.5",
+        "name": "ios_chrome_ui_eg2tests_module_iPad Air 2 14.5",
         "resultdb": {
           "enable": true
         },
@@ -36841,12 +36841,12 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_11e608c",
+              "name": "xcode_ios_12e262",
               "path": "Xcode.app"
             },
             {
-              "name": "runtime_ios_13_5",
-              "path": "Runtime-ios-13.5"
+              "name": "runtime_ios_14_5",
+              "path": "Runtime-ios-14.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
@@ -36859,13 +36859,13 @@
           "--platform",
           "iPhone X",
           "--version",
-          "13.5",
+          "14.5",
           "--args-json",
           "{\"test_args\": [\"--run-with-custom-webkit\"]}",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "11e608c",
+          "12e262",
           "--xctest",
           "--xcode-parallelization"
         ],
@@ -36874,7 +36874,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_chrome_ui_eg2tests_module_iPhone X 13.5",
+        "name": "ios_chrome_ui_eg2tests_module_iPhone X 14.5",
         "resultdb": {
           "enable": true
         },
@@ -36895,12 +36895,12 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_11e608c",
+              "name": "xcode_ios_12e262",
               "path": "Xcode.app"
             },
             {
-              "name": "runtime_ios_13_5",
-              "path": "Runtime-ios-13.5"
+              "name": "runtime_ios_14_5",
+              "path": "Runtime-ios-14.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
@@ -36913,13 +36913,13 @@
           "--platform",
           "iPad Air 2",
           "--version",
-          "13.5",
+          "14.5",
           "--args-json",
           "{\"test_args\": [\"--run-with-custom-webkit\"]}",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "11e608c",
+          "12e262",
           "--xctest"
         ],
         "isolate_name": "ios_chrome_unittests",
@@ -36927,7 +36927,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_chrome_unittests_iPad Air 2 13.5",
+        "name": "ios_chrome_unittests_iPad Air 2 14.5",
         "resultdb": {
           "enable": true
         },
@@ -36948,12 +36948,12 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_11e608c",
+              "name": "xcode_ios_12e262",
               "path": "Xcode.app"
             },
             {
-              "name": "runtime_ios_13_5",
-              "path": "Runtime-ios-13.5"
+              "name": "runtime_ios_14_5",
+              "path": "Runtime-ios-14.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -36965,13 +36965,13 @@
           "--platform",
           "iPhone X",
           "--version",
-          "13.5",
+          "14.5",
           "--args-json",
           "{\"test_args\": [\"--run-with-custom-webkit\"]}",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "11e608c",
+          "12e262",
           "--xctest"
         ],
         "isolate_name": "ios_chrome_unittests",
@@ -36979,7 +36979,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_chrome_unittests_iPhone X 13.5",
+        "name": "ios_chrome_unittests_iPhone X 14.5",
         "resultdb": {
           "enable": true
         },
@@ -37000,12 +37000,12 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_11e608c",
+              "name": "xcode_ios_12e262",
               "path": "Xcode.app"
             },
             {
-              "name": "runtime_ios_13_5",
-              "path": "Runtime-ios-13.5"
+              "name": "runtime_ios_14_5",
+              "path": "Runtime-ios-14.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -37017,13 +37017,13 @@
           "--platform",
           "iPad Air 2",
           "--version",
-          "13.5",
+          "14.5",
           "--args-json",
           "{\"test_args\": [\"--run-with-custom-webkit\"]}",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "11e608c",
+          "12e262",
           "--xctest",
           "--xcode-parallelization"
         ],
@@ -37032,7 +37032,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_chrome_web_eg2tests_module_iPad Air 2 13.5",
+        "name": "ios_chrome_web_eg2tests_module_iPad Air 2 14.5",
         "resultdb": {
           "enable": true
         },
@@ -37053,12 +37053,12 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_11e608c",
+              "name": "xcode_ios_12e262",
               "path": "Xcode.app"
             },
             {
-              "name": "runtime_ios_13_5",
-              "path": "Runtime-ios-13.5"
+              "name": "runtime_ios_14_5",
+              "path": "Runtime-ios-14.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -37070,13 +37070,13 @@
           "--platform",
           "iPhone X",
           "--version",
-          "13.5",
+          "14.5",
           "--args-json",
           "{\"test_args\": [\"--run-with-custom-webkit\"]}",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "11e608c",
+          "12e262",
           "--xctest",
           "--xcode-parallelization"
         ],
@@ -37085,7 +37085,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_chrome_web_eg2tests_module_iPhone X 13.5",
+        "name": "ios_chrome_web_eg2tests_module_iPhone X 14.5",
         "resultdb": {
           "enable": true
         },
@@ -37106,12 +37106,12 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_11e608c",
+              "name": "xcode_ios_12e262",
               "path": "Xcode.app"
             },
             {
-              "name": "runtime_ios_13_5",
-              "path": "Runtime-ios-13.5"
+              "name": "runtime_ios_14_5",
+              "path": "Runtime-ios-14.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -37123,13 +37123,13 @@
           "--platform",
           "iPad Air 2",
           "--version",
-          "13.5",
+          "14.5",
           "--args-json",
           "{\"test_args\": [\"--run-with-custom-webkit\"]}",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "11e608c",
+          "12e262",
           "--xctest"
         ],
         "isolate_name": "ios_components_unittests",
@@ -37137,7 +37137,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_components_unittests_iPad Air 2 13.5",
+        "name": "ios_components_unittests_iPad Air 2 14.5",
         "resultdb": {
           "enable": true
         },
@@ -37158,12 +37158,12 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_11e608c",
+              "name": "xcode_ios_12e262",
               "path": "Xcode.app"
             },
             {
-              "name": "runtime_ios_13_5",
-              "path": "Runtime-ios-13.5"
+              "name": "runtime_ios_14_5",
+              "path": "Runtime-ios-14.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -37175,13 +37175,13 @@
           "--platform",
           "iPhone X",
           "--version",
-          "13.5",
+          "14.5",
           "--args-json",
           "{\"test_args\": [\"--run-with-custom-webkit\"]}",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "11e608c",
+          "12e262",
           "--xctest"
         ],
         "isolate_name": "ios_components_unittests",
@@ -37189,7 +37189,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_components_unittests_iPhone X 13.5",
+        "name": "ios_components_unittests_iPhone X 14.5",
         "resultdb": {
           "enable": true
         },
@@ -37210,12 +37210,12 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_11e608c",
+              "name": "xcode_ios_12e262",
               "path": "Xcode.app"
             },
             {
-              "name": "runtime_ios_13_5",
-              "path": "Runtime-ios-13.5"
+              "name": "runtime_ios_14_5",
+              "path": "Runtime-ios-14.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -37227,13 +37227,13 @@
           "--platform",
           "iPad Air 2",
           "--version",
-          "13.5",
+          "14.5",
           "--args-json",
           "{\"test_args\": [\"--run-with-custom-webkit\"]}",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "11e608c",
+          "12e262",
           "--xctest"
         ],
         "isolate_name": "ios_net_unittests",
@@ -37241,7 +37241,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_net_unittests_iPad Air 2 13.5",
+        "name": "ios_net_unittests_iPad Air 2 14.5",
         "resultdb": {
           "enable": true
         },
@@ -37262,12 +37262,12 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_11e608c",
+              "name": "xcode_ios_12e262",
               "path": "Xcode.app"
             },
             {
-              "name": "runtime_ios_13_5",
-              "path": "Runtime-ios-13.5"
+              "name": "runtime_ios_14_5",
+              "path": "Runtime-ios-14.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -37279,13 +37279,13 @@
           "--platform",
           "iPhone X",
           "--version",
-          "13.5",
+          "14.5",
           "--args-json",
           "{\"test_args\": [\"--run-with-custom-webkit\"]}",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "11e608c",
+          "12e262",
           "--xctest"
         ],
         "isolate_name": "ios_net_unittests",
@@ -37293,7 +37293,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_net_unittests_iPhone X 13.5",
+        "name": "ios_net_unittests_iPhone X 14.5",
         "resultdb": {
           "enable": true
         },
@@ -37314,12 +37314,12 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_11e608c",
+              "name": "xcode_ios_12e262",
               "path": "Xcode.app"
             },
             {
-              "name": "runtime_ios_13_5",
-              "path": "Runtime-ios-13.5"
+              "name": "runtime_ios_14_5",
+              "path": "Runtime-ios-14.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -37331,13 +37331,13 @@
           "--platform",
           "iPad Air 2",
           "--version",
-          "13.5",
+          "14.5",
           "--args-json",
           "{\"test_args\": [\"--run-with-custom-webkit\"]}",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "11e608c",
+          "12e262",
           "--xctest"
         ],
         "isolate_name": "ios_remoting_unittests",
@@ -37345,7 +37345,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_remoting_unittests_iPad Air 2 13.5",
+        "name": "ios_remoting_unittests_iPad Air 2 14.5",
         "resultdb": {
           "enable": true
         },
@@ -37366,12 +37366,12 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_11e608c",
+              "name": "xcode_ios_12e262",
               "path": "Xcode.app"
             },
             {
-              "name": "runtime_ios_13_5",
-              "path": "Runtime-ios-13.5"
+              "name": "runtime_ios_14_5",
+              "path": "Runtime-ios-14.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -37383,13 +37383,13 @@
           "--platform",
           "iPhone X",
           "--version",
-          "13.5",
+          "14.5",
           "--args-json",
           "{\"test_args\": [\"--run-with-custom-webkit\"]}",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "11e608c",
+          "12e262",
           "--xctest"
         ],
         "isolate_name": "ios_remoting_unittests",
@@ -37397,7 +37397,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_remoting_unittests_iPhone X 13.5",
+        "name": "ios_remoting_unittests_iPhone X 14.5",
         "resultdb": {
           "enable": true
         },
@@ -37418,12 +37418,12 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_11e608c",
+              "name": "xcode_ios_12e262",
               "path": "Xcode.app"
             },
             {
-              "name": "runtime_ios_13_5",
-              "path": "Runtime-ios-13.5"
+              "name": "runtime_ios_14_5",
+              "path": "Runtime-ios-14.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -37435,13 +37435,13 @@
           "--platform",
           "iPad Air 2",
           "--version",
-          "13.5",
+          "14.5",
           "--args-json",
           "{\"test_args\": [\"--run-with-custom-webkit\"]}",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "11e608c",
+          "12e262",
           "--xctest",
           "--xcode-parallelization"
         ],
@@ -37450,7 +37450,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_showcase_eg2tests_module_iPad Air 2 13.5",
+        "name": "ios_showcase_eg2tests_module_iPad Air 2 14.5",
         "resultdb": {
           "enable": true
         },
@@ -37471,12 +37471,12 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_11e608c",
+              "name": "xcode_ios_12e262",
               "path": "Xcode.app"
             },
             {
-              "name": "runtime_ios_13_5",
-              "path": "Runtime-ios-13.5"
+              "name": "runtime_ios_14_5",
+              "path": "Runtime-ios-14.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -37488,13 +37488,13 @@
           "--platform",
           "iPhone X",
           "--version",
-          "13.5",
+          "14.5",
           "--args-json",
           "{\"test_args\": [\"--run-with-custom-webkit\"]}",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "11e608c",
+          "12e262",
           "--xctest",
           "--xcode-parallelization"
         ],
@@ -37503,7 +37503,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_showcase_eg2tests_module_iPhone X 13.5",
+        "name": "ios_showcase_eg2tests_module_iPhone X 14.5",
         "resultdb": {
           "enable": true
         },
@@ -37524,12 +37524,12 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_11e608c",
+              "name": "xcode_ios_12e262",
               "path": "Xcode.app"
             },
             {
-              "name": "runtime_ios_13_5",
-              "path": "Runtime-ios-13.5"
+              "name": "runtime_ios_14_5",
+              "path": "Runtime-ios-14.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -37541,13 +37541,13 @@
           "--platform",
           "iPad Air 2",
           "--version",
-          "13.5",
+          "14.5",
           "--args-json",
           "{\"test_args\": [\"--run-with-custom-webkit\"]}",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "11e608c",
+          "12e262",
           "--xctest"
         ],
         "isolate_name": "ios_testing_unittests",
@@ -37555,7 +37555,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_testing_unittests_iPad Air 2 13.5",
+        "name": "ios_testing_unittests_iPad Air 2 14.5",
         "resultdb": {
           "enable": true
         },
@@ -37576,12 +37576,12 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_11e608c",
+              "name": "xcode_ios_12e262",
               "path": "Xcode.app"
             },
             {
-              "name": "runtime_ios_13_5",
-              "path": "Runtime-ios-13.5"
+              "name": "runtime_ios_14_5",
+              "path": "Runtime-ios-14.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -37593,13 +37593,13 @@
           "--platform",
           "iPhone X",
           "--version",
-          "13.5",
+          "14.5",
           "--args-json",
           "{\"test_args\": [\"--run-with-custom-webkit\"]}",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "11e608c",
+          "12e262",
           "--xctest"
         ],
         "isolate_name": "ios_testing_unittests",
@@ -37607,7 +37607,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_testing_unittests_iPhone X 13.5",
+        "name": "ios_testing_unittests_iPhone X 14.5",
         "resultdb": {
           "enable": true
         },
@@ -37628,12 +37628,12 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_11e608c",
+              "name": "xcode_ios_12e262",
               "path": "Xcode.app"
             },
             {
-              "name": "runtime_ios_13_5",
-              "path": "Runtime-ios-13.5"
+              "name": "runtime_ios_14_5",
+              "path": "Runtime-ios-14.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -37645,13 +37645,13 @@
           "--platform",
           "iPad Air 2",
           "--version",
-          "13.5",
+          "14.5",
           "--args-json",
           "{\"test_args\": [\"--run-with-custom-webkit\"]}",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "11e608c",
+          "12e262",
           "--xctest"
         ],
         "isolate_name": "ios_web_inttests",
@@ -37659,7 +37659,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_web_inttests_iPad Air 2 13.5",
+        "name": "ios_web_inttests_iPad Air 2 14.5",
         "resultdb": {
           "enable": true
         },
@@ -37680,12 +37680,12 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_11e608c",
+              "name": "xcode_ios_12e262",
               "path": "Xcode.app"
             },
             {
-              "name": "runtime_ios_13_5",
-              "path": "Runtime-ios-13.5"
+              "name": "runtime_ios_14_5",
+              "path": "Runtime-ios-14.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -37697,13 +37697,13 @@
           "--platform",
           "iPhone X",
           "--version",
-          "13.5",
+          "14.5",
           "--args-json",
           "{\"test_args\": [\"--run-with-custom-webkit\"]}",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "11e608c",
+          "12e262",
           "--xctest"
         ],
         "isolate_name": "ios_web_inttests",
@@ -37711,7 +37711,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_web_inttests_iPhone X 13.5",
+        "name": "ios_web_inttests_iPhone X 14.5",
         "resultdb": {
           "enable": true
         },
@@ -37732,12 +37732,12 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_11e608c",
+              "name": "xcode_ios_12e262",
               "path": "Xcode.app"
             },
             {
-              "name": "runtime_ios_13_5",
-              "path": "Runtime-ios-13.5"
+              "name": "runtime_ios_14_5",
+              "path": "Runtime-ios-14.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -37749,13 +37749,13 @@
           "--platform",
           "iPad Air 2",
           "--version",
-          "13.5",
+          "14.5",
           "--args-json",
           "{\"test_args\": [\"--run-with-custom-webkit\"]}",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "11e608c",
+          "12e262",
           "--xctest",
           "--xcode-parallelization"
         ],
@@ -37764,7 +37764,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_web_shell_eg2tests_module_iPad Air 2 13.5",
+        "name": "ios_web_shell_eg2tests_module_iPad Air 2 14.5",
         "resultdb": {
           "enable": true
         },
@@ -37785,12 +37785,12 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_11e608c",
+              "name": "xcode_ios_12e262",
               "path": "Xcode.app"
             },
             {
-              "name": "runtime_ios_13_5",
-              "path": "Runtime-ios-13.5"
+              "name": "runtime_ios_14_5",
+              "path": "Runtime-ios-14.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -37802,13 +37802,13 @@
           "--platform",
           "iPhone X",
           "--version",
-          "13.5",
+          "14.5",
           "--args-json",
           "{\"test_args\": [\"--run-with-custom-webkit\"]}",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "11e608c",
+          "12e262",
           "--xctest",
           "--xcode-parallelization"
         ],
@@ -37817,7 +37817,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_web_shell_eg2tests_module_iPhone X 13.5",
+        "name": "ios_web_shell_eg2tests_module_iPhone X 14.5",
         "resultdb": {
           "enable": true
         },
@@ -37838,12 +37838,12 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_11e608c",
+              "name": "xcode_ios_12e262",
               "path": "Xcode.app"
             },
             {
-              "name": "runtime_ios_13_5",
-              "path": "Runtime-ios-13.5"
+              "name": "runtime_ios_14_5",
+              "path": "Runtime-ios-14.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -37855,13 +37855,13 @@
           "--platform",
           "iPad Air 2",
           "--version",
-          "13.5",
+          "14.5",
           "--args-json",
           "{\"test_args\": [\"--run-with-custom-webkit\"]}",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "11e608c",
+          "12e262",
           "--xctest"
         ],
         "isolate_name": "ios_web_unittests",
@@ -37869,7 +37869,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_web_unittests_iPad Air 2 13.5",
+        "name": "ios_web_unittests_iPad Air 2 14.5",
         "resultdb": {
           "enable": true
         },
@@ -37890,12 +37890,12 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_11e608c",
+              "name": "xcode_ios_12e262",
               "path": "Xcode.app"
             },
             {
-              "name": "runtime_ios_13_5",
-              "path": "Runtime-ios-13.5"
+              "name": "runtime_ios_14_5",
+              "path": "Runtime-ios-14.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -37907,13 +37907,13 @@
           "--platform",
           "iPhone X",
           "--version",
-          "13.5",
+          "14.5",
           "--args-json",
           "{\"test_args\": [\"--run-with-custom-webkit\"]}",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "11e608c",
+          "12e262",
           "--xctest"
         ],
         "isolate_name": "ios_web_unittests",
@@ -37921,7 +37921,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_web_unittests_iPhone X 13.5",
+        "name": "ios_web_unittests_iPhone X 14.5",
         "resultdb": {
           "enable": true
         },
@@ -37942,12 +37942,12 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_11e608c",
+              "name": "xcode_ios_12e262",
               "path": "Xcode.app"
             },
             {
-              "name": "runtime_ios_13_5",
-              "path": "Runtime-ios-13.5"
+              "name": "runtime_ios_14_5",
+              "path": "Runtime-ios-14.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -37959,13 +37959,13 @@
           "--platform",
           "iPad Air 2",
           "--version",
-          "13.5",
+          "14.5",
           "--args-json",
           "{\"test_args\": [\"--run-with-custom-webkit\"]}",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "11e608c",
+          "12e262",
           "--xctest"
         ],
         "isolate_name": "ios_web_view_inttests",
@@ -37973,7 +37973,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_web_view_inttests_iPad Air 2 13.5",
+        "name": "ios_web_view_inttests_iPad Air 2 14.5",
         "resultdb": {
           "enable": true
         },
@@ -37994,12 +37994,12 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_11e608c",
+              "name": "xcode_ios_12e262",
               "path": "Xcode.app"
             },
             {
-              "name": "runtime_ios_13_5",
-              "path": "Runtime-ios-13.5"
+              "name": "runtime_ios_14_5",
+              "path": "Runtime-ios-14.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -38011,13 +38011,13 @@
           "--platform",
           "iPhone X",
           "--version",
-          "13.5",
+          "14.5",
           "--args-json",
           "{\"test_args\": [\"--run-with-custom-webkit\"]}",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "11e608c",
+          "12e262",
           "--xctest"
         ],
         "isolate_name": "ios_web_view_inttests",
@@ -38025,7 +38025,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_web_view_inttests_iPhone X 13.5",
+        "name": "ios_web_view_inttests_iPhone X 14.5",
         "resultdb": {
           "enable": true
         },
@@ -38046,12 +38046,12 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_11e608c",
+              "name": "xcode_ios_12e262",
               "path": "Xcode.app"
             },
             {
-              "name": "runtime_ios_13_5",
-              "path": "Runtime-ios-13.5"
+              "name": "runtime_ios_14_5",
+              "path": "Runtime-ios-14.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -38063,13 +38063,13 @@
           "--platform",
           "iPad Air 2",
           "--version",
-          "13.5",
+          "14.5",
           "--args-json",
           "{\"test_args\": [\"--run-with-custom-webkit\"]}",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "11e608c",
+          "12e262",
           "--xctest"
         ],
         "isolate_name": "ios_web_view_unittests",
@@ -38077,7 +38077,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_web_view_unittests_iPad Air 2 13.5",
+        "name": "ios_web_view_unittests_iPad Air 2 14.5",
         "resultdb": {
           "enable": true
         },
@@ -38098,12 +38098,12 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_11e608c",
+              "name": "xcode_ios_12e262",
               "path": "Xcode.app"
             },
             {
-              "name": "runtime_ios_13_5",
-              "path": "Runtime-ios-13.5"
+              "name": "runtime_ios_14_5",
+              "path": "Runtime-ios-14.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -38115,13 +38115,13 @@
           "--platform",
           "iPhone X",
           "--version",
-          "13.5",
+          "14.5",
           "--args-json",
           "{\"test_args\": [\"--run-with-custom-webkit\"]}",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "11e608c",
+          "12e262",
           "--xctest"
         ],
         "isolate_name": "ios_web_view_unittests",
@@ -38129,7 +38129,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_web_view_unittests_iPhone X 13.5",
+        "name": "ios_web_view_unittests_iPhone X 14.5",
         "resultdb": {
           "enable": true
         },
@@ -38150,12 +38150,12 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_11e608c",
+              "name": "xcode_ios_12e262",
               "path": "Xcode.app"
             },
             {
-              "name": "runtime_ios_13_5",
-              "path": "Runtime-ios-13.5"
+              "name": "runtime_ios_14_5",
+              "path": "Runtime-ios-14.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -38167,13 +38167,13 @@
           "--platform",
           "iPad Air 2",
           "--version",
-          "13.5",
+          "14.5",
           "--args-json",
           "{\"test_args\": [\"--run-with-custom-webkit\"]}",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "11e608c",
+          "12e262",
           "--xctest"
         ],
         "isolate_name": "net_unittests",
@@ -38181,7 +38181,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "net_unittests_iPad Air 2 13.5",
+        "name": "net_unittests_iPad Air 2 14.5",
         "resultdb": {
           "enable": true
         },
@@ -38202,12 +38202,12 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_11e608c",
+              "name": "xcode_ios_12e262",
               "path": "Xcode.app"
             },
             {
-              "name": "runtime_ios_13_5",
-              "path": "Runtime-ios-13.5"
+              "name": "runtime_ios_14_5",
+              "path": "Runtime-ios-14.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -38219,13 +38219,13 @@
           "--platform",
           "iPhone X",
           "--version",
-          "13.5",
+          "14.5",
           "--args-json",
           "{\"test_args\": [\"--run-with-custom-webkit\"]}",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "11e608c",
+          "12e262",
           "--xctest"
         ],
         "isolate_name": "net_unittests",
@@ -38233,7 +38233,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "net_unittests_iPhone X 13.5",
+        "name": "net_unittests_iPhone X 14.5",
         "resultdb": {
           "enable": true
         },
@@ -38254,12 +38254,12 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_11e608c",
+              "name": "xcode_ios_12e262",
               "path": "Xcode.app"
             },
             {
-              "name": "runtime_ios_13_5",
-              "path": "Runtime-ios-13.5"
+              "name": "runtime_ios_14_5",
+              "path": "Runtime-ios-14.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -38271,13 +38271,13 @@
           "--platform",
           "iPad Air 2",
           "--version",
-          "13.5",
+          "14.5",
           "--args-json",
           "{\"test_args\": [\"--run-with-custom-webkit\"]}",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "11e608c",
+          "12e262",
           "--xctest"
         ],
         "isolate_name": "services_unittests",
@@ -38285,7 +38285,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "services_unittests_iPad Air 2 13.5",
+        "name": "services_unittests_iPad Air 2 14.5",
         "resultdb": {
           "enable": true
         },
@@ -38306,12 +38306,12 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_11e608c",
+              "name": "xcode_ios_12e262",
               "path": "Xcode.app"
             },
             {
-              "name": "runtime_ios_13_5",
-              "path": "Runtime-ios-13.5"
+              "name": "runtime_ios_14_5",
+              "path": "Runtime-ios-14.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -38323,13 +38323,13 @@
           "--platform",
           "iPhone X",
           "--version",
-          "13.5",
+          "14.5",
           "--args-json",
           "{\"test_args\": [\"--run-with-custom-webkit\"]}",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "11e608c",
+          "12e262",
           "--xctest"
         ],
         "isolate_name": "services_unittests",
@@ -38337,7 +38337,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "services_unittests_iPhone X 13.5",
+        "name": "services_unittests_iPhone X 14.5",
         "resultdb": {
           "enable": true
         },
@@ -38358,12 +38358,12 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_11e608c",
+              "name": "xcode_ios_12e262",
               "path": "Xcode.app"
             },
             {
-              "name": "runtime_ios_13_5",
-              "path": "Runtime-ios-13.5"
+              "name": "runtime_ios_14_5",
+              "path": "Runtime-ios-14.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -38375,13 +38375,13 @@
           "--platform",
           "iPad Air 2",
           "--version",
-          "13.5",
+          "14.5",
           "--args-json",
           "{\"test_args\": [\"--run-with-custom-webkit\"]}",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "11e608c",
+          "12e262",
           "--xctest"
         ],
         "isolate_name": "skia_unittests",
@@ -38389,7 +38389,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "skia_unittests_iPad Air 2 13.5",
+        "name": "skia_unittests_iPad Air 2 14.5",
         "resultdb": {
           "enable": true
         },
@@ -38410,12 +38410,12 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_11e608c",
+              "name": "xcode_ios_12e262",
               "path": "Xcode.app"
             },
             {
-              "name": "runtime_ios_13_5",
-              "path": "Runtime-ios-13.5"
+              "name": "runtime_ios_14_5",
+              "path": "Runtime-ios-14.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -38427,13 +38427,13 @@
           "--platform",
           "iPhone X",
           "--version",
-          "13.5",
+          "14.5",
           "--args-json",
           "{\"test_args\": [\"--run-with-custom-webkit\"]}",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "11e608c",
+          "12e262",
           "--xctest"
         ],
         "isolate_name": "skia_unittests",
@@ -38441,7 +38441,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "skia_unittests_iPhone X 13.5",
+        "name": "skia_unittests_iPhone X 14.5",
         "resultdb": {
           "enable": true
         },
@@ -38462,12 +38462,12 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_11e608c",
+              "name": "xcode_ios_12e262",
               "path": "Xcode.app"
             },
             {
-              "name": "runtime_ios_13_5",
-              "path": "Runtime-ios-13.5"
+              "name": "runtime_ios_14_5",
+              "path": "Runtime-ios-14.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -38479,13 +38479,13 @@
           "--platform",
           "iPad Air 2",
           "--version",
-          "13.5",
+          "14.5",
           "--args-json",
           "{\"test_args\": [\"--run-with-custom-webkit\"]}",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "11e608c",
+          "12e262",
           "--xctest"
         ],
         "isolate_name": "sql_unittests",
@@ -38493,7 +38493,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "sql_unittests_iPad Air 2 13.5",
+        "name": "sql_unittests_iPad Air 2 14.5",
         "resultdb": {
           "enable": true
         },
@@ -38514,12 +38514,12 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_11e608c",
+              "name": "xcode_ios_12e262",
               "path": "Xcode.app"
             },
             {
-              "name": "runtime_ios_13_5",
-              "path": "Runtime-ios-13.5"
+              "name": "runtime_ios_14_5",
+              "path": "Runtime-ios-14.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -38531,13 +38531,13 @@
           "--platform",
           "iPhone X",
           "--version",
-          "13.5",
+          "14.5",
           "--args-json",
           "{\"test_args\": [\"--run-with-custom-webkit\"]}",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "11e608c",
+          "12e262",
           "--xctest"
         ],
         "isolate_name": "sql_unittests",
@@ -38545,7 +38545,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "sql_unittests_iPhone X 13.5",
+        "name": "sql_unittests_iPhone X 14.5",
         "resultdb": {
           "enable": true
         },
@@ -38566,12 +38566,12 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_11e608c",
+              "name": "xcode_ios_12e262",
               "path": "Xcode.app"
             },
             {
-              "name": "runtime_ios_13_5",
-              "path": "Runtime-ios-13.5"
+              "name": "runtime_ios_14_5",
+              "path": "Runtime-ios-14.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -38583,13 +38583,13 @@
           "--platform",
           "iPad Air 2",
           "--version",
-          "13.5",
+          "14.5",
           "--args-json",
           "{\"test_args\": [\"--run-with-custom-webkit\"]}",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "11e608c",
+          "12e262",
           "--xctest"
         ],
         "isolate_name": "ui_base_unittests",
@@ -38597,7 +38597,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ui_base_unittests_iPad Air 2 13.5",
+        "name": "ui_base_unittests_iPad Air 2 14.5",
         "resultdb": {
           "enable": true
         },
@@ -38618,12 +38618,12 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_11e608c",
+              "name": "xcode_ios_12e262",
               "path": "Xcode.app"
             },
             {
-              "name": "runtime_ios_13_5",
-              "path": "Runtime-ios-13.5"
+              "name": "runtime_ios_14_5",
+              "path": "Runtime-ios-14.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -38635,13 +38635,13 @@
           "--platform",
           "iPhone X",
           "--version",
-          "13.5",
+          "14.5",
           "--args-json",
           "{\"test_args\": [\"--run-with-custom-webkit\"]}",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "11e608c",
+          "12e262",
           "--xctest"
         ],
         "isolate_name": "ui_base_unittests",
@@ -38649,7 +38649,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ui_base_unittests_iPhone X 13.5",
+        "name": "ui_base_unittests_iPhone X 14.5",
         "resultdb": {
           "enable": true
         },
@@ -38670,12 +38670,12 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_11e608c",
+              "name": "xcode_ios_12e262",
               "path": "Xcode.app"
             },
             {
-              "name": "runtime_ios_13_5",
-              "path": "Runtime-ios-13.5"
+              "name": "runtime_ios_14_5",
+              "path": "Runtime-ios-14.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -38687,13 +38687,13 @@
           "--platform",
           "iPad Air 2",
           "--version",
-          "13.5",
+          "14.5",
           "--args-json",
           "{\"test_args\": [\"--run-with-custom-webkit\"]}",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "11e608c",
+          "12e262",
           "--xctest"
         ],
         "isolate_name": "url_unittests",
@@ -38701,7 +38701,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "url_unittests_iPad Air 2 13.5",
+        "name": "url_unittests_iPad Air 2 14.5",
         "resultdb": {
           "enable": true
         },
@@ -38722,12 +38722,12 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_11e608c",
+              "name": "xcode_ios_12e262",
               "path": "Xcode.app"
             },
             {
-              "name": "runtime_ios_13_5",
-              "path": "Runtime-ios-13.5"
+              "name": "runtime_ios_14_5",
+              "path": "Runtime-ios-14.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -38739,13 +38739,13 @@
           "--platform",
           "iPhone X",
           "--version",
-          "13.5",
+          "14.5",
           "--args-json",
           "{\"test_args\": [\"--run-with-custom-webkit\"]}",
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "11e608c",
+          "12e262",
           "--xctest"
         ],
         "isolate_name": "url_unittests",
@@ -38753,7 +38753,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "url_unittests_iPhone X 13.5",
+        "name": "url_unittests_iPhone X 14.5",
         "resultdb": {
           "enable": true
         },
@@ -38774,12 +38774,12 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_11e608c",
+              "name": "xcode_ios_12e262",
               "path": "Xcode.app"
             },
             {
-              "name": "runtime_ios_13_5",
-              "path": "Runtime-ios-13.5"
+              "name": "runtime_ios_14_5",
+              "path": "Runtime-ios-14.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
diff --git a/testing/buildbot/mixins.pyl b/testing/buildbot/mixins.pyl
index 70be22c..8b4854e 100644
--- a/testing/buildbot/mixins.pyl
+++ b/testing/buildbot/mixins.pyl
@@ -463,18 +463,6 @@
       },
     },
   },
-  'ios_runtime_cache_13_5': {
-    '$mixin_append': {
-      'swarming': {
-        'named_caches': [
-          {
-            'name': 'runtime_ios_13_5',
-            'path': 'Runtime-ios-13.5',
-          },
-        ],
-      },
-    },
-  },
   'ios_runtime_cache_13_6': {
     '$mixin_append': {
       'swarming': {
@@ -1218,23 +1206,6 @@
       },
     },
   },
-  # in use by ios-webkit-tot
-  'xcode_11e608c': {
-    '$mixin_append': {
-      'args': [
-        '--xcode-build-version',
-        '11e608c'
-      ],
-     'swarming': {
-        'named_caches': [
-          {
-            'name': 'xcode_ios_11e608c',
-            'path': 'Xcode.app',
-          },
-        ],
-      },
-    },
-  },
   # (default in Chromium iOS) xcode 12.4 gm seed
   'xcode_12d4e': {
     '$mixin_append': {
diff --git a/testing/buildbot/test_suites.pyl b/testing/buildbot/test_suites.pyl
index db069fb..5864a6be0 100644
--- a/testing/buildbot/test_suites.pyl
+++ b/testing/buildbot/test_suites.pyl
@@ -7249,28 +7249,28 @@
     'ios_webkit_tot_tests': {
       'ios_common_tests': {
         'variants': [
-          'SIM_IPHONE_X_13_5',
-          'SIM_IPAD_AIR_2_13_5',
+          'SIM_IPHONE_X_14_5',
+          'SIM_IPAD_AIR_2_14_5',
         ]
       },
       'ios_eg2_cq_tests': {
         'mixins': ['xcode_parallelization'],
         'variants': [
-          'SIM_IPHONE_X_13_5',
-          'SIM_IPAD_AIR_2_13_5',
+          'SIM_IPHONE_X_14_5',
+          'SIM_IPAD_AIR_2_14_5',
         ]
       },
       'ios_eg2_tests': {
         'mixins': ['xcode_parallelization'],
         'variants': [
-          'SIM_IPHONE_X_13_5',
-          'SIM_IPAD_AIR_2_13_5',
+          'SIM_IPHONE_X_14_5',
+          'SIM_IPAD_AIR_2_14_5',
         ]
       },
       'ios_screen_size_dependent_tests': {
         'variants': [
-          'SIM_IPHONE_X_13_5',
-          'SIM_IPAD_AIR_2_13_5',
+          'SIM_IPHONE_X_14_5',
+          'SIM_IPAD_AIR_2_14_5',
         ]
       },
     },
diff --git a/testing/buildbot/variants.pyl b/testing/buildbot/variants.pyl
index 15247e4..9de8d2e 100644
--- a/testing/buildbot/variants.pyl
+++ b/testing/buildbot/variants.pyl
@@ -91,18 +91,6 @@
       'ios_runtime_cache_12_4',
     ],
   },
-  'SIM_IPAD_AIR_2_13_5': {
-    'args': [
-      '--platform',
-      'iPad Air 2',
-      '--version',
-      '13.5'
-    ],
-    'identifier': 'iPad Air 2 13.5',
-    'mixins': [
-      'ios_runtime_cache_13_5',
-    ],
-  },
   'SIM_IPAD_AIR_2_13_6': {
     'args': [
       '--platform',
@@ -345,18 +333,6 @@
       'ios_runtime_cache_12_4',
     ],
   },
-  'SIM_IPHONE_X_13_5': {
-    'args': [
-      '--platform',
-      'iPhone X',
-      '--version',
-      '13.5',
-    ],
-    'identifier': 'iPhone X 13.5',
-    'mixins': [
-      'ios_runtime_cache_13_5',
-    ],
-  },
   'SIM_IPHONE_X_13_6': {
     'args': [
       '--platform',
diff --git a/testing/buildbot/waterfalls.pyl b/testing/buildbot/waterfalls.pyl
index 4b2d14f7a..adf28df2 100644
--- a/testing/buildbot/waterfalls.pyl
+++ b/testing/buildbot/waterfalls.pyl
@@ -2934,7 +2934,7 @@
           'mac_10.15_or_mac_11',
           'mac_toolchain',
           'out_dir_arg',
-          'xcode_11e608c',
+          'xcode_12e262',
           'xctest',
         ],
         'test_suites': {
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index e5ade031..781f3ba 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -2962,6 +2962,24 @@
             ]
         }
     ],
+    "DiceWebSigninInterception": [
+        {
+            "platforms": [
+                "linux",
+                "mac",
+                "windows"
+            ],
+            "experiments": [
+                {
+                    "name": "EnabledWithEphemeralGuest20210526",
+                    "enable_features": [
+                        "DiceWebSigninInterception",
+                        "EnableEphemeralGuestProfilesOnDesktop"
+                    ]
+                }
+            ]
+        }
+    ],
     "DirectActions": [
         {
             "platforms": [
@@ -7386,24 +7404,6 @@
             ]
         }
     ],
-    "SpacesM2": [
-        {
-            "platforms": [
-                "linux",
-                "mac",
-                "windows"
-            ],
-            "experiments": [
-                {
-                    "name": "Enabled",
-                    "enable_features": [
-                        "DiceWebSigninInterception",
-                        "NewProfilePicker"
-                    ]
-                }
-            ]
-        }
-    ],
     "SplitCacheByNetworkIsolationKey": [
         {
             "platforms": [
diff --git a/third_party/blink/common/storage_key/storage_key.cc b/third_party/blink/common/storage_key/storage_key.cc
index 5f052c8..7c80d2b 100644
--- a/third_party/blink/common/storage_key/storage_key.cc
+++ b/third_party/blink/common/storage_key/storage_key.cc
@@ -9,13 +9,16 @@
 namespace blink {
 
 // static
-StorageKey StorageKey::Deserialize(const std::string& in) {
-  return StorageKey(url::Origin::Create(GURL(in)));
+absl::optional<StorageKey> StorageKey::Deserialize(base::StringPiece in) {
+  StorageKey result(url::Origin::Create(GURL(in)));
+  return result.opaque() ? absl::nullopt
+                         : absl::make_optional(std::move(result));
 }
 
 // static
 StorageKey StorageKey::CreateFromStringForTesting(const std::string& origin) {
-  return Deserialize(origin);
+  absl::optional<StorageKey> result = Deserialize(origin);
+  return result.value_or(StorageKey());
 }
 
 std::string StorageKey::Serialize() const {
diff --git a/third_party/blink/common/storage_key/storage_key_unittest.cc b/third_party/blink/common/storage_key/storage_key_unittest.cc
index bac69171..0720c50 100644
--- a/third_party/blink/common/storage_key/storage_key_unittest.cc
+++ b/third_party/blink/common/storage_key/storage_key_unittest.cc
@@ -5,6 +5,7 @@
 #include "third_party/blink/public/common/storage_key/storage_key.h"
 
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 #include "url/gurl.h"
 
 namespace blink {
@@ -82,15 +83,17 @@
   std::string test = "https://test.example/";
   std::string wrong = "I'm not a valid URL.";
 
-  StorageKey key1 = StorageKey::Deserialize(example);
-  StorageKey key2 = StorageKey::Deserialize(test);
-  StorageKey key3 = StorageKey::Deserialize(wrong);
-  StorageKey key4 = StorageKey::Deserialize(std::string());
+  absl::optional<StorageKey> key1 = StorageKey::Deserialize(example);
+  absl::optional<StorageKey> key2 = StorageKey::Deserialize(test);
+  absl::optional<StorageKey> key3 = StorageKey::Deserialize(wrong);
+  absl::optional<StorageKey> key4 = StorageKey::Deserialize(std::string());
 
-  EXPECT_FALSE(key1.opaque());
-  EXPECT_FALSE(key2.opaque());
-  EXPECT_TRUE(key3.opaque());
-  EXPECT_TRUE(key4.opaque());
+  EXPECT_TRUE(key1.has_value());
+  EXPECT_FALSE(key1->opaque());
+  EXPECT_TRUE(key2.has_value());
+  EXPECT_FALSE(key2->opaque());
+  EXPECT_FALSE(key3.has_value());
+  EXPECT_FALSE(key4.has_value());
 }
 
 // Test that string -> StorageKey test function performs as expected.
@@ -120,8 +123,8 @@
   std::string key1_string = key1.Serialize();
   std::string key2_string = key2.Serialize();
 
-  StorageKey key1_deserialized = StorageKey::Deserialize(key1_string);
-  StorageKey key2_deserialized = StorageKey::Deserialize(key2_string);
+  StorageKey key1_deserialized = *StorageKey::Deserialize(key1_string);
+  StorageKey key2_deserialized = *StorageKey::Deserialize(key2_string);
 
   EXPECT_EQ(key1, key1_deserialized);
   EXPECT_EQ(key2, key2_deserialized);
diff --git a/third_party/blink/public/common/storage_key/storage_key.h b/third_party/blink/public/common/storage_key/storage_key.h
index 0f6efa1..0aec1b1 100644
--- a/third_party/blink/public/common/storage_key/storage_key.h
+++ b/third_party/blink/public/common/storage_key/storage_key.h
@@ -7,6 +7,8 @@
 
 #include <string>
 
+#include "base/strings/string_piece.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/blink/public/common/common_export.h"
 #include "url/origin.h"
 
@@ -27,10 +29,11 @@
   ~StorageKey() = default;
 
   // Returns a newly constructed StorageKey from, a previously serialized, `in`.
-  // If `in` is invalid then the StorageKey will be opaque. A deserialized
-  // StorageKey will be equivalent to the StorageKey that was initially
-  // serialized.
-  static StorageKey Deserialize(const std::string& in);
+  // If `in` is invalid then the return value will be nullopt. If this returns a
+  // non-nullopt value, it will be a valid, non-opaque StorageKey. A
+  // deserialized StorageKey will be equivalent to the StorageKey that was
+  // initially serialized.
+  static absl::optional<StorageKey> Deserialize(base::StringPiece in);
 
   // Transforms a string into a StorageKey if possible (and an opaque StorageKey
   // if not). Currently calls Deserialize, but this may change in future.
diff --git a/third_party/blink/renderer/core/css/resolver/style_adjuster.cc b/third_party/blink/renderer/core/css/resolver/style_adjuster.cc
index b91abc2e..ea89d4e8 100644
--- a/third_party/blink/renderer/core/css/resolver/style_adjuster.cc
+++ b/third_party/blink/renderer/core/css/resolver/style_adjuster.cc
@@ -658,7 +658,8 @@
   style.SetTextShadow(ComputedStyleInitialValues::InitialTextShadow());
   style.SetBoxShadow(ComputedStyleInitialValues::InitialBoxShadow());
   style.SetColorScheme({"light", "dark"});
-  style.SetAccentColor(ComputedStyleInitialValues::InitialAccentColor());
+  if (style.ShouldForceColor(style.AccentColor()))
+    style.SetAccentColor(ComputedStyleInitialValues::InitialAccentColor());
   if (!style.HasUrlBackgroundImage())
     style.ClearBackgroundImage();
 }
diff --git a/third_party/blink/renderer/core/css/resolver/style_builder_converter.cc b/third_party/blink/renderer/core/css/resolver/style_builder_converter.cc
index 20078a4..d23e0ef 100644
--- a/third_party/blink/renderer/core/css/resolver/style_builder_converter.cc
+++ b/third_party/blink/renderer/core/css/resolver/style_builder_converter.cc
@@ -1590,8 +1590,11 @@
       return StyleAutoColor::AutoColor();
     if (StyleColor::IsSystemColor(value_id)) {
       CountSystemColorComputeToSelfUsage(state);
-      if (RuntimeEnabledFeatures::CSSSystemColorComputeToSelfEnabled())
-        return StyleAutoColor(value_id);
+      return StyleAutoColor(
+          state.GetDocument().GetTextLinkColors().ColorFromCSSValue(
+              value, Color(), state.Style()->UsedColorScheme(),
+              for_visited_link),
+          value_id);
     }
   }
   return StyleAutoColor(
diff --git a/third_party/blink/renderer/core/css/style_auto_color.h b/third_party/blink/renderer/core/css/style_auto_color.h
index ff143c2..0435f51 100644
--- a/third_party/blink/renderer/core/css/style_auto_color.h
+++ b/third_party/blink/renderer/core/css/style_auto_color.h
@@ -16,6 +16,8 @@
  public:
   explicit StyleAutoColor(Color color) : StyleColor(color) {}
   explicit StyleAutoColor(CSSValueID keyword) : StyleColor(keyword) {}
+  StyleAutoColor(Color color, CSSValueID keyword)
+      : StyleColor(color, keyword) {}
   static StyleAutoColor AutoColor() {
     return StyleAutoColor(CSSValueID::kAuto);
   }
diff --git a/third_party/blink/renderer/core/html/canvas/canvas_image_source.cc b/third_party/blink/renderer/core/html/canvas/canvas_image_source.cc
index ab99e036..692b2ac 100644
--- a/third_party/blink/renderer/core/html/canvas/canvas_image_source.cc
+++ b/third_party/blink/renderer/core/html/canvas/canvas_image_source.cc
@@ -47,6 +47,9 @@
 scoped_refptr<StaticBitmapImage> GetImageWithAlphaDisposition(
     scoped_refptr<StaticBitmapImage>&& image,
     const AlphaDisposition alpha_disposition) {
+  if (!image)
+    return nullptr;
+
   SkAlphaType alpha_type = (alpha_disposition == kPremultiplyAlpha)
                                ? kPremul_SkAlphaType
                                : kUnpremul_SkAlphaType;
diff --git a/third_party/blink/renderer/core/paint/svg_inline_text_box_painter.cc b/third_party/blink/renderer/core/paint/svg_inline_text_box_painter.cc
index 430e787..5b17d13 100644
--- a/third_party/blink/renderer/core/paint/svg_inline_text_box_painter.cc
+++ b/third_party/blink/renderer/core/paint/svg_inline_text_box_painter.cc
@@ -379,9 +379,12 @@
         if (decoration_style.HasFill()) {
           PaintFlags fill_flags;
           if (!SVGObjectPainter(*decoration_layout_object)
-                   .PreparePaint(paint_info, decoration_style, kApplyToFillMode,
-                                 fill_flags))
+                   .PreparePaint(paint_info.context,
+                                 paint_info.IsRenderingClipPathAsMaskImage(),
+                                 decoration_style, kApplyToFillMode,
+                                 fill_flags)) {
             break;
+          }
           fill_flags.setAntiAlias(true);
           paint_info.context.DrawPath(path.GetSkPath(), fill_flags);
         }
@@ -390,9 +393,12 @@
         if (decoration_style.HasVisibleStroke()) {
           PaintFlags stroke_flags;
           if (!SVGObjectPainter(*decoration_layout_object)
-                   .PreparePaint(paint_info, decoration_style,
-                                 kApplyToStrokeMode, stroke_flags))
+                   .PreparePaint(paint_info.context,
+                                 paint_info.IsRenderingClipPathAsMaskImage(),
+                                 decoration_style, kApplyToStrokeMode,
+                                 stroke_flags)) {
             break;
+          }
           stroke_flags.setAntiAlias(true);
           float stroke_scale_factor = decoration_style.VectorEffect() ==
                                               EVectorEffect::kNonScalingStroke
@@ -441,9 +447,13 @@
   }
 
   if (!SVGObjectPainter(ParentInlineLayoutObject())
-           .PreparePaint(paint_info, style, resource_mode, flags,
-                         base::OptionalOrNullptr(paint_server_transform)))
+           .PreparePaint(paint_info.context,
+                         paint_info.IsRenderingClipPathAsMaskImage(), style,
+                         resource_mode, flags,
+                         base::OptionalOrNullptr(paint_server_transform))) {
     return false;
+  }
+
   flags.setAntiAlias(true);
 
   if (style.TextShadow() &&
diff --git a/third_party/blink/renderer/core/paint/svg_object_painter.cc b/third_party/blink/renderer/core/paint/svg_object_painter.cc
index acd0c2d..027ee7b 100644
--- a/third_party/blink/renderer/core/paint/svg_object_painter.cc
+++ b/third_party/blink/renderer/core/paint/svg_object_painter.cc
@@ -14,7 +14,8 @@
 
 namespace {
 
-void CopyStateFromGraphicsContext(GraphicsContext& context, PaintFlags& flags) {
+void CopyStateFromGraphicsContext(const GraphicsContext& context,
+                                  PaintFlags& flags) {
   // TODO(fs): The color filter can be set when generating a picture for a mask
   // due to color-interpolation. We could also just apply the
   // color-interpolation property from the the shape itself (which could mean
@@ -57,12 +58,13 @@
 }
 
 bool SVGObjectPainter::PreparePaint(
-    const PaintInfo& paint_info,
+    const GraphicsContext& context,
+    bool is_rendering_clip_path_as_mask_image,
     const ComputedStyle& style,
     LayoutSVGResourceMode resource_mode,
     PaintFlags& flags,
     const AffineTransform* additional_paint_server_transform) {
-  if (paint_info.IsRenderingClipPathAsMaskImage()) {
+  if (is_rendering_clip_path_as_mask_image) {
     if (resource_mode == kApplyToStrokeMode)
       return false;
     flags.setColor(SK_ColorBLACK);
@@ -78,7 +80,7 @@
   if (paint.HasUrl()) {
     if (ApplyPaintResource(paint, additional_paint_server_transform, flags)) {
       flags.setColor(ScaleAlpha(SK_ColorBLACK, alpha));
-      CopyStateFromGraphicsContext(paint_info.context, flags);
+      CopyStateFromGraphicsContext(context, flags);
       return true;
     }
   }
@@ -88,7 +90,7 @@
     const Color color = style.VisitedDependentColor(property);
     flags.setColor(ScaleAlpha(color.Rgb(), alpha));
     flags.setShader(nullptr);
-    CopyStateFromGraphicsContext(paint_info.context, flags);
+    CopyStateFromGraphicsContext(context, flags);
     return true;
   }
   return false;
diff --git a/third_party/blink/renderer/core/paint/svg_object_painter.h b/third_party/blink/renderer/core/paint/svg_object_painter.h
index 2be9fa3..a45b590 100644
--- a/third_party/blink/renderer/core/paint/svg_object_painter.h
+++ b/third_party/blink/renderer/core/paint/svg_object_painter.h
@@ -11,7 +11,6 @@
 
 namespace blink {
 
-struct PaintInfo;
 class AffineTransform;
 class ComputedStyle;
 class GraphicsContext;
@@ -34,7 +33,8 @@
   // object. Returns true if successful, and the caller can continue to paint
   // using |paint_flags|.
   bool PreparePaint(
-      const PaintInfo&,
+      const GraphicsContext& context,
+      bool is_rendering_clip_path_as_mask_image,
       const ComputedStyle&,
       LayoutSVGResourceMode,
       PaintFlags& paint_flags,
diff --git a/third_party/blink/renderer/core/paint/svg_shape_painter.cc b/third_party/blink/renderer/core/paint/svg_shape_painter.cc
index fc56de03..e17f326 100644
--- a/third_party/blink/renderer/core/paint/svg_shape_painter.cc
+++ b/third_party/blink/renderer/core/paint/svg_shape_painter.cc
@@ -74,9 +74,11 @@
           case PT_FILL: {
             PaintFlags fill_flags;
             if (!SVGObjectPainter(layout_svg_shape_)
-                     .PreparePaint(paint_info, style, kApplyToFillMode,
-                                   fill_flags))
+                     .PreparePaint(paint_info.context,
+                                   paint_info.IsRenderingClipPathAsMaskImage(),
+                                   style, kApplyToFillMode, fill_flags)) {
               break;
+            }
             fill_flags.setAntiAlias(should_anti_alias);
             FillShape(paint_info.context, fill_flags,
                       FillRuleFromStyle(paint_info, style));
@@ -99,9 +101,12 @@
               PaintFlags stroke_flags;
               if (!SVGObjectPainter(layout_svg_shape_)
                        .PreparePaint(
-                           paint_info, style, kApplyToStrokeMode, stroke_flags,
-                           base::OptionalOrNullptr(non_scaling_transform)))
+                           paint_info.context,
+                           paint_info.IsRenderingClipPathAsMaskImage(), style,
+                           kApplyToStrokeMode, stroke_flags,
+                           base::OptionalOrNullptr(non_scaling_transform))) {
                 break;
+              }
               stroke_flags.setAntiAlias(should_anti_alias);
 
               StrokeData stroke_data;
diff --git a/third_party/blink/renderer/core/scroll/scrollable_area.cc b/third_party/blink/renderer/core/scroll/scrollable_area.cc
index e1da07c..1afb170 100644
--- a/third_party/blink/renderer/core/scroll/scrollable_area.cc
+++ b/third_party/blink/renderer/core/scroll/scrollable_area.cc
@@ -111,9 +111,6 @@
       uses_composited_scrolling_(false),
       compositor_task_runner_(std::move(compositor_task_runner)) {
   DCHECK(compositor_task_runner_);
-#if defined(OS_MAC)
-  scrollbar_animator_ = MacScrollbarAnimator::Create(this);
-#endif
 }
 
 ScrollableArea::~ScrollableArea() = default;
@@ -127,8 +124,8 @@
 }
 
 void ScrollableArea::ClearScrollableArea() {
-  if (scrollbar_animator_)
-    scrollbar_animator_->Dispose();
+  if (mac_scrollbar_animator_)
+    mac_scrollbar_animator_->Dispose();
   scroll_animator_.Clear();
   programmatic_scroll_animator_.Clear();
   if (fade_overlay_scrollbars_timer_)
@@ -136,7 +133,13 @@
 }
 
 MacScrollbarAnimator* ScrollableArea::GetMacScrollbarAnimator() const {
-  return scrollbar_animator_;
+#if defined(OS_MAC)
+  if (!mac_scrollbar_animator_) {
+    mac_scrollbar_animator_ =
+        MacScrollbarAnimator::Create(const_cast<ScrollableArea*>(this));
+  }
+#endif
+  return mac_scrollbar_animator_;
 }
 
 ScrollAnimatorBase& ScrollableArea::GetScrollAnimator() const {
@@ -476,23 +479,23 @@
 }
 
 void ScrollableArea::ContentAreaWillPaint() const {
-  if (GetMacScrollbarAnimator())
-    GetMacScrollbarAnimator()->ContentAreaWillPaint();
+  if (mac_scrollbar_animator_)
+    mac_scrollbar_animator_->ContentAreaWillPaint();
 }
 
 void ScrollableArea::MouseEnteredContentArea() const {
-  if (GetMacScrollbarAnimator())
-    GetMacScrollbarAnimator()->MouseEnteredContentArea();
+  if (mac_scrollbar_animator_)
+    mac_scrollbar_animator_->MouseEnteredContentArea();
 }
 
 void ScrollableArea::MouseExitedContentArea() const {
-  if (GetMacScrollbarAnimator())
-    GetMacScrollbarAnimator()->MouseExitedContentArea();
+  if (mac_scrollbar_animator_)
+    mac_scrollbar_animator_->MouseExitedContentArea();
 }
 
 void ScrollableArea::MouseMovedInContentArea() const {
-  if (GetMacScrollbarAnimator())
-    GetMacScrollbarAnimator()->MouseMovedInContentArea();
+  if (mac_scrollbar_animator_)
+    mac_scrollbar_animator_->MouseMovedInContentArea();
 }
 
 void ScrollableArea::MouseEnteredScrollbar(Scrollbar& scrollbar) {
@@ -545,17 +548,17 @@
 
 void ScrollableArea::WillRemoveScrollbar(Scrollbar& scrollbar,
                                          ScrollbarOrientation orientation) {
-  if (GetMacScrollbarAnimator()) {
+  if (mac_scrollbar_animator_) {
     if (orientation == kVerticalScrollbar)
-      GetMacScrollbarAnimator()->WillRemoveVerticalScrollbar(scrollbar);
+      mac_scrollbar_animator_->WillRemoveVerticalScrollbar(scrollbar);
     else
-      GetMacScrollbarAnimator()->WillRemoveHorizontalScrollbar(scrollbar);
+      mac_scrollbar_animator_->WillRemoveHorizontalScrollbar(scrollbar);
   }
 }
 
 void ScrollableArea::ContentsResized() {
-  if (GetMacScrollbarAnimator())
-    GetMacScrollbarAnimator()->ContentsResized();
+  if (mac_scrollbar_animator_)
+    mac_scrollbar_animator_->ContentsResized();
 }
 
 void ScrollableArea::InvalidateScrollTimeline() {
@@ -995,7 +998,7 @@
 
 void ScrollableArea::Trace(Visitor* visitor) const {
   visitor->Trace(scroll_animator_);
-  visitor->Trace(scrollbar_animator_);
+  visitor->Trace(mac_scrollbar_animator_);
   visitor->Trace(programmatic_scroll_animator_);
   visitor->Trace(fade_overlay_scrollbars_timer_);
 }
diff --git a/third_party/blink/renderer/core/scroll/scrollable_area.h b/third_party/blink/renderer/core/scroll/scrollable_area.h
index 85ec450..8d4ed276 100644
--- a/third_party/blink/renderer/core/scroll/scrollable_area.h
+++ b/third_party/blink/renderer/core/scroll/scrollable_area.h
@@ -227,6 +227,8 @@
         scrollbar_overlay_color_theme_);
   }
 
+  // This getter will create a MacScrollAnimator if it doesn't already exist,
+  // only on MacOS.
   MacScrollbarAnimator* GetMacScrollbarAnimator() const;
 
   // This getter will create a ScrollAnimatorBase if it doesn't already exist.
@@ -634,7 +636,7 @@
   // using AppKit-specific code (Cocoa APIs). It requires input from
   // ScrollableArea about changes on scrollbars. For other platforms, painting
   // is done by blink, and this member will be a nullptr.
-  mutable Member<MacScrollbarAnimator> scrollbar_animator_;
+  mutable Member<MacScrollbarAnimator> mac_scrollbar_animator_;
 
   mutable Member<ScrollAnimatorBase> scroll_animator_;
   mutable Member<ProgrammaticScrollAnimator> programmatic_scroll_animator_;
diff --git a/third_party/blink/renderer/modules/mediasource/media_source.cc b/third_party/blink/renderer/modules/mediasource/media_source.cc
index bd4e6f6..537a31ce 100644
--- a/third_party/blink/renderer/modules/mediasource/media_source.cc
+++ b/third_party/blink/renderer/modules/mediasource/media_source.cc
@@ -544,17 +544,15 @@
   // HTMLMediaElement knows it cannot play.
   String codecs = content_type.Parameter("codecs");
   MIMETypeRegistry::SupportsType get_supports_type_result;
-#if BUILDFLAG(ENABLE_PLATFORM_HEVC) && \
-    (BUILDFLAG(USE_CHROMEOS_PROTECTED_MEDIA) || defined(OS_WIN))
-  // Here, we special-case for HEVC on ChromeOS and Windows, which is only
-  // supported if encrypted. isTypeSupported(fully qualified type with hevc
-  // codec) should say false on such platform (except if
-  // kEnableClearHevcForTesting cmdline switch is used, enabling GetSupportsType
-  // success), but addSourceBuffer(same) and changeType(same) shouldn't fail
-  // just due to having HEVC codec. We use |enforce_codec_specificity| to
-  // understand if we are servicing iTS (if true) versus aSB (if false). If
-  // servicing aSB or cT, we'll remove any detected hevc codec from the codecs
-  // we use in the GetSupportsType() query.
+#if BUILDFLAG(ENABLE_PLATFORM_ENCRYPTED_HEVC)
+  // Here, we special-case when encrypted HEVC is supported.
+  // isTypeSupported(fully qualified type with hevc codec) should say false on
+  // such platform (except if kEnableClearHevcForTesting cmdline switch is used,
+  // enabling GetSupportsType success), but addSourceBuffer(same) and
+  // changeType(same) shouldn't fail just due to having HEVC codec. We use
+  // |enforce_codec_specificity| to understand if we are servicing iTS (if true)
+  // versus aSB (if false). If servicing aSB or cT, we'll remove any detected
+  // hevc codec from the codecs we use in the GetSupportsType() query.
   if (!enforce_codec_specificity) {
     // Remove any detected HEVC codec from the query to GetSupportsType.
     std::string filtered_codecs;
@@ -592,8 +590,7 @@
   }
 #else
   get_supports_type_result = HTMLMediaElement::GetSupportsType(content_type);
-#endif  // BUILDFLAG(ENABLE_PLATFORM_HEVC) &&
-        // (BUILDFLAG(USE_CHROMEOS_PROTECTED_MEDIA) || defined(OS_WIN))
+#endif  // BUILDFLAG(ENABLE_PLATFORM_ENCRYPTED_HEVC)
 
   if (get_supports_type_result == MIMETypeRegistry::kIsNotSupported) {
     DVLOG(1) << __func__ << "(" << type << ", "
diff --git a/third_party/blink/renderer/modules/mediastream/media_stream_audio_processor.cc b/third_party/blink/renderer/modules/mediastream/media_stream_audio_processor.cc
index 64bb0fe..5fc8ef0 100644
--- a/third_party/blink/renderer/modules/mediastream/media_stream_audio_processor.cc
+++ b/third_party/blink/renderer/modules/mediastream/media_stream_audio_processor.cc
@@ -59,8 +59,7 @@
 
 namespace blink {
 
-using EchoCancellationType =
-    blink::AudioProcessingProperties::EchoCancellationType;
+using EchoCancellationType = AudioProcessingProperties::EchoCancellationType;
 
 namespace {
 
@@ -96,6 +95,40 @@
           ::features::kWebRtcHybridAgc, "neon_allowed", true)};
 }
 
+absl::optional<WebRtcAnalogAgcClippingControlParams>
+GetWebRtcAnalogAgcClippingControlParams() {
+  if (!base::FeatureList::IsEnabled(
+          ::features::kWebRtcAnalogAgcClippingControl)) {
+    return absl::nullopt;
+  }
+  return WebRtcAnalogAgcClippingControlParams{
+      .mode = base::GetFieldTrialParamByFeatureAsInt(
+          ::features::kWebRtcAnalogAgcClippingControl, "mode", 0),
+      .window_length = base::GetFieldTrialParamByFeatureAsInt(
+          ::features::kWebRtcAnalogAgcClippingControl, "window_length", 5),
+      .reference_window_length = base::GetFieldTrialParamByFeatureAsInt(
+          ::features::kWebRtcAnalogAgcClippingControl,
+          "reference_window_length", 5),
+      .reference_window_delay = base::GetFieldTrialParamByFeatureAsInt(
+          ::features::kWebRtcAnalogAgcClippingControl, "reference_window_delay",
+          5),
+      .clipping_threshold = base::GetFieldTrialParamByFeatureAsDouble(
+          ::features::kWebRtcAnalogAgcClippingControl, "clipping_threshold",
+          -1.0),
+      .crest_factor_margin = base::GetFieldTrialParamByFeatureAsDouble(
+          ::features::kWebRtcAnalogAgcClippingControl, "crest_factor_margin",
+          3.0),
+      .clipped_level_step = base::GetFieldTrialParamByFeatureAsInt(
+          ::features::kWebRtcAnalogAgcClippingControl, "clipped_level_step",
+          15),
+      .clipped_ratio_threshold = base::GetFieldTrialParamByFeatureAsDouble(
+          ::features::kWebRtcAnalogAgcClippingControl,
+          "clipped_ratio_threshold", 0.1),
+      .clipped_wait_frames = base::GetFieldTrialParamByFeatureAsInt(
+          ::features::kWebRtcAnalogAgcClippingControl, "clipped_wait_frames",
+          300)};
+}
+
 constexpr int kBuffersPerSecond = 100;  // 10 ms per buffer.
 
 }  // namespace
@@ -261,7 +294,7 @@
 };
 
 MediaStreamAudioProcessor::MediaStreamAudioProcessor(
-    const blink::AudioProcessingProperties& properties,
+    const AudioProcessingProperties& properties,
     bool use_capture_multi_channel_processing,
     scoped_refptr<WebRtcAudioDeviceImpl> playout_data_source)
     : render_delay_ms_(0),
@@ -369,7 +402,7 @@
   if (!audio_processing_.get())
     return;
 
-  blink::StopEchoCancellationDump(audio_processing_.get());
+  StopEchoCancellationDump(audio_processing_.get());
   worker_queue_.reset(nullptr);
 
   if (playout_data_source_) {
@@ -410,8 +443,8 @@
     // Here tasks will be posted on the |worker_queue_|. It must be
     // kept alive until StopEchoCancellationDump is called or the
     // webrtc::AudioProcessing instance is destroyed.
-    blink::StartEchoCancellationDump(audio_processing_.get(),
-                                     std::move(dump_file), worker_queue_.get());
+    StartEchoCancellationDump(audio_processing_.get(), std::move(dump_file),
+                              worker_queue_.get());
   } else {
     // Post the file close to avoid blocking the main thread.
     worker_pool::PostTask(
@@ -423,7 +456,7 @@
 void MediaStreamAudioProcessor::OnStopDump() {
   DCHECK(main_thread_runner_->BelongsToCurrentThread());
   if (audio_processing_)
-    blink::StopEchoCancellationDump(audio_processing_.get());
+    StopEchoCancellationDump(audio_processing_.get());
 
   // Note that deleting an rtc::TaskQueue has to be done from the
   // thread that created it.
@@ -432,7 +465,7 @@
 
 // static
 bool MediaStreamAudioProcessor::WouldModifyAudio(
-    const blink::AudioProcessingProperties& properties) {
+    const AudioProcessingProperties& properties) {
   // Note: This method should by kept in-sync with any changes to the logic in
   // MediaStreamAudioProcessor::InitializeAudioProcessingModule().
 
@@ -531,7 +564,7 @@
 }
 
 void MediaStreamAudioProcessor::InitializeAudioProcessingModule(
-    const blink::AudioProcessingProperties& properties) {
+    const AudioProcessingProperties& properties) {
   DCHECK(main_thread_runner_->BelongsToCurrentThread());
   DCHECK(!audio_processing_);
   SendLogMessage(String::Format("%s()", __func__));
@@ -578,9 +611,11 @@
   // requires `goog_auto_gain_control` and `goog_experimental_auto_gain_control`
   // to be both active.
   absl::optional<WebRtcHybridAgcParams> hybrid_agc_params;
+  absl::optional<WebRtcAnalogAgcClippingControlParams> clipping_control_params;
   if (properties.goog_auto_gain_control &&
       properties.goog_experimental_auto_gain_control) {
     hybrid_agc_params = GetWebRtcHybridAgcParams();
+    clipping_control_params = GetWebRtcAnalogAgcClippingControlParams();
   }
   // If the experimental AGC is enabled, check for overridden config params.
   if (properties.goog_experimental_auto_gain_control) {
@@ -631,20 +666,20 @@
       use_capture_multi_channel_processing_;
 
   absl::optional<double> gain_control_compression_gain_db;
-  blink::PopulateApmConfig(&apm_config, properties,
-                           audio_processing_platform_config_json,
-                           &gain_control_compression_gain_db);
+  PopulateApmConfig(&apm_config, properties,
+                    audio_processing_platform_config_json,
+                    &gain_control_compression_gain_db);
 
   // Set up gain control functionalities.
-  blink::ConfigAutomaticGainControl(properties, hybrid_agc_params,
-                                    gain_control_compression_gain_db,
-                                    apm_config);
+  ConfigAutomaticGainControl(properties, hybrid_agc_params,
+                             clipping_control_params,
+                             gain_control_compression_gain_db, apm_config);
 
   if (goog_typing_detection) {
     // TODO(xians): Remove this |typing_detector_| after the typing suppression
     // is enabled by default.
     typing_detector_ = std::make_unique<webrtc::TypingDetection>();
-    blink::EnableTypingDetection(&apm_config, typing_detector_.get());
+    EnableTypingDetection(&apm_config, typing_detector_.get());
   }
 
   // Ensure that 48 kHz APM processing is always active. This overrules the
@@ -671,15 +706,15 @@
   // either use the input parameters (in which case, audio processing will
   // convert at output) or ideally, have a backchannel from the sink to know
   // what format it would prefer.
-  const int output_sample_rate = audio_processing_
-                                     ?
+  const int output_sample_rate =
+      audio_processing_
+          ?
 #if BUILDFLAG(IS_CHROMECAST)
-                                     std::min(blink::kAudioProcessingSampleRate,
-                                              input_format.sample_rate())
+          std::min(kAudioProcessingSampleRate, input_format.sample_rate())
 #else
-                                     blink::kAudioProcessingSampleRate
+          kAudioProcessingSampleRate
 #endif  // BUILDFLAG(IS_CHROMECAST)
-                                     : input_format.sample_rate();
+          : input_format.sample_rate();
 
   // The output channels from the fifo is normally the same as input.
   int fifo_output_channels = input_format.channels();
diff --git a/third_party/blink/renderer/modules/mediastream/media_stream_audio_processor.h b/third_party/blink/renderer/modules/mediastream/media_stream_audio_processor.h
index 401d74d..687848f 100644
--- a/third_party/blink/renderer/modules/mediastream/media_stream_audio_processor.h
+++ b/third_party/blink/renderer/modules/mediastream/media_stream_audio_processor.h
@@ -144,6 +144,10 @@
                            TestAgcEnableHybridAgcDryRun);
   FRIEND_TEST_ALL_PREFIXES(MediaStreamAudioProcessorTest,
                            TestAgcEnableHybridAgcSimdNotAllowed);
+  FRIEND_TEST_ALL_PREFIXES(MediaStreamAudioProcessorTest,
+                           TestAgcEnableClippingControl);
+  FRIEND_TEST_ALL_PREFIXES(MediaStreamAudioProcessorTest,
+                           TestAgcEnableClippingControlDefaultParams);
 
   // WebRtcPlayoutDataSource::Sink implementation.
   void OnPlayoutData(media::AudioBus* audio_bus,
diff --git a/third_party/blink/renderer/modules/mediastream/media_stream_audio_processor_test.cc b/third_party/blink/renderer/modules/mediastream/media_stream_audio_processor_test.cc
index c53c60c..2e55a9d 100644
--- a/third_party/blink/renderer/modules/mediastream/media_stream_audio_processor_test.cc
+++ b/third_party/blink/renderer/modules/mediastream/media_stream_audio_processor_test.cc
@@ -42,6 +42,9 @@
 
 using media::AudioParameters;
 
+using AnalogGainController =
+    webrtc::AudioProcessing::Config::GainController1::AnalogGainController;
+
 namespace blink {
 
 namespace {
@@ -168,6 +171,8 @@
     EXPECT_TRUE(config.noise_suppression.enabled);
     EXPECT_EQ(config.noise_suppression.level, config.noise_suppression.kHigh);
     EXPECT_FALSE(config.voice_detection.enabled);
+    EXPECT_FALSE(config.gain_controller1.analog_gain_controller
+                     .clipping_predictor.enabled);
 #if defined(OS_ANDROID)
     EXPECT_TRUE(config.echo_canceller.mobile_mode);
     EXPECT_EQ(config.gain_controller1.mode,
@@ -223,7 +228,7 @@
       new rtc::RefCountedObject<WebRtcAudioDeviceImpl>());
   scoped_refptr<MediaStreamAudioProcessor> audio_processor(
       new rtc::RefCountedObject<MediaStreamAudioProcessor>(
-          properties, /*use_multichannel_processing=*/true,
+          properties, /*use_capture_multi_channel_processing=*/true,
           webrtc_audio_device));
   EXPECT_FALSE(audio_processor->has_audio_processing());
   audio_processor->OnCaptureFormatChanged(params_);
@@ -303,7 +308,7 @@
   {
     scoped_refptr<MediaStreamAudioProcessor> audio_processor(
         new rtc::RefCountedObject<MediaStreamAudioProcessor>(
-            properties, /*use_multichannel_processing=*/true,
+            properties, /*use_capture_multi_channel_processing=*/true,
             webrtc_audio_device));
 
     // Start and stop recording.
@@ -456,7 +461,7 @@
       new rtc::RefCountedObject<WebRtcAudioDeviceImpl>());
   scoped_refptr<MediaStreamAudioProcessor> audio_processor(
       new rtc::RefCountedObject<MediaStreamAudioProcessor>(
-          properties, /*use_multichannel_processing=*/true,
+          properties, /*use_capture_multi_channel_processing=*/true,
           webrtc_audio_device));
 
   absl::optional<webrtc::AudioProcessing::Config> apm_config =
@@ -499,7 +504,7 @@
       new rtc::RefCountedObject<WebRtcAudioDeviceImpl>());
   scoped_refptr<MediaStreamAudioProcessor> audio_processor(
       new rtc::RefCountedObject<MediaStreamAudioProcessor>(
-          properties, /*use_multichannel_processing=*/true,
+          properties, /*use_capture_multi_channel_processing=*/true,
           webrtc_audio_device.get()));
 
   absl::optional<webrtc::AudioProcessing::Config> apm_config =
@@ -550,7 +555,7 @@
       new rtc::RefCountedObject<WebRtcAudioDeviceImpl>());
   scoped_refptr<MediaStreamAudioProcessor> audio_processor(
       new rtc::RefCountedObject<MediaStreamAudioProcessor>(
-          properties, /*use_multichannel_processing=*/true,
+          properties, /*use_capture_multi_channel_processing=*/true,
           webrtc_audio_device));
 
   absl::optional<webrtc::AudioProcessing::Config> apm_config =
@@ -613,7 +618,7 @@
       new rtc::RefCountedObject<WebRtcAudioDeviceImpl>());
   scoped_refptr<MediaStreamAudioProcessor> audio_processor(
       new rtc::RefCountedObject<MediaStreamAudioProcessor>(
-          properties, /*use_multichannel_processing=*/true,
+          properties, /*use_capture_multi_channel_processing=*/true,
           webrtc_audio_device.get()));
 
   absl::optional<webrtc::AudioProcessing::Config> apm_config =
@@ -669,7 +674,7 @@
       new rtc::RefCountedObject<WebRtcAudioDeviceImpl>());
   scoped_refptr<MediaStreamAudioProcessor> audio_processor(
       new rtc::RefCountedObject<MediaStreamAudioProcessor>(
-          properties, /*use_multichannel_processing=*/true,
+          properties, /*use_capture_multi_channel_processing=*/true,
           webrtc_audio_device));
 
   absl::optional<webrtc::AudioProcessing::Config> apm_config =
@@ -681,6 +686,87 @@
   EXPECT_FALSE(apm_config->gain_controller2.adaptive_digital.neon_allowed);
 }
 
+TEST_F(MediaStreamAudioProcessorTest,
+       TestAgcEnableClippingControlDefaultParams) {
+  ::base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndEnableFeature(features::kWebRtcAnalogAgcClippingControl);
+
+  blink::AudioProcessingProperties properties;
+  properties.goog_auto_gain_control = true;
+  properties.goog_experimental_auto_gain_control = true;
+
+  scoped_refptr<WebRtcAudioDeviceImpl> webrtc_audio_device(
+      new rtc::RefCountedObject<WebRtcAudioDeviceImpl>());
+  scoped_refptr<MediaStreamAudioProcessor> audio_processor(
+      new rtc::RefCountedObject<MediaStreamAudioProcessor>(
+          properties, /*use_capture_multi_channel_processing=*/true,
+          webrtc_audio_device));
+
+  absl::optional<webrtc::AudioProcessing::Config> apm_config =
+      audio_processor->GetAudioProcessingModuleConfig();
+  ASSERT_TRUE(apm_config);
+
+  const AnalogGainController& analog_agc =
+      apm_config->gain_controller1.analog_gain_controller;
+  EXPECT_TRUE(analog_agc.clipping_predictor.enabled);
+  EXPECT_EQ(
+      analog_agc.clipping_predictor.mode,
+      AnalogGainController::ClippingPredictor::Mode::kClippingEventPrediction);
+  EXPECT_EQ(analog_agc.clipping_predictor.window_length, 5);
+  EXPECT_EQ(analog_agc.clipping_predictor.reference_window_length, 5);
+  EXPECT_EQ(analog_agc.clipping_predictor.reference_window_delay, 5);
+  EXPECT_FLOAT_EQ(analog_agc.clipping_predictor.clipping_threshold, -1.0f);
+  EXPECT_FLOAT_EQ(analog_agc.clipping_predictor.crest_factor_margin, 3.0f);
+  EXPECT_EQ(analog_agc.clipped_level_step, 15);
+  EXPECT_FLOAT_EQ(analog_agc.clipped_ratio_threshold, 0.1f);
+  EXPECT_EQ(analog_agc.clipped_wait_frames, 300);
+}
+
+TEST_F(MediaStreamAudioProcessorTest, TestAgcEnableClippingControl) {
+  ::base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndEnableFeatureWithParameters(
+      features::kWebRtcAnalogAgcClippingControl,
+      {{"mode", "2"},  // kFixedStepClippingPeakPrediction
+       {"window_length", "111"},
+       {"reference_window_length", "222"},
+       {"reference_window_delay", "333"},
+       {"clipping_threshold", "4.44"},
+       {"crest_factor_margin", ".555"},
+       {"clipped_level_step", "255"},
+       {"clipped_ratio_threshold", "0.77"},
+       {"clipped_wait_frames", "888"}});
+
+  blink::AudioProcessingProperties properties;
+  properties.goog_auto_gain_control = true;
+  properties.goog_experimental_auto_gain_control = true;
+
+  scoped_refptr<WebRtcAudioDeviceImpl> webrtc_audio_device(
+      new rtc::RefCountedObject<WebRtcAudioDeviceImpl>());
+  scoped_refptr<MediaStreamAudioProcessor> audio_processor(
+      new rtc::RefCountedObject<MediaStreamAudioProcessor>(
+          properties, /*use_capture_multi_channel_processing=*/true,
+          webrtc_audio_device));
+
+  absl::optional<webrtc::AudioProcessing::Config> apm_config =
+      audio_processor->GetAudioProcessingModuleConfig();
+  ASSERT_TRUE(apm_config);
+
+  const AnalogGainController& analog_agc =
+      apm_config->gain_controller1.analog_gain_controller;
+  EXPECT_TRUE(analog_agc.clipping_predictor.enabled);
+  EXPECT_EQ(analog_agc.clipping_predictor.mode,
+            AnalogGainController::ClippingPredictor::Mode::
+                kFixedStepClippingPeakPrediction);
+  EXPECT_EQ(analog_agc.clipping_predictor.window_length, 111);
+  EXPECT_EQ(analog_agc.clipping_predictor.reference_window_length, 222);
+  EXPECT_EQ(analog_agc.clipping_predictor.reference_window_delay, 333);
+  EXPECT_FLOAT_EQ(analog_agc.clipping_predictor.clipping_threshold, 4.44f);
+  EXPECT_FLOAT_EQ(analog_agc.clipping_predictor.crest_factor_margin, 0.555f);
+  EXPECT_EQ(analog_agc.clipped_level_step, 255);
+  EXPECT_FLOAT_EQ(analog_agc.clipped_ratio_threshold, 0.77f);
+  EXPECT_EQ(analog_agc.clipped_wait_frames, 888);
+}
+
 // Ensure that discrete channel layouts do not crash with audio processing
 // enabled.
 TEST_F(MediaStreamAudioProcessorTest, DiscreteChannelLayout) {
@@ -689,7 +775,7 @@
       new rtc::RefCountedObject<WebRtcAudioDeviceImpl>());
   scoped_refptr<MediaStreamAudioProcessor> audio_processor(
       new rtc::RefCountedObject<MediaStreamAudioProcessor>(
-          properties, /*use_multichannel_processing=*/true,
+          properties, /*use_capture_multi_channel_processing=*/true,
           webrtc_audio_device));
   EXPECT_TRUE(audio_processor->has_audio_processing());
 
diff --git a/third_party/blink/renderer/modules/url_pattern/BUILD.gn b/third_party/blink/renderer/modules/url_pattern/BUILD.gn
index 6bc2956..fa665a44 100644
--- a/third_party/blink/renderer/modules/url_pattern/BUILD.gn
+++ b/third_party/blink/renderer/modules/url_pattern/BUILD.gn
@@ -8,6 +8,12 @@
   sources = [
     "url_pattern.cc",
     "url_pattern.h",
+    "url_pattern_canon.cc",
+    "url_pattern_canon.h",
+    "url_pattern_component.cc",
+    "url_pattern_component.h",
+    "url_pattern_parser.cc",
+    "url_pattern_parser.h",
   ]
 
   public_deps = [
diff --git a/third_party/blink/renderer/modules/url_pattern/DEPS b/third_party/blink/renderer/modules/url_pattern/DEPS
index f5a1614..35dd35a7 100644
--- a/third_party/blink/renderer/modules/url_pattern/DEPS
+++ b/third_party/blink/renderer/modules/url_pattern/DEPS
@@ -5,4 +5,6 @@
 include_rules = [
     "+base/strings/string_util.h",
     "+third_party/liburlpattern",
+    "+url/url_canon.h",
+    "+url/url_util.h",
 ]
diff --git a/third_party/blink/renderer/modules/url_pattern/url_pattern.cc b/third_party/blink/renderer/modules/url_pattern/url_pattern.cc
index d67de0c9..948ad2c 100644
--- a/third_party/blink/renderer/modules/url_pattern/url_pattern.cc
+++ b/third_party/blink/renderer/modules/url_pattern/url_pattern.cc
@@ -9,6 +9,9 @@
 #include "third_party/blink/renderer/bindings/modules/v8/v8_union_urlpatterninit_usvstring.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_url_pattern_component_result.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_url_pattern_result.h"
+#include "third_party/blink/renderer/modules/url_pattern/url_pattern_canon.h"
+#include "third_party/blink/renderer/modules/url_pattern/url_pattern_component.h"
+#include "third_party/blink/renderer/modules/url_pattern/url_pattern_parser.h"
 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
 #include "third_party/blink/renderer/platform/weborigin/kurl.h"
 #include "third_party/blink/renderer/platform/weborigin/security_origin.h"
@@ -16,88 +19,15 @@
 #include "third_party/blink/renderer/platform/wtf/text/string_utf8_adaptor.h"
 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
 #include "third_party/liburlpattern/pattern.h"
+#include "third_party/liburlpattern/tokenize.h"
 
 namespace blink {
 
-// A struct representing all the information needed to match a particular
-// component of a URL.
-class URLPattern::Component final
-    : public GarbageCollected<URLPattern::Component> {
- public:
-  bool Match(StringView input, Vector<String>* group_list) const {
-    return regexp->Match(input, /*start_from=*/0, /*match_length=*/nullptr,
-                         group_list) == 0;
-  }
-
-  void Trace(Visitor* visitor) const { visitor->Trace(regexp); }
-
-  // The parsed pattern.
-  liburlpattern::Pattern pattern;
-
-  // The pattern compiled down to a js regular expression.
-  Member<ScriptRegexp> regexp;
-
-  // The names to be applied to the regular expression capture groups.  Note,
-  // liburlpattern regular expressions do not use named capture groups directly.
-  Vector<String> name_list;
-
-  Component(liburlpattern::Pattern p, ScriptRegexp* r, Vector<String> n)
-      : pattern(p), regexp(r), name_list(std::move(n)) {}
-};
+using url_pattern::Component;
+using url_pattern::ValueType;
 
 namespace {
 
-// The default pattern string for components that are not specified in the
-// URLPattern constructor.
-const char* kDefaultPattern = "*";
-
-// The liburlpattern::Options to use for most component patterns.  We
-// default to strict mode and case sensitivity.  In addition, most
-// components have no concept of a delimiter or prefix character.
-const liburlpattern::Options& DefaultOptions() {
-  DEFINE_THREAD_SAFE_STATIC_LOCAL(liburlpattern::Options, options,
-                                  ({.delimiter_list = "",
-                                    .prefix_list = "",
-                                    .sensitive = true,
-                                    .strict = true}));
-  return options;
-}
-
-// The liburlpattern::Options to use for hostname patterns.  This uses a
-// "." delimiter controlling how far a named group like ":bar" will match
-// by default.  Note, hostnames are case insensitive but we require case
-// sensitivity here.  This assumes that the hostname values have already
-// been normalized to lower case as in URL().
-const liburlpattern::Options& HostnameOptions() {
-  DEFINE_STATIC_LOCAL(liburlpattern::Options, options,
-                      ({.delimiter_list = ".",
-                        .prefix_list = "",
-                        .sensitive = true,
-                        .strict = true}));
-  return options;
-}
-
-// The liburlpattern::Options to use for pathname patterns.  This uses a
-// "/" delimiter controlling how far a named group like ":bar" will match
-// by default.  It also configures "/" to be treated as an automatic
-// prefix before groups.
-const liburlpattern::Options& PathnameOptions() {
-  DEFINE_STATIC_LOCAL(liburlpattern::Options, options,
-                      ({.delimiter_list = "/",
-                        .prefix_list = "/",
-                        .sensitive = true,
-                        .strict = true}));
-  return options;
-}
-
-// An enum indicating whether the associated component values be operated
-// on are for patterns or URLs.  Validation and canonicalization will
-// do different things depending on the type.
-enum class ValueType {
-  kPattern,
-  kURL,
-};
-
 // Utility function to determine if a pathname is absolute or not.  For
 // kURL values this mainly consists of a check for a leading slash.  For
 // patterns we do some additional checking for escaped or grouped slashes.
@@ -127,213 +57,6 @@
   return false;
 }
 
-String StringFromCanonOutput(const url::CanonOutput& output,
-                             const url::Component& component) {
-  return String::FromUTF8(output.data() + component.begin, component.len);
-}
-
-std::string StdStringFromCanonOutput(const url::CanonOutput& output,
-                                     const url::Component& component) {
-  return std::string(output.data() + component.begin, component.len);
-}
-
-// A callback to be passed to the liburlpattern::Parse() method that performs
-// validation and encoding for the protocol component.
-absl::StatusOr<std::string> ProtocolEncodeCallback(absl::string_view input) {
-  if (input.empty())
-    return std::string();
-
-  url::RawCanonOutputT<char> canon_output;
-  url::Component component;
-
-  bool result = url::CanonicalizeScheme(
-      input.data(), url::Component(0, static_cast<int>(input.size())),
-      &canon_output, &component);
-
-  if (!result) {
-    return absl::InvalidArgumentError("Invalid protocol '" +
-                                      std::string(input) + "'.");
-  }
-
-  return StdStringFromCanonOutput(canon_output, component);
-}
-
-// Utility function to canonicalize a protocol string.  Throws an exception
-// if the input is invalid.  The canonicalization and/or validation will
-// differ depending on whether |type| is kURL or kPattern.
-String CanonicalizeProtocol(const String& input,
-                            ValueType type,
-                            ExceptionState& exception_state) {
-  if (type == ValueType::kPattern) {
-    // Canonicalization for patterns is handled during compilation via
-    // encoding callbacks.
-    return input;
-  }
-
-  bool result = false;
-  url::RawCanonOutputT<char> canon_output;
-  url::Component component;
-  if (input.Is8Bit()) {
-    StringUTF8Adaptor utf8(input);
-    result = url::CanonicalizeScheme(
-        utf8.data(), url::Component(0, utf8.size()), &canon_output, &component);
-  } else {
-    result = url::CanonicalizeScheme(input.Characters16(),
-                                     url::Component(0, input.length()),
-                                     &canon_output, &component);
-  }
-
-  if (!result) {
-    exception_state.ThrowTypeError("Invalid protocol '" + input + "'.");
-    return String();
-  }
-
-  return StringFromCanonOutput(canon_output, component);
-}
-
-// A callback to be passed to the liburlpattern::Parse() method that performs
-// validation and encoding for the username component.
-absl::StatusOr<std::string> UsernameEncodeCallback(absl::string_view input) {
-  if (input.empty())
-    return std::string();
-
-  url::RawCanonOutputT<char> canon_output;
-  url::Component username_component;
-  url::Component password_component;
-
-  bool result = url::CanonicalizeUserInfo(
-      input.data(), url::Component(0, static_cast<int>(input.size())), "",
-      url::Component(0, 0), &canon_output, &username_component,
-      &password_component);
-
-  if (!result) {
-    return absl::InvalidArgumentError("Invalid username pattern '" +
-                                      std::string(input) + "'.");
-  }
-
-  return StdStringFromCanonOutput(canon_output, username_component);
-}
-
-// A callback to be passed to the liburlpattern::Parse() method that performs
-// validation and encoding for the password component.
-absl::StatusOr<std::string> PasswordEncodeCallback(absl::string_view input) {
-  if (input.empty())
-    return std::string();
-
-  url::RawCanonOutputT<char> canon_output;
-  url::Component username_component;
-  url::Component password_component;
-
-  bool result = url::CanonicalizeUserInfo(
-      "", url::Component(0, 0), input.data(),
-      url::Component(0, static_cast<int>(input.size())), &canon_output,
-      &username_component, &password_component);
-
-  if (!result) {
-    return absl::InvalidArgumentError("Invalid password pattern '" +
-                                      std::string(input) + "'.");
-  }
-
-  return StdStringFromCanonOutput(canon_output, password_component);
-}
-
-// Utility function to canonicalize username and/or password strings. Throws
-// an exception if either is invalid.  The canonicalization and/or validation
-// will differ depending on whether |type| is kURL or kPattern.  On success
-// |username_out| and |password_out| will contain the canonical values.
-void CanonicalizeUsernameAndPassword(const String& username,
-                                     const String& password,
-                                     ValueType type,
-                                     String& username_out,
-                                     String& password_out,
-                                     ExceptionState& exception_state) {
-  if (type == ValueType::kPattern) {
-    // Canonicalization for patterns is handled during compilation via
-    // encoding callbacks.
-    username_out = username;
-    password_out = password;
-    return;
-  }
-
-  bool result = false;
-  url::RawCanonOutputT<char> canon_output;
-  url::Component username_component;
-  url::Component password_component;
-
-  if (username && password && username.Is8Bit() && password.Is8Bit()) {
-    StringUTF8Adaptor username_utf8(username);
-    StringUTF8Adaptor password_utf8(password);
-    result = url::CanonicalizeUserInfo(
-        username_utf8.data(), url::Component(0, username_utf8.size()),
-        password_utf8.data(), url::Component(0, password_utf8.size()),
-        &canon_output, &username_component, &password_component);
-
-  } else {
-    String username16(username);
-    String password16(password);
-    username16.Ensure16Bit();
-    password16.Ensure16Bit();
-    result = url::CanonicalizeUserInfo(
-        username16.Characters16(), url::Component(0, username16.length()),
-        password16.Characters16(), url::Component(0, password16.length()),
-        &canon_output, &username_component, &password_component);
-  }
-
-  if (!result) {
-    exception_state.ThrowTypeError("Invalid username '" + username +
-                                   "' and/or password '" + password + "'.");
-    return;
-  }
-
-  if (username_component.len != -1)
-    username_out = StringFromCanonOutput(canon_output, username_component);
-  if (password_component.len != -1)
-    password_out = StringFromCanonOutput(canon_output, password_component);
-}
-
-// A callback to be passed to the liburlpattern::Parse() method that performs
-// validation and encoding for the hostname component.
-absl::StatusOr<std::string> HostnameEncodeCallback(absl::string_view input) {
-  if (input.empty())
-    return std::string();
-
-  url::RawCanonOutputT<char> canon_output;
-  url::Component component;
-
-  bool result = url::CanonicalizeHost(
-      input.data(), url::Component(0, static_cast<int>(input.size())),
-      &canon_output, &component);
-
-  if (!result) {
-    return absl::InvalidArgumentError("Invalid hostname pattern '" +
-                                      std::string(input) + "'.");
-  }
-
-  return StdStringFromCanonOutput(canon_output, component);
-}
-
-// Utility function to canonicalize a hostname string.  Throws an exception
-// if the input is invalid.  The canonicalization and/or validation will
-// differ depending on whether |type| is kURL or kPattern.
-String CanonicalizeHostname(const String& input,
-                            ValueType type,
-                            ExceptionState& exception_state) {
-  if (type == ValueType::kPattern) {
-    // Canonicalization for patterns is handled during compilation via
-    // encoding callbacks.
-    return input;
-  }
-
-  bool success = false;
-  String result = SecurityOrigin::CanonicalizeHost(input, &success);
-  if (!success) {
-    exception_state.ThrowTypeError("Invalid hostname '" + input + "'.");
-    return String();
-  }
-
-  return result;
-}
-
 // Utility function to determine if the default port for the given protocol
 // matches the given port number.
 bool IsProtocolDefaultPort(const String& protocol, const String& port) {
@@ -352,260 +75,6 @@
   return default_port != url::PORT_UNSPECIFIED && default_port == port_number;
 }
 
-// A callback to be passed to the liburlpattern::Parse() method that performs
-// validation and encoding for the port component.
-absl::StatusOr<std::string> PortEncodeCallback(absl::string_view input) {
-  if (input.empty())
-    return std::string();
-
-  url::RawCanonOutputT<char> canon_output;
-  url::Component component;
-
-  bool result = url::CanonicalizePort(
-      input.data(), url::Component(0, static_cast<int>(input.size())),
-      url::PORT_UNSPECIFIED, &canon_output, &component);
-
-  if (!result) {
-    return absl::InvalidArgumentError("Invalid port pattern '" +
-                                      std::string(input) + "'.");
-  }
-
-  return StdStringFromCanonOutput(canon_output, component);
-}
-
-// Utility function to canonicalize a port string.  Throws an exception
-// if the input is invalid.  The canonicalization and/or validation will
-// differ depending on whether |type| is kURL or kPattern.  The |protocol|
-// must be provided in order to handle default ports correctly.
-String CanonicalizePort(const String& input,
-                        ValueType type,
-                        const String& protocol,
-                        ExceptionState& exception_state) {
-  if (type == ValueType::kPattern) {
-    // Canonicalization for patterns is handled during compilation via
-    // encoding callbacks.
-    return input;
-  }
-
-  int default_port = url::PORT_UNSPECIFIED;
-  if (!input.IsEmpty()) {
-    StringUTF8Adaptor protocol_utf8(protocol);
-    default_port =
-        url::DefaultPortForScheme(protocol_utf8.data(), protocol_utf8.size());
-  }
-
-  // Since ports only consist of digits there should be no encoding needed.
-  // Therefore we directly use the UTF8 encoding version of CanonicalizePort().
-  StringUTF8Adaptor utf8(input);
-  url::RawCanonOutputT<char> canon_output;
-  url::Component component;
-  if (!url::CanonicalizePort(utf8.data(), url::Component(0, utf8.size()),
-                             default_port, &canon_output, &component)) {
-    exception_state.ThrowTypeError("Invalid port '" + input + "'.");
-    return String();
-  }
-
-  return component.len == -1 ? g_empty_string
-                             : StringFromCanonOutput(canon_output, component);
-}
-
-// A callback to be passed to the liburlpattern::Parse() method that performs
-// validation and encoding for the pathname component using "standard" URL
-// behavior.
-absl::StatusOr<std::string> StandardURLPathnameEncodeCallback(
-    absl::string_view input) {
-  if (input.empty())
-    return std::string();
-
-  url::RawCanonOutputT<char> canon_output;
-  url::Component component;
-
-  bool result = url::CanonicalizePartialPath(
-      input.data(), url::Component(0, static_cast<int>(input.size())),
-      &canon_output, &component);
-
-  if (!result) {
-    return absl::InvalidArgumentError("Invalid pathname pattern '" +
-                                      std::string(input) + "'.");
-  }
-
-  return StdStringFromCanonOutput(canon_output, component);
-}
-
-// A callback to be passed to the liburlpattern::Parse() method that performs
-// validation and encoding for the pathname component using "path" URL
-// behavior.  This is like "cannot-be-a-base" URL behavior in the spec.
-absl::StatusOr<std::string> PathURLPathnameEncodeCallback(
-    absl::string_view input) {
-  if (input.empty())
-    return std::string();
-
-  url::RawCanonOutputT<char> canon_output;
-  url::Component component;
-
-  url::CanonicalizePathURLPath(
-      input.data(), url::Component(0, static_cast<int>(input.size())),
-      &canon_output, &component);
-
-  return StdStringFromCanonOutput(canon_output, component);
-}
-
-// Utility function to canonicalize a pathname string.  Throws an exception
-// if the input is invalid.  The canonicalization and/or validation will
-// differ depending on whether |type| is kURL or kPattern.
-String CanonicalizePathname(const String& protocol,
-                            const String& input,
-                            ValueType type,
-                            ExceptionState& exception_state) {
-  if (type == ValueType::kPattern) {
-    // Canonicalization for patterns is handled during compilation via
-    // encoding callbacks.
-    return input;
-  }
-
-  // Determine if we are using "standard" or "path" URL canonicalization
-  // for the pathname.  In spec terms the "path" URL behavior corresponds
-  // to "cannot-be-a-base" URLs.  We make this determination based on the
-  // protocol string since we cannot look at the number of slashes between
-  // components like the URL spec.  If this is inadequate the developer
-  // can use the baseURL property to get more strict URL behavior.
-  //
-  // We default to "standard" URL behavior to match how the empty protocol
-  // string in the URLPattern constructor results in the pathname pattern
-  // getting "standard" URL canonicalization.
-  bool standard = false;
-  if (protocol.IsEmpty()) {
-    standard = true;
-  } else if (protocol.Is8Bit()) {
-    StringUTF8Adaptor utf8(protocol);
-    standard = url::IsStandard(utf8.data(), url::Component(0, utf8.size()));
-  } else {
-    standard = url::IsStandard(protocol.Characters16(),
-                               url::Component(0, protocol.length()));
-  }
-
-  // Do not enforce absolute pathnames here since we can't enforce it
-  // it consistently in the URLPattern constructor.  This allows us to
-  // produce a match when the exact same fixed pathname string is passed
-  // to both the constructor and test()/exec().  Similarly, we use
-  // url::CanonicalizePartialPath() below instead of url::CanonicalizePath()
-  // to avoid pre-pending a slash at the start of the string.
-
-  bool result = false;
-  url::RawCanonOutputT<char> canon_output;
-  url::Component component;
-
-  const auto canonicalize_path = [&](const auto* data, int length) {
-    if (standard) {
-      return url::CanonicalizePartialPath(data, url::Component(0, length),
-                                          &canon_output, &component);
-    }
-    url::CanonicalizePathURLPath(data, url::Component(0, length), &canon_output,
-                                 &component);
-    return true;
-  };
-
-  if (input.Is8Bit()) {
-    StringUTF8Adaptor utf8(input);
-    result = canonicalize_path(utf8.data(), utf8.size());
-  } else {
-    result = canonicalize_path(input.Characters16(), input.length());
-  }
-
-  if (!result) {
-    exception_state.ThrowTypeError("Invalid pathname '" + input + "'.");
-    return String();
-  }
-
-  return StringFromCanonOutput(canon_output, component);
-}
-
-// A callback to be passed to the liburlpattern::Parse() method that performs
-// validation and encoding for the search component.
-absl::StatusOr<std::string> SearchEncodeCallback(absl::string_view input) {
-  if (input.empty())
-    return std::string();
-
-  url::RawCanonOutputT<char> canon_output;
-  url::Component component;
-
-  url::CanonicalizeQuery(input.data(),
-                         url::Component(0, static_cast<int>(input.size())),
-                         /*converter=*/nullptr, &canon_output, &component);
-
-  return StdStringFromCanonOutput(canon_output, component);
-}
-
-// Utility function to canonicalize a search string.  Throws an exception
-// if the input is invalid.  The canonicalization and/or validation will
-// differ depending on whether |type| is kURL or kPattern.
-String CanonicalizeSearch(const String& input,
-                          ValueType type,
-                          ExceptionState& exception_state) {
-  if (type == ValueType::kPattern) {
-    // Canonicalization for patterns is handled during compilation via
-    // encoding callbacks.
-    return input;
-  }
-
-  url::RawCanonOutputT<char> canon_output;
-  url::Component component;
-  if (input.Is8Bit()) {
-    StringUTF8Adaptor utf8(input);
-    url::CanonicalizeQuery(utf8.data(), url::Component(0, utf8.size()),
-                           /*converter=*/nullptr, &canon_output, &component);
-  } else {
-    url::CanonicalizeQuery(input.Characters16(),
-                           url::Component(0, input.length()),
-                           /*converter=*/nullptr, &canon_output, &component);
-  }
-
-  return StringFromCanonOutput(canon_output, component);
-}
-
-// A callback to be passed to the liburlpattern::Parse() method that performs
-// validation and encoding for the hash component.
-absl::StatusOr<std::string> HashEncodeCallback(absl::string_view input) {
-  if (input.empty())
-    return std::string();
-
-  url::RawCanonOutputT<char> canon_output;
-  url::Component component;
-
-  url::CanonicalizeRef(input.data(),
-                       url::Component(0, static_cast<int>(input.size())),
-                       &canon_output, &component);
-
-  return StdStringFromCanonOutput(canon_output, component);
-}
-
-// Utility function to canonicalize a hash string.  Throws an exception
-// if the input is invalid.  The canonicalization and/or validation will
-// differ depending on whether |type| is kURL or kPattern.
-String CanonicalizeHash(const String& input,
-                        ValueType type,
-                        ExceptionState& exception_state) {
-  if (type == ValueType::kPattern) {
-    // Canonicalization for patterns is handled during compilation via
-    // encoding callbacks.
-    return input;
-  }
-
-  url::RawCanonOutputT<char> canon_output;
-  url::Component component;
-  if (input.Is8Bit()) {
-    StringUTF8Adaptor utf8(input);
-    url::CanonicalizeRef(utf8.data(), url::Component(0, utf8.size()),
-                         &canon_output, &component);
-  } else {
-    url::CanonicalizeRef(input.Characters16(),
-                         url::Component(0, input.length()), &canon_output,
-                         &component);
-  }
-
-  return StringFromCanonOutput(canon_output, component);
-}
-
 // A utility method that takes a URLPatternInit, splits it apart, and applies
 // the individual component values in the given set of strings.  The strings
 // are only applied if a value is present in the init structure.
@@ -650,25 +119,29 @@
   // Apply the URLPatternInit component values on top of the default and
   // baseURL values.
   if (init->hasProtocol()) {
-    protocol = CanonicalizeProtocol(init->protocol(), type, exception_state);
+    protocol = url_pattern::CanonicalizeProtocol(init->protocol(), type,
+                                                 exception_state);
     if (exception_state.HadException())
       return;
   }
   if (init->hasUsername() || init->hasPassword()) {
     String init_username = init->hasUsername() ? init->username() : String();
     String init_password = init->hasPassword() ? init->password() : String();
-    CanonicalizeUsernameAndPassword(init_username, init_password, type,
-                                    username, password, exception_state);
+    url_pattern::CanonicalizeUsernameAndPassword(init_username, init_password,
+                                                 type, username, password,
+                                                 exception_state);
     if (exception_state.HadException())
       return;
   }
   if (init->hasHostname()) {
-    hostname = CanonicalizeHostname(init->hostname(), type, exception_state);
+    hostname = url_pattern::CanonicalizeHostname(init->hostname(), type,
+                                                 exception_state);
     if (exception_state.HadException())
       return;
   }
   if (init->hasPort()) {
-    port = CanonicalizePort(init->port(), type, protocol, exception_state);
+    port = url_pattern::CanonicalizePort(init->port(), type, protocol,
+                                         exception_state);
     if (exception_state.HadException())
       return;
   }
@@ -688,17 +161,19 @@
         pathname = base_url.GetPath().Substring(0, slash_index + 1) + pathname;
       }
     }
-    pathname = CanonicalizePathname(protocol, pathname, type, exception_state);
+    pathname = url_pattern::CanonicalizePathname(protocol, pathname, type,
+                                                 exception_state);
     if (exception_state.HadException())
       return;
   }
   if (init->hasSearch()) {
-    search = CanonicalizeSearch(init->search(), type, exception_state);
+    search =
+        url_pattern::CanonicalizeSearch(init->search(), type, exception_state);
     if (exception_state.HadException())
       return;
   }
   if (init->hasHash()) {
-    hash = CanonicalizeHash(init->hash(), type, exception_state);
+    hash = url_pattern::CanonicalizeHash(init->hash(), type, exception_state);
     if (exception_state.HadException())
       return;
   }
@@ -706,7 +181,52 @@
 
 }  // namespace
 
+URLPattern* URLPattern::Create(const V8URLPatternInput* input,
+                               const String& base_url,
+                               ExceptionState& exception_state) {
+  if (input->GetContentType() ==
+      V8URLPatternInput::ContentType::kURLPatternInit) {
+    exception_state.ThrowTypeError(
+        "Invalid second argument baseURL '" + base_url +
+        "' provided with a URLPatternInit input. Use the "
+        "URLPatternInit.baseURL property instead.");
+    return nullptr;
+  }
+
+  const auto& input_string = input->GetAsUSVString();
+
+  url_pattern::Parser parser(input_string);
+  parser.Parse(exception_state);
+  if (exception_state.HadException())
+    return nullptr;
+
+  URLPatternInit* init = parser.GetResult();
+  if (!base_url && !init->hasProtocol()) {
+    exception_state.ThrowTypeError(
+        "Relative constructor string '" + input_string +
+        "' must have a base URL passed as the second argument.");
+    return nullptr;
+  }
+
+  if (base_url)
+    init->setBaseURL(base_url);
+
+  return Create(init, parser.GetProtocolComponent(), exception_state);
+}
+
+URLPattern* URLPattern::Create(const V8URLPatternInput* input,
+                               ExceptionState& exception_state) {
+  if (input->IsURLPatternInit()) {
+    return URLPattern::Create(input->GetAsURLPatternInit(),
+                              /*precomputed_protocol_component=*/nullptr,
+                              exception_state);
+  }
+
+  return Create(input, /*base_url=*/String(), exception_state);
+}
+
 URLPattern* URLPattern::Create(const URLPatternInit* init,
+                               Component* precomputed_protocol_component,
                                ExceptionState& exception_state) {
   // Each component defaults to a wildcard matching any input.  We use
   // the null string as a shorthand for the default.
@@ -733,80 +253,54 @@
   if (IsProtocolDefaultPort(protocol, port))
     port = "";
 
-  // Compile each component pattern into a Component structure that can
-  // be used for matching.  Components that match any input may have a
-  // nullptr Component struct pointer.
+  // Compile each component pattern into a Component structure that
+  // can be used for matching.
 
-  auto* protocol_component =
-      CompilePattern(protocol, "protocol", ProtocolEncodeCallback,
-                     DefaultOptions(), exception_state);
+  auto* protocol_component = precomputed_protocol_component;
+  if (!protocol_component) {
+    protocol_component =
+        Component::Compile(protocol, Component::Type::kProtocol,
+                           /*protocol_component=*/nullptr, exception_state);
+  }
   if (exception_state.HadException())
     return nullptr;
 
   auto* username_component =
-      CompilePattern(username, "username", UsernameEncodeCallback,
-                     DefaultOptions(), exception_state);
+      Component::Compile(username, Component::Type::kUsername,
+                         protocol_component, exception_state);
   if (exception_state.HadException())
     return nullptr;
 
   auto* password_component =
-      CompilePattern(password, "password", PasswordEncodeCallback,
-                     DefaultOptions(), exception_state);
+      Component::Compile(password, Component::Type::kPassword,
+                         protocol_component, exception_state);
   if (exception_state.HadException())
     return nullptr;
 
   auto* hostname_component =
-      CompilePattern(hostname, "hostname", HostnameEncodeCallback,
-                     HostnameOptions(), exception_state);
+      Component::Compile(hostname, Component::Type::kHostname,
+                         protocol_component, exception_state);
   if (exception_state.HadException())
     return nullptr;
 
-  auto* port_component = CompilePattern(port, "port", PortEncodeCallback,
-                                        DefaultOptions(), exception_state);
+  auto* port_component = Component::Compile(
+      port, Component::Type::kPort, protocol_component, exception_state);
   if (exception_state.HadException())
     return nullptr;
 
-  // Different types of URLs use different canonicalization for pathname.
-  // A "standard" URL flattens `.`/`..` and performs full percent encoding.
-  // A "path" URL does not flatten and uses a more lax percent encoding.
-  // The spec calls "path" URLs as "cannot-be-a-base-URL" URLs:
-  //
-  //  https://url.spec.whatwg.org/#cannot-be-a-base-url-path-state
-  //
-  // We prefer "standard" URL here by checking to see if the protocol
-  // pattern matches any of the known standard protocol strings.  So
-  // an exact pattern of `http` will match, but so will `http{s}?` and
-  // `*`.
-  //
-  // If the protocol pattern does not match any of the known standard URL
-  // protocols then we fall back to the "path" URL behavior.  This will
-  // normally be triggered by `data`, `javascript`, `about`, etc.  It
-  // will also be triggered for custom protocol strings.  We favor "path"
-  // behavior here because its better to under canonicalize since the
-  // developer can always manually canonicalize the pathname for a custom
-  // protocol.
-  //
-  // ShouldTreatAsStandardURL can by a bit expensive, so only do it if we
-  // actually have a pathname pattern to compile.
-  liburlpattern::EncodeCallback pathname_encode = PathURLPathnameEncodeCallback;
-  if (!pathname.IsNull() && ShouldTreatAsStandardURL(protocol_component)) {
-    pathname_encode = StandardURLPathnameEncodeCallback;
-  }
-
   auto* pathname_component =
-      CompilePattern(pathname, "pathname", pathname_encode, PathnameOptions(),
-                     exception_state);
+      Component::Compile(pathname, Component::Type::kPathname,
+                         protocol_component, exception_state);
   if (exception_state.HadException())
     return nullptr;
 
-  auto* search_component =
-      CompilePattern(search, "search", SearchEncodeCallback, DefaultOptions(),
-                     exception_state);
+  auto* search_component = Component::Compile(
+      search, Component::Type::kSearch, protocol_component, exception_state);
   if (exception_state.HadException())
     return nullptr;
 
-  auto* hash_component = CompilePattern(hash, "hash", HashEncodeCallback,
-                                        DefaultOptions(), exception_state);
+  auto* hash_component = Component::Compile(
+      hash, Component::Type::kHash, protocol_component, exception_state);
   if (exception_state.HadException())
     return nullptr;
 
@@ -864,59 +358,35 @@
 }
 
 String URLPattern::protocol() const {
-  if (!protocol_)
-    return kDefaultPattern;
-  std::string result = protocol_->pattern.GeneratePatternString();
-  return String::FromUTF8(result);
+  return protocol_->GeneratePatternString();
 }
 
 String URLPattern::username() const {
-  if (!username_)
-    return kDefaultPattern;
-  std::string result = username_->pattern.GeneratePatternString();
-  return String::FromUTF8(result);
+  return username_->GeneratePatternString();
 }
 
 String URLPattern::password() const {
-  if (!password_)
-    return kDefaultPattern;
-  std::string result = password_->pattern.GeneratePatternString();
-  return String::FromUTF8(result);
+  return password_->GeneratePatternString();
 }
 
 String URLPattern::hostname() const {
-  if (!hostname_)
-    return kDefaultPattern;
-  std::string result = hostname_->pattern.GeneratePatternString();
-  return String::FromUTF8(result);
+  return hostname_->GeneratePatternString();
 }
 
 String URLPattern::port() const {
-  if (!port_)
-    return kDefaultPattern;
-  std::string result = port_->pattern.GeneratePatternString();
-  return String::FromUTF8(result);
+  return port_->GeneratePatternString();
 }
 
 String URLPattern::pathname() const {
-  if (!pathname_)
-    return kDefaultPattern;
-  std::string result = pathname_->pattern.GeneratePatternString();
-  return String::FromUTF8(result);
+  return pathname_->GeneratePatternString();
 }
 
 String URLPattern::search() const {
-  if (!search_)
-    return kDefaultPattern;
-  std::string result = search_->pattern.GeneratePatternString();
-  return String::FromUTF8(result);
+  return search_->GeneratePatternString();
 }
 
 String URLPattern::hash() const {
-  if (!hash_)
-    return kDefaultPattern;
-  std::string result = hash_->pattern.GeneratePatternString();
-  return String::FromUTF8(result);
+  return hash_->GeneratePatternString();
 }
 
 void URLPattern::Trace(Visitor* visitor) const {
@@ -931,81 +401,6 @@
   ScriptWrappable::Trace(visitor);
 }
 
-// static
-URLPattern::Component* URLPattern::CompilePattern(
-    const String& pattern,
-    StringView component,
-    liburlpattern::EncodeCallback encode_callback,
-    const liburlpattern::Options& options,
-    ExceptionState& exception_state) {
-  // If the pattern is null then optimize by not compiling a pattern.  Instead,
-  // a nullptr Component is interpreted as matching any input value.
-  if (pattern.IsNull())
-    return nullptr;
-
-  // Parse the pattern.
-  StringUTF8Adaptor utf8(pattern);
-  auto parse_result =
-      liburlpattern::Parse(absl::string_view(utf8.data(), utf8.size()),
-                           std::move(encode_callback), options);
-  if (!parse_result.ok()) {
-    exception_state.ThrowTypeError("Invalid " + component + " pattern '" +
-                                   pattern + "'.");
-    return nullptr;
-  }
-
-  // Extract a regular expression string from the parsed pattern.
-  std::vector<std::string> name_list;
-  std::string regexp_string =
-      parse_result.value().GenerateRegexString(&name_list);
-
-  // Compile the regular expression to verify it is valid.
-  auto case_sensitive = options.sensitive ? WTF::kTextCaseSensitive
-                                          : WTF::kTextCaseASCIIInsensitive;
-  DCHECK(base::IsStringASCII(regexp_string));
-  ScriptRegexp* regexp = MakeGarbageCollected<ScriptRegexp>(
-      String(regexp_string.data(), regexp_string.size()), case_sensitive,
-      kMultilineDisabled, ScriptRegexp::UTF16);
-  if (!regexp->IsValid()) {
-    // The regular expression failed to compile.  This means that some
-    // custom regexp group within the pattern is illegal.  Attempt to
-    // compile each regexp group individually in order to identify the
-    // culprit.
-    for (auto& part : parse_result.value().PartList()) {
-      if (part.type != liburlpattern::PartType::kRegex)
-        continue;
-      DCHECK(base::IsStringASCII(part.value));
-      String group_value(part.value.data(), part.value.size());
-      regexp = MakeGarbageCollected<ScriptRegexp>(
-          group_value, case_sensitive, kMultilineDisabled, ScriptRegexp::UTF16);
-      if (regexp->IsValid())
-        continue;
-      exception_state.ThrowTypeError("Invalid " + component + " pattern '" +
-                                     pattern +
-                                     "'. Custom regular expression group '" +
-                                     group_value + "' is invalid.");
-      return nullptr;
-    }
-    // We couldn't find a bad regexp group, but we still have an overall
-    // error.  This shouldn't happen, but we handle it anyway.
-    exception_state.ThrowTypeError("Invalid " + component + " pattern '" +
-                                   pattern +
-                                   "'. An unexpected error has occurred.");
-    return nullptr;
-  }
-
-  Vector<String> wtf_name_list;
-  wtf_name_list.ReserveInitialCapacity(
-      static_cast<wtf_size_t>(name_list.size()));
-  for (const auto& name : name_list) {
-    wtf_name_list.push_back(String::FromUTF8(name.data(), name.size()));
-  }
-
-  return MakeGarbageCollected<URLPattern::Component>(
-      std::move(parse_result.value()), std::move(regexp),
-      std::move(wtf_name_list));
-}
-
 bool URLPattern::Match(
     const V8URLPatternInput* input,
     const String& base_url,
@@ -1024,11 +419,8 @@
 
   HeapVector<USVStringOrURLPatternInit> inputs;
 
-  bool is_init =
-      input->GetContentType() ==
-      V8URLPatternInput::ContentType::kURLPatternInit;
-
-  if (is_init) {
+  if (input->GetContentType() ==
+      V8URLPatternInput::ContentType::kURLPatternInit) {
     if (base_url) {
       exception_state.ThrowTypeError(
           "Invalid second argument baseURL '" + base_url +
@@ -1110,18 +502,25 @@
   auto* search_group_list_ref = result ? &search_group_list : nullptr;
   auto* hash_group_list_ref = result ? &hash_group_list : nullptr;
 
+  CHECK(protocol_);
+  CHECK(username_);
+  CHECK(password_);
+  CHECK(hostname_);
+  CHECK(port_);
+  CHECK(pathname_);
+  CHECK(search_);
+  CHECK(hash_);
+
   // Each component of the pattern must match the corresponding component of
-  // the input.  If a pattern Component is nullptr, then it matches any
-  // input and we can avoid running a real regular expression match.
-  bool matched =
-      (!protocol_ || protocol_->Match(protocol, protocol_group_list_ref)) &&
-      (!username_ || username_->Match(username, username_group_list_ref)) &&
-      (!password_ || password_->Match(password, password_group_list_ref)) &&
-      (!hostname_ || hostname_->Match(hostname, hostname_group_list_ref)) &&
-      (!port_ || port_->Match(port, port_group_list_ref)) &&
-      (!pathname_ || pathname_->Match(pathname, pathname_group_list_ref)) &&
-      (!search_ || search_->Match(search, search_group_list_ref)) &&
-      (!hash_ || hash_->Match(hash, hash_group_list_ref));
+  // the input.
+  bool matched = protocol_->Match(protocol, protocol_group_list_ref) &&
+                 username_->Match(username, username_group_list_ref) &&
+                 password_->Match(password, password_group_list_ref) &&
+                 hostname_->Match(hostname, hostname_group_list_ref) &&
+                 port_->Match(port, port_group_list_ref) &&
+                 pathname_->Match(pathname, pathname_group_list_ref) &&
+                 search_->Match(search, search_group_list_ref) &&
+                 hash_->Match(hash, hash_group_list_ref);
 
   if (!matched || !result)
     return matched;
@@ -1129,55 +528,32 @@
   result->setInputs(std::move(inputs));
 
   result->setProtocol(
-      MakeComponentResult(protocol_, protocol, protocol_group_list));
+      MakeURLPatternComponentResult(protocol_, protocol, protocol_group_list));
   result->setUsername(
-      MakeComponentResult(username_, username, username_group_list));
+      MakeURLPatternComponentResult(username_, username, username_group_list));
   result->setPassword(
-      MakeComponentResult(password_, password, password_group_list));
+      MakeURLPatternComponentResult(password_, password, password_group_list));
   result->setHostname(
-      MakeComponentResult(hostname_, hostname, hostname_group_list));
-  result->setPort(MakeComponentResult(port_, port, port_group_list));
+      MakeURLPatternComponentResult(hostname_, hostname, hostname_group_list));
+  result->setPort(MakeURLPatternComponentResult(port_, port, port_group_list));
   result->setPathname(
-      MakeComponentResult(pathname_, pathname, pathname_group_list));
-  result->setSearch(MakeComponentResult(search_, search, search_group_list));
-  result->setHash(MakeComponentResult(hash_, hash, hash_group_list));
+      MakeURLPatternComponentResult(pathname_, pathname, pathname_group_list));
+  result->setSearch(
+      MakeURLPatternComponentResult(search_, search, search_group_list));
+  result->setHash(MakeURLPatternComponentResult(hash_, hash, hash_group_list));
 
   return true;
 }
 
 // static
-URLPatternComponentResult* URLPattern::MakeComponentResult(
+URLPatternComponentResult* URLPattern::MakeURLPatternComponentResult(
     Component* component,
     const String& input,
-    const Vector<String>& group_list) {
-  Vector<std::pair<String, String>> groups;
-  if (!component) {
-    // When there is not Component we must act as if there was a default
-    // wildcard pattern with a group.  The group includes the entire input.
-    groups.emplace_back("0", input);
-  } else {
-    DCHECK_EQ(component->name_list.size(), group_list.size());
-    for (wtf_size_t i = 0; i < group_list.size(); ++i) {
-      groups.emplace_back(component->name_list[i], group_list[i]);
-    }
-  }
-
+    const Vector<String>& group_values) {
   auto* result = URLPatternComponentResult::Create();
   result->setInput(input);
-  result->setGroups(groups);
+  result->setGroups(component->MakeGroupList(group_values));
   return result;
 }
 
-bool URLPattern::ShouldTreatAsStandardURL(Component* protocol) {
-  if (!protocol)
-    return true;
-  const auto protocol_matches = [&](const std::string& scheme) {
-    DCHECK(base::IsStringASCII(scheme));
-    return protocol->Match(
-        StringView(scheme.data(), static_cast<unsigned>(scheme.size())),
-        /*group_list=*/nullptr);
-  };
-  return base::ranges::any_of(url::GetStandardSchemes(), protocol_matches);
-}
-
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/url_pattern/url_pattern.h b/third_party/blink/renderer/modules/url_pattern/url_pattern.h
index 2cfa675..80b5813 100644
--- a/third_party/blink/renderer/modules/url_pattern/url_pattern.h
+++ b/third_party/blink/renderer/modules/url_pattern/url_pattern.h
@@ -10,10 +10,6 @@
 #include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
 #include "third_party/liburlpattern/parse.h"
 
-namespace liburlpattern {
-struct Options;
-}  // namespace liburlpattern
-
 namespace blink {
 
 class ExceptionState;
@@ -21,12 +17,24 @@
 class URLPatternInit;
 class URLPatternResult;
 
+namespace url_pattern {
+class Component;
+}  // namespace url_pattern
+
 class URLPattern : public ScriptWrappable {
   DEFINE_WRAPPERTYPEINFO();
-  class Component;
+  using Component = url_pattern::Component;
 
  public:
+  static URLPattern* Create(const V8URLPatternInput* input,
+                            const String& base_url,
+                            ExceptionState& exception_state);
+
+  static URLPattern* Create(const V8URLPatternInput* input,
+                            ExceptionState& exception_state);
+
   static URLPattern* Create(const URLPatternInit* init,
+                            Component* precomputed_protocol_component,
                             ExceptionState& exception_state);
 
   URLPattern(Component* protocol,
@@ -63,21 +71,6 @@
   void Trace(Visitor* visitor) const override;
 
  private:
-  // A utility function that takes a given |pattern| and compiles it into a
-  // Component structure.  If the |pattern| matches the given |default_pattern|
-  // then nullptr may be returned without throwing an exception.  In this case
-  // the Component is not constructed and the nullptr value should be treated as
-  // matching any input value for the component.  The |component| string is used
-  // for exception messages.  The |encode_callback| will be used to validate and
-  // encode plain text within the pattern during compilation.  |options| control
-  // how the pattern is compiled.
-  static Component* CompilePattern(
-      const String& pattern,
-      StringView component,
-      liburlpattern::EncodeCallback encode_callback,
-      const liburlpattern::Options& options,
-      ExceptionState& exception_state);
-
   // A utility function to determine if a given |input| matches the pattern
   // or not.  Returns |true| if there is a match and |false| otherwise.  If
   // |result| is not nullptr then the URLPatternResult contents will be filled.
@@ -87,17 +80,13 @@
              ExceptionState& exception_state) const;
 
   // A utility function that constructs a URLPatternComponentResult for
-  // a given |component|, |input|, and |group_list|.  The |component| may
-  // be nullptr.
-  static URLPatternComponentResult* MakeComponentResult(
+  // a given |component|, |input|, and |group_list|.
+  static URLPatternComponentResult* MakeURLPatternComponentResult(
       Component* component,
       const String& input,
-      const Vector<String>& group_list);
+      const Vector<String>& group_values);
 
-  static bool ShouldTreatAsStandardURL(Component* protocol);
-
-  // The compiled patterns for each URL component.  If a Component member is
-  // nullptr then it should be treated as a wildcard matching any input.
+  // The compiled patterns for each URL component.
   Member<Component> protocol_;
   Member<Component> username_;
   Member<Component> password_;
diff --git a/third_party/blink/renderer/modules/url_pattern/url_pattern.idl b/third_party/blink/renderer/modules/url_pattern/url_pattern.idl
index f3a619f..672fb5d 100644
--- a/third_party/blink/renderer/modules/url_pattern/url_pattern.idl
+++ b/third_party/blink/renderer/modules/url_pattern/url_pattern.idl
@@ -10,7 +10,7 @@
   Exposed=(Window,Worker),
   RuntimeEnabled=URLPattern
 ] interface URLPattern {
-  [RaisesException] constructor(URLPatternInit init);
+  [RaisesException] constructor(URLPatternInput input, optional USVString baseURL);
 
   [RaisesException]
   boolean test(URLPatternInput input, optional USVString baseURL);
diff --git a/third_party/blink/renderer/modules/url_pattern/url_pattern_canon.cc b/third_party/blink/renderer/modules/url_pattern/url_pattern_canon.cc
new file mode 100644
index 0000000..15a7bae
--- /dev/null
+++ b/third_party/blink/renderer/modules/url_pattern/url_pattern_canon.cc
@@ -0,0 +1,440 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/blink/renderer/modules/url_pattern/url_pattern_canon.h"
+
+#include "third_party/blink/renderer/modules/url_pattern/url_pattern_component.h"
+#include "third_party/blink/renderer/platform/bindings/exception_state.h"
+#include "third_party/blink/renderer/platform/weborigin/security_origin.h"
+#include "third_party/blink/renderer/platform/wtf/text/string_utf8_adaptor.h"
+#include "url/url_canon.h"
+#include "url/url_util.h"
+
+namespace blink {
+namespace url_pattern {
+
+namespace {
+
+String StringFromCanonOutput(const url::CanonOutput& output,
+                             const url::Component& component) {
+  return String::FromUTF8(output.data() + component.begin, component.len);
+}
+
+std::string StdStringFromCanonOutput(const url::CanonOutput& output,
+                                     const url::Component& component) {
+  return std::string(output.data() + component.begin, component.len);
+}
+
+}  // anonymous namespace
+
+absl::StatusOr<std::string> ProtocolEncodeCallback(absl::string_view input) {
+  if (input.empty())
+    return std::string();
+
+  url::RawCanonOutputT<char> canon_output;
+  url::Component component;
+
+  bool result = url::CanonicalizeScheme(
+      input.data(), url::Component(0, static_cast<int>(input.size())),
+      &canon_output, &component);
+
+  if (!result) {
+    return absl::InvalidArgumentError("Invalid protocol '" +
+                                      std::string(input) + "'.");
+  }
+
+  return StdStringFromCanonOutput(canon_output, component);
+}
+
+absl::StatusOr<std::string> UsernameEncodeCallback(absl::string_view input) {
+  if (input.empty())
+    return std::string();
+
+  url::RawCanonOutputT<char> canon_output;
+  url::Component username_component;
+  url::Component password_component;
+
+  bool result = url::CanonicalizeUserInfo(
+      input.data(), url::Component(0, static_cast<int>(input.size())), "",
+      url::Component(0, 0), &canon_output, &username_component,
+      &password_component);
+
+  if (!result) {
+    return absl::InvalidArgumentError("Invalid username pattern '" +
+                                      std::string(input) + "'.");
+  }
+
+  return StdStringFromCanonOutput(canon_output, username_component);
+}
+
+absl::StatusOr<std::string> PasswordEncodeCallback(absl::string_view input) {
+  if (input.empty())
+    return std::string();
+
+  url::RawCanonOutputT<char> canon_output;
+  url::Component username_component;
+  url::Component password_component;
+
+  bool result = url::CanonicalizeUserInfo(
+      "", url::Component(0, 0), input.data(),
+      url::Component(0, static_cast<int>(input.size())), &canon_output,
+      &username_component, &password_component);
+
+  if (!result) {
+    return absl::InvalidArgumentError("Invalid password pattern '" +
+                                      std::string(input) + "'.");
+  }
+
+  return StdStringFromCanonOutput(canon_output, password_component);
+}
+
+absl::StatusOr<std::string> HostnameEncodeCallback(absl::string_view input) {
+  if (input.empty())
+    return std::string();
+
+  url::RawCanonOutputT<char> canon_output;
+  url::Component component;
+
+  bool result = url::CanonicalizeHost(
+      input.data(), url::Component(0, static_cast<int>(input.size())),
+      &canon_output, &component);
+
+  if (!result) {
+    return absl::InvalidArgumentError("Invalid hostname pattern '" +
+                                      std::string(input) + "'.");
+  }
+
+  return StdStringFromCanonOutput(canon_output, component);
+}
+
+absl::StatusOr<std::string> PortEncodeCallback(absl::string_view input) {
+  if (input.empty())
+    return std::string();
+
+  url::RawCanonOutputT<char> canon_output;
+  url::Component component;
+
+  bool result = url::CanonicalizePort(
+      input.data(), url::Component(0, static_cast<int>(input.size())),
+      url::PORT_UNSPECIFIED, &canon_output, &component);
+
+  if (!result) {
+    return absl::InvalidArgumentError("Invalid port pattern '" +
+                                      std::string(input) + "'.");
+  }
+
+  return StdStringFromCanonOutput(canon_output, component);
+}
+
+absl::StatusOr<std::string> StandardURLPathnameEncodeCallback(
+    absl::string_view input) {
+  if (input.empty())
+    return std::string();
+
+  url::RawCanonOutputT<char> canon_output;
+  url::Component component;
+
+  bool result = url::CanonicalizePartialPath(
+      input.data(), url::Component(0, static_cast<int>(input.size())),
+      &canon_output, &component);
+
+  if (!result) {
+    return absl::InvalidArgumentError("Invalid pathname pattern '" +
+                                      std::string(input) + "'.");
+  }
+
+  return StdStringFromCanonOutput(canon_output, component);
+}
+
+absl::StatusOr<std::string> PathURLPathnameEncodeCallback(
+    absl::string_view input) {
+  if (input.empty())
+    return std::string();
+
+  url::RawCanonOutputT<char> canon_output;
+  url::Component component;
+
+  url::CanonicalizePathURLPath(
+      input.data(), url::Component(0, static_cast<int>(input.size())),
+      &canon_output, &component);
+
+  return StdStringFromCanonOutput(canon_output, component);
+}
+
+absl::StatusOr<std::string> SearchEncodeCallback(absl::string_view input) {
+  if (input.empty())
+    return std::string();
+
+  url::RawCanonOutputT<char> canon_output;
+  url::Component component;
+
+  url::CanonicalizeQuery(input.data(),
+                         url::Component(0, static_cast<int>(input.size())),
+                         /*converter=*/nullptr, &canon_output, &component);
+
+  return StdStringFromCanonOutput(canon_output, component);
+}
+
+absl::StatusOr<std::string> HashEncodeCallback(absl::string_view input) {
+  if (input.empty())
+    return std::string();
+
+  url::RawCanonOutputT<char> canon_output;
+  url::Component component;
+
+  url::CanonicalizeRef(input.data(),
+                       url::Component(0, static_cast<int>(input.size())),
+                       &canon_output, &component);
+
+  return StdStringFromCanonOutput(canon_output, component);
+}
+
+String CanonicalizeProtocol(const String& input,
+                            ValueType type,
+                            ExceptionState& exception_state) {
+  if (type == ValueType::kPattern) {
+    // Canonicalization for patterns is handled during compilation via
+    // encoding callbacks.
+    return input;
+  }
+
+  bool result = false;
+  url::RawCanonOutputT<char> canon_output;
+  url::Component component;
+  if (input.Is8Bit()) {
+    StringUTF8Adaptor utf8(input);
+    result = url::CanonicalizeScheme(
+        utf8.data(), url::Component(0, utf8.size()), &canon_output, &component);
+  } else {
+    result = url::CanonicalizeScheme(input.Characters16(),
+                                     url::Component(0, input.length()),
+                                     &canon_output, &component);
+  }
+
+  if (!result) {
+    exception_state.ThrowTypeError("Invalid protocol '" + input + "'.");
+    return String();
+  }
+
+  return StringFromCanonOutput(canon_output, component);
+}
+
+void CanonicalizeUsernameAndPassword(const String& username,
+                                     const String& password,
+                                     ValueType type,
+                                     String& username_out,
+                                     String& password_out,
+                                     ExceptionState& exception_state) {
+  if (type == ValueType::kPattern) {
+    // Canonicalization for patterns is handled during compilation via
+    // encoding callbacks.
+    username_out = username;
+    password_out = password;
+    return;
+  }
+
+  bool result = false;
+  url::RawCanonOutputT<char> canon_output;
+  url::Component username_component;
+  url::Component password_component;
+
+  if (username && password && username.Is8Bit() && password.Is8Bit()) {
+    StringUTF8Adaptor username_utf8(username);
+    StringUTF8Adaptor password_utf8(password);
+    result = url::CanonicalizeUserInfo(
+        username_utf8.data(), url::Component(0, username_utf8.size()),
+        password_utf8.data(), url::Component(0, password_utf8.size()),
+        &canon_output, &username_component, &password_component);
+
+  } else {
+    String username16(username);
+    String password16(password);
+    username16.Ensure16Bit();
+    password16.Ensure16Bit();
+    result = url::CanonicalizeUserInfo(
+        username16.Characters16(), url::Component(0, username16.length()),
+        password16.Characters16(), url::Component(0, password16.length()),
+        &canon_output, &username_component, &password_component);
+  }
+
+  if (!result) {
+    exception_state.ThrowTypeError("Invalid username '" + username +
+                                   "' and/or password '" + password + "'.");
+    return;
+  }
+
+  if (username_component.len != -1)
+    username_out = StringFromCanonOutput(canon_output, username_component);
+  if (password_component.len != -1)
+    password_out = StringFromCanonOutput(canon_output, password_component);
+}
+
+String CanonicalizeHostname(const String& input,
+                            ValueType type,
+                            ExceptionState& exception_state) {
+  if (type == ValueType::kPattern) {
+    // Canonicalization for patterns is handled during compilation via
+    // encoding callbacks.
+    return input;
+  }
+
+  bool success = false;
+  String result = SecurityOrigin::CanonicalizeHost(input, &success);
+  if (!success) {
+    exception_state.ThrowTypeError("Invalid hostname '" + input + "'.");
+    return String();
+  }
+
+  return result;
+}
+
+String CanonicalizePort(const String& input,
+                        ValueType type,
+                        const String& protocol,
+                        ExceptionState& exception_state) {
+  if (type == ValueType::kPattern) {
+    // Canonicalization for patterns is handled during compilation via
+    // encoding callbacks.
+    return input;
+  }
+
+  int default_port = url::PORT_UNSPECIFIED;
+  if (!input.IsEmpty()) {
+    StringUTF8Adaptor protocol_utf8(protocol);
+    default_port =
+        url::DefaultPortForScheme(protocol_utf8.data(), protocol_utf8.size());
+  }
+
+  // Since ports only consist of digits there should be no encoding needed.
+  // Therefore we directly use the UTF8 encoding version of CanonicalizePort().
+  StringUTF8Adaptor utf8(input);
+  url::RawCanonOutputT<char> canon_output;
+  url::Component component;
+  if (!url::CanonicalizePort(utf8.data(), url::Component(0, utf8.size()),
+                             default_port, &canon_output, &component)) {
+    exception_state.ThrowTypeError("Invalid port '" + input + "'.");
+    return String();
+  }
+
+  return component.len == -1 ? g_empty_string
+                             : StringFromCanonOutput(canon_output, component);
+}
+
+String CanonicalizePathname(const String& protocol,
+                            const String& input,
+                            ValueType type,
+                            ExceptionState& exception_state) {
+  if (type == ValueType::kPattern) {
+    // Canonicalization for patterns is handled during compilation via
+    // encoding callbacks.
+    return input;
+  }
+
+  // Determine if we are using "standard" or "path" URL canonicalization
+  // for the pathname.  In spec terms the "path" URL behavior corresponds
+  // to "cannot-be-a-base" URLs.  We make this determination based on the
+  // protocol string since we cannot look at the number of slashes between
+  // components like the URL spec.  If this is inadequate the developer
+  // can use the baseURL property to get more strict URL behavior.
+  //
+  // We default to "standard" URL behavior to match how the empty protocol
+  // string in the URLPattern constructor results in the pathname pattern
+  // getting "standard" URL canonicalization.
+  bool standard = false;
+  if (protocol.IsEmpty()) {
+    standard = true;
+  } else if (protocol.Is8Bit()) {
+    StringUTF8Adaptor utf8(protocol);
+    standard = url::IsStandard(utf8.data(), url::Component(0, utf8.size()));
+  } else {
+    standard = url::IsStandard(protocol.Characters16(),
+                               url::Component(0, protocol.length()));
+  }
+
+  // Do not enforce absolute pathnames here since we can't enforce it
+  // it consistently in the URLPattern constructor.  This allows us to
+  // produce a match when the exact same fixed pathname string is passed
+  // to both the constructor and test()/exec().  Similarly, we use
+  // url::CanonicalizePartialPath() below instead of url::CanonicalizePath()
+  // to avoid pre-pending a slash at the start of the string.
+
+  bool result = false;
+  url::RawCanonOutputT<char> canon_output;
+  url::Component component;
+
+  const auto canonicalize_path = [&](const auto* data, int length) {
+    if (standard) {
+      return url::CanonicalizePartialPath(data, url::Component(0, length),
+                                          &canon_output, &component);
+    }
+    url::CanonicalizePathURLPath(data, url::Component(0, length), &canon_output,
+                                 &component);
+    return true;
+  };
+
+  if (input.Is8Bit()) {
+    StringUTF8Adaptor utf8(input);
+    result = canonicalize_path(utf8.data(), utf8.size());
+  } else {
+    result = canonicalize_path(input.Characters16(), input.length());
+  }
+
+  if (!result) {
+    exception_state.ThrowTypeError("Invalid pathname '" + input + "'.");
+    return String();
+  }
+
+  return StringFromCanonOutput(canon_output, component);
+}
+
+String CanonicalizeSearch(const String& input,
+                          ValueType type,
+                          ExceptionState& exception_state) {
+  if (type == ValueType::kPattern) {
+    // Canonicalization for patterns is handled during compilation via
+    // encoding callbacks.
+    return input;
+  }
+
+  url::RawCanonOutputT<char> canon_output;
+  url::Component component;
+  if (input.Is8Bit()) {
+    StringUTF8Adaptor utf8(input);
+    url::CanonicalizeQuery(utf8.data(), url::Component(0, utf8.size()),
+                           /*converter=*/nullptr, &canon_output, &component);
+  } else {
+    url::CanonicalizeQuery(input.Characters16(),
+                           url::Component(0, input.length()),
+                           /*converter=*/nullptr, &canon_output, &component);
+  }
+
+  return StringFromCanonOutput(canon_output, component);
+}
+
+String CanonicalizeHash(const String& input,
+                        ValueType type,
+                        ExceptionState& exception_state) {
+  if (type == ValueType::kPattern) {
+    // Canonicalization for patterns is handled during compilation via
+    // encoding callbacks.
+    return input;
+  }
+
+  url::RawCanonOutputT<char> canon_output;
+  url::Component component;
+  if (input.Is8Bit()) {
+    StringUTF8Adaptor utf8(input);
+    url::CanonicalizeRef(utf8.data(), url::Component(0, utf8.size()),
+                         &canon_output, &component);
+  } else {
+    url::CanonicalizeRef(input.Characters16(),
+                         url::Component(0, input.length()), &canon_output,
+                         &component);
+  }
+
+  return StringFromCanonOutput(canon_output, component);
+}
+
+}  // namespace url_pattern
+}  // namespace blink
diff --git a/third_party/blink/renderer/modules/url_pattern/url_pattern_canon.h b/third_party/blink/renderer/modules/url_pattern/url_pattern_canon.h
new file mode 100644
index 0000000..eb6f3b38
--- /dev/null
+++ b/third_party/blink/renderer/modules/url_pattern/url_pattern_canon.h
@@ -0,0 +1,83 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_URL_PATTERN_URL_PATTERN_CANON_H_
+#define THIRD_PARTY_BLINK_RENDERER_MODULES_URL_PATTERN_URL_PATTERN_CANON_H_
+
+#include "third_party/abseil-cpp/absl/status/statusor.h"
+#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
+
+namespace blink {
+
+class ExceptionState;
+
+namespace url_pattern {
+
+// An enum indicating whether the associated component values to be operated
+// on are for patterns or URLs.  Validation and canonicalization will
+// do different things depending on the type.
+enum class ValueType {
+  kPattern,
+  kURL,
+};
+
+// The following functions are callbacks that may be passed to the
+// liburlpattern::Parse() method.  Each performs validation and encoding for
+// a different URL component.
+//
+// Note that there are two different pathname callbacks for "standard" URLs
+// like `https://foo` // vs "path" URLs like `data:foo`.  Select the correct
+// callback by calling `ShouldTreatAsStandardURL()`.
+absl::StatusOr<std::string> ProtocolEncodeCallback(absl::string_view input);
+absl::StatusOr<std::string> UsernameEncodeCallback(absl::string_view input);
+absl::StatusOr<std::string> PasswordEncodeCallback(absl::string_view input);
+absl::StatusOr<std::string> HostnameEncodeCallback(absl::string_view input);
+absl::StatusOr<std::string> PortEncodeCallback(absl::string_view input);
+absl::StatusOr<std::string> StandardURLPathnameEncodeCallback(
+    absl::string_view input);
+absl::StatusOr<std::string> PathURLPathnameEncodeCallback(
+    absl::string_view input);
+absl::StatusOr<std::string> SearchEncodeCallback(absl::string_view input);
+absl::StatusOr<std::string> HashEncodeCallback(absl::string_view input);
+
+// Utility functions to canonicalize different component strings.  They will
+// throw an exception if the input is invalid.  The canonicalization and/or
+// validation will only be applied if the `type` is kURL.  These functions
+// simply pass through the value when the `type` is kPattern.  Encoding is
+// for patterns are handled later during compilation via the encode callbacks
+// above.
+//
+// The result is returned, except for `CanonicalizeUsernameAndPassword` which
+// uses separate out parameters for the resulting username and password.
+String CanonicalizeProtocol(const String& input,
+                            ValueType type,
+                            ExceptionState& exception_state);
+void CanonicalizeUsernameAndPassword(const String& username,
+                                     const String& password,
+                                     ValueType type,
+                                     String& username_out,
+                                     String& password_out,
+                                     ExceptionState& exception_state);
+String CanonicalizeHostname(const String& input,
+                            ValueType type,
+                            ExceptionState& exception_state);
+String CanonicalizePort(const String& input,
+                        ValueType type,
+                        const String& protocol,
+                        ExceptionState& exception_state);
+String CanonicalizePathname(const String& protocol,
+                            const String& input,
+                            ValueType type,
+                            ExceptionState& exception_state);
+String CanonicalizeSearch(const String& input,
+                          ValueType type,
+                          ExceptionState& exception_state);
+String CanonicalizeHash(const String& input,
+                        ValueType type,
+                        ExceptionState& exception_state);
+
+}  // namespace url_pattern
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_MODULES_URL_PATTERN_URL_PATTERN_CANON_H_
diff --git a/third_party/blink/renderer/modules/url_pattern/url_pattern_component.cc b/third_party/blink/renderer/modules/url_pattern/url_pattern_component.cc
new file mode 100644
index 0000000..4059db19
--- /dev/null
+++ b/third_party/blink/renderer/modules/url_pattern/url_pattern_component.cc
@@ -0,0 +1,278 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/blink/renderer/modules/url_pattern/url_pattern_component.h"
+
+#include "base/strings/string_util.h"
+#include "third_party/blink/renderer/modules/url_pattern/url_pattern_canon.h"
+#include "third_party/blink/renderer/platform/bindings/exception_state.h"
+#include "third_party/blink/renderer/platform/wtf/text/string_utf8_adaptor.h"
+#include "url/url_util.h"
+
+namespace blink {
+namespace url_pattern {
+
+namespace {
+
+// Utility method to convert a type to a string.
+StringView TypeToString(Component::Type type) {
+  switch (type) {
+    case Component::Type::kProtocol:
+      return "protocol";
+    case Component::Type::kUsername:
+      return "username";
+    case Component::Type::kPassword:
+      return "password";
+    case Component::Type::kHostname:
+      return "hostname";
+    case Component::Type::kPort:
+      return "port";
+    case Component::Type::kPathname:
+      return "pathname";
+    case Component::Type::kSearch:
+      return "search";
+    case Component::Type::kHash:
+      return "hash";
+  }
+  NOTREACHED();
+}
+
+// Utility method to get the correct encoding callback for a given type.
+liburlpattern::EncodeCallback GetEncodeCallback(Component::Type type,
+                                                Component* protocol_component) {
+  switch (type) {
+    case Component::Type::kProtocol:
+      return ProtocolEncodeCallback;
+    case Component::Type::kUsername:
+      return UsernameEncodeCallback;
+    case Component::Type::kPassword:
+      return PasswordEncodeCallback;
+    case Component::Type::kHostname:
+      return HostnameEncodeCallback;
+    case Component::Type::kPort:
+      return PortEncodeCallback;
+    case Component::Type::kPathname:
+      // Different types of URLs use different canonicalization for pathname.
+      // A "standard" URL flattens `.`/`..` and performs full percent encoding.
+      // A "path" URL does not flatten and uses a more lax percent encoding.
+      // The spec calls "path" URLs as "cannot-be-a-base-URL" URLs:
+      //
+      //  https://url.spec.whatwg.org/#cannot-be-a-base-url-path-state
+      //
+      // We prefer "standard" URL here by checking to see if the protocol
+      // pattern matches any of the known standard protocol strings.  So
+      // an exact pattern of `http` will match, but so will `http{s}?` and
+      // `*`.
+      //
+      // If the protocol pattern does not match any of the known standard URL
+      // protocols then we fall back to the "path" URL behavior.  This will
+      // normally be triggered by `data`, `javascript`, `about`, etc.  It
+      // will also be triggered for custom protocol strings.  We favor "path"
+      // behavior here because its better to under canonicalize since the
+      // developer can always manually canonicalize the pathname for a custom
+      // protocol.
+      //
+      // ShouldTreatAsStandardURL can by a bit expensive, so only do it if we
+      // actually have a pathname pattern to compile.
+      CHECK(protocol_component);
+      if (protocol_component->ShouldTreatAsStandardURL())
+        return StandardURLPathnameEncodeCallback;
+      else
+        return PathURLPathnameEncodeCallback;
+    case Component::Type::kSearch:
+      return SearchEncodeCallback;
+    case Component::Type::kHash:
+      return HashEncodeCallback;
+  }
+  NOTREACHED();
+}
+
+// Utility method to get the correct liburlpattern parse options for a given
+// type.
+const liburlpattern::Options& GetOptions(Component::Type type) {
+  // The liburlpattern::Options to use for most component patterns.  We
+  // default to strict mode and case sensitivity.  In addition, most
+  // components have no concept of a delimiter or prefix character.
+  DEFINE_THREAD_SAFE_STATIC_LOCAL(liburlpattern::Options, default_options,
+                                  ({.delimiter_list = "",
+                                    .prefix_list = "",
+                                    .sensitive = true,
+                                    .strict = true}));
+
+  // The liburlpattern::Options to use for hostname patterns.  This uses a
+  // "." delimiter controlling how far a named group like ":bar" will match
+  // by default.  Note, hostnames are case insensitive but we require case
+  // sensitivity here.  This assumes that the hostname values have already
+  // been normalized to lower case as in URL().
+  DEFINE_THREAD_SAFE_STATIC_LOCAL(liburlpattern::Options, hostname_options,
+                                  ({.delimiter_list = ".",
+                                    .prefix_list = "",
+                                    .sensitive = true,
+                                    .strict = true}));
+
+  // The liburlpattern::Options to use for pathname patterns.  This uses a
+  // "/" delimiter controlling how far a named group like ":bar" will match
+  // by default.  It also configures "/" to be treated as an automatic
+  // prefix before groups.
+  DEFINE_THREAD_SAFE_STATIC_LOCAL(liburlpattern::Options, pathname_options,
+                                  ({.delimiter_list = "/",
+                                    .prefix_list = "/",
+                                    .sensitive = true,
+                                    .strict = true}));
+
+  switch (type) {
+    case Component::Type::kHostname:
+      return hostname_options;
+    case Component::Type::kPathname:
+      return pathname_options;
+    case Component::Type::kProtocol:
+    case Component::Type::kUsername:
+    case Component::Type::kPassword:
+    case Component::Type::kPort:
+    case Component::Type::kSearch:
+    case Component::Type::kHash:
+      return default_options;
+  }
+  NOTREACHED();
+}
+
+}  // anonymous namespace
+
+// static
+Component* Component::Compile(const String& pattern,
+                              Type type,
+                              Component* protocol_component,
+                              ExceptionState& exception_state) {
+  // If the pattern is null then return a special Component object that matches
+  // any input as if the pattern was `*`.
+  if (pattern.IsNull()) {
+    return MakeGarbageCollected<Component>(type, base::PassKey<Component>());
+  }
+
+  const liburlpattern::Options& options = GetOptions(type);
+
+  // Parse the pattern.
+  StringUTF8Adaptor utf8(pattern);
+  auto parse_result = liburlpattern::Parse(
+      absl::string_view(utf8.data(), utf8.size()),
+      GetEncodeCallback(type, protocol_component), options);
+  if (!parse_result.ok()) {
+    exception_state.ThrowTypeError("Invalid " + TypeToString(type) +
+                                   " pattern '" + pattern + "'.");
+    return nullptr;
+  }
+
+  // Extract a regular expression string from the parsed pattern.
+  std::vector<std::string> name_list;
+  std::string regexp_string =
+      parse_result.value().GenerateRegexString(&name_list);
+
+  // Compile the regular expression to verify it is valid.
+  auto case_sensitive = options.sensitive ? WTF::kTextCaseSensitive
+                                          : WTF::kTextCaseASCIIInsensitive;
+  DCHECK(base::IsStringASCII(regexp_string));
+  ScriptRegexp* regexp = MakeGarbageCollected<ScriptRegexp>(
+      String(regexp_string.data(), regexp_string.size()), case_sensitive,
+      kMultilineDisabled, ScriptRegexp::UTF16);
+  if (!regexp->IsValid()) {
+    // The regular expression failed to compile.  This means that some
+    // custom regexp group within the pattern is illegal.  Attempt to
+    // compile each regexp group individually in order to identify the
+    // culprit.
+    for (auto& part : parse_result.value().PartList()) {
+      if (part.type != liburlpattern::PartType::kRegex)
+        continue;
+      DCHECK(base::IsStringASCII(part.value));
+      String group_value(part.value.data(), part.value.size());
+      regexp = MakeGarbageCollected<ScriptRegexp>(
+          group_value, case_sensitive, kMultilineDisabled, ScriptRegexp::UTF16);
+      if (regexp->IsValid())
+        continue;
+      exception_state.ThrowTypeError("Invalid " + TypeToString(type) +
+                                     " pattern '" + pattern +
+                                     "'. Custom regular expression group '" +
+                                     group_value + "' is invalid.");
+      return nullptr;
+    }
+    // We couldn't find a bad regexp group, but we still have an overall
+    // error.  This shouldn't happen, but we handle it anyway.
+    exception_state.ThrowTypeError("Invalid " + TypeToString(type) +
+                                   " pattern '" + pattern +
+                                   "'. An unexpected error has occurred.");
+    return nullptr;
+  }
+
+  Vector<String> wtf_name_list;
+  wtf_name_list.ReserveInitialCapacity(
+      static_cast<wtf_size_t>(name_list.size()));
+  for (const auto& name : name_list) {
+    wtf_name_list.push_back(String::FromUTF8(name.data(), name.size()));
+  }
+
+  return MakeGarbageCollected<Component>(
+      type, std::move(parse_result.value()), std::move(regexp),
+      std::move(wtf_name_list), base::PassKey<Component>());
+}
+
+Component::Component(Type type,
+                     liburlpattern::Pattern pattern,
+                     ScriptRegexp* regexp,
+                     Vector<String> name_list,
+                     base::PassKey<Component> key)
+    : type_(type),
+      pattern_(std::move(pattern)),
+      regexp_(regexp),
+      name_list_(std::move(name_list)) {}
+
+Component::Component(Type type, base::PassKey<Component> key)
+    : type_(type), name_list_({"0"}) {}
+
+bool Component::Match(StringView input, Vector<String>* group_list) const {
+  if (regexp_) {
+    return regexp_->Match(input, /*start_from=*/0, /*match_length=*/nullptr,
+                          group_list) == 0;
+  } else {
+    if (group_list)
+      group_list->push_back(input.ToString());
+    return true;
+  }
+}
+
+String Component::GeneratePatternString() const {
+  if (pattern_.has_value())
+    return String::FromUTF8(pattern_->GeneratePatternString());
+  else
+    return "*";
+}
+
+Vector<std::pair<String, String>> Component::MakeGroupList(
+    const Vector<String>& group_values) const {
+  DCHECK_EQ(name_list_.size(), group_values.size());
+  Vector<std::pair<String, String>> result;
+  result.ReserveInitialCapacity(group_values.size());
+  for (wtf_size_t i = 0; i < group_values.size(); ++i) {
+    result.emplace_back(name_list_[i], group_values[i]);
+  }
+  return result;
+}
+
+bool Component::ShouldTreatAsStandardURL() const {
+  DCHECK(type_ == Type::kProtocol);
+  if (!pattern_.has_value())
+    return true;
+  const auto protocol_matches = [&](const std::string& scheme) {
+    DCHECK(base::IsStringASCII(scheme));
+    return Match(
+        StringView(scheme.data(), static_cast<unsigned>(scheme.size())),
+        /*group_list=*/nullptr);
+  };
+  return base::ranges::any_of(url::GetStandardSchemes(), protocol_matches);
+}
+
+void Component::Trace(Visitor* visitor) const {
+  visitor->Trace(regexp_);
+}
+
+}  // namespace url_pattern
+}  // namespace blink
diff --git a/third_party/blink/renderer/modules/url_pattern/url_pattern_component.h b/third_party/blink/renderer/modules/url_pattern/url_pattern_component.h
new file mode 100644
index 0000000..f5793e04
--- /dev/null
+++ b/third_party/blink/renderer/modules/url_pattern/url_pattern_component.h
@@ -0,0 +1,106 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_URL_PATTERN_URL_PATTERN_COMPONENT_H_
+#define THIRD_PARTY_BLINK_RENDERER_MODULES_URL_PATTERN_URL_PATTERN_COMPONENT_H_
+
+#include "base/types/pass_key.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+#include "third_party/blink/renderer/bindings/core/v8/script_regexp.h"
+#include "third_party/blink/renderer/platform/heap/heap.h"
+#include "third_party/blink/renderer/platform/heap/member.h"
+#include "third_party/blink/renderer/platform/heap/trace_traits.h"
+#include "third_party/blink/renderer/platform/wtf/vector.h"
+#include "third_party/liburlpattern/parse.h"
+#include "third_party/liburlpattern/pattern.h"
+
+namespace blink {
+
+class ExceptionState;
+
+namespace url_pattern {
+
+// A struct representing all the information needed to match a particular
+// component of a URL.
+class Component final : public GarbageCollected<Component> {
+ public:
+  // Enumeration defining the different types of components.  Each component
+  // type uses a slightly different kind of character encoding.  In addition,
+  // different component types using different liburlpattern parse options.
+  enum class Type {
+    kProtocol,
+    kUsername,
+    kPassword,
+    kHostname,
+    kPort,
+    kPathname,
+    kSearch,
+    kHash,
+  };
+
+  // A utility function that takes a given `pattern` and compiles it into a
+  // Component structure.  If the `pattern` is null then nullptr
+  // may be returned without throwing an exception.  In this case the
+  // Component is not constructed and the nullptr value should be
+  // treated as matching any input value for the component.  The `type`
+  // specifies which URL component is the pattern is being compiled for.  This
+  // will select the correct encoding callback, liburlpattern options, and
+  // populate errors messages with the correct component string.
+  static Component* Compile(const String& pattern,
+                            Type type,
+                            Component* protocol_component,
+                            ExceptionState& exception_state);
+
+  // Constructs a Component with a real `pattern` that compiled to the given
+  // `regexp`.
+  Component(Type type,
+            liburlpattern::Pattern pattern,
+            ScriptRegexp* regexp,
+            Vector<String> name_list,
+            base::PassKey<Component> key);
+
+  // Constructs an empty Component that matches any input as if it had the
+  // pattern `*`.
+  Component(Type type, base::PassKey<Component> key);
+
+  // Match the given `input` against the component pattern.  Returns `true`
+  // if there is a match.  If `group_list` is not nullptr, then it will be
+  // populated with group values captured by the pattern.
+  bool Match(StringView input, Vector<String>* group_list) const;
+
+  // Convert the compiled component pattern back into a pattern string.  This
+  // will be functionally equivalent to the original, but may differ based on
+  // canonicalization that occurred during parsing.
+  String GeneratePatternString() const;
+
+  // Combines the given list of group values with the group names specified in
+  // the original pattern.  The return result is a vector of name:value tuples.
+  Vector<std::pair<String, String>> MakeGroupList(
+      const Vector<String>& group_values) const;
+
+  // Method to determine if the URL associated with this component should be
+  // treated as a "standard" URL like `https://foo` vs a "path" URL like
+  // `data:foo`.  This should only be called for kProtocol components.
+  bool ShouldTreatAsStandardURL() const;
+
+  void Trace(Visitor* visitor) const;
+
+ private:
+  const Type type_;
+
+  // The parsed pattern.
+  const absl::optional<liburlpattern::Pattern> pattern_;
+
+  // The pattern compiled down to a js regular expression.
+  const Member<ScriptRegexp> regexp_;
+
+  // The names to be applied to the regular expression capture groups.  Note,
+  // liburlpattern regular expressions do not use named capture groups directly.
+  const Vector<String> name_list_;
+};
+
+}  // namespace url_pattern
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_MODULES_URL_PATTERN_URL_PATTERN_COMPONENT_H_
diff --git a/third_party/blink/renderer/modules/url_pattern/url_pattern_parser.cc b/third_party/blink/renderer/modules/url_pattern/url_pattern_parser.cc
new file mode 100644
index 0000000..acb89aa
--- /dev/null
+++ b/third_party/blink/renderer/modules/url_pattern/url_pattern_parser.cc
@@ -0,0 +1,346 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/blink/renderer/modules/url_pattern/url_pattern_parser.h"
+
+#include "base/notreached.h"
+#include "third_party/blink/renderer/modules/url_pattern/url_pattern_component.h"
+#include "third_party/blink/renderer/modules/url_pattern/url_pattern_init.h"
+#include "third_party/blink/renderer/platform/bindings/exception_state.h"
+#include "third_party/blink/renderer/platform/wtf/text/string_utf8_adaptor.h"
+#include "third_party/liburlpattern/tokenize.h"
+
+namespace blink {
+namespace url_pattern {
+
+Parser::Parser(const String& input) : input_(input), utf8_(input) {}
+
+void Parser::Parse(ExceptionState& exception_state) {
+  auto tokenize_result =
+      liburlpattern::Tokenize(absl::string_view(utf8_.data(), utf8_.size()),
+                              liburlpattern::TokenizePolicy::kLenient);
+  if (!tokenize_result.ok()) {
+    // This should not happen with kLenient mode, but we handle it anyway.
+    exception_state.ThrowTypeError("Invalid input string '" + input_ +
+                                   "'. It unexpectedly fails to tokenize.");
+    return;
+  }
+
+  token_list_ = std::move(tokenize_result.value());
+  result_ = MakeGarbageCollected<URLPatternInit>();
+
+  // We start in relative mode by default.  If we find a protocol `:` later,
+  // we will update the starting state to expect an absolute URL pattern.
+  DCHECK_EQ(state_, StringParseState::kPathname);
+
+  // When constructing a pattern using structured input like
+  // `new URLPattern({ pathname: 'foo' })` any missing components will be
+  // defaulted to wildcards.  In this case, however, we default any missing
+  // components to the empty string.  This is due to there being no way to
+  // simply "leave out" a component when writing a URL.  The behavior also
+  // matches the URL constructor.
+  //
+  // To that end we initialize components that would be set for a relative
+  // pattern to the empty string default.  We don't do this for other
+  // components right now so that any base URL value can set those components.
+  result_->setPathname(g_empty_string);
+  result_->setSearch(g_empty_string);
+  result_->setHash(g_empty_string);
+
+  // Scan for protocol `:` terminator.  This should be an invalid pattern
+  // character.  This automatically works for "https://" because a name
+  // cannot start with a `/`.  For URLs that do not include "//", however,
+  // the input string will need to escape the colon, e.g. "data\\:foo".
+  for (size_t i = 0; i < token_list_.size(); ++i) {
+    if (IsProtocolSuffix(i)) {
+      // Update the state to expect the start of an absolute URL.
+      state_ = StringParseState::kProtocol;
+
+      // Now that we are in absolute mode we know values will not be inherited
+      // from a base URL.  Therefore initialize the rest of the components to
+      // the empty string.
+      result_->setProtocol(g_empty_string);
+      result_->setUsername(g_empty_string);
+      result_->setPassword(g_empty_string);
+      result_->setHostname(g_empty_string);
+      result_->setPort(g_empty_string);
+      break;
+    }
+  }
+
+  // Iterate through the list of tokens and update our state machine as we go.
+  for (token_index_ = 0; token_index_ < token_list_.size(); ++token_index_) {
+    // All states must respect the end of the token list.  The liburlpattern
+    // tokenizer guarantees that the last token will have the type `kEnd`.
+    if (token_list_[token_index_].type == liburlpattern::TokenType::kEnd) {
+      ChangeState(StringParseState::kDone, Skip(0));
+      break;
+    }
+
+    // In addition, all states must handle pattern groups.  We do not permit
+    // a component to end in the middle of a pattern group.  Therefore we skip
+    // past any tokens that are within `{` and `}`.  Note, the tokenizer
+    // handles grouping `(` and `)` and `:foo` groups for us automatically, so
+    // we don't need special code for them here.
+    if (in_group_) {
+      if (IsGroupClose())
+        in_group_ = false;
+      else
+        continue;
+    }
+
+    if (IsGroupOpen()) {
+      in_group_ = true;
+      continue;
+    }
+
+    switch (state_) {
+      case StringParseState::kProtocol: {
+        // If we find the end of the protocol component...
+        if (IsProtocolSuffix(token_index_)) {
+          // First we eagerly compile the protocol pattern and use it to
+          // compute if this entire URLPattern should be treated as a
+          // "standard" URL.  If any of the special schemes, like `https`,
+          // match the protocol pattern then we treat it as standard.  This
+          // also forces the default pathname to be `/` instead of the empty
+          // string.
+          ComputeShouldTreatAsStandardURL(exception_state);
+          if (exception_state.HadException())
+            return;
+
+          // Next, if there are authority slashes, like `https://`, then
+          // we must transition to the authority section of the URLPattern.
+          // We explicitly don't support username and password here.  We
+          // instead go straight to hostname.
+          if (NextIsAuthoritySlashes())
+            ChangeState(StringParseState::kHostname, Skip(3));
+
+          // If there are no authority slashes, but the protocol is special
+          // then we still go to the hostname as this is a "standard" URL.
+          // This differs from the above case since we don't need to skip the
+          // extra slashes.
+          else if (should_treat_as_standard_url_)
+            ChangeState(StringParseState::kHostname, Skip(1));
+
+          // Otherwise we treat this as a "cannot-be-a-base-URL" or what chrome
+          // calls a "path" URL.  In this case we go straight to the pathname
+          // component.  The hostname and port are left with their default
+          // empty string values.
+          else
+            ChangeState(StringParseState::kPathname, Skip(1));
+        }
+        break;
+      }
+
+      case StringParseState::kHostname:
+        // If we find a `:` then we transition to the port component state.
+        if (IsPortPrefix())
+          ChangeState(StringParseState::kPort, Skip(1));
+
+        // If we find a `/` then we transition to the pathname component state.
+        else if (IsPathnameStart())
+          ChangeState(StringParseState::kPathname, Skip(0));
+
+        // If we find a `?` then we transition to the search component state.
+        else if (IsSearchPrefix())
+          ChangeState(StringParseState::kSearch, Skip(1));
+
+        // If we find a `#` then we transition to the hash component state.
+        else if (IsHashPrefix())
+          ChangeState(StringParseState::kHash, Skip(1));
+        break;
+
+      case StringParseState::kPort:
+        // If we find a `/` then we transition to the pathname component state.
+        if (IsPathnameStart())
+          ChangeState(StringParseState::kPathname, Skip(0));
+        // If we find a `?` then we transition to the search component state.
+        else if (IsSearchPrefix())
+          ChangeState(StringParseState::kSearch, Skip(1));
+        // If we find a `#` then we transition to the hash component state.
+        else if (IsHashPrefix())
+          ChangeState(StringParseState::kHash, Skip(1));
+        break;
+      case StringParseState::kPathname:
+        // If we find a `?` then we transition to the search component state.
+        if (IsSearchPrefix())
+          ChangeState(StringParseState::kSearch, Skip(1));
+        // If we find a `#` then we transition to the hash component state.
+        else if (IsHashPrefix())
+          ChangeState(StringParseState::kHash, Skip(1));
+        break;
+      case StringParseState::kSearch:
+        // If we find a `#` then we transition to the hash component state.
+        if (IsHashPrefix())
+          ChangeState(StringParseState::kHash, Skip(1));
+        break;
+      case StringParseState::kHash:
+        // Nothing to do here as we are just looking for the end.
+        break;
+      case StringParseState::kDone:
+        NOTREACHED();
+        break;
+    };
+  }
+}
+
+void Parser::ChangeState(StringParseState new_state, Skip skip) {
+  // First we convert the tokens between `component_start_` and `token_index_`
+  // a component pattern string.  This is stored in the appropriate result
+  // property based on the current `state_`.
+  switch (state_) {
+    case StringParseState::kProtocol: {
+      result_->setProtocol(MakeComponentString());
+      break;
+    }
+    case StringParseState::kHostname:
+      result_->setHostname(MakeComponentString());
+      break;
+    case StringParseState::kPort:
+      result_->setPort(MakeComponentString());
+      break;
+    case StringParseState::kPathname:
+      result_->setPathname(MakeComponentString());
+      break;
+    case StringParseState::kSearch:
+      result_->setSearch(MakeComponentString());
+      break;
+    case StringParseState::kHash:
+      result_->setHash(MakeComponentString());
+      break;
+    case StringParseState::kDone:
+      NOTREACHED();
+      break;
+  }
+
+  // Next move to the new state.
+  state_ = new_state;
+
+  // Now update `component_start_` to point to the new component.  The `skip`
+  // argument tells us how many tokens to ignore to get to the next start.
+  component_start_ = SafeToken(token_index_ + skip.value()).index;
+
+  // Next, move the `token_index_` so that the top of the loop will begin
+  // parsing the new component.  The index will be automatically incremented by
+  // the parse loop, so we move one less than the indicated `skip` amount.  This
+  // means `kNone` and `kOne` are equivalent for setting `token_index_`.  Note,
+  // however, these enums do have a different effect on setting
+  // `component_start_` above.
+  if (skip.value() > 1)
+    token_index_ += (skip.value() - 1);
+}
+
+const liburlpattern::Token& Parser::SafeToken(size_t index) const {
+  if (index < token_list_.size())
+    return token_list_[index];
+  DCHECK(!token_list_.empty());
+  DCHECK(token_list_.back().type == liburlpattern::TokenType::kEnd);
+  return token_list_.back();
+}
+
+bool Parser::IsNonSpecialPatternChar(size_t index, const char* value) const {
+  const liburlpattern::Token& token = SafeToken(index);
+  return token.value == value &&
+         (token.type == liburlpattern::TokenType::kChar ||
+          token.type == liburlpattern::TokenType::kEscapedChar ||
+          token.type == liburlpattern::TokenType::kInvalidChar);
+}
+
+bool Parser::IsProtocolSuffix(size_t index) const {
+  return IsNonSpecialPatternChar(index, ":");
+}
+
+bool Parser::NextIsAuthoritySlashes() const {
+  return IsNonSpecialPatternChar(token_index_ + 1, "/") &&
+         IsNonSpecialPatternChar(token_index_ + 2, "/");
+}
+
+bool Parser::IsPortPrefix() const {
+  return IsNonSpecialPatternChar(token_index_, ":");
+}
+
+bool Parser::IsPathnameStart() const {
+  return IsNonSpecialPatternChar(token_index_, "/");
+}
+
+bool Parser::IsSearchPrefix() const {
+  if (IsNonSpecialPatternChar(token_index_, "?"))
+    return true;
+
+  if (token_list_[token_index_].value != "?")
+    return false;
+
+  // If we have a "?" that is not a normal character, then it must be an
+  // optional group modifier.
+  DCHECK_EQ(SafeToken(token_index_).type,
+            liburlpattern::TokenType::kOtherModifier);
+
+  // We have a `?` tokenized as a modifier.  We only want to treat this as
+  // the search prefix if it would not normally be valid in a liburlpattern
+  // string.  A modifier must follow a matching group.  Therefore we inspect
+  // the preceding token to see if the `?` is immediately following a group
+  // construct.
+  //
+  // So if the string is:
+  //
+  //  https://example.com/foo?bar
+  //
+  // Then we return true because the previous token is a `o` with type kChar.
+  // For the string:
+  //
+  //  https://example.com/:name?bar
+  //
+  // Then we return false because the previous token is `:name` with type
+  // kName.  If the developer intended this to be a search prefix then they
+  // would need to escape like question mark like `:name\\?bar`.
+  //
+  // Note, if `token_index_` is zero the index will wrap around and
+  // `SafeToken()` will return the kEnd token.  This will correctly return true
+  // from this method as a pattern cannot normally begin with an unescaped `?`.
+  const auto& previous_token = SafeToken(token_index_ - 1);
+  return previous_token.type != liburlpattern::TokenType::kName &&
+         previous_token.type != liburlpattern::TokenType::kRegex &&
+         previous_token.type != liburlpattern::TokenType::kClose &&
+         previous_token.type != liburlpattern::TokenType::kAsterisk;
+}
+
+bool Parser::IsHashPrefix() const {
+  return IsNonSpecialPatternChar(token_index_, "#");
+}
+
+bool Parser::IsGroupOpen() const {
+  return token_list_[token_index_].type == liburlpattern::TokenType::kOpen;
+}
+
+bool Parser::IsGroupClose() const {
+  return token_list_[token_index_].type == liburlpattern::TokenType::kClose;
+}
+
+String Parser::MakeComponentString() const {
+  DCHECK_LT(token_index_, token_list_.size());
+  const auto& token = token_list_[token_index_];
+
+  DCHECK_LE(component_start_, utf8_.size());
+  DCHECK_GE(token.index, component_start_);
+  DCHECK(token.index < utf8_.size() ||
+         (token.index == utf8_.size() &&
+          token.type == liburlpattern::TokenType::kEnd));
+
+  return String::FromUTF8(utf8_.data() + component_start_,
+                          token.index - component_start_);
+}
+
+void Parser::ComputeShouldTreatAsStandardURL(ExceptionState& exception_state) {
+  DCHECK_EQ(state_, StringParseState::kProtocol);
+  protocol_component_ =
+      Component::Compile(MakeComponentString(), Component::Type::kProtocol,
+                         /*protocol_component=*/nullptr, exception_state);
+  if (protocol_component_ && protocol_component_->ShouldTreatAsStandardURL()) {
+    should_treat_as_standard_url_ = true;
+    result_->setPathname("/");
+  }
+}
+
+}  // namespace url_pattern
+}  // namespace blink
diff --git a/third_party/blink/renderer/modules/url_pattern/url_pattern_parser.h b/third_party/blink/renderer/modules/url_pattern/url_pattern_parser.h
new file mode 100644
index 0000000..02cbe51
--- /dev/null
+++ b/third_party/blink/renderer/modules/url_pattern/url_pattern_parser.h
@@ -0,0 +1,130 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_URL_PATTERN_URL_PATTERN_PARSER_H_
+#define THIRD_PARTY_BLINK_RENDERER_MODULES_URL_PATTERN_URL_PATTERN_PARSER_H_
+
+#include <vector>
+
+#include "base/types/strong_alias.h"
+#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
+#include "third_party/blink/renderer/platform/wtf/text/string_utf8_adaptor.h"
+#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
+
+namespace liburlpattern {
+struct Token;
+}  // namespace liburlpattern
+
+namespace blink {
+
+class ExceptionState;
+class URLPatternInit;
+
+namespace url_pattern {
+
+class Component;
+
+class Parser final {
+  STACK_ALLOCATED();
+
+ public:
+  explicit Parser(const String& input);
+
+  // Attempt to parse the the input string used to construct the Parser object.
+  // This method may only be called once.  Any errors will be thrown on the
+  // give `exception_state`.  Retrieve the parse result by calling
+  // `GetResult()`.  A protocol component will also be eagerly compiled for
+  // absolute pattern strings.  It is not compiled for relative pattern string.
+  // The compiled protocol Component can be accessed by calling
+  // `GetProtocolComponent()`.
+  void Parse(ExceptionState& exception_state);
+
+  // Return the parse result.  Should only be called after `Parse()` succeeds.
+  URLPatternInit* GetResult() const { return result_; }
+
+  // Return the protocol component if it was compiled as part of parsing the
+  // input string.  This should only be called after `Parse()` succeeds.
+  // This will return nullptr if the input was a relative pattern string.
+  Component* GetProtocolComponent() const { return protocol_component_; }
+
+ private:
+  enum class StringParseState {
+    kProtocol,
+    kHostname,
+    kPort,
+    kPathname,
+    kSearch,
+    kHash,
+    kDone,
+  };
+
+  using Skip = base::StrongAlias<class SkipTag, int>;
+
+  // A utility function to move from the current `state_` to `new_state`.  This
+  // method will populate the component string in `result_` corresponding to the
+  // current `state_` automatically.  It will also set `component_start_` and
+  // `token_index_` to point to the first token of the next section based on how
+  // many tokens the `skip` argument indicates should be ignored.
+  void ChangeState(StringParseState new_state, Skip skip);
+
+  // Attempt to access the Token at the given `index`.  If the `index` is out
+  // of bounds for the `token_list_`, then the last Token in the list is
+  // returned.  This will always be a `TokenType::kEnd` token.
+  const liburlpattern::Token& SafeToken(size_t index) const;
+
+  // Returns true if the token at the given `index` is not a special pattern
+  // character and if it matches the given `value`.  This simply checks that the
+  // token type is kChar, kEscapedChar, or kInvalidChar.
+  bool IsNonSpecialPatternChar(size_t index, const char* value) const;
+
+  // Returns true if the token at the given `index` is the protocol component
+  // suffix; e.g. ':'.
+  bool IsProtocolSuffix(size_t index) const;
+
+  // Returns true if the next two tokens are slashes; e.g. `//`.
+  bool NextIsAuthoritySlashes() const;
+
+  // Returns true if the current token is the port prefix; e.g. `:`.
+  bool IsPortPrefix() const;
+
+  // Returns true if the current token is the start of the pathname; e.g. `/`.
+  bool IsPathnameStart() const;
+
+  // Returns true if the current token is the search component prefix; e.g. `?`.
+  // This also takes into account if this could be a valid pattern modifier by
+  // looking at the preceding tokens.
+  bool IsSearchPrefix() const;
+
+  // Returns true if the current token is the hsah component prefix; e.g. `#`.
+  bool IsHashPrefix() const;
+
+  // These methods indicate if the current token is opening or closing a pattern
+  // grouping; e.g. `{` or `}`.
+  bool IsGroupOpen() const;
+  bool IsGroupClose() const;
+
+  // This method returns a String consisting of the tokens between
+  // `component_start_` and the current `token_index_`.
+  String MakeComponentString() const;
+
+  // Returns true if this URL should be treated as a "standard URL".  These URLs
+  // automatically append a `/` for the pathname if one is not specified.
+  void ComputeShouldTreatAsStandardURL(ExceptionState& exception_state);
+
+  const String input_;
+  const StringUTF8Adaptor utf8_;
+  URLPatternInit* result_ = nullptr;
+  Component* protocol_component_ = nullptr;
+  std::vector<liburlpattern::Token> token_list_;
+  size_t component_start_ = 0;
+  size_t token_index_ = 0;
+  StringParseState state_ = StringParseState::kPathname;
+  bool in_group_ = false;
+  bool should_treat_as_standard_url_ = false;
+};
+
+}  // namespace url_pattern
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_MODULES_URL_PATTERN_URL_PATTERN_PARSER_H_
diff --git a/third_party/blink/renderer/modules/webcodecs/audio_encoder.cc b/third_party/blink/renderer/modules/webcodecs/audio_encoder.cc
index 9bd5e638..8c8e1b33 100644
--- a/third_party/blink/renderer/modules/webcodecs/audio_encoder.cc
+++ b/third_party/blink/renderer/modules/webcodecs/audio_encoder.cc
@@ -9,6 +9,8 @@
 
 #include "base/metrics/histogram_macros.h"
 #include "base/numerics/safe_conversions.h"
+#include "base/trace_event/common/trace_event_common.h"
+#include "base/trace_event/trace_event.h"
 #include "media/audio/audio_opus_encoder.h"
 #include "media/base/audio_parameters.h"
 #include "media/base/limits.h"
@@ -28,6 +30,8 @@
 
 namespace {
 
+constexpr const char kCategory[] = "media";
+
 AudioEncoderTraits::ParsedConfig* ParseConfigStatic(
     const AudioEncoderConfig* config,
     ExceptionState& exception_state) {
@@ -166,6 +170,8 @@
   DCHECK_EQ(active_config_->codec, media::kCodecOpus);
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
+  request->StartTracing();
+
   auto software_encoder = std::make_unique<media::AudioOpusEncoder>();
   media_encoder_ = std::make_unique<media::OffloadingAudioEncoder>(
       std::move(software_encoder));
@@ -176,10 +182,12 @@
       // the time the callback is executed.
       WrapCrossThreadPersistent(active_config_.Get()), reset_count_));
 
-  auto done_callback = [](AudioEncoder* self, uint32_t reset_count,
-                          media::AudioCodec codec, media::Status status) {
-    if (!self || self->reset_count_ != reset_count)
+  auto done_callback = [](AudioEncoder* self, media::AudioCodec codec,
+                          Request* req, media::Status status) {
+    if (!self || self->reset_count_ != req->reset_count) {
+      req->EndTracing(/*aborted=*/true);
       return;
+    }
     DCHECK_CALLED_ON_VALID_SEQUENCE(self->sequence_checker_);
     if (!status.is_ok()) {
       self->HandleError(
@@ -188,6 +196,8 @@
       UMA_HISTOGRAM_ENUMERATION("Blink.WebCodecs.AudioEncoder.Codec", codec,
                                 media::kAudioCodecMax + 1);
     }
+
+    req->EndTracing();
     self->stall_request_processing_ = false;
     self->ProcessRequests();
   };
@@ -197,8 +207,8 @@
   media_encoder_->Initialize(
       active_config_->options, std::move(output_cb),
       ConvertToBaseOnceCallback(CrossThreadBindOnce(
-          done_callback, WrapCrossThreadWeakPersistent(this), reset_count_,
-          active_config_->codec)));
+          done_callback, WrapCrossThreadWeakPersistent(this),
+          active_config_->codec, WrapCrossThreadPersistent(request))));
 }
 
 void AudioEncoder::ProcessEncode(Request* request) {
@@ -208,6 +218,8 @@
   DCHECK_EQ(request->type, Request::Type::kEncode);
   DCHECK_GT(requested_encodes_, 0);
 
+  request->StartTracing();
+
   auto* audio_data = request->input.Release();
 
   auto data = audio_data->data();
@@ -215,15 +227,19 @@
   // The data shouldn't be closed at this point.
   DCHECK(data);
 
-  auto done_callback = [](AudioEncoder* self, uint32_t reset_count,
+  auto done_callback = [](AudioEncoder* self, Request* req,
                           media::Status status) {
-    if (!self || self->reset_count_ != reset_count)
+    if (!self || self->reset_count_ != req->reset_count) {
+      req->EndTracing(/*aborted=*/true);
       return;
+    }
     DCHECK_CALLED_ON_VALID_SEQUENCE(self->sequence_checker_);
     if (!status.is_ok()) {
       self->HandleError(
           self->logger_->MakeException("Encoding error.", status));
     }
+
+    req->EndTracing();
     self->ProcessRequests();
   };
 
@@ -235,6 +251,9 @@
 
     HandleError(logger_->MakeException(
         "Input audio buffer is incompatible with codec parameters", error));
+
+    request->EndTracing();
+
     audio_data->close();
     return;
   }
@@ -244,10 +263,12 @@
   auto audio_bus = media::AudioBuffer::WrapOrCopyToAudioBus(data);
 
   base::TimeTicks timestamp = base::TimeTicks() + data->timestamp();
-  media_encoder_->Encode(
-      std::move(audio_bus), timestamp,
-      ConvertToBaseOnceCallback(CrossThreadBindOnce(
-          done_callback, WrapCrossThreadWeakPersistent(this), reset_count_)));
+
+  --requested_encodes_;
+  media_encoder_->Encode(std::move(audio_bus), timestamp,
+                         ConvertToBaseOnceCallback(CrossThreadBindOnce(
+                             done_callback, WrapCrossThreadWeakPersistent(this),
+                             WrapCrossThreadPersistent(request))));
 
   audio_data->close();
 }
@@ -313,8 +334,13 @@
     metadata->setDecoderConfig(decoder_config);
   }
 
+  TRACE_EVENT_BEGIN1(kCategory, GetTraceNames()->output.c_str(), "timestamp",
+                     chunk->timestamp());
+
   ScriptState::Scope scope(script_state_);
   output_callback_->InvokeAndReportException(nullptr, chunk, metadata);
+
+  TRACE_EVENT_END0(kCategory, GetTraceNames()->output.c_str());
 }
 
 // static
diff --git a/third_party/blink/renderer/modules/webcodecs/codec_trace_names.h b/third_party/blink/renderer/modules/webcodecs/codec_trace_names.h
index 776c9f1..7827b050 100644
--- a/third_party/blink/renderer/modules/webcodecs/codec_trace_names.h
+++ b/third_party/blink/renderer/modules/webcodecs/codec_trace_names.h
@@ -17,8 +17,11 @@
     encode = codec_name + "::Encode";
     decode = codec_name + "::Decode";
     flush = codec_name + "::Flush";
+    handle_error = codec_name + "::HandleError";
     output = codec_name + "::Ouput";
     reset = codec_name + "::Reset";
+    reconfigure = codec_name + "::Reconfigure";
+    requests_counter = codec_name + " requests";
     shutdown = codec_name + "::Shutdown";
   }
 
@@ -32,8 +35,11 @@
   std::string encode;
   std::string decode;
   std::string flush;
+  std::string handle_error;
   std::string output;
   std::string reset;
+  std::string reconfigure;
+  std::string requests_counter;
   std::string shutdown;
 };
 
diff --git a/third_party/blink/renderer/modules/webcodecs/decoder_template.cc b/third_party/blink/renderer/modules/webcodecs/decoder_template.cc
index e4ca1009..85289a2 100644
--- a/third_party/blink/renderer/modules/webcodecs/decoder_template.cc
+++ b/third_party/blink/renderer/modules/webcodecs/decoder_template.cc
@@ -8,6 +8,7 @@
 #include <utility>
 #include <vector>
 
+#include "base/atomic_sequence_num.h"
 #include "base/logging.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/metrics/histogram_functions.h"
@@ -51,6 +52,8 @@
 
 namespace {
 constexpr const char kCategory[] = "media";
+
+base::AtomicSequenceNumber g_sequence_num_for_counters;
 }  // namespace
 
 // static
@@ -67,7 +70,8 @@
                                          ExceptionState& exception_state)
     : ExecutionContextLifecycleObserver(ExecutionContext::From(script_state)),
       script_state_(script_state),
-      state_(V8CodecState::Enum::kUnconfigured) {
+      state_(V8CodecState::Enum::kUnconfigured),
+      trace_counter_id_(g_sequence_num_for_counters.GetNext()) {
   DVLOG(1) << __func__;
   DCHECK(init->hasOutput());
   DCHECK(init->hasError());
@@ -678,8 +682,8 @@
 
 template <typename Traits>
 void DecoderTemplate<Traits>::TraceQueueSizes() const {
-  TRACE_COUNTER_ID2(kCategory, "pending requests", this, "decodes",
-                    num_pending_decodes_, "other",
+  TRACE_COUNTER_ID2(kCategory, GetTraceNames()->requests_counter.c_str(),
+                    trace_counter_id_, "decodes", num_pending_decodes_, "other",
                     requests_.size() - num_pending_decodes_);
 }
 
diff --git a/third_party/blink/renderer/modules/webcodecs/decoder_template.h b/third_party/blink/renderer/modules/webcodecs/decoder_template.h
index 8324589..00859e1 100644
--- a/third_party/blink/renderer/modules/webcodecs/decoder_template.h
+++ b/third_party/blink/renderer/modules/webcodecs/decoder_template.h
@@ -210,6 +210,10 @@
 
   // TODO(sandersd): Can this just be a HashSet by ptr comparison?
   uint32_t pending_decode_id_ = 0;
+
+  // Used to differentiate Decoders' counters during tracing.
+  int trace_counter_id_;
+
   HeapHashMap<uint32_t, Member<Request>> pending_decodes_;
 };
 
diff --git a/third_party/blink/renderer/modules/webcodecs/encoder_base.cc b/third_party/blink/renderer/modules/webcodecs/encoder_base.cc
index 5bc5fc8f..4113fde 100644
--- a/third_party/blink/renderer/modules/webcodecs/encoder_base.cc
+++ b/third_party/blink/renderer/modules/webcodecs/encoder_base.cc
@@ -6,12 +6,15 @@
 
 #include <string>
 
+#include "base/atomic_sequence_num.h"
 #include "base/bind.h"
 #include "base/callback.h"
 #include "base/callback_helpers.h"
 #include "base/logging.h"
 #include "base/macros.h"
 #include "base/metrics/histogram_functions.h"
+#include "base/trace_event/common/trace_event_common.h"
+#include "base/trace_event/trace_event.h"
 #include "third_party/blink/public/platform/platform.h"
 #include "third_party/blink/public/platform/task_type.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_function.h"
@@ -36,13 +39,28 @@
 
 namespace blink {
 
+namespace {
+constexpr const char kCategory[] = "media";
+
+base::AtomicSequenceNumber g_sequence_num_for_counters;
+}  // namespace
+
+// static
+template <typename Traits>
+const CodecTraceNames* EncoderBase<Traits>::GetTraceNames() {
+  DEFINE_THREAD_SAFE_STATIC_LOCAL(CodecTraceNames, trace_names,
+                                  (Traits::GetName()));
+  return &trace_names;
+}
+
 template <typename Traits>
 EncoderBase<Traits>::EncoderBase(ScriptState* script_state,
                                  const InitType* init,
                                  ExceptionState& exception_state)
     : ExecutionContextLifecycleObserver(ExecutionContext::From(script_state)),
       state_(V8CodecState::Enum::kUnconfigured),
-      script_state_(script_state) {
+      script_state_(script_state),
+      trace_counter_id_(g_sequence_num_for_counters.GetNext()) {
   logger_ = std::make_unique<CodecLogger>(GetExecutionContext(),
                                           Thread::Current()->GetTaskRunner());
 
@@ -175,6 +193,8 @@
   if (ThrowIfCodecStateClosed(state_, "reset", exception_state))
     return;
 
+  TRACE_EVENT0(kCategory, GetTraceNames()->reset.c_str());
+
   state_ = V8CodecState(V8CodecState::Enum::kUnconfigured);
   ResetInternal();
   media_encoder_.reset();
@@ -192,6 +212,7 @@
     if (pending_req->input)
       pending_req->input.Release()->close();
   }
+  requested_encodes_ = 0;
   stall_request_processing_ = false;
 }
 
@@ -200,6 +221,8 @@
   if (state_.AsEnum() == V8CodecState::Enum::kClosed)
     return;
 
+  TRACE_EVENT0(kCategory, GetTraceNames()->handle_error.c_str());
+
   // Save a temp before we clear the callback.
   V8WebCodecsErrorCallback* error_callback = error_callback_.Get();
 
@@ -231,6 +254,8 @@
 template <typename Traits>
 void EncoderBase<Traits>::ProcessRequests() {
   while (!requests_.empty() && !stall_request_processing_) {
+    TraceQueueSizes();
+
     Request* request = requests_.TakeFirst();
     DCHECK(request);
     switch (request->type) {
@@ -250,6 +275,8 @@
         NOTREACHED();
     }
   }
+
+  TraceQueueSizes();
 }
 
 template <typename Traits>
@@ -266,12 +293,14 @@
 
     if (!self) {
       req->resolver.Release()->Reject();
+      req->EndTracing(/*aborted=*/true);
       return;
     }
 
     DCHECK_CALLED_ON_VALID_SEQUENCE(self->sequence_checker_);
     if (self->reset_count_ != req->reset_count) {
       req->resolver.Release()->Reject();
+      req->EndTracing(/*aborted=*/true);
       return;
     }
     if (status.is_ok()) {
@@ -281,10 +310,14 @@
           self->logger_->MakeException("Flushing error.", status));
       req->resolver.Release()->Reject();
     }
+    req->EndTracing();
+
     self->stall_request_processing_ = false;
     self->ProcessRequests();
   };
 
+  request->StartTracing();
+
   stall_request_processing_ = true;
   media_encoder_->Flush(ConvertToBaseOnceCallback(
       CrossThreadBindOnce(done_callback, WrapCrossThreadWeakPersistent(this),
@@ -304,6 +337,13 @@
 }
 
 template <typename Traits>
+void EncoderBase<Traits>::TraceQueueSizes() const {
+  TRACE_COUNTER_ID2(kCategory, GetTraceNames()->requests_counter.c_str(),
+                    trace_counter_id_, "encodes", requested_encodes_, "other",
+                    requests_.size() - requested_encodes_);
+}
+
+template <typename Traits>
 void EncoderBase<Traits>::Trace(Visitor* visitor) const {
   visitor->Trace(active_config_);
   visitor->Trace(script_state_);
@@ -321,6 +361,54 @@
   visitor->Trace(resolver);
 }
 
+template <typename Traits>
+const char* EncoderBase<Traits>::Request::TraceNameFromType() {
+  using RequestType = typename EncoderBase<Traits>::Request::Type;
+
+  const CodecTraceNames* trace_names = EncoderBase<Traits>::GetTraceNames();
+
+  switch (type) {
+    case RequestType::kConfigure:
+      return trace_names->configure.c_str();
+    case RequestType::kEncode:
+      return trace_names->encode.c_str();
+    case RequestType::kFlush:
+      return trace_names->flush.c_str();
+    case RequestType::kReconfigure:
+      return trace_names->reconfigure.c_str();
+  }
+  return "InvalidCodecTraceName";
+}
+
+template <typename Traits>
+void EncoderBase<Traits>::Request::StartTracingVideoEncode(bool is_keyframe) {
+#if DCHECK_IS_ON()
+  DCHECK(!is_tracing);
+  is_tracing = true;
+#endif
+  TRACE_EVENT_NESTABLE_ASYNC_BEGIN1(kCategory, TraceNameFromType(), this,
+                                    "key_frame", is_keyframe);
+}
+
+template <typename Traits>
+void EncoderBase<Traits>::Request::StartTracing() {
+#if DCHECK_IS_ON()
+  DCHECK(!is_tracing);
+  is_tracing = true;
+#endif
+  TRACE_EVENT_NESTABLE_ASYNC_BEGIN0(kCategory, TraceNameFromType(), this);
+}
+
+template <typename Traits>
+void EncoderBase<Traits>::Request::EndTracing(bool aborted) {
+#if DCHECK_IS_ON()
+  DCHECK(is_tracing);
+  is_tracing = false;
+#endif
+  TRACE_EVENT_NESTABLE_ASYNC_END1(kCategory, TraceNameFromType(), this,
+                                  "aborted", aborted);
+}
+
 template class EncoderBase<VideoEncoderTraits>;
 template class EncoderBase<AudioEncoderTraits>;
 
diff --git a/third_party/blink/renderer/modules/webcodecs/encoder_base.h b/third_party/blink/renderer/modules/webcodecs/encoder_base.h
index e8dd404..4ae1fcb 100644
--- a/third_party/blink/renderer/modules/webcodecs/encoder_base.h
+++ b/third_party/blink/renderer/modules/webcodecs/encoder_base.h
@@ -9,12 +9,14 @@
 
 #include "media/base/media_log.h"
 #include "media/base/status.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/blink/renderer/bindings/core/v8/active_script_wrappable.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_codec_state.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_webcodecs_error_callback.h"
 #include "third_party/blink/renderer/modules/modules_export.h"
 #include "third_party/blink/renderer/modules/webcodecs/codec_logger.h"
+#include "third_party/blink/renderer/modules/webcodecs/codec_trace_names.h"
 #include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
 #include "third_party/blink/renderer/platform/context_lifecycle_observer.h"
 #include "third_party/blink/renderer/platform/heap/visitor.h"
@@ -39,6 +41,8 @@
   using OutputCallbackType = typename Traits::OutputCallback;
   using MediaEncoderType = typename Traits::MediaEncoder;
 
+  static const CodecTraceNames* GetTraceNames();
+
   EncoderBase(ScriptState*, const InitType*, ExceptionState&);
   ~EncoderBase() override;
 
@@ -81,12 +85,29 @@
 
     void Trace(Visitor*) const;
 
+    // Starts an async trace event.
+    void StartTracing();
+
+    // Starts an async encode trace.
+    void StartTracingVideoEncode(bool is_keyframe);
+
+    // Ends the async trace event associated with |this|.
+    void EndTracing(bool aborted = false);
+
+    // Get a trace event name from DecoderTemplate::GetTraceNames() and |type|.
+    const char* TraceNameFromType();
+
     Type type;
     // Current value of EncoderBase.reset_count_ when request was created.
     uint32_t reset_count = 0;
     Member<InputType> input;                     // used by kEncode
     Member<const EncodeOptionsType> encodeOpts;  // used by kEncode
     Member<ScriptPromiseResolver> resolver;      // used by kFlush
+
+#if DCHECK_IS_ON()
+    // Tracks the state of tracing for debug purposes.
+    bool is_tracing;
+#endif
   };
 
   virtual void HandleError(DOMException* ex);
@@ -104,6 +125,8 @@
                                           ExceptionState&) = 0;
   virtual bool VerifyCodecSupport(InternalConfigType*, ExceptionState&) = 0;
 
+  void TraceQueueSizes() const;
+
   std::unique_ptr<CodecLogger> logger_;
 
   std::unique_ptr<MediaEncoderType> media_encoder_;
@@ -129,6 +152,9 @@
 
   bool first_output_after_configure_ = true;
 
+  // Used to differentiate Encoders' counters during tracing.
+  int trace_counter_id_;
+
   SEQUENCE_CHECKER(sequence_checker_);
 };
 
diff --git a/third_party/blink/renderer/modules/webcodecs/video_encoder.cc b/third_party/blink/renderer/modules/webcodecs/video_encoder.cc
index 43a128d..72e65715 100644
--- a/third_party/blink/renderer/modules/webcodecs/video_encoder.cc
+++ b/third_party/blink/renderer/modules/webcodecs/video_encoder.cc
@@ -12,6 +12,8 @@
 #include "base/logging.h"
 #include "base/macros.h"
 #include "base/metrics/histogram_macros.h"
+#include "base/trace_event/common/trace_event_common.h"
+#include "base/trace_event/trace_event.h"
 #include "build/build_config.h"
 #include "components/viz/common/gpu/raster_context_provider.h"
 #include "gpu/GLES2/gl2extchromium.h"
@@ -77,6 +79,8 @@
 
 namespace {
 
+constexpr const char kCategory[] = "media";
+
 // Use this function in cases when we can't immediately delete |ptr| because
 // there might be its methods on the call stack.
 template <typename T>
@@ -445,6 +449,7 @@
         media::Status(media::StatusCode::kEncoderInitializationError,
                       "Unable to create encoder (most likely unsupported "
                       "codec/acceleration requirement combination)")));
+    request->EndTracing();
     return;
   }
 
@@ -456,8 +461,10 @@
 
   auto done_callback = [](VideoEncoder* self, Request* req,
                           media::VideoCodec codec, media::Status status) {
-    if (!self || self->reset_count_ != req->reset_count)
+    if (!self || self->reset_count_ != req->reset_count) {
+      req->EndTracing(/*aborted=*/true);
       return;
+    }
     DCHECK_CALLED_ON_VALID_SEQUENCE(self->sequence_checker_);
     DCHECK(self->active_config_);
 
@@ -468,6 +475,7 @@
       UMA_HISTOGRAM_ENUMERATION("Blink.WebCodecs.VideoEncoder.Codec", codec,
                                 media::kVideoCodecMax + 1);
     }
+    req->EndTracing();
 
     self->stall_request_processing_ = false;
     self->ProcessRequests();
@@ -498,15 +506,23 @@
   DCHECK_EQ(request->type, Request::Type::kEncode);
   DCHECK_GT(requested_encodes_, 0);
 
+  bool keyframe = request->encodeOpts->hasKeyFrameNonNull() &&
+                  request->encodeOpts->keyFrameNonNull();
+
+  request->StartTracingVideoEncode(keyframe);
+
   auto done_callback = [](VideoEncoder* self, Request* req,
                           media::Status status) {
-    if (!self || self->reset_count_ != req->reset_count)
+    if (!self || self->reset_count_ != req->reset_count) {
+      req->EndTracing(/*aborted=*/true);
       return;
+    }
     DCHECK_CALLED_ON_VALID_SEQUENCE(self->sequence_checker_);
     if (!status.is_ok()) {
       self->HandleError(
           self->logger_->MakeException("Encoding error.", status));
     }
+    req->EndTracing();
     self->ProcessRequests();
   };
 
@@ -550,8 +566,6 @@
     frame = media::WrapAsI420VideoFrame(std::move(frame));
   }
 
-  bool keyframe = request->encodeOpts->hasKeyFrameNonNull() &&
-                  request->encodeOpts->keyFrameNonNull();
   --requested_encodes_;
   media_encoder_->Encode(frame, keyframe,
                          ConvertToBaseOnceCallback(CrossThreadBindOnce(
@@ -568,6 +582,8 @@
   DCHECK(active_config_);
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
+  request->StartTracing();
+
   stall_request_processing_ = true;
 
   if (active_config_->hw_pref == HardwarePreference::kDeny) {
@@ -587,13 +603,19 @@
   DCHECK(media_encoder_);
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
+  request->StartTracing();
+
   auto reconf_done_callback = [](VideoEncoder* self, Request* req,
                                  media::Status status) {
-    if (!self || self->reset_count_ != req->reset_count)
+    if (!self || self->reset_count_ != req->reset_count) {
+      req->EndTracing(/*aborted=*/true);
       return;
+    }
     DCHECK_CALLED_ON_VALID_SEQUENCE(self->sequence_checker_);
     DCHECK(self->active_config_);
 
+    req->EndTracing();
+
     if (status.is_ok()) {
       self->stall_request_processing_ = false;
       self->ProcessRequests();
@@ -609,13 +631,16 @@
   auto flush_done_callback = [](VideoEncoder* self, Request* req,
                                 decltype(reconf_done_callback) reconf_callback,
                                 media::Status status) {
-    if (!self || self->reset_count_ != req->reset_count)
+    if (!self || self->reset_count_ != req->reset_count) {
+      req->EndTracing(/*aborted=*/true);
       return;
+    }
     DCHECK_CALLED_ON_VALID_SEQUENCE(self->sequence_checker_);
     if (!status.is_ok()) {
       self->HandleError(self->logger_->MakeException(
           "Encoder initialization error.", status));
       self->stall_request_processing_ = false;
+      req->EndTracing();
       return;
     }
 
@@ -650,8 +675,9 @@
   DCHECK(active_config);
   if (!script_state_->ContextIsValid() || !output_callback_ ||
       state_.AsEnum() != V8CodecState::Enum::kConfigured ||
-      reset_count != reset_count_)
+      reset_count != reset_count_) {
     return;
+  }
 
   auto deleter = [](void* data, size_t length, void*) {
     delete[] static_cast<uint8_t*>(data);
@@ -698,8 +724,13 @@
     metadata->setDecoderConfig(decoder_config);
   }
 
+  TRACE_EVENT_BEGIN1(kCategory, GetTraceNames()->output.c_str(), "timestamp",
+                     chunk->timestamp());
+
   ScriptState::Scope scope(script_state_);
   output_callback_->InvokeAndReportException(nullptr, chunk, metadata);
+
+  TRACE_EVENT_END0(kCategory, GetTraceNames()->output.c_str());
 }
 
 static void isConfigSupportedWithSoftwareOnly(
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_queue.cc b/third_party/blink/renderer/modules/webgpu/gpu_queue.cc
index a7c4e670..54b3e5e 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_queue.cc
+++ b/third_party/blink/renderer/modules/webgpu/gpu_queue.cc
@@ -83,6 +83,25 @@
   }
 }
 
+// TODO(crbug.com/1197369)This duplicate code is for supporting deprecated API
+// copyImageBitmapToTexture and should be remove in future.
+bool IsValidCopyIB2TDestinationFormat(WGPUTextureFormat dawn_texture_format) {
+  switch (dawn_texture_format) {
+    case WGPUTextureFormat_RGBA8Unorm:
+    case WGPUTextureFormat_RGBA8UnormSrgb:
+    case WGPUTextureFormat_BGRA8Unorm:
+    case WGPUTextureFormat_BGRA8UnormSrgb:
+    case WGPUTextureFormat_RGB10A2Unorm:
+    case WGPUTextureFormat_RGBA16Float:
+    case WGPUTextureFormat_RGBA32Float:
+    case WGPUTextureFormat_RG8Unorm:
+    case WGPUTextureFormat_RG16Float:
+      return true;
+    default:
+      return false;
+  }
+}
+
 // TODO(crubg.com/dawn/465): Cover more formats.
 bool IsValidCopyTextureForBrowserFormats(SkColorType src_color_type,
                                          WGPUTextureFormat dst_texture_format) {
@@ -92,10 +111,6 @@
     return false;
   }
 
-  // This function should be called after |IsValidCopyEI2TDestinationFormat|.
-  // Use DCHECK to guard this assumption.
-  DCHECK(IsValidExternalImageDestinationFormat(dst_texture_format));
-
   // CopyTextureForBrowser() supports neither RGBA8UnormSrgb nor BGRA8UnormSrgb
   // as dst texture format.
   if (dst_texture_format == WGPUTextureFormat_RGBA8UnormSrgb ||
@@ -483,13 +498,15 @@
   }
 }
 
+// TODO(crbug.com/1197369): This API contains duplicated code is to stop
+// breaking current workable codes. Will be removed when it is deprecated.
 void GPUQueue::copyImageBitmapToTexture(GPUImageCopyImageBitmap* source,
                                         GPUImageCopyTexture* destination,
                                         const V8GPUExtent3D* copy_size,
                                         ExceptionState& exception_state) {
   device_->AddConsoleWarning(
-      "The copyImageBitmapToTexture has been deprecated in favor of the "
-      "copyExternalImageToTexture"
+      "The copyImageBitmapToTexture() has been deprecated in favor of the "
+      "copyExternalImageToTexture() "
       "and will soon be removed.");
 
   if (!source->imageBitmap()) {
@@ -504,14 +521,63 @@
     return;
   }
 
-  GPUImageCopyExternalImage imageCopyExternalImage;
-  imageCopyExternalImage.setSource(
-      ImageBitmapOrHTMLCanvasElementOrOffscreenCanvas::FromImageBitmap(
-          source->imageBitmap()));
-  imageCopyExternalImage.setOrigin(source->origin());
+  scoped_refptr<StaticBitmapImage> image = source->imageBitmap()->BitmapImage();
 
-  this->copyExternalImageToTexture(&imageCopyExternalImage, destination,
-                                   copy_size, exception_state);
+  // TODO(shaobo.yan@intel.com) : Check that the destination GPUTexture has an
+  // appropriate format. Now only support texture format exactly the same. The
+  // compatible formats need to be defined in WebGPU spec.
+
+  WGPUExtent3D dawn_copy_size = AsDawnType(copy_size, device_);
+
+  // Extract imageBitmap attributes
+  WGPUOrigin3D origin_in_image_bitmap =
+      GPUOrigin2DToWGPUOrigin3D(&(source->origin()));
+
+  // Validate copy depth
+  if (dawn_copy_size.depthOrArrayLayers > 1) {
+    GetProcs().deviceInjectError(device_->GetHandle(), WGPUErrorType_Validation,
+                                 "Copy depth is out of bounds of imageBitmap.");
+    return;
+  }
+
+  // Validate origin value
+  if (static_cast<uint32_t>(image->width()) < origin_in_image_bitmap.x ||
+      static_cast<uint32_t>(image->height()) < origin_in_image_bitmap.y) {
+    GetProcs().deviceInjectError(
+        device_->GetHandle(), WGPUErrorType_Validation,
+        "Copy origin is out of bounds of imageBitmap.");
+    return;
+  }
+
+  // Validate the copy rect is inside the imageBitmap
+  if (image->width() - origin_in_image_bitmap.x < dawn_copy_size.width ||
+      image->height() - origin_in_image_bitmap.y < dawn_copy_size.height) {
+    GetProcs().deviceInjectError(device_->GetHandle(), WGPUErrorType_Validation,
+                                 "Copy rect is out of bounds of imageBitmap.");
+    return;
+  }
+
+  WGPUTextureCopyView dawn_destination = AsDawnType(destination, device_);
+
+  if (!IsValidCopyIB2TDestinationFormat(destination->texture()->Format())) {
+    return exception_state.ThrowTypeError("Invalid gpu texture format.");
+    return;
+  }
+
+  bool isNoopCopy = dawn_copy_size.width == 0 || dawn_copy_size.height == 0 ||
+                    dawn_copy_size.depthOrArrayLayers == 0;
+
+  if (image->IsTextureBacked() && !isNoopCopy) {  // Try GPU uploading path.
+    // Fallback to CPU path, GPU uploading requests RENDER_ATTACHMENT usage for
+    // dst texture.
+    image = image->MakeUnaccelerated();
+  }
+  // CPU path is the fallback path and should always work.
+  if (!CopyContentFromCPU(image.get(), origin_in_image_bitmap, dawn_copy_size,
+                          dawn_destination, destination->texture()->Format())) {
+    exception_state.ThrowTypeError("Failed to copy content from imageBitmap.");
+    return;
+  }
 }  // namespace blink
 
 bool GPUQueue::CopyContentFromCPU(StaticBitmapImage* image,
diff --git a/third_party/blink/renderer/platform/mediastream/media_stream_audio_processor_options.cc b/third_party/blink/renderer/platform/mediastream/media_stream_audio_processor_options.cc
index 90c507f8..775efa7 100644
--- a/third_party/blink/renderer/platform/mediastream/media_stream_audio_processor_options.cc
+++ b/third_party/blink/renderer/platform/mediastream/media_stream_audio_processor_options.cc
@@ -72,6 +72,21 @@
   *noise_suppression_level = GetNoiseSuppressionLevel(*config);
 }
 
+using ClippingPredictor = webrtc::AudioProcessing::Config::GainController1::
+    AnalogGainController::ClippingPredictor;
+
+ClippingPredictor::Mode GetClippingPredictorMode(
+    int clipping_predictor_param_mode) {
+  switch (clipping_predictor_param_mode) {
+    case 1:
+      return ClippingPredictor::Mode::kAdaptiveStepClippingPeakPrediction;
+    case 2:
+      return ClippingPredictor::Mode::kFixedStepClippingPeakPrediction;
+    default:
+      return ClippingPredictor::Mode::kClippingEventPrediction;
+  }
+}
+
 }  // namespace
 
 void AudioProcessingProperties::DisableDefaultProperties() {
@@ -182,6 +197,8 @@
 void ConfigAutomaticGainControl(
     const AudioProcessingProperties& properties,
     const absl::optional<WebRtcHybridAgcParams>& hybrid_agc_params,
+    const absl::optional<WebRtcAnalogAgcClippingControlParams>&
+        clipping_control_params,
     absl::optional<double> compression_gain_db,
     AudioProcessing::Config& apm_config) {
   // If system level gain control is activated, turn off all gain control
@@ -253,6 +270,39 @@
       apm_config.gain_controller1.analog_gain_controller
           .enable_digital_adaptive = true;
     }
+
+    // When experimental AGC is enabled, we enable clipping control given that
+    // 1. `clipping_control_params` is not nullopt,
+    // 2. AGC1 is used,
+    // 3. AGC1 uses analog gain controller.
+    if (apm_config.gain_controller1.enabled &&
+        apm_config.gain_controller1.analog_gain_controller.enabled &&
+        clipping_control_params.has_value()) {
+      auto* const analog_gain_controller =
+          &apm_config.gain_controller1.analog_gain_controller;
+      analog_gain_controller->clipped_level_step =
+          clipping_control_params->clipped_level_step;
+      analog_gain_controller->clipped_ratio_threshold =
+          clipping_control_params->clipped_ratio_threshold;
+      analog_gain_controller->clipped_wait_frames =
+          clipping_control_params->clipped_wait_frames;
+
+      auto* const clipping_predictor =
+          &analog_gain_controller->clipping_predictor;
+      clipping_predictor->enabled = true;
+      clipping_predictor->mode =
+          GetClippingPredictorMode(clipping_control_params->mode);
+      clipping_predictor->window_length =
+          clipping_control_params->window_length;
+      clipping_predictor->reference_window_length =
+          clipping_control_params->reference_window_length;
+      clipping_predictor->reference_window_delay =
+          clipping_control_params->reference_window_delay;
+      clipping_predictor->clipping_threshold =
+          clipping_control_params->clipping_threshold;
+      clipping_predictor->crest_factor_margin =
+          clipping_control_params->crest_factor_margin;
+    }
   } else if (use_fixed_digital_agc2) {
     // Experimental AGC is disabled, thus hybrid AGC is disabled. Config AGC2
     // with fixed gain mode.
diff --git a/third_party/blink/renderer/platform/mediastream/media_stream_audio_processor_options.h b/third_party/blink/renderer/platform/mediastream/media_stream_audio_processor_options.h
index 8e5816a..f3c8cdb 100644
--- a/third_party/blink/renderer/platform/mediastream/media_stream_audio_processor_options.h
+++ b/third_party/blink/renderer/platform/mediastream/media_stream_audio_processor_options.h
@@ -134,6 +134,24 @@
   bool neon_allowed;
 };
 
+// WebRTC analog AGC clipping control parameters.
+struct PLATFORM_EXPORT WebRtcAnalogAgcClippingControlParams {
+  int mode;
+  // Mode can be the following:
+  // 0: Clipping event prediction,
+  // 1: Adaptive step clipping peak prediction,
+  // 2: Fixed step clipping peak prediction.
+
+  int window_length;
+  int reference_window_length;
+  int reference_window_delay;
+  float clipping_threshold;
+  float crest_factor_margin;
+  int clipped_level_step;
+  float clipped_ratio_threshold;
+  int clipped_wait_frames;
+};
+
 // Changes the automatic gain control configuration in `apm_config` if
 // `properties.goog_auto_gain_control` or
 // `properties.goog_experimental_auto_gain_control` are true. If both are true
@@ -147,6 +165,8 @@
 PLATFORM_EXPORT void ConfigAutomaticGainControl(
     const AudioProcessingProperties& properties,
     const absl::optional<WebRtcHybridAgcParams>& hybrid_agc_params,
+    const absl::optional<WebRtcAnalogAgcClippingControlParams>&
+        clipping_control_params,
     absl::optional<double> compression_gain_db,
     webrtc::AudioProcessing::Config& apm_config);
 
diff --git a/third_party/blink/renderer/platform/mediastream/media_stream_audio_processor_options_test.cc b/third_party/blink/renderer/platform/mediastream/media_stream_audio_processor_options_test.cc
index 6bd4a012..a552b779 100644
--- a/third_party/blink/renderer/platform/mediastream/media_stream_audio_processor_options_test.cc
+++ b/third_party/blink/renderer/platform/mediastream/media_stream_audio_processor_options_test.cc
@@ -11,6 +11,8 @@
 namespace blink {
 namespace {
 using Agc2Config = webrtc::AudioProcessing::Config::GainController2;
+using ClippingPredictor = webrtc::AudioProcessing::Config::GainController1::
+    AnalogGainController::ClippingPredictor;
 
 constexpr WebRtcHybridAgcParams kHybridAgcParams{
     .dry_run = false,
@@ -36,6 +38,16 @@
 
 constexpr double kCompressionGainDb = 10.0;
 
+constexpr WebRtcAnalogAgcClippingControlParams kClippingControlParams{
+    .mode = 2,  // ClippingPredictor::Mode::kFixedStepClippingPeakPrediction
+    .window_length = 111,
+    .reference_window_length = 222,
+    .reference_window_delay = 333,
+    .clipping_threshold = 4.44f,
+    .crest_factor_margin = 5.55f,
+    .clipped_level_step = 666,
+    .clipped_ratio_threshold = 0.777f,
+    .clipped_wait_frames = 300};
 }  // namespace
 
 TEST(ConfigAutomaticGainControlTest, DoNotChangeApmConfig) {
@@ -43,12 +55,14 @@
   webrtc::AudioProcessing::Config apm_config;
 
   ConfigAutomaticGainControl(kAudioProcessingNoAgc, kHybridAgcParams,
+                             kClippingControlParams,
                              /*compression_gain_db=*/7, apm_config);
   EXPECT_EQ(apm_config.gain_controller1, kDefaultConfig.gain_controller1);
   EXPECT_EQ(apm_config.gain_controller2, kDefaultConfig.gain_controller2);
 
   ConfigAutomaticGainControl(kAudioProcessingNoAgc,
                              /*hybrid_agc_params=*/absl::nullopt,
+                             /*clipping_control_params=*/absl::nullopt,
                              /*compression_gain_db=*/absl::nullopt, apm_config);
   EXPECT_EQ(apm_config.gain_controller1, kDefaultConfig.gain_controller1);
   EXPECT_EQ(apm_config.gain_controller2, kDefaultConfig.gain_controller2);
@@ -62,6 +76,7 @@
       .goog_experimental_auto_gain_control = true};
 
   ConfigAutomaticGainControl(kProperties, kHybridAgcParams,
+                             kClippingControlParams,
                              /*compression_gain_db=*/10.0, apm_config);
   EXPECT_FALSE(apm_config.gain_controller1.enabled);
   EXPECT_FALSE(apm_config.gain_controller2.enabled);
@@ -71,7 +86,8 @@
   webrtc::AudioProcessing::Config apm_config;
 
   ConfigAutomaticGainControl(kAudioProcessingNoExperimentalAgc,
-                             /*agc2_properties=*/absl::nullopt,
+                             /*hybrid_agc_params=*/absl::nullopt,
+                             /*clipping_control_params=*/absl::nullopt,
                              /*compression_gain_db=*/absl::nullopt, apm_config);
 
   EXPECT_TRUE(apm_config.gain_controller1.enabled);
@@ -82,6 +98,9 @@
 #else
       webrtc::AudioProcessing::Config::GainController1::Mode::kAdaptiveAnalog);
 #endif  // defined(OS_ANDROID)
+
+  EXPECT_FALSE(apm_config.gain_controller1.analog_gain_controller
+                   .clipping_predictor.enabled);
 }
 
 TEST(ConfigAutomaticGainControlTest, EnableFixedDigitalAGC2) {
@@ -89,6 +108,7 @@
 
   ConfigAutomaticGainControl(kAudioProcessingNoExperimentalAgc,
                              /*hybrid_agc_params=*/absl::nullopt,
+                             /*clipping_control_params=*/absl::nullopt,
                              kCompressionGainDb, apm_config);
   EXPECT_FALSE(apm_config.gain_controller1.enabled);
   EXPECT_TRUE(apm_config.gain_controller2.enabled);
@@ -101,6 +121,7 @@
   webrtc::AudioProcessing::Config apm_config;
 
   ConfigAutomaticGainControl(kAudioProcessingExperimentalAgc, kHybridAgcParams,
+                             /*clipping_control_params=*/absl::nullopt,
                              kCompressionGainDb, apm_config);
   EXPECT_TRUE(apm_config.gain_controller1.enabled);
   EXPECT_EQ(
@@ -135,6 +156,39 @@
             kHybridAgcParams.neon_allowed);
 }
 
+TEST(ConfigAutomaticGainControlTest, EnableClippingControl) {
+  webrtc::AudioProcessing::Config apm_config;
+  ConfigAutomaticGainControl(
+      kAudioProcessingExperimentalAgc, /*hybrid_agc_params=*/absl::nullopt,
+      kClippingControlParams, kCompressionGainDb, apm_config);
+  EXPECT_TRUE(apm_config.gain_controller1.enabled);
+  EXPECT_TRUE(apm_config.gain_controller1.analog_gain_controller.enabled);
+
+  const auto& analog_gain_controller =
+      apm_config.gain_controller1.analog_gain_controller;
+  EXPECT_EQ(analog_gain_controller.clipped_level_step,
+            kClippingControlParams.clipped_level_step);
+  EXPECT_FLOAT_EQ(analog_gain_controller.clipped_ratio_threshold,
+                  kClippingControlParams.clipped_ratio_threshold);
+  EXPECT_EQ(analog_gain_controller.clipped_wait_frames,
+            kClippingControlParams.clipped_wait_frames);
+
+  const auto& clipping_predictor = analog_gain_controller.clipping_predictor;
+  EXPECT_TRUE(clipping_predictor.enabled);
+  EXPECT_EQ(clipping_predictor.mode,
+            ClippingPredictor::Mode::kFixedStepClippingPeakPrediction);
+  EXPECT_EQ(clipping_predictor.window_length,
+            kClippingControlParams.window_length);
+  EXPECT_EQ(clipping_predictor.reference_window_length,
+            kClippingControlParams.reference_window_length);
+  EXPECT_EQ(clipping_predictor.reference_window_delay,
+            kClippingControlParams.reference_window_delay);
+  EXPECT_FLOAT_EQ(clipping_predictor.clipping_threshold,
+                  kClippingControlParams.clipping_threshold);
+  EXPECT_FLOAT_EQ(clipping_predictor.crest_factor_margin,
+                  kClippingControlParams.crest_factor_margin);
+}
+
 TEST(PopulateApmConfigTest, DefaultWithoutConfigJson) {
   webrtc::AudioProcessing::Config apm_config;
   const AudioProcessingProperties properties;
diff --git a/third_party/blink/tools/blinkpy/presubmit/audit_non_blink_usage.py b/third_party/blink/tools/blinkpy/presubmit/audit_non_blink_usage.py
index 7128a68..8d4bc9f 100755
--- a/third_party/blink/tools/blinkpy/presubmit/audit_non_blink_usage.py
+++ b/third_party/blink/tools/blinkpy/presubmit/audit_non_blink_usage.py
@@ -1260,6 +1260,9 @@
 
             # The liburlpattern API requires using std::vector.
             'std::vector',
+
+            # Internal namespace used by url_pattern module.
+            'url_pattern::.+',
         ],
     },
     {
diff --git a/third_party/blink/tools/blinkpy/w3c/wpt_expectations_updater.py b/third_party/blink/tools/blinkpy/w3c/wpt_expectations_updater.py
index 2cd58bf4..4fe8e77 100644
--- a/third_party/blink/tools/blinkpy/w3c/wpt_expectations_updater.py
+++ b/third_party/blink/tools/blinkpy/w3c/wpt_expectations_updater.py
@@ -477,9 +477,10 @@
                 merged_dict[tuple([current_key])] = dictionary[current_key]
                 keys.remove(current_key)
                 break
-
+            current_result_set = set(dictionary[current_key].actual.split())
             for next_item in keys[1:]:
-                if dictionary[current_key] == dictionary[next_item]:
+                if (current_result_set ==
+                        set(dictionary[next_item].actual.split())):
                     found_match = True
                     matching_value_keys.update([current_key, next_item])
 
diff --git a/third_party/blink/tools/blinkpy/w3c/wpt_expectations_updater_unittest.py b/third_party/blink/tools/blinkpy/w3c/wpt_expectations_updater_unittest.py
index 9e968ab4..b95556e 100644
--- a/third_party/blink/tools/blinkpy/w3c/wpt_expectations_updater_unittest.py
+++ b/third_party/blink/tools/blinkpy/w3c/wpt_expectations_updater_unittest.py
@@ -348,44 +348,36 @@
         updater = WPTExpectationsUpdater(self.mock_host())
         self.assertEqual(
             updater.merge_same_valued_keys({
-                'one': {
-                    'expected': 'FAIL',
-                    'actual': 'PASS'
-                },
-                'two': {
-                    'expected': 'FAIL',
-                    'actual': 'PASS'
-                },
-            }), {('two', 'one'): {
-                     'expected': 'FAIL',
-                     'actual': 'PASS'
-                 }})
+                'one': SimpleTestResult(actual='FAIL TIMEOUT',
+                                        expected='FAIL',
+                                        bug=''),
+                'two': SimpleTestResult(actual='TIMEOUT FAIL',
+                                        expected='TIMEOUT',
+                                        bug='')
+            }), {('two', 'one'): SimpleTestResult(actual='FAIL TIMEOUT',
+                                                  expected='FAIL',
+                                                  bug='')})
 
     def test_merge_same_valued_keys_one_mismatch(self):
         updater = WPTExpectationsUpdater(self.mock_host())
         self.assertEqual(
             updater.merge_same_valued_keys({
-                'one': {
-                    'expected': 'FAIL',
-                    'actual': 'PASS'
-                },
-                'two': {
-                    'expected': 'FAIL',
-                    'actual': 'TIMEOUT'
-                },
-                'three': {
-                    'expected': 'FAIL',
-                    'actual': 'PASS'
-                },
+                'one': SimpleTestResult(actual='FAIL TIMEOUT',
+                                        expected='FAIL',
+                                        bug=''),
+                'two': SimpleTestResult(actual='TIMEOUT FAIL',
+                                        expected='TIMEOUT',
+                                        bug=''),
+                'three': SimpleTestResult(actual='TIMEOUT',
+                                          expected='FAIL',
+                                          bug='')
             }), {
-                ('three', 'one'): {
-                    'expected': 'FAIL',
-                    'actual': 'PASS'
-                },
-                ('two',): {
-                    'expected': 'FAIL',
-                    'actual': 'TIMEOUT'
-                },
+                ('two', 'one'): SimpleTestResult(actual='FAIL TIMEOUT',
+                                                 expected='FAIL',
+                                                 bug=''),
+                ('three',): SimpleTestResult(actual='TIMEOUT',
+                                             expected='FAIL',
+                                             bug='')
             })
 
     def test_get_expectations(self):
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index f9e2268..79432b53 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -1490,6 +1490,7 @@
 crbug.com/889952 fast/selectors/selection-window-inactive.html [ Pass Failure ]
 
 crbug.com/731731 inspector-protocol/layers/paint-profiler-load-empty.js [ Failure Pass ]
+crbug.com/1107923 inspector-protocol/debugger/wasm-streaming-url.js [ Pass Timeout Failure ]
 
 # Script let/const redeclaration errors
 crbug.com/1042162 http/tests/inspector-protocol/console/console-let-const-with-api.js [ Pass Failure ]
@@ -2690,7 +2691,6 @@
 crbug.com/626703 [ Mac11.0 ] external/wpt/websockets/stream/tentative/backpressure-send.any.serviceworker.html?wpt_flags=h2 [ Failure Timeout ]
 crbug.com/626703 [ Mac10.14 ] external/wpt/css/css-sizing/aspect-ratio/grid-aspect-ratio-020.html [ Failure Timeout ]
 crbug.com/626703 [ Mac10.14 ] external/wpt/worklets/paint-worklet-credentials.https.html [ Failure Timeout ]
-crbug.com/626703 [ Mac10.14 ] inspector-protocol/debugger/wasm-streaming-url.js [ Failure Timeout ]
 crbug.com/626703 [ Mac11.0 ] external/wpt/css/css-images/image-set/image-set-parsing.html [ Failure Timeout ]
 crbug.com/626703 [ Mac11.0 ] external/wpt/dom/xslt/transformToFragment.tentative.window.html [ Failure Timeout ]
 crbug.com/626703 [ Mac11.0 ] external/wpt/editing/other/editing-around-select-element.tentative.html?insertText [ Failure Timeout ]
@@ -3527,6 +3527,9 @@
 crbug.com/626703 external/wpt/encoding/eof-utf-8-three.html [ Failure ]
 crbug.com/626703 external/wpt/encoding/eof-utf-8-two.html [ Failure ]
 crbug.com/626703 external/wpt/html/browsers/windows/noreferrer-window-name.html [ Timeout ]
+crbug.com/1215956 [ Linux ] external/wpt/html/cross-origin-embedder-policy/credentialless/iframe-coep-credentialless.tentative.https.html [ Pass Failure ]
+crbug.com/1215956 [ Linux ] external/wpt/html/cross-origin-embedder-policy/credentialless/iframe-coep-require-corp.tentative.https.html [ Pass Failure ]
+crbug.com/1215956 [ Linux ] external/wpt/html/cross-origin-embedder-policy/credentialless/iframe-coep-none.tentative.https.html [ Pass Failure ]
 crbug.com/626703 external/wpt/html/semantics/embedded-content/the-embed-element/embed-represent-nothing-04.html [ Failure ]
 crbug.com/626703 external/wpt/html/semantics/embedded-content/the-video-element/video_initially_paused.html [ Failure ]
 crbug.com/626703 external/wpt/html/semantics/grouping-content/the-li-element/grouping-li-reftest-list-owner-menu.html [ Failure ]
@@ -3543,12 +3546,6 @@
 crbug.com/626703 virtual/plz-dedicated-worker/external/wpt/xhr/event-readystatechange-loaded.htm [ Failure Timeout ]
 crbug.com/626703 [ Win10 ] external/wpt/preload/delaying-onload-link-preload-after-discovery.html [ Timeout ]
 crbug.com/626703 external/wpt/html/dom/elements/global-attributes/dir_auto-N-EN-ref.html [ Failure ]
-crbug.com/626703 [ Linux ] external/wpt/html/cross-origin-embedder-policy/credentialless/iframe-coep-credentialless.tentative.https.html [ Pass Failure ]
-crbug.com/626703 [ Linux ] external/wpt/html/cross-origin-embedder-policy/credentialless/iframe-coep-require-corp.tentative.https.html [ Pass Failure ]
-crbug.com/626703 [ Linux ] virtual/plz-dedicated-worker/external/wpt/html/cross-origin-embedder-policy/credentialless/iframe-coep-credentialless.tentative.https.html [ Pass Failure ]
-crbug.com/626703 [ Linux ] virtual/plz-dedicated-worker/external/wpt/html/cross-origin-embedder-policy/credentialless/iframe-coep-require-corp.tentative.https.html [ Pass Failure ]
-crbug.com/626703 [ Linux ] virtual/plz-service-worker/external/wpt/html/cross-origin-embedder-policy/credentialless/iframe-coep-credentialless.tentative.https.html [ Pass Failure ]
-crbug.com/626703 [ Linux ] virtual/plz-service-worker/external/wpt/html/cross-origin-embedder-policy/credentialless/iframe-coep-require-corp.tentative.https.html [ Pass Failure ]
 
 # Synthetic modules report the wrong location in errors
 crbug.com/994315 virtual/json-modules/external/wpt/html/semantics/scripting-1/the-script-element/json-module/parse-error.tentative.html [ Skip ]
@@ -5916,8 +5913,6 @@
 # Sheriff 2020-07-22
 crbug.com/1107722 [ Mac ] http/tests/devtools/tracing/timeline-js/timeline-script-id.js [ Pass Failure ]
 crbug.com/1107722 [ Mac ] virtual/threaded/http/tests/devtools/tracing/timeline-misc/timeline-event-causes.js [ Pass Failure ]
-crbug.com/1107923 [ Mac10.15 ] inspector-protocol/debugger/wasm-streaming-url.js [ Pass Timeout Failure ]
-crbug.com/1107923 [ Mac11.0 ] inspector-protocol/debugger/wasm-streaming-url.js [ Pass Timeout Failure ]
 # Failing on Webkit Linux Leak
 crbug.com/1046784 [ Linux ] http/tests/devtools/tracing/timeline-paint/timeline-paint.js [ Pass Failure ]
 crbug.com/1046784 [ Linux ] http/tests/devtools/tracing/timeline-misc/timeline-event-causes.js [ Pass Failure ]
@@ -6685,12 +6680,6 @@
 external/wpt/css/css-transforms/group/svg-transform-nested-027.html [ Failure ]
 external/wpt/css/css-transforms/group/svg-transform-nested-028.html [ Failure ]
 external/wpt/css/css-transforms/group/svg-transform-nested-029.html [ Failure ]
-external/wpt/css/css-transforms/inline-styles/svg-inline-styles-001.html [ Failure ]
-external/wpt/css/css-transforms/inline-styles/svg-inline-styles-002.html [ Failure ]
-external/wpt/css/css-transforms/inline-styles/svg-inline-styles-003.html [ Failure ]
-external/wpt/css/css-transforms/inline-styles/svg-inline-styles-004.html [ Failure ]
-external/wpt/css/css-transforms/inline-styles/svg-inline-styles-012.html [ Failure ]
-external/wpt/css/css-transforms/inline-styles/svg-inline-styles-013.html [ Failure ]
 external/wpt/css/css-transforms/rotate/svg-rotate-3args-invalid-001.html [ Failure ]
 external/wpt/css/css-transforms/rotate/svg-rotate-3args-invalid-003.html [ Failure ]
 external/wpt/css/css-transforms/rotate/svg-rotate-3args-invalid-004.html [ Failure ]
@@ -6973,7 +6962,6 @@
 crbug.com/1215575 [ Mac11.0 ] fast/peerconnection/RTCPeerConnection-datachannel.html [ Pass Timeout ]
 crbug.com/1215575 [ Mac11.0 ] fast/peerconnection/RTCPeerConnection-lifetime.html [ Pass Timeout ]
 crbug.com/1215575 [ Mac11.0 ] fast/peerconnection/RTCPeerConnection-sdes-gcm.html [ Pass Timeout ]
-crbug.com/1215581 [ Mac11.0 ] svg/filters/feImage-preserveAspectRatio-all.svg [ Pass Failure ]
 crbug.com/1215584 [ Mac11.0 ] inspector-protocol/layout-fonts/lang-fallback.js [ Pass Failure ]
 
 # Sheriff 2021-06-03
diff --git a/third_party/blink/web_tests/external/wpt/css/css-transforms/inline-styles/svg-inline-styles-001.html b/third_party/blink/web_tests/external/wpt/css/css-transforms/inline-styles/svg-inline-styles-001.html
index 86719bf..c0afbe4c 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-transforms/inline-styles/svg-inline-styles-001.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-transforms/inline-styles/svg-inline-styles-001.html
@@ -26,7 +26,8 @@
             </linearGradient>
         </defs>
         <rect x="1" y="1" width="98" height="98" fill="red"/>
-        <rect y="-100" width="100" height="100" fill="url(#grad)" style="transform: rotate(90)" transform="scale(0.5)"/>
+        <rect y="-100" width="100" height="100" fill="url(#grad)"
+              style="transform: rotate(90deg)" transform="scale(0.5)"/>
     </svg>
 </body>
 </html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-transforms/inline-styles/svg-inline-styles-002.html b/third_party/blink/web_tests/external/wpt/css/css-transforms/inline-styles/svg-inline-styles-002.html
index 2f0c4725..5228afc 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-transforms/inline-styles/svg-inline-styles-002.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-transforms/inline-styles/svg-inline-styles-002.html
@@ -27,7 +27,8 @@
         </defs>
         <rect x="1" y="1" width="98" height="98" fill="red"/>
         <g transform="rotate(90)">
-            <rect width="100" height="100" fill="url(#grad)" style="transform: translateY(-100)" transform="scale(0.5)"/>
+          <rect width="100" height="100" fill="url(#grad)"
+                style="transform: translateY(-100px)" transform="scale(0.5)"/>
         </g>
     </svg>
 </body>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-transforms/inline-styles/svg-inline-styles-003.html b/third_party/blink/web_tests/external/wpt/css/css-transforms/inline-styles/svg-inline-styles-003.html
index 8d4714d..327ff13 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-transforms/inline-styles/svg-inline-styles-003.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-transforms/inline-styles/svg-inline-styles-003.html
@@ -26,7 +26,7 @@
             </linearGradient>
         </defs>
         <rect x="1" y="1" width="98" height="98" fill="red"/>
-        <g style="transform: rotate(90)">
+        <g style="transform: rotate(90deg)">
             <rect width="100" height="100" fill="url(#grad)" transform="translate(0 -100)"/>
         </g>
     </svg>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-transforms/inline-styles/svg-inline-styles-004.html b/third_party/blink/web_tests/external/wpt/css/css-transforms/inline-styles/svg-inline-styles-004.html
index b7e0a7d8..0a0dc02 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-transforms/inline-styles/svg-inline-styles-004.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-transforms/inline-styles/svg-inline-styles-004.html
@@ -26,7 +26,7 @@
             </linearGradient>
         </defs>
         <rect x="1" y="1" width="98" height="98" fill="red"/>
-        <g style="transform: rotate(90)" transform="scale(0.5)">
+        <g style="transform: rotate(90deg)" transform="scale(0.5)">
             <rect width="100" height="100" fill="url(#grad)" transform="translate(0 -100)"/>
         </g>
     </svg>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-transforms/inline-styles/svg-inline-styles-012.html b/third_party/blink/web_tests/external/wpt/css/css-transforms/inline-styles/svg-inline-styles-012.html
index f046d4c..ceb2595 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-transforms/inline-styles/svg-inline-styles-012.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-transforms/inline-styles/svg-inline-styles-012.html
@@ -27,7 +27,9 @@
             </linearGradient>
         </defs>
         <rect x="1" y="1" width="98" height="98" fill="red"/>
-        <rect y="-60" width="100" height="100" fill="url(#grad)" style="transform: rotate(90,20px,20px)" transform="scale(0.5)"/>
+        <rect y="-60" width="100" height="100" fill="url(#grad)"
+              style="transform: translate(20px, 20px) rotate(90deg) translate(-20px, -20px)"
+              transform="scale(0.5)"/>
     </svg>
 </body>
-</html>
\ No newline at end of file
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-transforms/inline-styles/svg-inline-styles-013.html b/third_party/blink/web_tests/external/wpt/css/css-transforms/inline-styles/svg-inline-styles-013.html
index a06a937..100f90e 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-transforms/inline-styles/svg-inline-styles-013.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-transforms/inline-styles/svg-inline-styles-013.html
@@ -27,7 +27,8 @@
             </linearGradient>
         </defs>
         <rect x="1" y="1" width="98" height="98" fill="red"/>
-        <rect y="-60" width="100" height="100" fill="url(#grad)" style="transform: scale(invalid)" transform="rotate(90,20px,20px)"/>
+        <rect y="-60" width="100" height="100" fill="url(#grad)"
+              style="transform: scale(invalid)" transform="rotate(90,20,20)"/>
     </svg>
 </body>
-</html>
\ No newline at end of file
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/forced-colors-mode/forced-colors-mode-41.html b/third_party/blink/web_tests/external/wpt/forced-colors-mode/forced-colors-mode-41.html
index cf2c527..7b616c9 100644
--- a/third_party/blink/web_tests/external/wpt/forced-colors-mode/forced-colors-mode-41.html
+++ b/third_party/blink/web_tests/external/wpt/forced-colors-mode/forced-colors-mode-41.html
@@ -10,6 +10,9 @@
 <script src="/resources/testharnessreport.js"></script>
 <style>
   div {
+    /* Unlike the other properties listed, accent-color is forced at computed
+    value time. */
+    accent-color: green;
     background-color: green;
     border-color: green;
     caret-color: green;
@@ -21,6 +24,7 @@
 
 <script>
   const properties_to_test = [
+    "accent-color",
     "background-color",
     "border-bottom-color",
     "border-left-color",
diff --git a/third_party/blink/web_tests/external/wpt/forced-colors-mode/forced-colors-mode-50.html b/third_party/blink/web_tests/external/wpt/forced-colors-mode/forced-colors-mode-50.html
index 5a98534..42957248 100644
--- a/third_party/blink/web_tests/external/wpt/forced-colors-mode/forced-colors-mode-50.html
+++ b/third_party/blink/web_tests/external/wpt/forced-colors-mode/forced-colors-mode-50.html
@@ -5,19 +5,24 @@
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <style>
-  input {
+  #a, #b {
     accent-color: green;
   }
-  #a {
+  #a, #c {
     forced-color-adjust: none;
   }
+  #c, #d {
+    accent-color: CanvasText;
+  }
 </style>
 <input type="checkbox" id="a" checked>
 <input type="checkbox" id="b" checked>
-
+<input type="checkbox" id="c" checked>
+<input type="checkbox" id="d" checked>
 <script>
   test(function(){
     assert_equals(getComputedStyle(a).accentColor, "rgb(0, 128, 0)");
     assert_equals(getComputedStyle(b).accentColor, "auto");
-  }, "Accent-color computes to auto in forced colors mode, unless forced-color-adjust is none.");
+    assert_equals(getComputedStyle(c).accentColor, getComputedStyle(d).accentColor)
+  }, "Accent-color computes to auto in forced colors mode, unless forced-color-adjust is none or accent-color is a system color.");
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/urlpattern/resources/urlpatterntestdata.json b/third_party/blink/web_tests/external/wpt/urlpattern/resources/urlpatterntestdata.json
index 135c623a..7ca6ca9 100644
--- a/third_party/blink/web_tests/external/wpt/urlpattern/resources/urlpatterntestdata.json
+++ b/third_party/blink/web_tests/external/wpt/urlpattern/resources/urlpatterntestdata.json
@@ -1,28 +1,28 @@
 [
   {
-    "pattern": { "pathname": "/foo/bar" },
+    "pattern": [{ "pathname": "/foo/bar" }],
     "inputs": [{ "pathname": "/foo/bar" }],
     "expected_match": {
       "pathname": { "input": "/foo/bar", "groups": {} }
     }
   },
   {
-    "pattern": { "pathname": "/foo/bar" },
+    "pattern": [{ "pathname": "/foo/bar" }],
     "inputs": [{ "pathname": "/foo/ba" }],
     "expected_match": null
   },
   {
-    "pattern": { "pathname": "/foo/bar" },
+    "pattern": [{ "pathname": "/foo/bar" }],
     "inputs": [{ "pathname": "/foo/bar/" }],
     "expected_match": null
   },
   {
-    "pattern": { "pathname": "/foo/bar" },
+    "pattern": [{ "pathname": "/foo/bar" }],
     "inputs": [{ "pathname": "/foo/bar/baz" }],
     "expected_match": null
   },
   {
-    "pattern": { "pathname": "/foo/bar" },
+    "pattern": [{ "pathname": "/foo/bar" }],
     "inputs": [ "https://example.com/foo/bar" ],
     "expected_match": {
       "hostname": { "input": "example.com", "groups": { "0": "example.com" } },
@@ -31,12 +31,12 @@
     }
   },
   {
-    "pattern": { "pathname": "/foo/bar" },
+    "pattern": [{ "pathname": "/foo/bar" }],
     "inputs": [ "https://example.com/foo/bar/baz" ],
     "expected_match": null
   },
   {
-    "pattern": { "pathname": "/foo/bar" },
+    "pattern": [{ "pathname": "/foo/bar" }],
     "inputs": [{ "hostname": "example.com", "pathname": "/foo/bar" }],
     "expected_match": {
       "hostname": { "input": "example.com", "groups": { "0": "example.com" } },
@@ -44,12 +44,12 @@
     }
   },
   {
-    "pattern": { "pathname": "/foo/bar" },
+    "pattern": [{ "pathname": "/foo/bar" }],
     "inputs": [{ "hostname": "example.com", "pathname": "/foo/bar/baz" }],
     "expected_match": null
   },
   {
-    "pattern": { "pathname": "/foo/bar" },
+    "pattern": [{ "pathname": "/foo/bar" }],
     "inputs": [{ "pathname": "/foo/bar", "baseURL": "https://example.com" }],
     "expected_match": {
       "hostname": { "input": "example.com", "groups": { "0": "example.com" } },
@@ -58,50 +58,50 @@
     }
   },
   {
-    "pattern": { "pathname": "/foo/bar" },
+    "pattern": [{ "pathname": "/foo/bar" }],
     "inputs": [{ "pathname": "/foo/bar/baz",
                  "baseURL": "https://example.com" }],
     "expected_match": null
   },
   {
-    "pattern": { "pathname": "/foo/bar",
-                 "baseURL": "https://example.com?query#hash" },
+    "pattern": [{ "pathname": "/foo/bar",
+                 "baseURL": "https://example.com?query#hash" }],
     "inputs": [{ "pathname": "/foo/bar" }],
     "expected_match": null
   },
   {
-    "pattern": { "pathname": "/foo/bar",
-                 "baseURL": "https://example.com?query#hash" },
+    "pattern": [{ "pathname": "/foo/bar",
+                 "baseURL": "https://example.com?query#hash" }],
     "inputs": [{ "hostname": "example.com", "pathname": "/foo/bar" }],
     "expected_match": null
   },
   {
-    "pattern": { "pathname": "/foo/bar",
-                 "baseURL": "https://example.com?query#hash" },
+    "pattern": [{ "pathname": "/foo/bar",
+                 "baseURL": "https://example.com?query#hash" }],
     "inputs": [{ "protocol": "https", "hostname": "example.com",
                  "pathname": "/foo/bar" }],
+    "exactly_empty_components": [ "username", "password", "port" ],
     "expected_match": {
-      "exactly_empty_components": [ "username", "password", "port" ],
       "hostname": { "input": "example.com", "groups": {} },
       "pathname": { "input": "/foo/bar", "groups": {} },
       "protocol": { "input": "https", "groups": {} }
     }
   },
   {
-    "pattern": { "pathname": "/foo/bar",
-                 "baseURL": "https://example.com?query#hash" },
+    "pattern": [{ "pathname": "/foo/bar",
+                 "baseURL": "https://example.com?query#hash" }],
     "inputs": [{ "protocol": "https", "hostname": "example.com",
                  "pathname": "/foo/bar/baz" }],
     "expected_match": null
   },
   {
-    "pattern": { "pathname": "/foo/bar",
-                 "baseURL": "https://example.com?query#hash" },
+    "pattern": [{ "pathname": "/foo/bar",
+                 "baseURL": "https://example.com?query#hash" }],
     "inputs": [{ "protocol": "https", "hostname": "example.com",
                  "pathname": "/foo/bar", "search": "otherquery",
                  "hash": "otherhash" }],
+    "exactly_empty_components": [ "username", "password", "port" ],
     "expected_match": {
-      "exactly_empty_components": [ "username", "password", "port" ],
       "hash": { "input": "otherhash", "groups": { "0": "otherhash" } },
       "hostname": { "input": "example.com", "groups": {} },
       "pathname": { "input": "/foo/bar", "groups": {} },
@@ -110,22 +110,22 @@
     }
   },
   {
-    "pattern": { "pathname": "/foo/bar",
-                 "baseURL": "https://example.com?query#hash" },
+    "pattern": [{ "pathname": "/foo/bar",
+                 "baseURL": "https://example.com?query#hash" }],
     "inputs": [ "https://example.com/foo/bar" ],
+    "exactly_empty_components": [ "username", "password", "port" ],
     "expected_match": {
-      "exactly_empty_components": [ "username", "password", "port" ],
       "hostname": { "input": "example.com", "groups": {} },
       "pathname": { "input": "/foo/bar", "groups": {} },
       "protocol": { "input": "https", "groups": {} }
     }
   },
   {
-    "pattern": { "pathname": "/foo/bar",
-                 "baseURL": "https://example.com?query#hash" },
+    "pattern": [{ "pathname": "/foo/bar",
+                 "baseURL": "https://example.com?query#hash" }],
     "inputs": [ "https://example.com/foo/bar?otherquery#otherhash" ],
+    "exactly_empty_components": [ "username", "password", "port" ],
     "expected_match": {
-      "exactly_empty_components": [ "username", "password", "port" ],
       "hash": { "input": "otherhash", "groups": { "0": "otherhash" } },
       "hostname": { "input": "example.com", "groups": {} },
       "pathname": { "input": "/foo/bar", "groups": {} },
@@ -134,79 +134,79 @@
     }
   },
   {
-    "pattern": { "pathname": "/foo/bar",
-                 "baseURL": "https://example.com?query#hash" },
+    "pattern": [{ "pathname": "/foo/bar",
+                 "baseURL": "https://example.com?query#hash" }],
     "inputs": [ "https://example.com/foo/bar/baz" ],
     "expected_match": null
   },
   {
-    "pattern": { "pathname": "/foo/bar",
-                 "baseURL": "https://example.com?query#hash" },
+    "pattern": [{ "pathname": "/foo/bar",
+                 "baseURL": "https://example.com?query#hash" }],
     "inputs": [ "https://other.com/foo/bar" ],
     "expected_match": null
   },
   {
-    "pattern": { "pathname": "/foo/bar",
-                 "baseURL": "https://example.com?query#hash" },
+    "pattern": [{ "pathname": "/foo/bar",
+                 "baseURL": "https://example.com?query#hash" }],
     "inputs": [ "http://other.com/foo/bar" ],
     "expected_match": null
   },
   {
-    "pattern": { "pathname": "/foo/bar",
-                 "baseURL": "https://example.com?query#hash" },
+    "pattern": [{ "pathname": "/foo/bar",
+                 "baseURL": "https://example.com?query#hash" }],
     "inputs": [{ "pathname": "/foo/bar", "baseURL": "https://example.com" }],
+    "exactly_empty_components": [ "username", "password", "port" ],
     "expected_match": {
-      "exactly_empty_components": [ "username", "password", "port" ],
       "hostname": { "input": "example.com", "groups": {} },
       "pathname": { "input": "/foo/bar", "groups": {} },
       "protocol": { "input": "https", "groups": {} }
     }
   },
   {
-    "pattern": { "pathname": "/foo/bar",
-                 "baseURL": "https://example.com?query#hash" },
+    "pattern": [{ "pathname": "/foo/bar",
+                 "baseURL": "https://example.com?query#hash" }],
     "inputs": [{ "pathname": "/foo/bar/baz",
                  "baseURL": "https://example.com" }],
     "expected_match": null
   },
   {
-    "pattern": { "pathname": "/foo/bar",
-                 "baseURL": "https://example.com?query#hash" },
+    "pattern": [{ "pathname": "/foo/bar",
+                 "baseURL": "https://example.com?query#hash" }],
     "inputs": [{ "pathname": "/foo/bar", "baseURL": "https://other.com" }],
     "expected_match": null
   },
   {
-    "pattern": { "pathname": "/foo/bar",
-                 "baseURL": "https://example.com?query#hash" },
+    "pattern": [{ "pathname": "/foo/bar",
+                 "baseURL": "https://example.com?query#hash" }],
     "inputs": [{ "pathname": "/foo/bar", "baseURL": "http://example.com" }],
     "expected_match": null
   },
   {
-    "pattern": { "pathname": "/foo/:bar" },
+    "pattern": [{ "pathname": "/foo/:bar" }],
     "inputs": [{ "pathname": "/foo/bar" }],
     "expected_match": {
       "pathname": { "input": "/foo/bar", "groups": { "bar": "bar" } }
     }
   },
   {
-    "pattern": { "pathname": "/foo/:bar" },
+    "pattern": [{ "pathname": "/foo/:bar" }],
     "inputs": [{ "pathname": "/foo/index.html" }],
     "expected_match": {
       "pathname": { "input": "/foo/index.html", "groups": { "bar": "index.html" } }
     }
   },
   {
-    "pattern": { "pathname": "/foo/:bar" },
+    "pattern": [{ "pathname": "/foo/:bar" }],
     "inputs": [{ "pathname": "/foo/bar/" }],
     "expected_match": null
   },
   {
-    "pattern": { "pathname": "/foo/:bar" },
+    "pattern": [{ "pathname": "/foo/:bar" }],
     "inputs": [{ "pathname": "/foo/" }],
     "expected_match": null
   },
   {
-    "pattern": { "pathname": "/foo/(.*)" },
+    "pattern": [{ "pathname": "/foo/(.*)" }],
     "inputs": [{ "pathname": "/foo/bar" }],
     "expected_obj": {
       "pathname": "/foo/*"
@@ -216,14 +216,14 @@
     }
   },
   {
-    "pattern": { "pathname": "/foo/*" },
+    "pattern": [{ "pathname": "/foo/*" }],
     "inputs": [{ "pathname": "/foo/bar" }],
     "expected_match": {
       "pathname": { "input": "/foo/bar", "groups": { "0": "bar" } }
     }
   },
   {
-    "pattern": { "pathname": "/foo/(.*)" },
+    "pattern": [{ "pathname": "/foo/(.*)" }],
     "inputs": [{ "pathname": "/foo/bar/baz" }],
     "expected_obj": {
       "pathname": "/foo/*"
@@ -233,14 +233,14 @@
     }
   },
   {
-    "pattern": { "pathname": "/foo/*" },
+    "pattern": [{ "pathname": "/foo/*" }],
     "inputs": [{ "pathname": "/foo/bar/baz" }],
     "expected_match": {
       "pathname": { "input": "/foo/bar/baz", "groups": { "0": "bar/baz" } }
     }
   },
   {
-    "pattern": { "pathname": "/foo/(.*)" },
+    "pattern": [{ "pathname": "/foo/(.*)" }],
     "inputs": [{ "pathname": "/foo/" }],
     "expected_obj": {
       "pathname": "/foo/*"
@@ -250,14 +250,14 @@
     }
   },
   {
-    "pattern": { "pathname": "/foo/*" },
+    "pattern": [{ "pathname": "/foo/*" }],
     "inputs": [{ "pathname": "/foo/" }],
     "expected_match": {
       "pathname": { "input": "/foo/", "groups": { "0": "" } }
     }
   },
   {
-    "pattern": { "pathname": "/foo/(.*)" },
+    "pattern": [{ "pathname": "/foo/(.*)" }],
     "inputs": [{ "pathname": "/foo" }],
     "expected_obj": {
       "pathname": "/foo/*"
@@ -265,127 +265,127 @@
     "expected_match": null
   },
   {
-    "pattern": { "pathname": "/foo/*" },
+    "pattern": [{ "pathname": "/foo/*" }],
     "inputs": [{ "pathname": "/foo" }],
     "expected_match": null
   },
   {
-    "pattern": { "pathname": "/foo/:bar(.*)" },
+    "pattern": [{ "pathname": "/foo/:bar(.*)" }],
     "inputs": [{ "pathname": "/foo/bar" }],
     "expected_match": {
       "pathname": { "input": "/foo/bar", "groups": { "bar": "bar" } }
     }
   },
   {
-    "pattern": { "pathname": "/foo/:bar(.*)" },
+    "pattern": [{ "pathname": "/foo/:bar(.*)" }],
     "inputs": [{ "pathname": "/foo/bar/baz" }],
     "expected_match": {
       "pathname": { "input": "/foo/bar/baz", "groups": { "bar": "bar/baz" } }
     }
   },
   {
-    "pattern": { "pathname": "/foo/:bar(.*)" },
+    "pattern": [{ "pathname": "/foo/:bar(.*)" }],
     "inputs": [{ "pathname": "/foo/" }],
     "expected_match": {
       "pathname": { "input": "/foo/", "groups": { "bar": "" } }
     }
   },
   {
-    "pattern": { "pathname": "/foo/:bar(.*)" },
+    "pattern": [{ "pathname": "/foo/:bar(.*)" }],
     "inputs": [{ "pathname": "/foo" }],
     "expected_match": null
   },
   {
-    "pattern": { "pathname": "/foo/:bar?" },
+    "pattern": [{ "pathname": "/foo/:bar?" }],
     "inputs": [{ "pathname": "/foo/bar" }],
     "expected_match": {
       "pathname": { "input": "/foo/bar", "groups": { "bar": "bar" } }
     }
   },
   {
-    "pattern": { "pathname": "/foo/:bar?" },
+    "pattern": [{ "pathname": "/foo/:bar?" }],
     "inputs": [{ "pathname": "/foo" }],
     "expected_match": {
       "pathname": { "input": "/foo", "groups": { "bar": "" } }
     }
   },
   {
-    "pattern": { "pathname": "/foo/:bar?" },
+    "pattern": [{ "pathname": "/foo/:bar?" }],
     "inputs": [{ "pathname": "/foo/" }],
     "expected_match": null
   },
   {
-    "pattern": { "pathname": "/foo/:bar?" },
+    "pattern": [{ "pathname": "/foo/:bar?" }],
     "inputs": [{ "pathname": "/foobar" }],
     "expected_match": null
   },
   {
-    "pattern": { "pathname": "/foo/:bar?" },
+    "pattern": [{ "pathname": "/foo/:bar?" }],
     "inputs": [{ "pathname": "/foo/bar/baz" }],
     "expected_match": null
   },
   {
-    "pattern": { "pathname": "/foo/:bar+" },
+    "pattern": [{ "pathname": "/foo/:bar+" }],
     "inputs": [{ "pathname": "/foo/bar" }],
     "expected_match": {
       "pathname": { "input": "/foo/bar", "groups": { "bar": "bar" } }
     }
   },
   {
-    "pattern": { "pathname": "/foo/:bar+" },
+    "pattern": [{ "pathname": "/foo/:bar+" }],
     "inputs": [{ "pathname": "/foo/bar/baz" }],
     "expected_match": {
       "pathname": { "input": "/foo/bar/baz", "groups": { "bar": "bar/baz" } }
     }
   },
   {
-    "pattern": { "pathname": "/foo/:bar+" },
+    "pattern": [{ "pathname": "/foo/:bar+" }],
     "inputs": [{ "pathname": "/foo" }],
     "expected_match": null
   },
   {
-    "pattern": { "pathname": "/foo/:bar+" },
+    "pattern": [{ "pathname": "/foo/:bar+" }],
     "inputs": [{ "pathname": "/foo/" }],
     "expected_match": null
   },
   {
-    "pattern": { "pathname": "/foo/:bar+" },
+    "pattern": [{ "pathname": "/foo/:bar+" }],
     "inputs": [{ "pathname": "/foobar" }],
     "expected_match": null
   },
   {
-    "pattern": { "pathname": "/foo/:bar*" },
+    "pattern": [{ "pathname": "/foo/:bar*" }],
     "inputs": [{ "pathname": "/foo/bar" }],
     "expected_match": {
       "pathname": { "input": "/foo/bar", "groups": { "bar": "bar" } }
     }
   },
   {
-    "pattern": { "pathname": "/foo/:bar*" },
+    "pattern": [{ "pathname": "/foo/:bar*" }],
     "inputs": [{ "pathname": "/foo/bar/baz" }],
     "expected_match": {
       "pathname": { "input": "/foo/bar/baz", "groups": { "bar": "bar/baz" } }
     }
   },
   {
-    "pattern": { "pathname": "/foo/:bar*" },
+    "pattern": [{ "pathname": "/foo/:bar*" }],
     "inputs": [{ "pathname": "/foo" }],
     "expected_match": {
       "pathname": { "input": "/foo", "groups": { "bar": "" } }
     }
   },
   {
-    "pattern": { "pathname": "/foo/:bar*" },
+    "pattern": [{ "pathname": "/foo/:bar*" }],
     "inputs": [{ "pathname": "/foo/" }],
     "expected_match": null
   },
   {
-    "pattern": { "pathname": "/foo/:bar*" },
+    "pattern": [{ "pathname": "/foo/:bar*" }],
     "inputs": [{ "pathname": "/foobar" }],
     "expected_match": null
   },
   {
-    "pattern": { "pathname": "/foo/(.*)?" },
+    "pattern": [{ "pathname": "/foo/(.*)?" }],
     "inputs": [{ "pathname": "/foo/bar" }],
     "expected_obj": {
       "pathname": "/foo/*?"
@@ -395,14 +395,14 @@
     }
   },
   {
-    "pattern": { "pathname": "/foo/*?" },
+    "pattern": [{ "pathname": "/foo/*?" }],
     "inputs": [{ "pathname": "/foo/bar" }],
     "expected_match": {
       "pathname": { "input": "/foo/bar", "groups": { "0": "bar" } }
     }
   },
   {
-    "pattern": { "pathname": "/foo/(.*)?" },
+    "pattern": [{ "pathname": "/foo/(.*)?" }],
     "inputs": [{ "pathname": "/foo/bar/baz" }],
     "expected_obj": {
       "pathname": "/foo/*?"
@@ -412,14 +412,14 @@
     }
   },
   {
-    "pattern": { "pathname": "/foo/*?" },
+    "pattern": [{ "pathname": "/foo/*?" }],
     "inputs": [{ "pathname": "/foo/bar/baz" }],
     "expected_match": {
       "pathname": { "input": "/foo/bar/baz", "groups": { "0": "bar/baz" } }
     }
   },
   {
-    "pattern": { "pathname": "/foo/(.*)?" },
+    "pattern": [{ "pathname": "/foo/(.*)?" }],
     "inputs": [{ "pathname": "/foo" }],
     "expected_obj": {
       "pathname": "/foo/*?"
@@ -429,14 +429,14 @@
     }
   },
   {
-    "pattern": { "pathname": "/foo/*?" },
+    "pattern": [{ "pathname": "/foo/*?" }],
     "inputs": [{ "pathname": "/foo" }],
     "expected_match": {
       "pathname": { "input": "/foo", "groups": { "0": "" } }
     }
   },
   {
-    "pattern": { "pathname": "/foo/(.*)?" },
+    "pattern": [{ "pathname": "/foo/(.*)?" }],
     "inputs": [{ "pathname": "/foo/" }],
     "expected_obj": {
       "pathname": "/foo/*?"
@@ -446,14 +446,14 @@
     }
   },
   {
-    "pattern": { "pathname": "/foo/*?" },
+    "pattern": [{ "pathname": "/foo/*?" }],
     "inputs": [{ "pathname": "/foo/" }],
     "expected_match": {
       "pathname": { "input": "/foo/", "groups": { "0": "" } }
     }
   },
   {
-    "pattern": { "pathname": "/foo/(.*)?" },
+    "pattern": [{ "pathname": "/foo/(.*)?" }],
     "inputs": [{ "pathname": "/foobar" }],
     "expected_obj": {
       "pathname": "/foo/*?"
@@ -461,12 +461,12 @@
     "expected_match": null
   },
   {
-    "pattern": { "pathname": "/foo/*?" },
+    "pattern": [{ "pathname": "/foo/*?" }],
     "inputs": [{ "pathname": "/foobar" }],
     "expected_match": null
   },
   {
-    "pattern": { "pathname": "/foo/(.*)?" },
+    "pattern": [{ "pathname": "/foo/(.*)?" }],
     "inputs": [{ "pathname": "/fo" }],
     "expected_obj": {
       "pathname": "/foo/*?"
@@ -474,12 +474,12 @@
     "expected_match": null
   },
   {
-    "pattern": { "pathname": "/foo/*?" },
+    "pattern": [{ "pathname": "/foo/*?" }],
     "inputs": [{ "pathname": "/fo" }],
     "expected_match": null
   },
   {
-    "pattern": { "pathname": "/foo/(.*)+" },
+    "pattern": [{ "pathname": "/foo/(.*)+" }],
     "inputs": [{ "pathname": "/foo/bar" }],
     "expected_obj": {
       "pathname": "/foo/*+"
@@ -489,14 +489,14 @@
     }
   },
   {
-    "pattern": { "pathname": "/foo/*+" },
+    "pattern": [{ "pathname": "/foo/*+" }],
     "inputs": [{ "pathname": "/foo/bar" }],
     "expected_match": {
       "pathname": { "input": "/foo/bar", "groups": { "0": "bar" } }
     }
   },
   {
-    "pattern": { "pathname": "/foo/(.*)+" },
+    "pattern": [{ "pathname": "/foo/(.*)+" }],
     "inputs": [{ "pathname": "/foo/bar/baz" }],
     "expected_obj": {
       "pathname": "/foo/*+"
@@ -506,14 +506,14 @@
     }
   },
   {
-    "pattern": { "pathname": "/foo/*+" },
+    "pattern": [{ "pathname": "/foo/*+" }],
     "inputs": [{ "pathname": "/foo/bar/baz" }],
     "expected_match": {
       "pathname": { "input": "/foo/bar/baz", "groups": { "0": "bar/baz" } }
     }
   },
   {
-    "pattern": { "pathname": "/foo/(.*)+" },
+    "pattern": [{ "pathname": "/foo/(.*)+" }],
     "inputs": [{ "pathname": "/foo" }],
     "expected_obj": {
       "pathname": "/foo/*+"
@@ -521,12 +521,12 @@
     "expected_match": null
   },
   {
-    "pattern": { "pathname": "/foo/*+" },
+    "pattern": [{ "pathname": "/foo/*+" }],
     "inputs": [{ "pathname": "/foo" }],
     "expected_match": null
   },
   {
-    "pattern": { "pathname": "/foo/(.*)+" },
+    "pattern": [{ "pathname": "/foo/(.*)+" }],
     "inputs": [{ "pathname": "/foo/" }],
     "expected_obj": {
       "pathname": "/foo/*+"
@@ -536,14 +536,14 @@
     }
   },
   {
-    "pattern": { "pathname": "/foo/*+" },
+    "pattern": [{ "pathname": "/foo/*+" }],
     "inputs": [{ "pathname": "/foo/" }],
     "expected_match": {
       "pathname": { "input": "/foo/", "groups": { "0": "" } }
     }
   },
   {
-    "pattern": { "pathname": "/foo/(.*)+" },
+    "pattern": [{ "pathname": "/foo/(.*)+" }],
     "inputs": [{ "pathname": "/foobar" }],
     "expected_obj": {
       "pathname": "/foo/*+"
@@ -551,12 +551,12 @@
     "expected_match": null
   },
   {
-    "pattern": { "pathname": "/foo/*+" },
+    "pattern": [{ "pathname": "/foo/*+" }],
     "inputs": [{ "pathname": "/foobar" }],
     "expected_match": null
   },
   {
-    "pattern": { "pathname": "/foo/(.*)+" },
+    "pattern": [{ "pathname": "/foo/(.*)+" }],
     "inputs": [{ "pathname": "/fo" }],
     "expected_obj": {
       "pathname": "/foo/*+"
@@ -564,12 +564,12 @@
     "expected_match": null
   },
   {
-    "pattern": { "pathname": "/foo/*+" },
+    "pattern": [{ "pathname": "/foo/*+" }],
     "inputs": [{ "pathname": "/fo" }],
     "expected_match": null
   },
   {
-    "pattern": { "pathname": "/foo/(.*)*" },
+    "pattern": [{ "pathname": "/foo/(.*)*" }],
     "inputs": [{ "pathname": "/foo/bar" }],
     "expected_obj": {
       "pathname": "/foo/**"
@@ -579,14 +579,14 @@
     }
   },
   {
-    "pattern": { "pathname": "/foo/**" },
+    "pattern": [{ "pathname": "/foo/**" }],
     "inputs": [{ "pathname": "/foo/bar" }],
     "expected_match": {
       "pathname": { "input": "/foo/bar", "groups": { "0": "bar" } }
     }
   },
   {
-    "pattern": { "pathname": "/foo/(.*)*" },
+    "pattern": [{ "pathname": "/foo/(.*)*" }],
     "inputs": [{ "pathname": "/foo/bar/baz" }],
     "expected_obj": {
       "pathname": "/foo/**"
@@ -596,14 +596,14 @@
     }
   },
   {
-    "pattern": { "pathname": "/foo/**" },
+    "pattern": [{ "pathname": "/foo/**" }],
     "inputs": [{ "pathname": "/foo/bar/baz" }],
     "expected_match": {
       "pathname": { "input": "/foo/bar/baz", "groups": { "0": "bar/baz" } }
     }
   },
   {
-    "pattern": { "pathname": "/foo/(.*)*" },
+    "pattern": [{ "pathname": "/foo/(.*)*" }],
     "inputs": [{ "pathname": "/foo" }],
     "expected_obj": {
       "pathname": "/foo/**"
@@ -613,14 +613,14 @@
     }
   },
   {
-    "pattern": { "pathname": "/foo/**" },
+    "pattern": [{ "pathname": "/foo/**" }],
     "inputs": [{ "pathname": "/foo" }],
     "expected_match": {
       "pathname": { "input": "/foo", "groups": { "0": "" } }
     }
   },
   {
-    "pattern": { "pathname": "/foo/(.*)*" },
+    "pattern": [{ "pathname": "/foo/(.*)*" }],
     "inputs": [{ "pathname": "/foo/" }],
     "expected_obj": {
       "pathname": "/foo/**"
@@ -630,14 +630,14 @@
     }
   },
   {
-    "pattern": { "pathname": "/foo/**" },
+    "pattern": [{ "pathname": "/foo/**" }],
     "inputs": [{ "pathname": "/foo/" }],
     "expected_match": {
       "pathname": { "input": "/foo/", "groups": { "0": "" } }
     }
   },
   {
-    "pattern": { "pathname": "/foo/(.*)*" },
+    "pattern": [{ "pathname": "/foo/(.*)*" }],
     "inputs": [{ "pathname": "/foobar" }],
     "expected_obj": {
       "pathname": "/foo/**"
@@ -645,12 +645,12 @@
     "expected_match": null
   },
   {
-    "pattern": { "pathname": "/foo/**" },
+    "pattern": [{ "pathname": "/foo/**" }],
     "inputs": [{ "pathname": "/foobar" }],
     "expected_match": null
   },
   {
-    "pattern": { "pathname": "/foo/(.*)*" },
+    "pattern": [{ "pathname": "/foo/(.*)*" }],
     "inputs": [{ "pathname": "/fo" }],
     "expected_obj": {
       "pathname": "/foo/**"
@@ -658,12 +658,12 @@
     "expected_match": null
   },
   {
-    "pattern": { "pathname": "/foo/**" },
+    "pattern": [{ "pathname": "/foo/**" }],
     "inputs": [{ "pathname": "/fo" }],
     "expected_match": null
   },
   {
-    "pattern": { "pathname": "/foo{/bar}" },
+    "pattern": [{ "pathname": "/foo{/bar}" }],
     "inputs": [{ "pathname": "/foo/bar" }],
     "expected_obj": {
       "pathname": "/foo/bar"
@@ -673,7 +673,7 @@
     }
   },
   {
-    "pattern": { "pathname": "/foo{/bar}" },
+    "pattern": [{ "pathname": "/foo{/bar}" }],
     "inputs": [{ "pathname": "/foo/bar/baz" }],
     "expected_obj": {
       "pathname": "/foo/bar"
@@ -681,7 +681,7 @@
     "expected_match": null
   },
   {
-    "pattern": { "pathname": "/foo{/bar}" },
+    "pattern": [{ "pathname": "/foo{/bar}" }],
     "inputs": [{ "pathname": "/foo" }],
     "expected_obj": {
       "pathname": "/foo/bar"
@@ -689,7 +689,7 @@
     "expected_match": null
   },
   {
-    "pattern": { "pathname": "/foo{/bar}" },
+    "pattern": [{ "pathname": "/foo{/bar}" }],
     "inputs": [{ "pathname": "/foo/" }],
     "expected_obj": {
       "pathname": "/foo/bar"
@@ -697,266 +697,266 @@
     "expected_match": null
   },
   {
-    "pattern": { "pathname": "/foo{/bar}?" },
+    "pattern": [{ "pathname": "/foo{/bar}?" }],
     "inputs": [{ "pathname": "/foo/bar" }],
     "expected_match": {
       "pathname": { "input": "/foo/bar", "groups": {} }
     }
   },
   {
-    "pattern": { "pathname": "/foo{/bar}?" },
+    "pattern": [{ "pathname": "/foo{/bar}?" }],
     "inputs": [{ "pathname": "/foo/bar/baz" }],
     "expected_match": null
   },
   {
-    "pattern": { "pathname": "/foo{/bar}?" },
+    "pattern": [{ "pathname": "/foo{/bar}?" }],
     "inputs": [{ "pathname": "/foo" }],
     "expected_match": {
       "pathname": { "input": "/foo", "groups": {} }
     }
   },
   {
-    "pattern": { "pathname": "/foo{/bar}?" },
+    "pattern": [{ "pathname": "/foo{/bar}?" }],
     "inputs": [{ "pathname": "/foo/" }],
     "expected_match": null
   },
   {
-    "pattern": { "pathname": "/foo{/bar}+" },
+    "pattern": [{ "pathname": "/foo{/bar}+" }],
     "inputs": [{ "pathname": "/foo/bar" }],
     "expected_match": {
       "pathname": { "input": "/foo/bar", "groups": {} }
     }
   },
   {
-    "pattern": { "pathname": "/foo{/bar}+" },
+    "pattern": [{ "pathname": "/foo{/bar}+" }],
     "inputs": [{ "pathname": "/foo/bar/bar" }],
     "expected_match": {
       "pathname": { "input": "/foo/bar/bar", "groups": {} }
     }
   },
   {
-    "pattern": { "pathname": "/foo{/bar}+" },
+    "pattern": [{ "pathname": "/foo{/bar}+" }],
     "inputs": [{ "pathname": "/foo/bar/baz" }],
     "expected_match": null
   },
   {
-    "pattern": { "pathname": "/foo{/bar}+" },
+    "pattern": [{ "pathname": "/foo{/bar}+" }],
     "inputs": [{ "pathname": "/foo" }],
     "expected_match": null
   },
   {
-    "pattern": { "pathname": "/foo{/bar}+" },
+    "pattern": [{ "pathname": "/foo{/bar}+" }],
     "inputs": [{ "pathname": "/foo/" }],
     "expected_match": null
   },
   {
-    "pattern": { "pathname": "/foo{/bar}*" },
+    "pattern": [{ "pathname": "/foo{/bar}*" }],
     "inputs": [{ "pathname": "/foo/bar" }],
     "expected_match": {
       "pathname": { "input": "/foo/bar", "groups": {} }
     }
   },
   {
-    "pattern": { "pathname": "/foo{/bar}*" },
+    "pattern": [{ "pathname": "/foo{/bar}*" }],
     "inputs": [{ "pathname": "/foo/bar/bar" }],
     "expected_match": {
       "pathname": { "input": "/foo/bar/bar", "groups": {} }
     }
   },
   {
-    "pattern": { "pathname": "/foo{/bar}*" },
+    "pattern": [{ "pathname": "/foo{/bar}*" }],
     "inputs": [{ "pathname": "/foo/bar/baz" }],
     "expected_match": null
   },
   {
-    "pattern": { "pathname": "/foo{/bar}*" },
+    "pattern": [{ "pathname": "/foo{/bar}*" }],
     "inputs": [{ "pathname": "/foo" }],
     "expected_match": {
       "pathname": { "input": "/foo", "groups": {} }
     }
   },
   {
-    "pattern": { "pathname": "/foo{/bar}*" },
+    "pattern": [{ "pathname": "/foo{/bar}*" }],
     "inputs": [{ "pathname": "/foo/" }],
     "expected_match": null
   },
   {
-    "pattern": { "protocol": "(café)" },
+    "pattern": [{ "protocol": "(café)" }],
     "expected_obj": "error"
   },
   {
-    "pattern": { "username": "(café)" },
+    "pattern": [{ "username": "(café)" }],
     "expected_obj": "error"
   },
   {
-    "pattern": { "password": "(café)" },
+    "pattern": [{ "password": "(café)" }],
     "expected_obj": "error"
   },
   {
-    "pattern": { "hostname": "(café)" },
+    "pattern": [{ "hostname": "(café)" }],
     "expected_obj": "error"
   },
   {
-    "pattern": { "pathname": "(café)" },
+    "pattern": [{ "pathname": "(café)" }],
     "expected_obj": "error"
   },
   {
-    "pattern": { "search": "(café)" },
+    "pattern": [{ "search": "(café)" }],
     "expected_obj": "error"
   },
   {
-    "pattern": { "hash": "(café)" },
+    "pattern": [{ "hash": "(café)" }],
     "expected_obj": "error"
   },
   {
-    "pattern": { "protocol": ":café" },
+    "pattern": [{ "protocol": ":café" }],
     "inputs": [{ "protocol": "foo" }],
     "expected_match": {
       "protocol": { "input": "foo", "groups": { "café": "foo" } }
     }
   },
   {
-    "pattern": { "username": ":café" },
+    "pattern": [{ "username": ":café" }],
     "inputs": [{ "username": "foo" }],
     "expected_match": {
       "username": { "input": "foo", "groups": { "café": "foo" } }
     }
   },
   {
-    "pattern": { "password": ":café" },
+    "pattern": [{ "password": ":café" }],
     "inputs": [{ "password": "foo" }],
     "expected_match": {
       "password": { "input": "foo", "groups": { "café": "foo" } }
     }
   },
   {
-    "pattern": { "hostname": ":café" },
+    "pattern": [{ "hostname": ":café" }],
     "inputs": [{ "hostname": "foo" }],
     "expected_match": {
       "hostname": { "input": "foo", "groups": { "café": "foo" } }
     }
   },
   {
-    "pattern": { "pathname": "/:café" },
+    "pattern": [{ "pathname": "/:café" }],
     "inputs": [{ "pathname": "/foo" }],
     "expected_match": {
       "pathname": { "input": "/foo", "groups": { "café": "foo" } }
     }
   },
   {
-    "pattern": { "search": ":café" },
+    "pattern": [{ "search": ":café" }],
     "inputs": [{ "search": "foo" }],
     "expected_match": {
       "search": { "input": "foo", "groups": { "café": "foo" } }
     }
   },
   {
-    "pattern": { "hash": ":café" },
+    "pattern": [{ "hash": ":café" }],
     "inputs": [{ "hash": "foo" }],
     "expected_match": {
       "hash": { "input": "foo", "groups": { "café": "foo" } }
     }
   },
   {
-    "pattern": { "protocol": ":\u2118" },
+    "pattern": [{ "protocol": ":\u2118" }],
     "inputs": [{ "protocol": "foo" }],
     "expected_match": {
       "protocol": { "input": "foo", "groups": { "\u2118": "foo" } }
     }
   },
   {
-    "pattern": { "username": ":\u2118" },
+    "pattern": [{ "username": ":\u2118" }],
     "inputs": [{ "username": "foo" }],
     "expected_match": {
       "username": { "input": "foo", "groups": { "\u2118": "foo" } }
     }
   },
   {
-    "pattern": { "password": ":\u2118" },
+    "pattern": [{ "password": ":\u2118" }],
     "inputs": [{ "password": "foo" }],
     "expected_match": {
       "password": { "input": "foo", "groups": { "\u2118": "foo" } }
     }
   },
   {
-    "pattern": { "hostname": ":\u2118" },
+    "pattern": [{ "hostname": ":\u2118" }],
     "inputs": [{ "hostname": "foo" }],
     "expected_match": {
       "hostname": { "input": "foo", "groups": { "\u2118": "foo" } }
     }
   },
   {
-    "pattern": { "pathname": "/:\u2118" },
+    "pattern": [{ "pathname": "/:\u2118" }],
     "inputs": [{ "pathname": "/foo" }],
     "expected_match": {
       "pathname": { "input": "/foo", "groups": { "\u2118": "foo" } }
     }
   },
   {
-    "pattern": { "search": ":\u2118" },
+    "pattern": [{ "search": ":\u2118" }],
     "inputs": [{ "search": "foo" }],
     "expected_match": {
       "search": { "input": "foo", "groups": { "\u2118": "foo" } }
     }
   },
   {
-    "pattern": { "hash": ":\u2118" },
+    "pattern": [{ "hash": ":\u2118" }],
     "inputs": [{ "hash": "foo" }],
     "expected_match": {
       "hash": { "input": "foo", "groups": { "\u2118": "foo" } }
     }
   },
   {
-    "pattern": { "protocol": ":\u3400" },
+    "pattern": [{ "protocol": ":\u3400" }],
     "inputs": [{ "protocol": "foo" }],
     "expected_match": {
       "protocol": { "input": "foo", "groups": { "\u3400": "foo" } }
     }
   },
   {
-    "pattern": { "username": ":\u3400" },
+    "pattern": [{ "username": ":\u3400" }],
     "inputs": [{ "username": "foo" }],
     "expected_match": {
       "username": { "input": "foo", "groups": { "\u3400": "foo" } }
     }
   },
   {
-    "pattern": { "password": ":\u3400" },
+    "pattern": [{ "password": ":\u3400" }],
     "inputs": [{ "password": "foo" }],
     "expected_match": {
       "password": { "input": "foo", "groups": { "\u3400": "foo" } }
     }
   },
   {
-    "pattern": { "hostname": ":\u3400" },
+    "pattern": [{ "hostname": ":\u3400" }],
     "inputs": [{ "hostname": "foo" }],
     "expected_match": {
       "hostname": { "input": "foo", "groups": { "\u3400": "foo" } }
     }
   },
   {
-    "pattern": { "pathname": "/:\u3400" },
+    "pattern": [{ "pathname": "/:\u3400" }],
     "inputs": [{ "pathname": "/foo" }],
     "expected_match": {
       "pathname": { "input": "/foo", "groups": { "\u3400": "foo" } }
     }
   },
   {
-    "pattern": { "search": ":\u3400" },
+    "pattern": [{ "search": ":\u3400" }],
     "inputs": [{ "search": "foo" }],
     "expected_match": {
       "search": { "input": "foo", "groups": { "\u3400": "foo" } }
     }
   },
   {
-    "pattern": { "hash": ":\u3400" },
+    "pattern": [{ "hash": ":\u3400" }],
     "inputs": [{ "hash": "foo" }],
     "expected_match": {
       "hash": { "input": "foo", "groups": { "\u3400": "foo" } }
     }
   },
   {
-    "pattern": { "protocol": "(.*)" },
+    "pattern": [{ "protocol": "(.*)" }],
     "inputs": [{ "protocol" : "café" }],
     "expected_obj": {
       "protocol": "*"
@@ -964,7 +964,7 @@
     "expected_match": null
   },
   {
-    "pattern": { "protocol": "(.*)" },
+    "pattern": [{ "protocol": "(.*)" }],
     "inputs": [{ "protocol": "cafe" }],
     "expected_obj": {
       "protocol": "*"
@@ -974,21 +974,21 @@
     }
   },
   {
-    "pattern": { "protocol": "foo-bar" },
+    "pattern": [{ "protocol": "foo-bar" }],
     "inputs": [{ "protocol": "foo-bar" }],
     "expected_match": {
       "protocol": { "input": "foo-bar", "groups": {} }
     }
   },
   {
-    "pattern": { "username": "caf%C3%A9" },
+    "pattern": [{ "username": "caf%C3%A9" }],
     "inputs": [{ "username" : "café" }],
     "expected_match": {
       "username": { "input": "caf%C3%A9", "groups": {}}
     }
   },
   {
-    "pattern": { "username": "café" },
+    "pattern": [{ "username": "café" }],
     "inputs": [{ "username" : "café" }],
     "expected_obj": {
       "username": "caf%C3%A9"
@@ -998,19 +998,19 @@
     }
   },
   {
-    "pattern": { "username": "caf%c3%a9" },
+    "pattern": [{ "username": "caf%c3%a9" }],
     "inputs": [{ "username" : "café" }],
     "expected_match": null
   },
   {
-    "pattern": { "password": "caf%C3%A9" },
+    "pattern": [{ "password": "caf%C3%A9" }],
     "inputs": [{ "password" : "café" }],
     "expected_match": {
       "password": { "input": "caf%C3%A9", "groups": {}}
     }
   },
   {
-    "pattern": { "password": "café" },
+    "pattern": [{ "password": "café" }],
     "inputs": [{ "password" : "café" }],
     "expected_obj": {
       "password": "caf%C3%A9"
@@ -1020,19 +1020,19 @@
     }
   },
   {
-    "pattern": { "password": "caf%c3%a9" },
+    "pattern": [{ "password": "caf%c3%a9" }],
     "inputs": [{ "password" : "café" }],
     "expected_match": null
   },
   {
-    "pattern": { "hostname": "xn--caf-dma.com" },
+    "pattern": [{ "hostname": "xn--caf-dma.com" }],
     "inputs": [{ "hostname" : "café.com" }],
     "expected_match": {
       "hostname": { "input": "xn--caf-dma.com", "groups": {}}
     }
   },
   {
-    "pattern": { "hostname": "café.com" },
+    "pattern": [{ "hostname": "café.com" }],
     "inputs": [{ "hostname" : "café.com" }],
     "expected_obj": {
       "hostname": "xn--caf-dma.com"
@@ -1042,50 +1042,50 @@
     }
   },
   {
-    "pattern": { "port": "" },
+    "pattern": [{ "port": "" }],
     "inputs": [{ "protocol": "http", "port": "80" }],
+    "exactly_empty_components": [ "port" ],
     "expected_match": {
-      "protocol": { "input": "http", "groups": { "0": "http" }},
-      "port": { "input": "", "groups": {}}
+      "protocol": { "input": "http", "groups": { "0": "http" }}
     }
   },
   {
-    "pattern": { "protocol": "http", "port": "80" },
+    "pattern": [{ "protocol": "http", "port": "80" }],
     "inputs": [{ "protocol": "http", "port": "80" }],
+    "exactly_empty_components": [ "port" ],
     "expected_match": {
-      "protocol": { "input": "http", "groups": {}},
-      "port": { "input": "", "groups": {}}
+      "protocol": { "input": "http", "groups": {}}
     }
   },
   {
-    "pattern": { "protocol": "http", "port": "80{20}?" },
+    "pattern": [{ "protocol": "http", "port": "80{20}?" }],
     "inputs": [{ "protocol": "http", "port": "80" }],
     "expected_match": null
   },
   {
-    "pattern": { "protocol": "http", "port": "80 " },
+    "pattern": [{ "protocol": "http", "port": "80 " }],
     "inputs": [{ "protocol": "http", "port": "80" }],
     "expected_obj": "error"
   },
   {
-    "pattern": { "port": "80" },
+    "pattern": [{ "port": "80" }],
     "inputs": [{ "protocol": "http", "port": "80" }],
     "expected_match": null
   },
   {
-    "pattern": { "protocol": "http{s}?", "port": "80" },
+    "pattern": [{ "protocol": "http{s}?", "port": "80" }],
     "inputs": [{ "protocol": "http", "port": "80" }],
     "expected_match": null
   },
   {
-    "pattern": { "port": "80" },
+    "pattern": [{ "port": "80" }],
     "inputs": [{ "port": "80" }],
     "expected_match": {
       "port": { "input": "80", "groups": {}}
     }
   },
   {
-    "pattern": { "port": "(.*)" },
+    "pattern": [{ "port": "(.*)" }],
     "inputs": [{ "port": "invalid80" }],
     "expected_obj": {
       "port": "*"
@@ -1093,28 +1093,28 @@
     "expected_match": null
   },
   {
-    "pattern": { "pathname": "/foo/bar" },
+    "pattern": [{ "pathname": "/foo/bar" }],
     "inputs": [{ "pathname": "/foo/./bar" }],
     "expected_match": {
       "pathname": { "input": "/foo/bar", "groups": {}}
     }
   },
   {
-    "pattern": { "pathname": "/foo/baz" },
+    "pattern": [{ "pathname": "/foo/baz" }],
     "inputs": [{ "pathname": "/foo/bar/../baz" }],
     "expected_match": {
       "pathname": { "input": "/foo/baz", "groups": {}}
     }
   },
   {
-    "pattern": { "pathname": "/caf%C3%A9" },
+    "pattern": [{ "pathname": "/caf%C3%A9" }],
     "inputs": [{ "pathname": "/café" }],
     "expected_match": {
       "pathname": { "input": "/caf%C3%A9", "groups": {}}
     }
   },
   {
-    "pattern": { "pathname": "/café" },
+    "pattern": [{ "pathname": "/café" }],
     "inputs": [{ "pathname": "/café" }],
     "expected_obj": {
       "pathname": "/caf%C3%A9"
@@ -1124,17 +1124,17 @@
     }
   },
   {
-    "pattern": { "pathname": "/caf%c3%a9" },
+    "pattern": [{ "pathname": "/caf%c3%a9" }],
     "inputs": [{ "pathname": "/café" }],
     "expected_match": null
   },
   {
-    "pattern": { "pathname": "/foo/bar" },
+    "pattern": [{ "pathname": "/foo/bar" }],
     "inputs": [{ "pathname": "foo/bar" }],
     "expected_match": null
   },
   {
-    "pattern": { "pathname": "/foo/bar" },
+    "pattern": [{ "pathname": "/foo/bar" }],
     "inputs": [{ "pathname": "foo/bar", "baseURL": "https://example.com" }],
     "expected_match": {
       "protocol": { "input": "https", "groups": { "0": "https" }},
@@ -1143,7 +1143,7 @@
     }
   },
   {
-    "pattern": { "pathname": "/foo/../bar" },
+    "pattern": [{ "pathname": "/foo/../bar" }],
     "inputs": [{ "pathname": "/bar" }],
     "expected_obj": {
       "pathname": "/bar"
@@ -1153,58 +1153,58 @@
     }
   },
   {
-    "pattern": { "pathname": "./foo/bar", "baseURL": "https://example.com" },
+    "pattern": [{ "pathname": "./foo/bar", "baseURL": "https://example.com" }],
     "inputs": [{ "pathname": "foo/bar", "baseURL": "https://example.com" }],
     "expected_obj": {
       "pathname": "/foo/bar"
     },
+    "exactly_empty_components": [ "username", "password", "port" ],
     "expected_match": {
-      "exactly_empty_components": [ "username", "password", "port" ],
       "protocol": { "input": "https", "groups": {}},
       "hostname": { "input": "example.com", "groups": {}},
       "pathname": { "input": "/foo/bar", "groups": {}}
     }
   },
   {
-    "pattern": { "pathname": "foo/bar" },
+    "pattern": [{ "pathname": "foo/bar" }],
     "inputs": [ "https://example.com/foo/bar" ],
     "expected_match": null
   },
   {
-    "pattern": { "pathname": "foo/bar", "baseURL": "https://example.com" },
+    "pattern": [{ "pathname": "foo/bar", "baseURL": "https://example.com" }],
     "inputs": [ "https://example.com/foo/bar" ],
     "expected_obj": {
       "pathname": "/foo/bar"
     },
+    "exactly_empty_components": [ "username", "password", "port" ],
     "expected_match": {
-      "exactly_empty_components": [ "username", "password", "port" ],
       "protocol": { "input": "https", "groups": {}},
       "hostname": { "input": "example.com", "groups": {}},
       "pathname": { "input": "/foo/bar", "groups": {}}
     }
   },
   {
-    "pattern": { "pathname": ":name.html", "baseURL": "https://example.com" },
+    "pattern": [{ "pathname": ":name.html", "baseURL": "https://example.com" }],
     "inputs": [ "https://example.com/foo.html"] ,
     "expected_obj": {
       "pathname": "/:name.html"
     },
+    "exactly_empty_components": [ "username", "password", "port" ],
     "expected_match": {
-      "exactly_empty_components": [ "username", "password", "port" ],
       "protocol": { "input": "https", "groups": {}},
       "hostname": { "input": "example.com", "groups": {}},
       "pathname": { "input": "/foo.html", "groups": { "name": "foo" }}
     }
   },
   {
-    "pattern": { "search": "q=caf%C3%A9" },
+    "pattern": [{ "search": "q=caf%C3%A9" }],
     "inputs": [{ "search": "q=café" }],
     "expected_match": {
       "search": { "input": "q=caf%C3%A9", "groups": {}}
     }
   },
   {
-    "pattern": { "search": "q=café" },
+    "pattern": [{ "search": "q=café" }],
     "inputs": [{ "search": "q=café" }],
     "expected_obj": {
       "search": "q=caf%C3%A9"
@@ -1214,19 +1214,19 @@
     }
   },
   {
-    "pattern": { "search": "q=caf%c3%a9" },
+    "pattern": [{ "search": "q=caf%c3%a9" }],
     "inputs": [{ "search": "q=café" }],
     "expected_match": null
   },
   {
-    "pattern": { "hash": "caf%C3%A9" },
+    "pattern": [{ "hash": "caf%C3%A9" }],
     "inputs": [{ "hash": "café" }],
     "expected_match": {
       "hash": { "input": "caf%C3%A9", "groups": {}}
     }
   },
   {
-    "pattern": { "hash": "café" },
+    "pattern": [{ "hash": "café" }],
     "inputs": [{ "hash": "café" }],
     "expected_obj": {
       "hash": "caf%C3%A9"
@@ -1236,12 +1236,12 @@
     }
   },
   {
-    "pattern": { "hash": "caf%c3%a9" },
+    "pattern": [{ "hash": "caf%c3%a9" }],
     "inputs": [{ "hash": "café" }],
     "expected_match": null
   },
   {
-    "pattern": { "protocol": "about", "pathname": "(blank|sourcedoc)" },
+    "pattern": [{ "protocol": "about", "pathname": "(blank|sourcedoc)" }],
     "inputs": [ "about:blank" ],
     "expected_match": {
       "protocol": { "input": "about", "groups": {}},
@@ -1249,7 +1249,7 @@
     }
   },
   {
-    "pattern": { "protocol": "data", "pathname": ":number([0-9]+)" },
+    "pattern": [{ "protocol": "data", "pathname": ":number([0-9]+)" }],
     "inputs": [ "data:8675309" ],
     "expected_match": {
       "protocol": { "input": "data", "groups": {}},
@@ -1257,25 +1257,25 @@
     }
   },
   {
-    "pattern": { "pathname": "/(\\m)" },
+    "pattern": [{ "pathname": "/(\\m)" }],
     "expected_obj": "error"
   },
   {
-    "pattern": { "pathname": "/foo!" },
+    "pattern": [{ "pathname": "/foo!" }],
     "inputs": [{ "pathname": "/foo!" }],
     "expected_match": {
       "pathname": { "input": "/foo!", "groups": {}}
     }
   },
   {
-    "pattern": { "pathname": "/foo\\:" },
+    "pattern": [{ "pathname": "/foo\\:" }],
     "inputs": [{ "pathname": "/foo:" }],
     "expected_match": {
       "pathname": { "input": "/foo:", "groups": {}}
     }
   },
   {
-    "pattern": { "pathname": "/foo\\{" },
+    "pattern": [{ "pathname": "/foo\\{" }],
     "inputs": [{ "pathname": "/foo{" }],
     "expected_obj": {
       "pathname": "/foo%7B"
@@ -1285,14 +1285,14 @@
     }
   },
   {
-    "pattern": { "pathname": "/foo\\(" },
+    "pattern": [{ "pathname": "/foo\\(" }],
     "inputs": [{ "pathname": "/foo(" }],
     "expected_match": {
       "pathname": { "input": "/foo(", "groups": {}}
     }
   },
   {
-    "pattern": { "protocol": "javascript", "pathname": "var x = 1;" },
+    "pattern": [{ "protocol": "javascript", "pathname": "var x = 1;" }],
     "inputs": [{ "protocol": "javascript", "pathname": "var x = 1;" }],
     "expected_match": {
       "protocol": { "input": "javascript", "groups": {}},
@@ -1300,7 +1300,7 @@
     }
   },
   {
-    "pattern": { "pathname": "var x = 1;" },
+    "pattern": [{ "pathname": "var x = 1;" }],
     "inputs": [{ "protocol": "javascript", "pathname": "var x = 1;" }],
     "expected_obj": {
       "pathname": "var%20x%20=%201;"
@@ -1308,7 +1308,7 @@
     "expected_match": null
   },
   {
-    "pattern": { "protocol": "javascript", "pathname": "var x = 1;" },
+    "pattern": [{ "protocol": "javascript", "pathname": "var x = 1;" }],
     "inputs": [{ "baseURL": "javascript:var x = 1;" }],
     "expected_match": {
       "protocol": { "input": "javascript", "groups": {}},
@@ -1316,7 +1316,7 @@
     }
   },
   {
-    "pattern": { "protocol": "(data|javascript)", "pathname": "var x = 1;" },
+    "pattern": [{ "protocol": "(data|javascript)", "pathname": "var x = 1;" }],
     "inputs": [{ "protocol": "javascript", "pathname": "var x = 1;" }],
     "expected_match": {
       "protocol": { "input": "javascript", "groups": {"0": "javascript"}},
@@ -1324,7 +1324,7 @@
     }
   },
   {
-    "pattern": { "protocol": "(https|javascript)", "pathname": "var x = 1;" },
+    "pattern": [{ "protocol": "(https|javascript)", "pathname": "var x = 1;" }],
     "inputs": [{ "protocol": "javascript", "pathname": "var x = 1;" }],
     "expected_obj": {
       "pathname": "var%20x%20=%201;"
@@ -1332,7 +1332,7 @@
     "expected_match": null
   },
   {
-    "pattern": { "pathname": "var x = 1;" },
+    "pattern": [{ "pathname": "var x = 1;" }],
     "inputs": [{ "pathname": "var x = 1;" }],
     "expected_obj": {
       "pathname": "var%20x%20=%201;"
@@ -1342,7 +1342,7 @@
     }
   },
   {
-    "pattern": { "pathname": "/foo/bar" },
+    "pattern": [{ "pathname": "/foo/bar" }],
     "inputs": [ "./foo/bar", "https://example.com" ],
     "expected_match": {
       "hostname": { "input": "example.com", "groups": { "0": "example.com" } },
@@ -1351,8 +1351,449 @@
     }
   },
   {
-    "pattern": { "pathname": "/foo/bar" },
+    "pattern": [{ "pathname": "/foo/bar" }],
     "inputs": [ { "pathname": "/foo/bar" }, "https://example.com" ],
     "expected_match": "error"
+  },
+  {
+    "pattern": [ "https://example.com:8080/foo?bar#baz" ],
+    "inputs": [{ "pathname": "/foo", "search": "bar", "hash": "baz",
+                 "baseURL": "https://example.com:8080" }],
+    "exactly_empty_components": [ "username", "password" ],
+    "expected_obj": {
+      "protocol": "https",
+      "hostname": "example.com",
+      "port": "8080",
+      "pathname": "/foo",
+      "search": "bar",
+      "hash": "baz"
+    },
+    "expected_match": {
+      "protocol": { "input": "https", "groups": {} },
+      "hostname": { "input": "example.com", "groups": {} },
+      "port": { "input": "8080", "groups": {} },
+      "pathname": { "input": "/foo", "groups": {} },
+      "search": { "input": "bar", "groups": {} },
+      "hash": { "input": "baz", "groups": {} }
+    }
+  },
+  {
+    "pattern": [ "/foo?bar#baz", "https://example.com:8080" ],
+    "inputs": [{ "pathname": "/foo", "search": "bar", "hash": "baz",
+                 "baseURL": "https://example.com:8080" }],
+    "exactly_empty_components": [ "username", "password" ],
+    "expected_obj": {
+      "pathname": "/foo",
+      "search": "bar",
+      "hash": "baz"
+    },
+    "expected_match": {
+      "protocol": { "input": "https", "groups": {} },
+      "hostname": { "input": "example.com", "groups": {} },
+      "port": { "input": "8080", "groups": {} },
+      "pathname": { "input": "/foo", "groups": {} },
+      "search": { "input": "bar", "groups": {} },
+      "hash": { "input": "baz", "groups": {} }
+    }
+  },
+  {
+    "pattern": [ "/foo" ],
+    "expected_obj": "error"
+  },
+  {
+    "pattern": [ "example.com/foo" ],
+    "expected_obj": "error"
+  },
+  {
+    "pattern": [ "http{s}?://{*.}?example.com/:product/:endpoint" ],
+    "inputs": [ "https://sub.example.com/foo/bar" ],
+    "exactly_empty_components": [ "username", "password", "port", "search",
+                                  "hash" ],
+    "expected_obj": {
+      "protocol": "http{s}?",
+      "hostname": "{*.}?example.com",
+      "pathname": "/:product/:endpoint"
+    },
+    "expected_match": {
+      "protocol": { "input": "https", "groups": {} },
+      "hostname": { "input": "sub.example.com", "groups": { "0": "sub" } },
+      "pathname": { "input": "/foo/bar", "groups": { "product": "foo",
+                                                     "endpoint": "bar" } }
+    }
+  },
+  {
+    "pattern": [ "https://example.com?foo" ],
+    "inputs": [ "https://example.com/?foo" ],
+    "exactly_empty_components": [ "username", "password", "port", "search",
+                                  "hash" ],
+    "expected_obj": {
+      "protocol": "https",
+      "hostname": "example.com",
+      "pathname": "/",
+      "search": "foo"
+    },
+    "expected_match": {
+      "protocol": { "input": "https", "groups": {} },
+      "hostname": { "input": "example.com", "groups": {} },
+      "pathname": { "input": "/", "groups": {} },
+      "search": { "input": "foo", "groups": {} }
+    }
+  },
+  {
+    "pattern": [ "https://example.com#foo" ],
+    "inputs": [ "https://example.com/#foo" ],
+    "exactly_empty_components": [ "username", "password", "port", "search",
+                                  "hash" ],
+    "expected_obj": {
+      "protocol": "https",
+      "hostname": "example.com",
+      "pathname": "/",
+      "hash": "foo"
+    },
+    "expected_match": {
+      "protocol": { "input": "https", "groups": {} },
+      "hostname": { "input": "example.com", "groups": {} },
+      "pathname": { "input": "/", "groups": {} },
+      "hash": { "input": "foo", "groups": {} }
+    }
+  },
+  {
+    "pattern": [ "https://example.com:8080?foo" ],
+    "inputs": [ "https://example.com:8080/?foo" ],
+    "exactly_empty_components": [ "username", "password", "hash" ],
+    "expected_obj": {
+      "protocol": "https",
+      "hostname": "example.com",
+      "port": "8080",
+      "pathname": "/",
+      "search": "foo"
+    },
+    "expected_match": {
+      "protocol": { "input": "https", "groups": {} },
+      "hostname": { "input": "example.com", "groups": {} },
+      "port": { "input": "8080", "groups": {} },
+      "pathname": { "input": "/", "groups": {} },
+      "search": { "input": "foo", "groups": {} }
+    }
+  },
+  {
+    "pattern": [ "https://example.com:8080#foo" ],
+    "inputs": [ "https://example.com:8080/#foo" ],
+    "exactly_empty_components": [ "username", "password", "search" ],
+    "expected_obj": {
+      "protocol": "https",
+      "hostname": "example.com",
+      "port": "8080",
+      "pathname": "/",
+      "hash": "foo"
+    },
+    "expected_match": {
+      "protocol": { "input": "https", "groups": {} },
+      "hostname": { "input": "example.com", "groups": {} },
+      "port": { "input": "8080", "groups": {} },
+      "pathname": { "input": "/", "groups": {} },
+      "hash": { "input": "foo", "groups": {} }
+    }
+  },
+  {
+    "pattern": [ "https://example.com/?foo" ],
+    "inputs": [ "https://example.com/?foo" ],
+    "exactly_empty_components": [ "username", "password", "port", "hash" ],
+    "expected_obj": {
+      "protocol": "https",
+      "hostname": "example.com",
+      "pathname": "/",
+      "search": "foo"
+    },
+    "expected_match": {
+      "protocol": { "input": "https", "groups": {} },
+      "hostname": { "input": "example.com", "groups": {} },
+      "pathname": { "input": "/", "groups": {} },
+      "search": { "input": "foo", "groups": {} }
+    }
+  },
+  {
+    "pattern": [ "https://example.com/#foo" ],
+    "inputs": [ "https://example.com/#foo" ],
+    "exactly_empty_components": [ "username", "password", "port", "search" ],
+    "expected_obj": {
+      "protocol": "https",
+      "hostname": "example.com",
+      "pathname": "/",
+      "hash": "foo"
+    },
+    "expected_match": {
+      "protocol": { "input": "https", "groups": {} },
+      "hostname": { "input": "example.com", "groups": {} },
+      "pathname": { "input": "/", "groups": {} },
+      "hash": { "input": "foo", "groups": {} }
+    }
+  },
+  {
+    "pattern": [ "https://example.com/*?foo" ],
+    "inputs": [ "https://example.com/?foo" ],
+    "exactly_empty_components": [ "username", "password", "port", "search",
+                                  "hash" ],
+    "expected_obj": {
+      "protocol": "https",
+      "hostname": "example.com",
+      "pathname": "/*?foo"
+    },
+    "expected_match": null
+  },
+  {
+    "pattern": [ "https://example.com/*\\?foo" ],
+    "inputs": [ "https://example.com/?foo" ],
+    "exactly_empty_components": [ "username", "password", "port", "hash" ],
+    "expected_obj": {
+      "protocol": "https",
+      "hostname": "example.com",
+      "pathname": "/*",
+      "search": "foo"
+    },
+    "expected_match": {
+      "protocol": { "input": "https", "groups": {} },
+      "hostname": { "input": "example.com", "groups": {} },
+      "pathname": { "input": "/", "groups": { "0": "" } },
+      "search": { "input": "foo", "groups": {} }
+    }
+  },
+  {
+    "pattern": [ "https://example.com/:name?foo" ],
+    "inputs": [ "https://example.com/bar?foo" ],
+    "exactly_empty_components": [ "username", "password", "port", "search",
+                                  "hash" ],
+    "expected_obj": {
+      "protocol": "https",
+      "hostname": "example.com",
+      "pathname": "/:name?foo"
+    },
+    "expected_match": null
+  },
+  {
+    "pattern": [ "https://example.com/:name\\?foo" ],
+    "inputs": [ "https://example.com/bar?foo" ],
+    "exactly_empty_components": [ "username", "password", "port", "hash" ],
+    "expected_obj": {
+      "protocol": "https",
+      "hostname": "example.com",
+      "pathname": "/:name",
+      "search": "foo"
+    },
+    "expected_match": {
+      "protocol": { "input": "https", "groups": {} },
+      "hostname": { "input": "example.com", "groups": {} },
+      "pathname": { "input": "/bar", "groups": { "name": "bar" } },
+      "search": { "input": "foo", "groups": {} }
+    }
+  },
+  {
+    "pattern": [ "https://example.com/(bar)?foo" ],
+    "inputs": [ "https://example.com/bar?foo" ],
+    "exactly_empty_components": [ "username", "password", "port", "search", "hash" ],
+    "expected_obj": {
+      "protocol": "https",
+      "hostname": "example.com",
+      "pathname": "/(bar)?foo"
+    },
+    "expected_match": null
+  },
+  {
+    "pattern": [ "https://example.com/(bar)\\?foo" ],
+    "inputs": [ "https://example.com/bar?foo" ],
+    "exactly_empty_components": [ "username", "password", "port", "hash" ],
+    "expected_obj": {
+      "protocol": "https",
+      "hostname": "example.com",
+      "pathname": "/(bar)",
+      "search": "foo"
+    },
+    "expected_match": {
+      "protocol": { "input": "https", "groups": {} },
+      "hostname": { "input": "example.com", "groups": {} },
+      "pathname": { "input": "/bar", "groups": { "0": "bar" } },
+      "search": { "input": "foo", "groups": {} }
+    }
+  },
+  {
+    "pattern": [ "https://example.com/{bar}?foo" ],
+    "inputs": [ "https://example.com/bar?foo" ],
+    "exactly_empty_components": [ "username", "password", "port", "search", "hash" ],
+    "expected_obj": {
+      "protocol": "https",
+      "hostname": "example.com",
+      "pathname": "/{bar}?foo"
+    },
+    "expected_match": null
+  },
+  {
+    "pattern": [ "https://example.com/{bar}\\?foo" ],
+    "inputs": [ "https://example.com/bar?foo" ],
+    "exactly_empty_components": [ "username", "password", "port", "hash" ],
+    "expected_obj": {
+      "protocol": "https",
+      "hostname": "example.com",
+      "pathname": "/bar",
+      "search": "foo"
+    },
+    "expected_match": {
+      "protocol": { "input": "https", "groups": {} },
+      "hostname": { "input": "example.com", "groups": {} },
+      "pathname": { "input": "/bar", "groups": {} },
+      "search": { "input": "foo", "groups": {} }
+    }
+  },
+  {
+    "pattern": [ "https://example.com/" ],
+    "inputs": [ "https://example.com:8080/" ],
+    "exactly_empty_components": [ "username", "password", "port", "search",
+                                  "hash" ],
+    "expected_obj": {
+      "protocol": "https",
+      "hostname": "example.com",
+      "port": "",
+      "pathname": "/"
+    },
+    "expected_match": null
+  },
+  {
+    "pattern": [ "data:foobar" ],
+    "inputs": [ "data:foobar" ],
+    "expected_obj": "error"
+  },
+  {
+    "pattern": [ "data\\:foobar" ],
+    "inputs": [ "data:foobar" ],
+    "exactly_empty_components": [ "username", "password", "hostname", "port",
+                                  "search", "hash" ],
+    "expected_obj": {
+      "protocol": "data",
+      "pathname": "foobar"
+    },
+    "expected_match": {
+      "protocol": { "input": "data", "groups": {} },
+      "pathname": { "input": "foobar", "groups": {} }
+    }
+  },
+  {
+    "pattern": [ "https://{sub.}?example.com/foo" ],
+    "inputs": [ "https://example.com/foo" ],
+    "exactly_empty_components": [ "username", "password", "port", "search",
+                                  "hash" ],
+    "expected_obj": {
+      "protocol": "https",
+      "hostname": "{sub.}?example.com",
+      "pathname": "/foo"
+    },
+    "expected_match": {
+      "protocol": { "input": "https", "groups": {} },
+      "hostname": { "input": "example.com", "groups": {} },
+      "pathname": { "input": "/foo", "groups": {} }
+    }
+  },
+  {
+    "pattern": [ "https://{sub.}?example{.com/}foo" ],
+    "inputs": [ "https://example.com/foo" ],
+    "expected_obj": "error"
+  },
+  {
+    "pattern": [ "{https://}example.com/foo" ],
+    "inputs": [ "https://example.com/foo" ],
+    "expected_obj": "error"
+  },
+  {
+    "pattern": [ "https://(sub.)?example.com/foo" ],
+    "inputs": [ "https://example.com/foo" ],
+    "exactly_empty_components": [ "username", "password", "port", "search",
+                                  "hash" ],
+    "expected_obj": {
+      "protocol": "https",
+      "hostname": "(sub.)?example.com",
+      "pathname": "/foo"
+    },
+    "expected_match": {
+      "protocol": { "input": "https", "groups": {} },
+      "hostname": { "input": "example.com", "groups": { "0": "" } },
+      "pathname": { "input": "/foo", "groups": {} }
+    }
+  },
+  {
+    "pattern": [ "https://(sub.)?example(.com/)foo" ],
+    "inputs": [ "https://example.com/foo" ],
+    "exactly_empty_components": [ "username", "password", "port", "search",
+                                  "hash" ],
+    "expected_obj": {
+      "protocol": "https",
+      "hostname": "(sub.)?example(.com/)foo",
+      "pathname": "/"
+    },
+    "expected_match": null
+  },
+  {
+    "pattern": [ "(https://)example.com/foo" ],
+    "inputs": [ "https://example.com/foo" ],
+    "expected_obj": "error"
+  },
+  {
+    "pattern": [ "https://{sub{.}}example.com/foo" ],
+    "inputs": [ "https://example.com/foo" ],
+    "expected_obj": "error"
+  },
+  {
+    "pattern": [ "https://(sub(?:.))?example.com/foo" ],
+    "inputs": [ "https://example.com/foo" ],
+    "exactly_empty_components": [ "username", "password", "port", "search",
+                                  "hash" ],
+    "expected_obj": {
+      "protocol": "https",
+      "hostname": "(sub(?:.))?example.com",
+      "pathname": "/foo"
+    },
+    "expected_match": {
+      "protocol": { "input": "https", "groups": {} },
+      "hostname": { "input": "example.com", "groups": { "0": "" } },
+      "pathname": { "input": "/foo", "groups": {} }
+    }
+  },
+  {
+    "pattern": [ "file:///foo/bar" ],
+    "inputs": [ "file:///foo/bar" ],
+    "exactly_empty_components": [ "username", "password", "hostname", "port",
+                                  "search", "hash" ],
+    "expected_obj": {
+      "protocol": "file",
+      "pathname": "/foo/bar"
+    },
+    "expected_match": {
+      "protocol": { "input": "file", "groups": {} },
+      "pathname": { "input": "/foo/bar", "groups": {} }
+    }
+  },
+  {
+    "pattern": [ "data:" ],
+    "inputs": [ "data:" ],
+    "exactly_empty_components": [ "username", "password", "hostname", "port",
+                                  "pathname", "search", "hash" ],
+    "expected_obj": {
+      "protocol": "data"
+    },
+    "expected_match": {
+      "protocol": { "input": "data", "groups": {} }
+    }
+  },
+  {
+    "pattern": [ "foo://bar" ],
+    "inputs": [ "foo://bad_url_browser_interop" ],
+    "exactly_empty_components": [ "username", "password", "port", "pathname",
+                                  "search", "hash" ],
+    "expected_obj": {
+      "protocol": "foo",
+      "hostname": "bar"
+    },
+    "expected_match": null
+  },
+  {
+    "pattern": [ "(café)://foo" ],
+    "expected_obj": "error"
   }
 ]
diff --git a/third_party/blink/web_tests/external/wpt/urlpattern/urlpattern.https.any.js b/third_party/blink/web_tests/external/wpt/urlpattern/urlpattern.https.any.js
index d3872af..ec8ff1c9 100644
--- a/third_party/blink/web_tests/external/wpt/urlpattern/urlpattern.https.any.js
+++ b/third_party/blink/web_tests/external/wpt/urlpattern/urlpattern.https.any.js
@@ -5,6 +5,7 @@
   'username',
   'password',
   'hostname',
+  'port',
   'password',
   'pathname',
   'search',
@@ -15,12 +16,12 @@
   for (let entry of data) {
     test(function() {
       if (entry.expected_obj === 'error') {
-        assert_throws_js(TypeError, _ => new URLPattern(entry.pattern),
+        assert_throws_js(TypeError, _ => new URLPattern(...entry.pattern),
                          'URLPattern() constructor');
         return;
       }
 
-      const pattern = new URLPattern(entry.pattern);
+      const pattern = new URLPattern(...entry.pattern);
 
       // If the expected_obj property is not present we will automatically
       // fill it with the most likely expected values.
@@ -36,12 +37,16 @@
 
         // If there is no explicit expected pattern string, then compute
         // the expected value based on the URLPattern constructor args.
-        if (!expected) {
+        if (expected == undefined) {
           // First determine if there is a baseURL present in the pattern
           // input.  A baseURL can be the source for many component patterns.
           let baseURL = null;
-          if (entry.pattern.baseURL)
-            baseURL = new URL(entry.pattern.baseURL);
+          if (entry.pattern[0].baseURL) {
+            baseURL = new URL(entry.pattern[0].baseURL);
+          } else if (entry.pattern.length > 1 &&
+                     typeof entry.pattern[1] === 'string') {
+            baseURL = new URL(entry.pattern[1]);
+          }
 
           // We automatically populate the expected pattern string using
           // the following options in priority order:
@@ -53,8 +58,12 @@
           //     does not provide search/hash component values.
           //  3. Otherwise fall back on the default pattern of `*` for an
           //     empty component pattern.
-          if (entry.pattern[component]) {
-            expected = entry.pattern[component];
+          if (entry.exactly_empty_components &&
+              entry.exactly_empty_components.includes(component)) {
+            expected = '';
+          } else if (typeof entry.pattern[0] === 'object' &&
+              entry.pattern[0][component]) {
+            expected = entry.pattern[0][component];
           } else if (baseURL &&
                      component !== 'search' && component !== 'hash') {
             let base_value = baseURL[component];
@@ -134,8 +143,8 @@
           // string pattern does not.  The expectation object must list which
           // components should be empty instead of wildcards in
           // |exactly_empty_components|.
-          if (!entry.expected_match.exactly_empty_components ||
-              !entry.expected_match.exactly_empty_components.includes(component)) {
+          if (!entry.exactly_empty_components ||
+              !entry.exactly_empty_components.includes(component)) {
             expected_obj.groups['0'] = '';
           }
         }
diff --git a/third_party/blink/web_tests/flag-specific/enable-use-zoom-for-dsf/platform/mac/fast/hidpi/resize-corner-hidpi-expected.png b/third_party/blink/web_tests/flag-specific/enable-use-zoom-for-dsf/platform/mac/fast/hidpi/resize-corner-hidpi-expected.png
new file mode 100644
index 0000000..a152c445
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/enable-use-zoom-for-dsf/platform/mac/fast/hidpi/resize-corner-hidpi-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/enable-use-zoom-for-dsf/platform/mac/virtual/scalefactor200/css3/filters/backdrop-filter-clip-radius-zoom-expected.png b/third_party/blink/web_tests/flag-specific/enable-use-zoom-for-dsf/platform/mac/virtual/scalefactor200/css3/filters/backdrop-filter-clip-radius-zoom-expected.png
new file mode 100644
index 0000000..52a31a6
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/enable-use-zoom-for-dsf/platform/mac/virtual/scalefactor200/css3/filters/backdrop-filter-clip-radius-zoom-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.15/svg/filters/feImage-preserveAspectRatio-all-expected.png b/third_party/blink/web_tests/platform/mac-mac10.15/svg/filters/feImage-preserveAspectRatio-all-expected.png
deleted file mode 100644
index 8bc59d61..0000000
--- a/third_party/blink/web_tests/platform/mac-mac10.15/svg/filters/feImage-preserveAspectRatio-all-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/svg/filters/feImage-preserveAspectRatio-all-expected.png b/third_party/blink/web_tests/platform/win/svg/filters/feImage-preserveAspectRatio-all-expected.png
deleted file mode 100644
index 8bc59d61..0000000
--- a/third_party/blink/web_tests/platform/win/svg/filters/feImage-preserveAspectRatio-all-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/svg/filters/feImage-preserveAspectRatio-all-expected.png b/third_party/blink/web_tests/svg/filters/feImage-preserveAspectRatio-all-expected.png
index 2803746f..8bc59d61 100644
--- a/third_party/blink/web_tests/svg/filters/feImage-preserveAspectRatio-all-expected.png
+++ b/third_party/blink/web_tests/svg/filters/feImage-preserveAspectRatio-all-expected.png
Binary files differ
diff --git a/third_party/harfbuzz-ng/BUILD.gn b/third_party/harfbuzz-ng/BUILD.gn
index ead6bb6..26c8e06 100644
--- a/third_party/harfbuzz-ng/BUILD.gn
+++ b/third_party/harfbuzz-ng/BUILD.gn
@@ -266,6 +266,9 @@
 
       # TODO(https://crbug.com/949962): Remove once this is fixed upstream.
       "U_DISABLE_VERSION_SUFFIX=0",
+
+      # https:/crbug.com/1203071
+      "HB_NO_PRAGMA_GCC_DIAGNOSTIC_WARNING",
     ]
 
     if (!is_win && !is_mac) {
diff --git a/third_party/libusb/src/libusb/os/darwin_usb.c b/third_party/libusb/src/libusb/os/darwin_usb.c
index ee6f31b..f6b397e 100644
--- a/third_party/libusb/src/libusb/os/darwin_usb.c
+++ b/third_party/libusb/src/libusb/os/darwin_usb.c
@@ -1326,14 +1326,51 @@
 
 static int darwin_reset_device(struct libusb_device_handle *dev_handle) {
   struct darwin_cached_device *dpriv = DARWIN_CACHED_DEVICE(dev_handle->dev);
+  IOUSBDeviceDescriptor descriptor;
+  IOUSBConfigurationDescriptorPtr cached_configuration;
+  IOUSBConfigurationDescriptor configuration;
+  bool reenumerate = false;
   IOReturn kresult;
+  int i;
 
-  kresult = (*(dpriv->device))->USBDeviceReEnumerate (dpriv->device, 0);
+  kresult = (*(dpriv->device))->ResetDevice (dpriv->device);
   if (kresult) {
-    usbi_err (HANDLE_CTX (dev_handle), "USBDeviceReEnumerate: %s", darwin_error_str (kresult));
+    usbi_err (HANDLE_CTX (dev_handle), "ResetDevice: %s", darwin_error_str (kresult));
     return darwin_to_libusb (kresult);
   }
 
+  do {
+    usbi_dbg ("darwin/reset_device: checking if device descriptor changed");
+
+    /* ignore return code. if we can't get a descriptor it might be worthwhile re-enumerating anway */
+    (void) darwin_request_descriptor (dpriv->device, kUSBDeviceDesc, 0, &descriptor, sizeof (descriptor));
+
+    /* check if the device descriptor has changed */
+    if (0 != memcmp (&dpriv->dev_descriptor, &descriptor, sizeof (descriptor))) {
+      reenumerate = true;
+      break;
+    }
+
+    /* check if any configuration descriptor has changed */
+    for (i = 0 ; i < descriptor.bNumConfigurations ; ++i) {
+      usbi_dbg ("darwin/reset_device: checking if configuration descriptor %d changed", i);
+
+      (void) darwin_request_descriptor (dpriv->device, kUSBConfDesc, i, &configuration, sizeof (configuration));
+      (*(dpriv->device))->GetConfigurationDescriptorPtr (dpriv->device, i, &cached_configuration);
+
+      if (!cached_configuration || 0 != memcmp (cached_configuration, &configuration, sizeof (configuration))) {
+        reenumerate = true;
+        break;
+      }
+    }
+  } while (0);
+
+  if (reenumerate) {
+    usbi_dbg ("darwin/reset_device: device requires reenumeration");
+    (void) (*(dpriv->device))->USBDeviceReEnumerate (dpriv->device, 0);
+    return LIBUSB_ERROR_NOT_FOUND;
+  }
+
   usbi_dbg ("darwin/reset_device: device reset complete");
 
   return LIBUSB_SUCCESS;
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 683c7a76..a44bfcf 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -21930,6 +21930,8 @@
     portrait dimension has 1 subtracted from the enum index to differentiate it
     from the landscape index value.
   </summary>
+  <int value="345599" label="480 x 720 (deprecated)"/>
+  <int value="345600" label="720 x 480 (deprecated)"/>
   <int value="349183" label="512 x 682"/>
   <int value="349184" label="682 x 512"/>
   <int value="387203" label="492 x 787"/>
@@ -21948,6 +21950,8 @@
   <int value="479037" label="923 x 519"/>
   <int value="482797" label="566 x 853"/>
   <int value="482798" label="853 x 566"/>
+  <int value="497663" label="576 x 864 (deprecated)"/>
+  <int value="497664" label="864 x 576 (deprecated)"/>
   <int value="502865" label="614 x 819"/>
   <int value="502866" label="819 x 614"/>
   <int value="505799" label="562 x 900"/>
@@ -21982,6 +21986,8 @@
   <int value="619500" label="1050 x 590"/>
   <int value="639599" label="600 x 1066"/>
   <int value="639600" label="1066 x 600"/>
+  <int value="641573" label="654 x 981 (deprecated)"/>
+  <int value="641574" label="981 x 654 (deprecated)"/>
   <int value="649139" label="698 x 930"/>
   <int value="649140" label="930 x 698"/>
   <int value="655359" label="640 x 1024"/>
@@ -22016,6 +22022,8 @@
   <int value="760602" label="1163 x 654"/>
   <int value="773534" label="695 x 1113"/>
   <int value="773535" label="1113 x 695"/>
+  <int value="777599" label="720 x 1080 (deprecated)"/>
+  <int value="777600" label="1080 x 720 (deprecated)"/>
   <int value="783731" label="723 x 1084"/>
   <int value="783732" label="1084 x 723"/>
   <int value="784183" label="664 x 1181"/>
@@ -22104,6 +22112,8 @@
   <int value="1183704" label="1333 x 888"/>
   <int value="1189371" label="818 x 1454"/>
   <int value="1189372" label="1454 x 818"/>
+  <int value="1214999" label="900 x 1350 (deprecated)"/>
+  <int value="1215000" label="1350 x 900 (deprecated)"/>
   <int value="1217311" label="872 x 1396"/>
   <int value="1217312" label="1396 x 872"/>
   <int value="1225079" label="830 x 1476"/>
@@ -22150,6 +22160,8 @@
   <int value="1536721" label="1517 x 1013"/>
   <int value="1567190" label="939 x 1669"/>
   <int value="1567191" label="1669 x 939"/>
+  <int value="1585175" label="1028 x 1542 (deprecated)"/>
+  <int value="1585176" label="1542 x 1028 (deprecated)"/>
   <int value="1599999" label="1000 x 1600"/>
   <int value="1600000" label="1600 x 1000"/>
   <int value="1603813" label="1097 x 1462"/>
@@ -22198,6 +22210,8 @@
   <int value="2070350" label="1762 x 1175"/>
   <int value="2073599" label="1080 x 1920"/>
   <int value="2073600" label="1920 x 1080"/>
+  <int value="2159999" label="1200 x 1800 (deprecated)"/>
+  <int value="2160000" label="1800 x 1200 (deprecated)"/>
   <int value="2183679" label="1280 x 1706"/>
   <int value="2183680" label="1706 x 1280"/>
   <int value="2219191" label="1214 x 1828"/>
diff --git a/tools/metrics/histograms/histograms_xml/sync/histograms.xml b/tools/metrics/histograms/histograms_xml/sync/histograms.xml
index a63874a..36a504d 100644
--- a/tools/metrics/histograms/histograms_xml/sync/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/sync/histograms.xml
@@ -173,6 +173,18 @@
   </summary>
 </histogram>
 
+<histogram name="Sync.BookmarkSpecificsExcludingFoldersContainFavicon"
+    enum="Boolean" expires_after="2021-10-25">
+  <owner>rushans@google.com</owner>
+  <owner>mastiz@chromium.org</owner>
+  <component>Services&gt;Sync</component>
+  <summary>
+    Records if a remote sync update for a bookmark, excluding folders, contains
+    a favicon. It is recorded upon initial and incremental updates, when the
+    local state is about to be modified.
+  </summary>
+</histogram>
+
 <histogram name="Sync.BookmarksWithoutFullTitle.OnInitialMerge"
     units="bookmarks" expires_after="M89">
   <obsolete>
diff --git a/tools/perf/contrib/system_health_scroll_jank/system_health_scroll_jank.py b/tools/perf/contrib/system_health_scroll_jank/system_health_scroll_jank.py
index 775d4f0..d58f472c 100644
--- a/tools/perf/contrib/system_health_scroll_jank/system_health_scroll_jank.py
+++ b/tools/perf/contrib/system_health_scroll_jank/system_health_scroll_jank.py
@@ -17,6 +17,10 @@
     'Event.Latency.ScrollBegin.Wheel.TimeToScrollUpdateSwapBegin4',
     'Event.Latency.ScrollUpdate.Wheel.TimeToScrollUpdateSwapBegin4',
     'Event.Latency.ScrollJank',
+    'Event.Latency.ScrollUpdate.JankyDuration',
+    'Event.Latency.ScrollUpdate.JankyEvents',
+    'Event.Latency.ScrollUpdate.TotalDuration',
+    'Event.Latency.ScrollUpdate.TotalEvents',
     'Graphics.Smoothness.Checkerboarding.TouchScroll',
     'Graphics.Smoothness.Checkerboarding.WheelScroll',
     'Graphics.Smoothness.Jank.Compositor.TouchScroll',
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps.json b/tools/perf/core/perfetto_binary_roller/binary_deps.json
index 27482ca6..f0d07b53 100644
--- a/tools/perf/core/perfetto_binary_roller/binary_deps.json
+++ b/tools/perf/core/perfetto_binary_roller/binary_deps.json
@@ -9,8 +9,8 @@
             "remote_path": "perfetto_binaries/trace_processor_shell/mac/bae8193de6c017394901163b7817157342914679/trace_processor_shell"
         },
         "linux": {
-            "hash": "5b5e6db44c3cd71a42adcad20800c9a4e35b3fd8",
-            "remote_path": "perfetto_binaries/trace_processor_shell/linux/8545284860afe328993751b80a9cf856872463df/trace_processor_shell"
+            "hash": "4f34e5d1f2413ba3a665c0c07229753fca005f6c",
+            "remote_path": "perfetto_binaries/trace_processor_shell/linux/5d06d8b9235d81033914a28c91cd72f0ffb4820e/trace_processor_shell"
         }
     },
     "power_profile.sql": {
diff --git a/tools/typescript/definitions/tabs.d.ts b/tools/typescript/definitions/tabs.d.ts
index b3c1e7b..c87db457 100644
--- a/tools/typescript/definitions/tabs.d.ts
+++ b/tools/typescript/definitions/tabs.d.ts
@@ -61,6 +61,6 @@
     }
 
     export function create(createProperties: CreateProperties,
-                           callback?: (Tab) => void): void;
+                           callback?: (p1: Tab) => void): void;
   }
 }
diff --git a/tools/typescript/definitions/windows.d.ts b/tools/typescript/definitions/windows.d.ts
index 6fcb395..b3fc288 100644
--- a/tools/typescript/definitions/windows.d.ts
+++ b/tools/typescript/definitions/windows.d.ts
@@ -53,11 +53,11 @@
     export const WINDOW_ID_CURRENT: number;
 
     export function get(windowId: number, getInfo: (GetInfo|null),
-                        callback: (Window) => void): void;
+                        callback: (p1: Window) => void): void;
     export function getCurrent(getInfo: (GetInfo|null),
-                               callback: (Window) => void): void;
+                               callback: (p1: Window) => void): void;
     export function getLastFocused(getInfo: (GetInfo|null),
-                                   callback: (Window) => void): void;
+                                   callback: (p1: Window) => void): void;
     export function getAll(getInfo: (GetInfo|null),
                            callback: (p1: Window[]) => void): void;
     type CreateData = {
@@ -75,7 +75,7 @@
     }
 
     export function create(createData?: CreateData,
-                           callback?: (Window) => void): void;
+                           callback?: (p1: Window) => void): void;
 
     type UpdateInfo = {
       left?: number;
@@ -88,7 +88,7 @@
     }
 
     export function update(windowId: number, updateInfo: UpdateInfo,
-                           callback?: (Window) => void): void;
+                           callback?: (p1: Window) => void): void;
     export function remove(windowId: number, callback?: () => void): void;
   }
 }
diff --git a/tools/typescript/ts_library.py b/tools/typescript/ts_library.py
index 4f04b48..f4069cf 100644
--- a/tools/typescript/ts_library.py
+++ b/tools/typescript/ts_library.py
@@ -80,6 +80,23 @@
 
   _write_tsconfig_json(args.gen_dir, tsconfig)
 
+  # Delete any obsolete .ts files (from previous builds) corresponding to .js
+  # |in_files| in the |root_dir| folder, as they would cause the following error
+  # to be thrown:
+  #
+  # "error TS5056: Cannot write file '...' because it would be overwritten by
+  # multiple input files."
+  #
+  # This can happen when a ts_library() is migrating JS to TS one file at a time
+  # and a bot is switched from building a later CL to building an earlier CL.
+  if args.in_files is not None:
+    for f in args.in_files:
+      [pathname, extension] = os.path.splitext(f)
+      if extension == '.js':
+        to_check = os.path.join(args.root_dir, pathname + '.ts')
+        if os.path.exists(to_check):
+          os.remove(to_check)
+
   node.RunNode([
       node_modules.PathToTypescript(), '--project',
       os.path.join(args.gen_dir, 'tsconfig.json')
diff --git a/ui/webui/resources/BUILD.gn b/ui/webui/resources/BUILD.gn
index 7e91df13..3ba0036f 100644
--- a/ui/webui/resources/BUILD.gn
+++ b/ui/webui/resources/BUILD.gn
@@ -158,6 +158,7 @@
   "cr_elements/policy/cr_tooltip_icon.m.d.ts",
   "js/cr/ui/focus_row_behavior.m.d.ts",
   "js/i18n_behavior.m.d.ts",
+  "js/list_property_update_behavior.m.d.ts",
   "js/promise_resolver.m.d.ts",
   "js/web_ui_listener_behavior.m.d.ts",
 ]
@@ -212,11 +213,12 @@
     "$root_dir/js/cr/ui/focus_grid.m.d.ts",
     "$root_dir/js/cr/ui/focus_outline_manager.m.d.ts",
     "$root_dir/js/cr/ui/focus_row.m.d.ts",
-    "$root_dir/js/cr/ui/store.m.d.ts",
+    "$root_dir/js/cr/ui/keyboard_shortcut_list.m.d.ts",
     "$root_dir/js/cr/ui/store_client.m.d.ts",
     "$root_dir/js/event_tracker.m.d.ts",
     "$root_dir/js/load_time_data.m.d.ts",
     "$root_dir/js/parse_html_subset.m.d.ts",
+    "$root_dir/js/plural_string_proxy.d.ts",
     "$root_dir/js/icon.m.d.ts",
     "$root_dir/js/util.m.d.ts",
   ]
@@ -264,12 +266,14 @@
     "js/cr/ui/focus_grid.m.js",
     "js/cr/ui/focus_outline_manager.m.js",
     "js/cr/ui/focus_row.m.js",
-    "js/cr/ui/store_client.m.js",
+    "js/cr/ui/keyboard_shortcut_list.m.js",
     "js/cr/ui/store.m.js",
+    "js/cr/ui/store_client.m.js",
     "js/event_tracker.m.js",
     "js/icon.m.js",
     "js/load_time_data.m.js",
     "js/parse_html_subset.m.js",
+    "js/plural_string_proxy.js",
     "js/util.m.js",
   ]
 
diff --git a/ui/webui/resources/js/list_property_update_behavior.m.d.ts b/ui/webui/resources/js/list_property_update_behavior.m.d.ts
new file mode 100644
index 0000000..4af6603
--- /dev/null
+++ b/ui/webui/resources/js/list_property_update_behavior.m.d.ts
@@ -0,0 +1,17 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+export interface ListPropertyUpdateBehaviorInterface {
+  updateList(
+      propertyPath: string,
+      identityGetter: ((arg0: object) => (object | string)),
+      updatedList: object[], identityBasedUpdate?: boolean): boolean;
+}
+
+export {ListPropertyUpdateBehavior};
+
+interface ListPropertyUpdateBehavior extends
+    ListPropertyUpdateBehaviorInterface {}
+
+declare const ListPropertyUpdateBehavior: object;
diff --git a/ui/webui/resources/js/plural_string_proxy.js b/ui/webui/resources/js/plural_string_proxy.js
index 801a414..1a314a1 100644
--- a/ui/webui/resources/js/plural_string_proxy.js
+++ b/ui/webui/resources/js/plural_string_proxy.js
@@ -7,7 +7,7 @@
  */
 
 // clang-format off
-import {addSingletonGetter, sendWithPromise} from './cr.m.js';
+import {sendWithPromise} from './cr.m.js';
 // clang-format on
 
 /** @interface */
@@ -70,6 +70,17 @@
         'getPluralStringTupleWithPeriods', messageName1, itemCount1,
         messageName2, itemCount2);
   }
+
+  /** @return {!PluralStringProxy} */
+  static getInstance() {
+    return instance || (instance = new PluralStringProxyImpl());
+  }
+
+  /** @param {PluralStringProxy} obj */
+  static setInstance(obj) {
+    instance = obj;
+  }
 }
 
-addSingletonGetter(PluralStringProxyImpl);
+/** @type {?PluralStringProxy} */
+let instance = null;
diff --git a/url/android/javatests/src/org/chromium/url/JUnitTestGURLsTest.java b/url/android/javatests/src/org/chromium/url/JUnitTestGURLsTest.java
index 1a8e3dc..e7ab1ba 100644
--- a/url/android/javatests/src/org/chromium/url/JUnitTestGURLsTest.java
+++ b/url/android/javatests/src/org/chromium/url/JUnitTestGURLsTest.java
@@ -49,12 +49,12 @@
 
     @SmallTest
     @Test
-    public void testGURLEquivalence() throws Exception {
+    public void testGURLEquivalence() throws Throwable {
         doThrow(new RuntimeException("Deserialization required re-initialization."))
                 .when(mGURLMocks)
                 .init(any(), any());
 
-        Exception exception = null;
+        Throwable exception = null;
         for (Map.Entry<String, String> entry : JUnitTestGURLs.sGURLMap.entrySet()) {
             GURL gurl = new GURL(entry.getKey());
             try {
@@ -62,7 +62,7 @@
                 GURL deserialized = JUnitTestGURLs.getGURL(entry.getKey());
                 GURLJni.TEST_HOOKS.setInstanceForTesting(null);
                 GURLJavaTest.deepAssertEquals(deserialized, gurl);
-            } catch (Exception e) {
+            } catch (Throwable e) {
                 GURLJni.TEST_HOOKS.setInstanceForTesting(null);
                 exception = getErrorForGURL(gurl);
                 Log.e(TAG, "Error: ", exception);
diff --git a/url/android/test/java/src/org/chromium/url/JUnitTestGURLs.java b/url/android/test/java/src/org/chromium/url/JUnitTestGURLs.java
index 1f6b056..93d2992c 100644
--- a/url/android/test/java/src/org/chromium/url/JUnitTestGURLs.java
+++ b/url/android/test/java/src/org/chromium/url/JUnitTestGURLs.java
@@ -41,6 +41,10 @@
     public static final String BLUE_1 = "https://www.blue.com/page1";
     public static final String BLUE_2 = "https://www.blue.com/page2";
     public static final String BLUE_3 = "https://www.blue.com/page3";
+    public static final String AMP_URL =
+            "https://www.google.com/amp/www.nyt.com/ampthml/blogs.html";
+    public static final String AMP_CACHE_URL =
+            "https://www.google.com/amp/s/www.nyt.com/ampthml/blogs.html";
 
     // Map of URL string to GURL serialization.
     /* package */ static final Map<String, String> sGURLMap;
@@ -98,6 +102,10 @@
                         + "false,false,distiller://url");
         map.put(MAPS_URL,
                 "82,1,true,0,5,0,-1,0,-1,8,15,0,-1,23,1,0,-1,0,-1,false,false,https://maps.google.com/");
+        map.put(AMP_URL,
+                "116,1,true,0,5,0,-1,0,-1,8,14,0,-1,22,35,0,-1,0,-1,false,false,https://www.google.com/amp/www.nyt.com/ampthml/blogs.html");
+        map.put(AMP_CACHE_URL,
+                "118,1,true,0,5,0,-1,0,-1,8,14,0,-1,22,37,0,-1,0,-1,false,false,https://www.google.com/amp/s/www.nyt.com/ampthml/blogs.html");
         sGURLMap = Collections.unmodifiableMap(map);
     }
 
diff --git a/weblayer/browser/tab_impl.cc b/weblayer/browser/tab_impl.cc
index 8c282aa..cfe0d8c 100644
--- a/weblayer/browser/tab_impl.cc
+++ b/weblayer/browser/tab_impl.cc
@@ -989,12 +989,12 @@
 #endif
 }
 
-content::ColorChooser* TabImpl::OpenColorChooser(
+std::unique_ptr<content::ColorChooser> TabImpl::OpenColorChooser(
     content::WebContents* web_contents,
     SkColor color,
     const std::vector<blink::mojom::ColorSuggestionPtr>& suggestions) {
 #if defined(OS_ANDROID)
-  return new web_contents_delegate_android::ColorChooserAndroid(
+  return std::make_unique<web_contents_delegate_android::ColorChooserAndroid>(
       web_contents, color, suggestions);
 #else
   return nullptr;
diff --git a/weblayer/browser/tab_impl.h b/weblayer/browser/tab_impl.h
index 077e8a5..9fcca6a 100644
--- a/weblayer/browser/tab_impl.h
+++ b/weblayer/browser/tab_impl.h
@@ -16,6 +16,7 @@
 #include "build/build_config.h"
 #include "cc/input/browser_controls_state.h"
 #include "components/find_in_page/find_result_observer.h"
+#include "content/public/browser/color_chooser.h"
 #include "content/public/browser/web_contents_delegate.h"
 #include "content/public/browser/web_contents_observer.h"
 #include "weblayer/browser/i18n_util.h"
@@ -252,7 +253,7 @@
                               content::InvalidateTypes changed_flags) override;
   content::JavaScriptDialogManager* GetJavaScriptDialogManager(
       content::WebContents* web_contents) override;
-  content::ColorChooser* OpenColorChooser(
+  std::unique_ptr<content::ColorChooser> OpenColorChooser(
       content::WebContents* web_contents,
       SkColor color,
       const std::vector<blink::mojom::ColorSuggestionPtr>& suggestions)