diff --git a/AUTHORS b/AUTHORS
index 5437b639..599cd0f 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -975,6 +975,7 @@
 Siddharth Shankar <funkysidd@gmail.com>
 Simeon Kuran <simeon.kuran@gmail.com>
 Simon Arlott <simon.arlott@gmail.com>
+Simon Jackson <simon.jackson@sonocent.com>
 Simon La Macchia <smacchia@amazon.com>
 Siva Kumar Gunturi <siva.gunturi@samsung.com>
 Sohan Jyoti Ghosh <sohan.jyoti@huawei.com>
diff --git a/DEPS b/DEPS
index b742ef8..b167905 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:67aba6e3373bb0b9e3ef9871362045736cd29b6e',
+  'luci_go': 'git_revision:16e6d735358b0166f06fd2e4daa0da4cff9918e9',
 
   # This can be overridden, e.g. with custom_vars, to build clang from HEAD
   # instead of downloading the prebuilt pinned revision.
@@ -199,11 +199,11 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Skia
   # and whatever else without interference from each other.
-  'skia_revision': 'ace3f2939f8970c7eb6693c5da0c2ab372c30b87',
+  'skia_revision': '0a69b4bb071c7868dda1cd734351146898a74aba',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
-  'v8_revision': '16b9bbbd581c25391981aa03180b76aa60463a3e',
+  'v8_revision': '1e60bce4522aa7c92b1010988f8815f96c8243b7',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling swarming_client
   # and whatever else without interference from each other.
@@ -211,7 +211,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': 'c0ef8ccfcd8bd983dc3d7469e810ad133b80b4cd',
+  'angle_revision': '2ed9671a0dddaab9c2582b62d3e479895ee0d333',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
@@ -314,7 +314,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': '22eff1eb8c2ff44e1928121d93559fcf0b81f834',
+  'dawn_revision': '7faa362ea9c2bffbb8e9cc2e2d9f4f4af2fe4dde',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -888,12 +888,12 @@
 
   # For Linux and Chromium OS.
   'src/third_party/cros_system_api': {
-      'url': Var('chromium_git') + '/chromiumos/platform2/system_api.git' + '@' + 'e5fc0c7ccb3c64ec9679dd00e95b504121745912',
+      'url': Var('chromium_git') + '/chromiumos/platform2/system_api.git' + '@' + '42f3f3cea40612784b38532b9e5d1a54a3d2174c',
       'condition': 'checkout_linux',
   },
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '9ce03f80a4b507d4b6424c1706069c0d26335b92',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + 'fdd89469d6813514814b420ba51168cbd243a9d4',
 
   'src/third_party/devtools-frontend/src':
     Var('chromium_git') + '/devtools/devtools-frontend' + '@' + Var('devtools_frontend_revision'),
@@ -1169,7 +1169,7 @@
     Var('chromium_git') + '/webm/libwebm.git' + '@' + '51ca718c3adf0ddedacd7df25fe45f67dc5a9ce1',
 
   'src/third_party/libyuv':
-    Var('chromium_git') + '/libyuv/libyuv.git' + '@' + '1d3f901aa016d42b5bc0148be2ef6c0fd56f3b81',  # from r1768
+    Var('chromium_git') + '/libyuv/libyuv.git' + '@' + '93b1b332cd60b56ab90aea14182755e379c28a80',  # from r1770
 
   'src/third_party/lighttpd': {
       'url': Var('chromium_git') + '/chromium/deps/lighttpd.git' + '@' + Var('lighttpd_revision'),
@@ -1266,7 +1266,7 @@
   },
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' + '2881d6b22d1eba089a772a15733151b67685cdaf',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' + '44ba8e1708abec70b1c85015aae59026fb214c83',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '6f3e5028eb65d0b4c5fdd792106ac4c84eee1eb3',
@@ -1556,7 +1556,7 @@
       'packages': [
         {
           'package': 'skia/tools/goldctl/windows-amd64',
-          'version': '4r6X5OSxy-ZDOlqtn41HMmKG2qlEfMNdLwpzyufLFzcC',
+          'version': 'AefJEwE3M-ke_pwYEVGy2Ct2Ra80EaxvK1KADBuG8-EC',
         },
       ],
       'dep_type': 'cipd',
@@ -1566,7 +1566,7 @@
       'packages': [
         {
           'package': 'skia/tools/goldctl/mac-amd64',
-          'version': 'GMDKIbJ4mwbVXaPYncGej4OyIS1ZjiCVhESk0xO0fVMC',
+          'version': 'zFHl-BrDnpmevfoDmWcr04CNai3vqwCPB6AYeJXJ744C',
         },
       ],
       'dep_type': 'cipd',
@@ -1580,7 +1580,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@98eb0a70aead159530fe95fb1957dedc677a2eea',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@76d89fa22033c340522095168b69c33ad97e4a57',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/WATCHLISTS b/WATCHLISTS
index 283209c..40beb5b 100644
--- a/WATCHLISTS
+++ b/WATCHLISTS
@@ -40,7 +40,8 @@
                   'chrome/android/javatests/src/org/chromium/chrome/browser/webapps/|'\
                   'chrome/android/junit/src/org/chromium/chrome/browser/webapps/|'\
                   'chrome/browser/android/shortcut_.*|'\
-                  'chrome/browser/android/webapps/',
+                  'chrome/browser/android/webapps/|'\
+                  'components/webapps/browser/android/',
     },
     'agent_scheduling_group': {
       'filepath': '.*agent_scheduling_group.*|.*agent_group_scheduler.*',
@@ -230,6 +231,7 @@
                   'chrome/android/javatests/src/org/chromium/chrome/browser/banners/|'\
                   'chrome/browser/banners/|'\
                   'chrome/browser/ui/android/infobars/app_banner_.*|'\
+                  'components/webapps/browser/banners/|'\
                   'third_party/blink/public/platform/modules/app_banner/|'\
                   'third_party/blink/renderer/modules/app_banner/',
     },
@@ -1153,7 +1155,8 @@
     },
     'installable': {
       'filepath': 'chrome/browser/extensions/bookmark_app*'\
-                  '|chrome/browser/installable/',
+                  '|chrome/browser/installable/'
+                  '|components/webapps/browser/installable/',
     },
     'installer_linux': {
       'filepath': 'chrome/installer/linux/',
diff --git a/ash/accessibility/accessibility_controller_impl.cc b/ash/accessibility/accessibility_controller_impl.cc
index df90960..2bf5129 100644
--- a/ash/accessibility/accessibility_controller_impl.cc
+++ b/ash/accessibility/accessibility_controller_impl.cc
@@ -1649,10 +1649,6 @@
 void AccessibilityControllerImpl::UpdateCursorColorFromPrefs() {
   DCHECK(active_user_prefs_);
 
-  // Not yet released: cursor color is behind a flag.
-  if (!features::IsAccessibilityCursorColorEnabled())
-    return;
-
   const bool enabled =
       active_user_prefs_->GetBoolean(prefs::kAccessibilityCursorColorEnabled);
   Shell* shell = Shell::Get();
diff --git a/ash/ash_strings.grd b/ash/ash_strings.grd
index 50a0573..ebf2c17a 100644
--- a/ash/ash_strings.grd
+++ b/ash/ash_strings.grd
@@ -1232,6 +1232,9 @@
       <message name="IDS_ASH_STYLUS_BATTERY_LOW_LABEL" desc="The label next to the stylus battery indicator when the battery level is below 24%">
         Low
       </message>
+      <message name="IDS_ASH_STYLUS_BATTERY_PERCENT_ACCESSIBLE" desc="The message used by accessibility to show stylus battery level.">
+        Stylus battery at <ph name="percentage">$1<ex>56</ex></ph> percent.
+      </message>
       <message name="IDS_ASH_STYLUS_TOOLS_CAPTURE_REGION_ACTION" desc="Title of the capture region action in the stylus tools (a pop-up panel next to the status tray). This causes a partial screenshot to be taken (the user selects an area of the screen to take a screenshot of).">
         Capture region
       </message>
@@ -1556,6 +1559,9 @@
       <message name="IDS_ASH_STATUS_TRAY_OTHER_WIFI" desc="The label used for the item to display other Wi-Fi networks.">
         Join other Wi-Fi networks
       </message>
+      <message name="IDS_ASH_STATUS_TRAY_ADD_CELLULAR_LABEL" desc="The label for button in network quick settings that allows user to add new cellular networks.">
+        Add new cellular network
+      </message>
       <message name="IDS_ASH_STATUS_TRAY_BLUETOOTH_DISCOVERABLE" desc="Notification shown when a Bluetooth adapter is discoverable.">
         Your computer is discoverable to nearby Bluetooth devices and will appear as "<ph name="NAME">$1<ex>Chromebook</ex></ph>" with address <ph name="ADDRESS">$2<ex>01:23:45:67:89:0A</ex></ph>
       </message>
diff --git a/ash/ash_strings_grd/IDS_ASH_STATUS_TRAY_ADD_CELLULAR_LABEL.png.sha1 b/ash/ash_strings_grd/IDS_ASH_STATUS_TRAY_ADD_CELLULAR_LABEL.png.sha1
new file mode 100644
index 0000000..6f472ee
--- /dev/null
+++ b/ash/ash_strings_grd/IDS_ASH_STATUS_TRAY_ADD_CELLULAR_LABEL.png.sha1
@@ -0,0 +1 @@
+d2ef7556e3dca52f809cde1e65d84843f8187cc2
\ No newline at end of file
diff --git a/ash/ash_strings_grd/IDS_ASH_STYLUS_BATTERY_PERCENT_ACCESSIBLE.png.sha1 b/ash/ash_strings_grd/IDS_ASH_STYLUS_BATTERY_PERCENT_ACCESSIBLE.png.sha1
new file mode 100644
index 0000000..980110ba
--- /dev/null
+++ b/ash/ash_strings_grd/IDS_ASH_STYLUS_BATTERY_PERCENT_ACCESSIBLE.png.sha1
@@ -0,0 +1 @@
+00c68e1647b1b8ce844dae493475be213cdbf8e3
\ No newline at end of file
diff --git a/ash/clipboard/clipboard_history_controller_impl.cc b/ash/clipboard/clipboard_history_controller_impl.cc
index 1dd5ab9..32fa36e 100644
--- a/ash/clipboard/clipboard_history_controller_impl.cc
+++ b/ash/clipboard/clipboard_history_controller_impl.cc
@@ -21,6 +21,7 @@
 #include "base/base64.h"
 #include "base/files/file_path.h"
 #include "base/location.h"
+#include "base/metrics/histogram_functions.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/threading/thread_task_runner_handle.h"
@@ -219,16 +220,17 @@
     ExecuteSelectedMenuItem(ui::EF_COMMAND_DOWN);
     return;
   }
-  ShowMenu(CalculateAnchorRect(), ui::MENU_SOURCE_KEYBOARD);
+  ShowMenu(CalculateAnchorRect(), ui::MENU_SOURCE_KEYBOARD,
+           ShowSource::kAccelerator);
 }
 
 gfx::Rect ClipboardHistoryControllerImpl::GetMenuBoundsInScreenForTest() const {
   return context_menu_->GetMenuBoundsInScreenForTest();
 }
 
-void ClipboardHistoryControllerImpl::ShowMenu(
-    const gfx::Rect& anchor_rect,
-    ui::MenuSourceType source_type) {
+void ClipboardHistoryControllerImpl::ShowMenu(const gfx::Rect& anchor_rect,
+                                              ui::MenuSourceType source_type,
+                                              ShowSource show_source) {
   if (IsMenuShowing() || !CanShowMenu())
     return;
 
@@ -248,6 +250,9 @@
   DCHECK(IsMenuShowing());
   accelerator_target_->OnMenuShown();
 
+  base::UmaHistogramEnumeration("Ash.ClipboardHistory.ContextMenu.ShowMenu",
+                                show_source);
+
   // The first menu item should be selected as default after the clipboard
   // history menu shows. Note that the menu item is selected asynchronously
   // to avoid the interference from synthesized mouse events.
diff --git a/ash/clipboard/clipboard_history_controller_impl.h b/ash/clipboard/clipboard_history_controller_impl.h
index 24e7aec..b06eb33 100644
--- a/ash/clipboard/clipboard_history_controller_impl.h
+++ b/ash/clipboard/clipboard_history_controller_impl.h
@@ -87,7 +87,8 @@
   // ClipboardHistoryController:
   bool CanShowMenu() const override;
   void ShowMenu(const gfx::Rect& anchor_rect,
-                ui::MenuSourceType source_type) override;
+                ui::MenuSourceType source_type,
+                ShowSource show_source) override;
   std::unique_ptr<ScopedClipboardHistoryPause> CreateScopedPause() override;
   base::Value GetHistoryValues(
       const std::set<std::string>& item_id_filter) const override;
diff --git a/ash/fast_ink/cursor/cursor_view.cc b/ash/fast_ink/cursor/cursor_view.cc
index 02d4ff7..67e745d 100644
--- a/ash/fast_ink/cursor/cursor_view.cc
+++ b/ash/fast_ink/cursor/cursor_view.cc
@@ -205,8 +205,7 @@
 
     // Create directional blur filter for |sigma|.
     motion_blur_filter_ = sk_make_sp<cc::BlurPaintFilter>(
-        sigma, 0.f, SkBlurImageFilter::TileMode::kClampToBlack_TileMode,
-        nullptr);
+        sigma, 0.f, SkTileMode::kDecal, nullptr);
 
     // Compute blur offset.
     motion_blur_offset_ =
diff --git a/ash/public/cpp/clipboard_history_controller.h b/ash/public/cpp/clipboard_history_controller.h
index 3e81d3dc..44c4620 100644
--- a/ash/public/cpp/clipboard_history_controller.h
+++ b/ash/public/cpp/clipboard_history_controller.h
@@ -28,6 +28,26 @@
 // clipboard history menu.
 class ASH_PUBLIC_EXPORT ClipboardHistoryController {
  public:
+  // The different ways the multipaste menu can be shown. These values are
+  // written to logs. New enum values can be added, but existing enums must
+  // never be renumbered, deleted, or reused.
+  enum class ShowSource {
+    // Shown by the accelerator.
+    kAccelerator = 0,
+
+    // Shown by a render view's context menu.
+    kRenderViewContextMenu = 1,
+
+    // Shown by a textfield's context menu.
+    kTextfieldContextMenu = 2,
+
+    // Shown by the virtual keyboard.
+    kVirtualKeyboard = 3,
+
+    // Insert new types above this line.
+    kMaxValue = kVirtualKeyboard
+  };
+
   class Observer : public base::CheckedObserver {
    public:
     // Called when the clipboard history menu is shown.
@@ -54,7 +74,8 @@
   // Shows the clipboard history menu triggered by `source_type` at the
   // specified position.
   virtual void ShowMenu(const gfx::Rect& anchor_rect,
-                        ui::MenuSourceType source_type) = 0;
+                        ui::MenuSourceType source_type,
+                        ShowSource show_source) = 0;
 
   // Creates a ScopedClipboardHistoryPause, which pauses ClipboardHistory for
   // its lifetime.
diff --git a/ash/public/cpp/views_text_services_context_menu_impl.cc b/ash/public/cpp/views_text_services_context_menu_impl.cc
index 7fd34053..155aa07 100644
--- a/ash/public/cpp/views_text_services_context_menu_impl.cc
+++ b/ash/public/cpp/views_text_services_context_menu_impl.cc
@@ -65,8 +65,9 @@
     else
       source_type = ui::MENU_SOURCE_KEYBOARD;
 
-    clipboard_history_controller->ShowMenu(client()->GetCaretBounds(),
-                                           source_type);
+    clipboard_history_controller->ShowMenu(
+        client()->GetCaretBounds(), source_type,
+        ClipboardHistoryController::ShowSource::kTextfieldContextMenu);
     return;
   }
 
diff --git a/ash/system/network/network_section_header_view.cc b/ash/system/network/network_section_header_view.cc
index 95ef0138..1d38b8e 100644
--- a/ash/system/network/network_section_header_view.cc
+++ b/ash/system/network/network_section_header_view.cc
@@ -14,6 +14,7 @@
 #include "ash/system/network/tray_network_state_model.h"
 #include "ash/system/unified/top_shortcut_button.h"
 #include "base/bind.h"
+#include "chromeos/constants/chromeos_features.h"
 #include "chromeos/services/network_config/public/mojom/cros_network_config.mojom.h"
 #include "chromeos/strings/grit/chromeos_strings.h"
 #include "components/onc/onc_constants.h"
@@ -274,6 +275,26 @@
   model()->SetNetworkTypeEnabledState(NetworkType::kTether, is_on);
 }
 
+void MobileSectionHeaderView::AddExtraButtons(bool enabled) {
+  if (!base::FeatureList::IsEnabled(
+          chromeos::features::kUpdatedCellularActivationUi)) {
+    return;
+  }
+
+  TopShortcutButton* add_cellular_button = new TopShortcutButton(
+      base::BindRepeating(&MobileSectionHeaderView::AddCellularButtonPressed,
+                          base::Unretained(this)),
+      vector_icons::kAddCellularNetworkIcon,
+      IDS_ASH_STATUS_TRAY_ADD_CELLULAR_LABEL);
+  add_cellular_button->SetEnabled(enabled);
+  container()->AddView(TriView::Container::END, add_cellular_button);
+}
+
+void MobileSectionHeaderView::AddCellularButtonPressed() {
+  Shell::Get()->system_tray_model()->client()->ShowNetworkCreate(
+      ::onc::network_type::kCellular);
+}
+
 void MobileSectionHeaderView::EnableBluetooth() {
   DCHECK(!waiting_for_tether_initialize_);
 
diff --git a/ash/system/network/network_section_header_view.h b/ash/system/network/network_section_header_view.h
index de734c2..9b3eca5 100644
--- a/ash/system/network/network_section_header_view.h
+++ b/ash/system/network/network_section_header_view.h
@@ -101,6 +101,9 @@
  private:
   // NetworkListView::NetworkSectionHeaderView:
   void OnToggleToggled(bool is_on) override;
+  void AddExtraButtons(bool enabled) override;
+
+  void AddCellularButtonPressed();
 
   // When Tether is disabled because Bluetooth is off, then enabling Bluetooth
   // will enable Tether. If enabling Bluetooth takes longer than some timeout
diff --git a/ash/system/palette/palette_tray.cc b/ash/system/palette/palette_tray.cc
index 61afa9e..d1976a9b 100644
--- a/ash/system/palette/palette_tray.cc
+++ b/ash/system/palette/palette_tray.cc
@@ -35,6 +35,8 @@
 #include "components/prefs/pref_change_registrar.h"
 #include "components/prefs/pref_registry_simple.h"
 #include "components/prefs/pref_service.h"
+#include "ui/accessibility/ax_enums.mojom-shared.h"
+#include "ui/accessibility/ax_node_data.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/resource/resource_bundle.h"
 #include "ui/display/display.h"
@@ -91,10 +93,12 @@
 
 class BatteryView : public views::View {
  public:
-  explicit BatteryView(StylusBatteryDelegate* delegate) {
+  explicit BatteryView(StylusBatteryDelegate* delegate) : delegate_(delegate) {
     SetLayoutManager(std::make_unique<views::BoxLayout>(
         views::BoxLayout::Orientation::kHorizontal, gfx::Insets(), 4));
 
+    SetFocusBehavior(FocusBehavior::ACCESSIBLE_ONLY);
+
     icon_ = AddChildView(std::make_unique<views::ImageView>());
     icon_->SetImage(delegate->GetBatteryImage());
 
@@ -107,7 +111,15 @@
     }
   }
 
+  void GetAccessibleNodeData(ui::AXNodeData* node_data) override {
+    node_data->role = ax::mojom::Role::kLabelText;
+    node_data->SetName(l10n_util::GetStringFUTF16(
+        IDS_ASH_STYLUS_BATTERY_PERCENT_ACCESSIBLE,
+        base::NumberToString16(delegate_->battery_level().value_or(0))));
+  }
+
  private:
+  StylusBatteryDelegate* const delegate_;
   views::ImageView* icon_ = nullptr;
   views::Label* label_ = nullptr;
 };
diff --git a/ash/system/phonehub/phone_hub_notification_controller.cc b/ash/system/phonehub/phone_hub_notification_controller.cc
index e04276c..121e045 100644
--- a/ash/system/phonehub/phone_hub_notification_controller.cc
+++ b/ash/system/phonehub/phone_hub_notification_controller.cc
@@ -462,34 +462,20 @@
 int PhoneHubNotificationController::GetSystemPriorityForNotification(
     const chromeos::phonehub::Notification* notification,
     bool is_update) {
-  switch (notification->importance()) {
-    case chromeos::phonehub::Notification::Importance::kNone:
-      FALLTHROUGH;
-    case chromeos::phonehub::Notification::Importance::kMin:
-      return message_center::MIN_PRIORITY;
+  bool has_notification_been_shown =
+      base::Contains(shown_notification_ids_, notification->id());
 
-    case chromeos::phonehub::Notification::Importance::kUnspecified:
-      FALLTHROUGH;
-    case chromeos::phonehub::Notification::Importance::kLow:
-      FALLTHROUGH;
-    case chromeos::phonehub::Notification::Importance::kDefault:
-      FALLTHROUGH;
-    case chromeos::phonehub::Notification::Importance::kHigh:
-      bool has_notification_been_shown =
-          base::Contains(shown_notification_ids_, notification->id());
+  // If the same notification was already shown and has not been updated,
+  // use LOW_PRIORITY so that the notification is silently added to the
+  // notification shade. This ensures that we don't spam users with the same
+  // information multiple times.
+  if (has_notification_been_shown && !is_update)
+    return message_center::LOW_PRIORITY;
 
-      // If the same notification was already shown and has not been updated,
-      // use LOW_PRIORITY so that the notification is silently added to the
-      // notification shade. This ensures that we don't spam users with the same
-      // information multiple times.
-      if (has_notification_been_shown && !is_update)
-        return message_center::LOW_PRIORITY;
-
-      // Use MAX_PRIORITY, which causes the notification to be shown in a popup
-      // so that users can see new messages come in as they are chatting. See
-      // https://crbug.com/1159063.
-      return message_center::MAX_PRIORITY;
-  }
+  // Use MAX_PRIORITY, which causes the notification to be shown in a popup
+  // so that users can see new messages come in as they are chatting. See
+  // https://crbug.com/1159063.
+  return message_center::MAX_PRIORITY;
 }
 
 // static
diff --git a/ash/system/phonehub/phone_hub_notification_controller_unittest.cc b/ash/system/phonehub/phone_hub_notification_controller_unittest.cc
index f3394d4..76fccdb 100644
--- a/ash/system/phonehub/phone_hub_notification_controller_unittest.cc
+++ b/ash/system/phonehub/phone_hub_notification_controller_unittest.cc
@@ -361,4 +361,23 @@
   EXPECT_TRUE(cros_notification->renotify());
 }
 
+// Regression test for https://crbug.com/1165646.
+TEST_F(PhoneHubNotificationControllerTest, MinPriorityNotification) {
+  chromeos::phonehub::Notification fake_notification(
+      kPhoneHubNotificationId0,
+      chromeos::phonehub::Notification::AppMetadata(base::UTF8ToUTF16(kAppName),
+                                                    kPackageName,
+                                                    /*icon=*/gfx::Image()),
+      base::Time::Now(), chromeos::phonehub::Notification::Importance::kMin,
+      /*inline_reply_id=*/0, base::UTF8ToUTF16(kTitle),
+      base::UTF8ToUTF16(kTextContent));
+
+  // Adding the notification for the first time shows a pop-up (MAX_PRIORITY),
+  // even though the notification itself is Importance::kMin.
+  notification_manager_->SetNotification(fake_notification);
+  auto* cros_notification = FindNotification(kCrOSNotificationId0);
+  ASSERT_TRUE(cros_notification);
+  EXPECT_EQ(message_center::MAX_PRIORITY, cros_notification->priority());
+}
+
 }  // namespace ash
diff --git a/ash/wallpaper/wallpaper_view.cc b/ash/wallpaper/wallpaper_view.cc
index be4773f..0b0faa6 100644
--- a/ash/wallpaper/wallpaper_view.cc
+++ b/ash/wallpaper/wallpaper_view.cc
@@ -155,8 +155,8 @@
 
   // Create the blur and brightness filter to apply to the downsampled image.
   cc::FilterOperations operations;
-  operations.Append(cc::FilterOperation::CreateBlurFilter(
-      blur, SkBlurImageFilter::kClamp_TileMode));
+  operations.Append(
+      cc::FilterOperation::CreateBlurFilter(blur, SkTileMode::kClamp));
   sk_sp<cc::PaintFilter> filter = cc::RenderSurfaceFilters::BuildImageFilter(
       operations, gfx::SizeF(dst.size()), gfx::Vector2dF());
 
diff --git a/ash/wm/desks/desk_animation_impl.cc b/ash/wm/desks/desk_animation_impl.cc
index 7dca6a2..b4454c7b 100644
--- a/ash/wm/desks/desk_animation_impl.cc
+++ b/ash/wm/desks/desk_animation_impl.cc
@@ -39,6 +39,12 @@
 constexpr char kDeskEndGestureSmoothnessHistogramName[] =
     "Ash.Desks.AnimationSmoothness.DeskEndGesture";
 
+// Swipes which are below this threshold are considered fast, and
+// RootWindowDeskSwitchAnimator will determine a different ending desk for these
+// swipes.
+constexpr base::TimeDelta kFastSwipeThresholdDuration =
+    base::TimeDelta::FromMilliseconds(500);
+
 bool IsForContinuousGestures(DesksSwitchSource source) {
   return source == DesksSwitchSource::kDeskSwitchTouchpad &&
          features::IsEnhancedDeskAnimations();
@@ -60,6 +66,7 @@
       switch_source_(source),
       update_window_activation_(update_window_activation),
       visible_desk_index_(starting_desk_index),
+      last_start_or_replace_time_(base::TimeTicks::Now()),
       presentation_time_recorder_(CreatePresentationTimeHistogramRecorder(
           desks_util::GetSelectedCompositorForPerformanceMetrics(),
           kDeskUpdateGestureHistogramName,
@@ -108,6 +115,8 @@
 
   ending_desk_index_ = new_ending_desk_index;
 
+  last_start_or_replace_time_ = base::TimeTicks::Now();
+
   // Similar to on starting, for touchpad, the user can replace the animation
   // without switching visible desks.
   if (switch_source_ != DesksSwitchSource::kDeskSwitchTouchpad)
@@ -195,8 +204,11 @@
   // End the animation. The animator will determine which desk to animate to,
   // and update their ending desk index. When the animation is finished we will
   // activate that desk.
+  const bool is_fast_swipe =
+      base::TimeTicks::Now() - last_start_or_replace_time_ <
+      kFastSwipeThresholdDuration;
   for (const auto& animator : desk_switch_animators_)
-    ending_desk_index_ = animator->EndSwipeAnimation();
+    ending_desk_index_ = animator->EndSwipeAnimation(is_fast_swipe);
 
   return true;
 }
diff --git a/ash/wm/desks/desk_animation_impl.h b/ash/wm/desks/desk_animation_impl.h
index 0983dc4d..a6869bf 100644
--- a/ash/wm/desks/desk_animation_impl.h
+++ b/ash/wm/desks/desk_animation_impl.h
@@ -51,6 +51,10 @@
   // transform of the animation layer.
   int visible_desk_index_;
 
+  // The last time an animation has been started or replaced. This is used to
+  // help determine which desk to animate to when EndSwipeAnimation is called.
+  base::TimeTicks last_start_or_replace_time_;
+
   // Used to measure the presentation time of a continuous gesture swipe.
   std::unique_ptr<PresentationTimeRecorder> presentation_time_recorder_;
 };
diff --git a/ash/wm/desks/desk_preview_view.cc b/ash/wm/desks/desk_preview_view.cc
index d007c2f..6acb986 100644
--- a/ash/wm/desks/desk_preview_view.cc
+++ b/ash/wm/desks/desk_preview_view.cc
@@ -8,12 +8,19 @@
 #include <utility>
 
 #include "ash/multi_user/multi_user_window_manager_impl.h"
+#include "ash/public/cpp/ash_features.h"
 #include "ash/public/cpp/window_properties.h"
+#include "ash/shell.h"
 #include "ash/wallpaper/wallpaper_base_view.h"
 #include "ash/wm/desks/desk_mini_view.h"
+#include "ash/wm/desks/desks_controller.h"
+#include "ash/wm/desks/desks_util.h"
+#include "ash/wm/mru_window_tracker.h"
 #include "ash/wm/window_state.h"
 #include "ash/wm/wm_highlight_item_border.h"
 #include "base/containers/flat_map.h"
+#include "base/containers/flat_set.h"
+#include "ui/aura/client/aura_constants.h"
 #include "ui/compositor/layer.h"
 #include "ui/compositor/layer_tree_owner.h"
 #include "ui/compositor/layer_type.h"
@@ -63,6 +70,11 @@
   // should always be visible (even for inactive desks) to be able to see their
   // contents in the mini_views.
   bool should_force_mirror_visible = false;
+
+  // If true, transformations will be cleared for this layer. This is used,
+  // for example, for visible on all desk windows to clear their overview
+  // transformation since they don't belong to inactive desks.
+  bool should_clear_transform = false;
 };
 
 // Returns true if |window| can be shown in the desk's preview according to its
@@ -83,14 +95,51 @@
   return account_id == multi_user_window_manager->CurrentAccountId();
 }
 
+// Appends clones of all the visible on all desks windows' layers to
+// |out_desk_container_children|. Should only be called if
+// |visible_on_all_desks_windows| is not empty.
+void AppendVisibleOnAllDesksWindowsToDeskLayer(
+    const base::flat_set<aura::Window*>& visible_on_all_desks_windows,
+    std::vector<ui::Layer*>* out_desk_container_children) {
+  DCHECK(!visible_on_all_desks_windows.empty());
+  auto mru_windows =
+      Shell::Get()->mru_window_tracker()->BuildMruWindowList(kAllDesks);
+
+  for (auto* window : visible_on_all_desks_windows) {
+    auto window_iter =
+        std::find(mru_windows.begin(), mru_windows.end(), window);
+    DCHECK(window_iter != mru_windows.end());
+
+    auto closest_window_below_iter = std::next(window_iter);
+    while (closest_window_below_iter != mru_windows.end() &&
+           !base::Contains(*out_desk_container_children,
+                           (*closest_window_below_iter)->layer())) {
+      // Find the closest window to |window| in the MRU tracker whose layer also
+      // is in |out_desk_container_children|. This window will be used to
+      // determine the stacking order of the visible on all desks window in the
+      // preview view.
+      closest_window_below_iter = std::next(closest_window_below_iter);
+    }
+
+    auto insertion_point_iter =
+        closest_window_below_iter == mru_windows.end()
+            ? out_desk_container_children->begin()
+            : std::next(std::find(out_desk_container_children->begin(),
+                                  out_desk_container_children->end(),
+                                  (*closest_window_below_iter)->layer()));
+    out_desk_container_children->insert(insertion_point_iter, window->layer());
+  }
+}
+
 // Recursively mirrors |source_layer| and its children and adds them as children
-// of |parent|, taking into account the given |layers_data|.
-// The transforms of the mirror layers of the direct children of
-// |desk_container_layer| will be reset to identity.
-void MirrorLayerTree(ui::Layer* desk_container_layer,
-                     ui::Layer* source_layer,
+// of |parent|, taking into account the given |layers_data|. If the layer data
+// of |source_layer| has |should_clear_transform| set to true, the transforms of
+// its mirror layers will be reset to identity.
+void MirrorLayerTree(ui::Layer* source_layer,
                      ui::Layer* parent,
-                     const base::flat_map<ui::Layer*, LayerData>& layers_data) {
+                     const base::flat_map<ui::Layer*, LayerData>& layers_data,
+                     const base::flat_set<aura::Window*>&
+                         visible_on_all_desks_windows_to_mirror) {
   const auto iter = layers_data.find(source_layer);
   const LayerData layer_data =
       iter == layers_data.end() ? LayerData{} : iter->second;
@@ -100,8 +149,20 @@
   auto* mirror = source_layer->Mirror().release();
   parent->Add(mirror);
 
-  for (auto* child : source_layer->children())
-    MirrorLayerTree(desk_container_layer, child, mirror, layers_data);
+  std::vector<ui::Layer*> children = source_layer->children();
+  if (!visible_on_all_desks_windows_to_mirror.empty()) {
+    // Windows that are visible on all desks should show up in each desk
+    // preview so for inactive desks, we need to append the layers of visible on
+    // all desks windows.
+    AppendVisibleOnAllDesksWindowsToDeskLayer(
+        visible_on_all_desks_windows_to_mirror, &children);
+  }
+  for (auto* child : children) {
+    // Visible on all desks windows only needed to be added to the subtree once
+    // so use an empty set for subsequent calls.
+    MirrorLayerTree(child, mirror, layers_data,
+                    base::flat_set<aura::Window*>());
+  }
 
   mirror->set_sync_bounds_with_source(true);
   if (layer_data.should_force_mirror_visible) {
@@ -110,10 +171,7 @@
     mirror->set_sync_visibility_with_source(false);
   }
 
-  // Windows in overview mode are transformed into their positions in the grid,
-  // but we want to show a preview of the windows in their untransformed state
-  // outside of overview mode.
-  if (source_layer->parent() == desk_container_layer)
+  if (layer_data.should_clear_transform)
     mirror->SetTransform(gfx::Transform());
 }
 
@@ -150,6 +208,15 @@
   if (window->GetProperty(kForceVisibleInMiniViewKey))
     layer_data.should_force_mirror_visible = true;
 
+  // Visible on all desks windows aren't children of inactive desk's container
+  // so mark them explicitly to clear overview transforms. Additionally, windows
+  // in overview mode are transformed into their positions in the grid, but we
+  // want to show a preview of the windows in their untransformed state.
+  if (window->GetProperty(aura::client::kVisibleOnAllWorkspacesKey) ||
+      desks_util::IsDeskContainer(window->parent())) {
+    layer_data.should_clear_transform = true;
+  }
+
   for (auto* child : window->children())
     GetLayersData(child, out_layers_data);
 }
@@ -289,9 +356,22 @@
   mirrored_content_root_layer->SetName("mirrored contents root layer");
   base::flat_map<ui::Layer*, LayerData> layers_data;
   GetLayersData(desk_container, &layers_data);
+
+  base::flat_set<aura::Window*> visible_on_all_desks_windows_to_mirror;
+  if (features::IsBentoEnabled() &&
+      !desks_util::IsActiveDeskContainer(desk_container)) {
+    // Since visible on all desks windows reside on the active desk, only mirror
+    // them in the layer tree if |this| is not the preview view for the active
+    // desk.
+    visible_on_all_desks_windows_to_mirror =
+        Shell::Get()->desks_controller()->visible_on_all_desks_windows();
+    for (auto* window : visible_on_all_desks_windows_to_mirror)
+      GetLayersData(window, &layers_data);
+  }
+
   auto* desk_container_layer = desk_container->layer();
-  MirrorLayerTree(desk_container_layer, desk_container_layer,
-                  mirrored_content_root_layer.get(), layers_data);
+  MirrorLayerTree(desk_container_layer, mirrored_content_root_layer.get(),
+                  layers_data, visible_on_all_desks_windows_to_mirror);
 
   // Add the root of the mirrored layer tree as a child of the
   // |desk_mirrored_contents_view_|'s layer.
diff --git a/ash/wm/desks/desks_controller.cc b/ash/wm/desks/desks_controller.cc
index b33dd81..0d28e972 100644
--- a/ash/wm/desks/desks_controller.cc
+++ b/ash/wm/desks/desks_controller.cc
@@ -546,6 +546,9 @@
 }
 
 void DesksController::AddVisibleOnAllDesksWindow(aura::Window* window) {
+  if (!features::IsBentoEnabled())
+    return;
+
   const bool added = visible_on_all_desks_windows_.emplace(window).second;
   DCHECK(added);
 }
diff --git a/ash/wm/desks/root_window_desk_switch_animator.cc b/ash/wm/desks/root_window_desk_switch_animator.cc
index 567004a..56e68971 100644
--- a/ash/wm/desks/root_window_desk_switch_animator.cc
+++ b/ash/wm/desks/root_window_desk_switch_animator.cc
@@ -50,6 +50,11 @@
 constexpr base::TimeDelta kRemovedDeskWindowTranslationDuration =
     base::TimeDelta::FromMilliseconds(100);
 
+// When ending a swipe that is deemed fast, the target desk only needs to be
+// 10% shown for us to animate to that desk, compared to 50% shown for a non
+// fast swipe.
+constexpr float kFastSwipeVisibilityRatio = 0.1f;
+
 // Create the layer that will be the parent of the screenshot layer, with a
 // solid black color to act as the background showing behind the two
 // screenshot layers in the |kDesksSpacing| region between them. It will get
@@ -327,7 +332,7 @@
   ending_desk_screenshot_taken_ = false;
 }
 
-int RootWindowDeskSwitchAnimator::EndSwipeAnimation() {
+int RootWindowDeskSwitchAnimator::EndSwipeAnimation(bool is_fast_swipe) {
   // If the starting screenshot has not finished, just let our delegate know
   // that the desk animation is finished (and |this| will soon be deleted), and
   // go back to the starting desk.
@@ -350,10 +355,35 @@
   // In tests, StartAnimation() may trigger OnDeskSwitchAnimationFinished()
   // right away which may delete |this|. Store the target index in a
   // local so we do not try to access a member of a deleted object.
-  const int ending_desk_index = GetIndexOfMostVisibleDeskScreenshot();
-  ending_desk_index_ = ending_desk_index;
+  int local_ending_desk_index = -1;
+
+  // If the swipe we are ending with is deemed a fast swipe, we animate to
+  // |ending_desk_index_| if more than 10% of it is currently visible.
+  // Otherwise, we animate to the most visible desk.
+  if (is_fast_swipe) {
+    ui::Layer* layer = screenshot_layers_[ending_desk_index_];
+    if (layer) {
+      const gfx::Transform transform =
+          animation_layer_owner_->root()->transform();
+      gfx::RectF screenshot_bounds(layer->bounds());
+      transform.TransformRect(&screenshot_bounds);
+
+      const gfx::RectF root_window_bounds(root_window_->bounds());
+      const gfx::RectF intersection_rect =
+          gfx::IntersectRects(screenshot_bounds, root_window_bounds);
+      if (intersection_rect.width() >
+          root_window_bounds.width() * kFastSwipeVisibilityRatio) {
+        local_ending_desk_index = ending_desk_index_;
+      }
+    }
+  }
+
+  if (local_ending_desk_index == -1)
+    local_ending_desk_index = GetIndexOfMostVisibleDeskScreenshot();
+
+  ending_desk_index_ = local_ending_desk_index;
   StartAnimation();
-  return ending_desk_index;
+  return local_ending_desk_index;
 }
 
 int RootWindowDeskSwitchAnimator::GetIndexOfMostVisibleDeskScreenshot() const {
diff --git a/ash/wm/desks/root_window_desk_switch_animator.h b/ash/wm/desks/root_window_desk_switch_animator.h
index 2b997a6..03819e7 100644
--- a/ash/wm/desks/root_window_desk_switch_animator.h
+++ b/ash/wm/desks/root_window_desk_switch_animator.h
@@ -272,8 +272,10 @@
   void PrepareForEndingDeskScreenshot(int new_ending_desk_index);
 
   // Called when a user ends a touchpad swipe. This will animate to the most
-  // visible desk, whose index is also returned.
-  int EndSwipeAnimation();
+  // visible desk, whose index is also returned. If |is_fast_swipe| is true, we
+  // will use a different logic to determine which ending desk index we want to
+  // end at.
+  int EndSwipeAnimation(bool is_fast_swipe);
 
   // Gets the index of the desk whose screenshot of the animation layer is most
   // visible to the user. That desk screenshot is the one which aligns the most
diff --git a/ash/wm/desks/root_window_desk_switch_animator_unittest.cc b/ash/wm/desks/root_window_desk_switch_animator_unittest.cc
index 5f6b9f7..3bdbcb24 100644
--- a/ash/wm/desks/root_window_desk_switch_animator_unittest.cc
+++ b/ash/wm/desks/root_window_desk_switch_animator_unittest.cc
@@ -424,7 +424,7 @@
   // should still be the most visible desk, so on ending the swipe animation,
   // desk indexed 0 is the target desk.
   animator()->UpdateSwipeAnimation(-touchpad_swipe_length_for_desk_change / 10);
-  animator()->EndSwipeAnimation();
+  animator()->EndSwipeAnimation(/*is_fast_swipe=*/false);
   EXPECT_EQ(
       Shell::GetPrimaryRootWindow()->bounds(),
       GetTargetVisibleBounds(test_api()->GetScreenshotLayerOfDeskWithIndex(0),
@@ -442,7 +442,30 @@
   // indexed 1 is the target desk.
   animator()->UpdateSwipeAnimation(-9 * touchpad_swipe_length_for_desk_change /
                                    10);
-  animator()->EndSwipeAnimation();
+  animator()->EndSwipeAnimation(/*is_fast_swipe=*/false);
+  EXPECT_EQ(
+      Shell::GetPrimaryRootWindow()->bounds(),
+      GetTargetVisibleBounds(test_api()->GetScreenshotLayerOfDeskWithIndex(1),
+                             animation_layer));
+}
+
+// Tests that a fast swipe, even if it is small will result in switching desks.
+TEST_F(RootWindowDeskSwitchAnimatorTest, FastSwipe) {
+  // Add one more desk as we need two desks for this test.
+  DesksController::Get()->NewDesk(DesksCreationRemovalSource::kButton);
+
+  InitAnimator(0, 1);
+  TakeStartingDeskScreenshotAndWait();
+  TakeEndingDeskScreenshotAndWait();
+  auto* animation_layer = test_api()->GetAnimationLayer();
+
+  const int touchpad_swipe_length_for_desk_change =
+      RootWindowDeskSwitchAnimator::kTouchpadSwipeLengthForDeskChange;
+  // Make a small left swipe headed towards desk indexed 1. We should still
+  // animate to desk indexed 1 even though desk indexed 0 is the most visible
+  // desk since it is a fast swipe.
+  animator()->UpdateSwipeAnimation(-touchpad_swipe_length_for_desk_change / 5);
+  animator()->EndSwipeAnimation(/*is_fast_swipe=*/true);
   EXPECT_EQ(
       Shell::GetPrimaryRootWindow()->bounds(),
       GetTargetVisibleBounds(test_api()->GetScreenshotLayerOfDeskWithIndex(1),
@@ -456,14 +479,14 @@
        EndSwipeAnimationBeforeScreenshotTaken) {
   InitAnimator(0, 1);
   animator()->TakeStartingDeskScreenshot();
-  animator()->EndSwipeAnimation();
+  animator()->EndSwipeAnimation(/*is_fast_swipe=*/false);
 
   // Reinitialize the animator as each animator only supports one
   // EndSwipeAnimation during its lifetime.
   InitAnimator(0, 1);
   TakeStartingDeskScreenshotAndWait();
   animator()->TakeEndingDeskScreenshot();
-  animator()->EndSwipeAnimation();
+  animator()->EndSwipeAnimation(/*is_fast_swipe=*/false);
 }
 
 }  // namespace ash
diff --git a/ash/wm/gestures/wm_gesture_handler.h b/ash/wm/gestures/wm_gesture_handler.h
index a41fdcc3..98b636b8c 100644
--- a/ash/wm/gestures/wm_gesture_handler.h
+++ b/ash/wm/gestures/wm_gesture_handler.h
@@ -29,7 +29,7 @@
   // The amount in trackpad units the fingers must move in a direction before a
   // continuous gesture animation is started. This is to minimize accidental
   // scrolls.
-  static constexpr int kContinuousGestureMoveThresholdDp = 10;
+  static constexpr int kContinuousGestureMoveThresholdDp = 5;
 
   WmGestureHandler();
   WmGestureHandler(const WmGestureHandler&) = delete;
diff --git a/base/allocator/partition_allocator/partition_root.h b/base/allocator/partition_allocator/partition_root.h
index cb818ae1..51192dd 100644
--- a/base/allocator/partition_allocator/partition_root.h
+++ b/base/allocator/partition_allocator/partition_root.h
@@ -693,7 +693,7 @@
 // TODO(glazunov): Simplify the function once the non-thread-safe PartitionRoot
 // is no longer used.
 ALWAYS_INLINE void PartitionAllocFreeForRefCounting(void* slot_start) {
-  PA_DCHECK(!internal::PartitionRefCountPointer(slot_start)->IsAlive());
+  PA_DCHECK(!internal::PartitionRefCountPointerNoDCheck(slot_start)->IsAlive());
 
   auto* slot_span =
       SlotSpanMetadata<ThreadSafe>::FromPointerNoAlignmentCheck(slot_start);
@@ -870,7 +870,7 @@
 #if ENABLE_REF_COUNT_FOR_BACKUP_REF_PTR
   if (allow_ref_count) {
     if (LIKELY(!slot_span->bucket->is_direct_mapped())) {
-      auto* ref_count = internal::PartitionRefCountPointer(slot_start);
+      auto* ref_count = internal::PartitionRefCountPointerNoDCheck(slot_start);
       // If we are holding the last reference to the allocation, it can be freed
       // immediately. Otherwise, defer the operation and zap the memory to turn
       // potential use-after-free issues into unexploitable crashes.
diff --git a/base/android/java/src/org/chromium/base/compat/ApiHelperForP.java b/base/android/java/src/org/chromium/base/compat/ApiHelperForP.java
index b0f4bcf..c4b41cf 100644
--- a/base/android/java/src/org/chromium/base/compat/ApiHelperForP.java
+++ b/base/android/java/src/org/chromium/base/compat/ApiHelperForP.java
@@ -5,19 +5,13 @@
 package org.chromium.base.compat;
 
 import android.annotation.TargetApi;
-import android.app.RemoteAction;
 import android.content.pm.PackageInfo;
 import android.location.LocationManager;
 import android.net.LinkProperties;
 import android.os.Build;
-import android.view.textclassifier.TextClassification;
-
-import androidx.annotation.NonNull;
 
 import org.chromium.base.annotations.VerifiesOnP;
 
-import java.util.List;
-
 /**
  * Utility class to use new APIs that were added in P (API level 28). These need to exist in a
  * separate class so that Android framework can successfully verify classes without
@@ -47,9 +41,4 @@
     public static boolean isLocationEnabled(LocationManager locationManager) {
         return locationManager.isLocationEnabled();
     }
-
-    /** See {@link TextClassification#getActions() } */
-    public static @NonNull List<RemoteAction> getActions(TextClassification classification) {
-        return classification.getActions();
-    }
 }
diff --git a/base/mac/mac_util.h b/base/mac/mac_util.h
index 8d1ccdd..23c46c6 100644
--- a/base/mac/mac_util.h
+++ b/base/mac/mac_util.h
@@ -51,9 +51,21 @@
 // specified hide flag.
 BASE_EXPORT void AddToLoginItems(bool hide_on_startup);
 
+// Adds the specified application to the set of Login Items with specified
+// "hide" flag. This has the same effect as adding/removing the application in
+// SystemPreferences->Accounts->LoginItems or marking Application in the Dock
+// as "Options->Open on Login".
+// Does nothing if the application is already set up as Login Item with
+// specified hide flag.
+BASE_EXPORT void AddToLoginItems(const FilePath& app_bundle_file_path,
+                                 bool hide_on_startup);
+
 // Removes the current application from the list Of Login Items.
 BASE_EXPORT void RemoveFromLoginItems();
 
+// Removes the specified application from the list Of Login Items.
+BASE_EXPORT void RemoveFromLoginItems(const FilePath& app_bundle_file_path);
+
 // Returns true if the current process was automatically launched as a
 // 'Login Item' or via Lion's Resume. Used to suppress opening windows.
 BASE_EXPORT bool WasLaunchedAsLoginOrResumeItem();
diff --git a/base/mac/mac_util.mm b/base/mac/mac_util.mm
index 2586fc2e..7666fdc 100644
--- a/base/mac/mac_util.mm
+++ b/base/mac/mac_util.mm
@@ -62,11 +62,9 @@
   // representing the specified bundle.  If such an item is found, returns a
   // retained reference to it. Caller is responsible for releasing the
   // reference.
-  ScopedCFTypeRef<LSSharedFileListItemRef> GetLoginItemForApp() {
+  ScopedCFTypeRef<LSSharedFileListItemRef> GetLoginItemForApp(NSURL* url) {
     DCHECK(login_items_.get()) << "Initialize() failed or not called.";
 
-    NSURL* url = [NSURL fileURLWithPath:[base::mac::MainBundle() bundlePath]];
-
 #pragma clang diagnostic push  // https://crbug.com/1154377
 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
     base::scoped_nsobject<NSArray> login_items_array(
@@ -91,6 +89,11 @@
     return ScopedCFTypeRef<LSSharedFileListItemRef>();
   }
 
+  ScopedCFTypeRef<LSSharedFileListItemRef> GetLoginItemForMainApp() {
+    NSURL* url = [NSURL fileURLWithPath:[base::mac::MainBundle() bundlePath]];
+    return GetLoginItemForApp(url);
+  }
+
  private:
   ScopedCFTypeRef<LSSharedFileListRef> login_items_;
 };
@@ -174,7 +177,7 @@
     return false;
 
   base::ScopedCFTypeRef<LSSharedFileListItemRef> item(
-      login_items.GetLoginItemForApp());
+      login_items.GetLoginItemForMainApp());
   if (!item.get())
     return false;
 
@@ -185,12 +188,18 @@
 }
 
 void AddToLoginItems(bool hide_on_startup) {
+  AddToLoginItems(base::mac::MainBundlePath(), hide_on_startup);
+}
+
+void AddToLoginItems(const FilePath& app_bundle_file_path,
+                     bool hide_on_startup) {
   LoginItemsFileList login_items;
   if (!login_items.Initialize())
     return;
 
+  NSURL* app_bundle_url = base::mac::FilePathToNSURL(app_bundle_file_path);
   base::ScopedCFTypeRef<LSSharedFileListItemRef> item(
-      login_items.GetLoginItemForApp());
+      login_items.GetLoginItemForApp(app_bundle_url));
 
   if (item.get() && (IsHiddenLoginItem(item) == hide_on_startup)) {
     return;  // Already is a login item with required hide flag.
@@ -204,8 +213,6 @@
 #pragma clang diagnostic pop
   }
 
-  NSURL* url = [NSURL fileURLWithPath:[base::mac::MainBundle() bundlePath]];
-
 #pragma clang diagnostic push  // https://crbug.com/1154377
 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
   BOOL hide = hide_on_startup ? YES : NO;
@@ -215,7 +222,7 @@
   ScopedCFTypeRef<LSSharedFileListItemRef> new_item(
       LSSharedFileListInsertItemURL(
           login_items.GetLoginFileList(), kLSSharedFileListItemLast, nullptr,
-          nullptr, reinterpret_cast<CFURLRef>(url),
+          nullptr, reinterpret_cast<CFURLRef>(app_bundle_url),
           reinterpret_cast<CFDictionaryRef>(properties), nullptr));
 #pragma clang diagnostic pop
 
@@ -225,12 +232,17 @@
 }
 
 void RemoveFromLoginItems() {
+  RemoveFromLoginItems(base::mac::MainBundlePath());
+}
+
+void RemoveFromLoginItems(const FilePath& app_bundle_file_path) {
   LoginItemsFileList login_items;
   if (!login_items.Initialize())
     return;
 
+  NSURL* app_bundle_url = base::mac::FilePathToNSURL(app_bundle_file_path);
   base::ScopedCFTypeRef<LSSharedFileListItemRef> item(
-      login_items.GetLoginItemForApp());
+      login_items.GetLoginItemForApp(app_bundle_url));
   if (!item.get())
     return;
 
@@ -297,7 +309,7 @@
     return false;
 
   base::ScopedCFTypeRef<LSSharedFileListItemRef> item(
-      login_items.GetLoginItemForApp());
+      login_items.GetLoginItemForMainApp());
   if (!item.get()) {
     // OS X can launch items for the resume feature.
     return false;
diff --git a/cc/layers/layer_unittest.cc b/cc/layers/layer_unittest.cc
index 65d4f04..8023902 100644
--- a/cc/layers/layer_unittest.cc
+++ b/cc/layers/layer_unittest.cc
@@ -1719,7 +1719,7 @@
   // Setting a filter that moves pixels.
   FilterOperations move_pixel_filters;
   move_pixel_filters.Append(
-      FilterOperation::CreateBlurFilter(2, SkBlurImageFilter::kClamp_TileMode));
+      FilterOperation::CreateBlurFilter(2, SkTileMode::kClamp));
   ASSERT_TRUE(move_pixel_filters.HasFilterThatMovesPixels());
   clipped_3->SetFilters(move_pixel_filters);
 
diff --git a/cc/layers/video_layer_impl_unittest.cc b/cc/layers/video_layer_impl_unittest.cc
index 00af2c8..6a205ea4 100644
--- a/cc/layers/video_layer_impl_unittest.cc
+++ b/cc/layers/video_layer_impl_unittest.cc
@@ -395,7 +395,7 @@
           gfx::Size(10, 10), gfx::Rect(10, 10), gfx::Size(10, 10),
           base::TimeDelta());
   ASSERT_TRUE(video_frame);
-  video_frame->metadata()->allow_overlay = true;
+  video_frame->metadata().allow_overlay = true;
   FakeVideoFrameProvider provider;
   provider.set_frame(video_frame);
 
@@ -438,7 +438,7 @@
           media::PIXEL_FORMAT_ARGB, mailbox_holders, base::DoNothing(),
           resource_size, gfx::Rect(10, 10), resource_size, base::TimeDelta());
   ASSERT_TRUE(video_frame);
-  video_frame->metadata()->allow_overlay = true;
+  video_frame->metadata().allow_overlay = true;
   FakeVideoFrameProvider provider;
   provider.set_frame(video_frame);
 
diff --git a/cc/metrics/video_playback_roughness_reporter.cc b/cc/metrics/video_playback_roughness_reporter.cc
index 69474ed..33ef9c5 100644
--- a/cc/metrics/video_playback_roughness_reporter.cc
+++ b/cc/metrics/video_playback_roughness_reporter.cc
@@ -59,11 +59,11 @@
 
   FrameInfo info;
   info.token = token;
-  info.decode_time = frame.metadata()->decode_end_time;
+  info.decode_time = frame.metadata().decode_end_time;
   info.refresh_rate_hz = int{std::round(1.0 / render_interval.InSecondsF())};
   info.size = frame.natural_size();
 
-  info.intended_duration = frame.metadata()->wallclock_frame_duration;
+  info.intended_duration = frame.metadata().wallclock_frame_duration;
   if (info.intended_duration) {
     if (render_interval > info.intended_duration.value()) {
       // In videos with FPS higher than display refresh rate we acknowledge
diff --git a/cc/metrics/video_playback_roughness_reporter_unittest.cc b/cc/metrics/video_playback_roughness_reporter_unittest.cc
index f028506..ffe3602 100644
--- a/cc/metrics/video_playback_roughness_reporter_unittest.cc
+++ b/cc/metrics/video_playback_roughness_reporter_unittest.cc
@@ -39,7 +39,7 @@
                                       int frame_size = 100) {
     scoped_refptr<VideoFrame> result = media::VideoFrame::CreateColorFrame(
         gfx::Size(frame_size, frame_size), 0x80, 0x80, 0x80, base::TimeDelta());
-    result->metadata()->wallclock_frame_duration = duration;
+    result->metadata().wallclock_frame_duration = duration;
     return result;
   }
 
diff --git a/cc/paint/filter_operation.cc b/cc/paint/filter_operation.cc
index c92ce18..dae02259 100644
--- a/cc/paint/filter_operation.cc
+++ b/cc/paint/filter_operation.cc
@@ -59,7 +59,7 @@
 
 FilterOperation::FilterOperation(FilterType type,
                                  float amount,
-                                 SkBlurImageFilter::TileMode tile_mode)
+                                 SkTileMode tile_mode)
     : type_(type),
       amount_(amount),
       outer_threshold_(0),
diff --git a/cc/paint/filter_operation.h b/cc/paint/filter_operation.h
index 7e3b17b..c989741e 100644
--- a/cc/paint/filter_operation.h
+++ b/cc/paint/filter_operation.h
@@ -14,7 +14,7 @@
 #include "cc/paint/paint_filter.h"
 #include "third_party/skia/include/core/SkColor.h"
 #include "third_party/skia/include/core/SkScalar.h"
-#include "third_party/skia/include/effects/SkBlurImageFilter.h"
+#include "third_party/skia/include/core/SkTileMode.h"
 #include "ui/gfx/geometry/point.h"
 #include "ui/gfx/geometry/rect.h"
 
@@ -98,7 +98,7 @@
     return shape_;
   }
 
-  SkBlurImageFilter::TileMode blur_tile_mode() const {
+  SkTileMode blur_tile_mode() const {
     DCHECK_EQ(type_, BLUR);
     return blur_tile_mode_;
   }
@@ -137,8 +137,7 @@
 
   static FilterOperation CreateBlurFilter(
       float amount,
-      SkBlurImageFilter::TileMode tile_mode =
-          SkBlurImageFilter::kClampToBlack_TileMode) {
+      SkTileMode tile_mode = SkTileMode::kDecal) {
     return FilterOperation(BLUR, amount, tile_mode);
   }
 
@@ -227,7 +226,7 @@
     shape_ = shape;
   }
 
-  void set_blur_tile_mode(SkBlurImageFilter::TileMode tile_mode) {
+  void set_blur_tile_mode(SkTileMode tile_mode) {
     DCHECK_EQ(type_, BLUR);
     blur_tile_mode_ = tile_mode;
   }
@@ -255,9 +254,7 @@
  private:
   FilterOperation(FilterType type, float amount);
 
-  FilterOperation(FilterType type,
-                  float amount,
-                  SkBlurImageFilter::TileMode tile_mode);
+  FilterOperation(FilterType type, float amount, SkTileMode tile_mode);
 
   FilterOperation(FilterType type,
                   const gfx::Point& offset,
@@ -286,7 +283,7 @@
 
   // Use a collection of |gfx::Rect| to make serialization simpler.
   ShapeRects shape_;
-  SkBlurImageFilter::TileMode blur_tile_mode_;
+  SkTileMode blur_tile_mode_;
 };
 
 }  // namespace cc
diff --git a/cc/paint/filter_operations_unittest.cc b/cc/paint/filter_operations_unittest.cc
index b8cc7fa..a51ea0f 100644
--- a/cc/paint/filter_operations_unittest.cc
+++ b/cc/paint/filter_operations_unittest.cc
@@ -55,7 +55,7 @@
       FilterOperation::CreateReferenceFilter(sk_make_sp<DropShadowPaintFilter>(
           SkIntToScalar(3), SkIntToScalar(8), SkIntToScalar(4),
           SkIntToScalar(9), SK_ColorBLACK,
-          SkDropShadowImageFilter::kDrawShadowAndForeground_ShadowMode,
+          DropShadowPaintFilter::ShadowMode::kDrawShadowAndForeground,
           nullptr)));
   EXPECT_EQ(gfx::Rect(-9, -19, 34, 64),
             ops.MapRect(gfx::Rect(0, 0, 10, 10), SkMatrix::I()));
@@ -71,7 +71,7 @@
       FilterOperation::CreateReferenceFilter(sk_make_sp<DropShadowPaintFilter>(
           SkIntToScalar(3), SkIntToScalar(8), SkIntToScalar(4),
           SkIntToScalar(9), SK_ColorBLACK,
-          SkDropShadowImageFilter::kDrawShadowAndForeground_ShadowMode,
+          DropShadowPaintFilter::ShadowMode::kDrawShadowAndForeground,
           nullptr)));
   EXPECT_EQ(gfx::Rect(-15, -35, 34, 64),
             ops.MapRectReverse(gfx::Rect(0, 0, 10, 10), SkMatrix::I()));
@@ -222,8 +222,7 @@
       FilterOperation::CreateReferenceFilter(sk_make_sp<XfermodePaintFilter>(
           SkBlendMode::kSrcOver,
           sk_make_sp<OffsetPaintFilter>(-big_offset, -big_offset, nullptr),
-          sk_make_sp<OffsetPaintFilter>(big_offset, big_offset, nullptr),
-          nullptr)));
+          sk_make_sp<OffsetPaintFilter>(big_offset, big_offset, nullptr))));
   gfx::Rect rect = ops.MapRect(gfx::Rect(-10, -10, 20, 20), SkMatrix::I());
   EXPECT_GT(rect.width(), 0);
   EXPECT_GT(rect.height(), 0);
@@ -699,10 +698,10 @@
 }
 
 TEST(FilterOperationsTest, BlendReferenceFilters) {
-  sk_sp<PaintFilter> from_filter(sk_make_sp<BlurPaintFilter>(
-      1.f, 1.f, BlurPaintFilter::TileMode::kClampToBlack_TileMode, nullptr));
-  sk_sp<PaintFilter> to_filter(sk_make_sp<BlurPaintFilter>(
-      2.f, 2.f, BlurPaintFilter::TileMode::kClampToBlack_TileMode, nullptr));
+  sk_sp<PaintFilter> from_filter(
+      sk_make_sp<BlurPaintFilter>(1.f, 1.f, SkTileMode::kDecal, nullptr));
+  sk_sp<PaintFilter> to_filter(
+      sk_make_sp<BlurPaintFilter>(2.f, 2.f, SkTileMode::kDecal, nullptr));
   FilterOperation from =
       FilterOperation::CreateReferenceFilter(std::move(from_filter));
   FilterOperation to =
@@ -722,8 +721,8 @@
 }
 
 TEST(FilterOperationsTest, BlendReferenceWithNull) {
-  sk_sp<PaintFilter> image_filter(sk_make_sp<BlurPaintFilter>(
-      1.f, 1.f, BlurPaintFilter::TileMode::kClampToBlack_TileMode, nullptr));
+  sk_sp<PaintFilter> image_filter(
+      sk_make_sp<BlurPaintFilter>(1.f, 1.f, SkTileMode::kDecal, nullptr));
   FilterOperation filter =
       FilterOperation::CreateReferenceFilter(std::move(image_filter));
   FilterOperation null_filter = FilterOperation::CreateReferenceFilter(nullptr);
@@ -917,8 +916,8 @@
 
   filters.Append(FilterOperation::CreateGrayscaleFilter(0.5f));
   filters.Append(FilterOperation::CreateBlurFilter(20));
-  sk_sp<PaintFilter> filter(sk_make_sp<BlurPaintFilter>(
-      1.f, 1.f, BlurPaintFilter::TileMode::kClampToBlack_TileMode, nullptr));
+  sk_sp<PaintFilter> filter(
+      sk_make_sp<BlurPaintFilter>(1.f, 1.f, SkTileMode::kDecal, nullptr));
   filters.Append(FilterOperation::CreateReferenceFilter(std::move(filter)));
 
   EXPECT_TRUE(filters.HasFilterOfType(FilterOperation::GRAYSCALE));
diff --git a/cc/paint/paint_filter.cc b/cc/paint/paint_filter.cc
index 5018eb8b..58314f3f 100644
--- a/cc/paint/paint_filter.cc
+++ b/cc/paint/paint_filter.cc
@@ -59,6 +59,86 @@
   return filter->SnapshotWithImages(image_provider);
 }
 
+// TODO(michaelludwig): These conversion functions can be removed once the
+// PaintFilter implementations are updated to use the new image factory APIs.
+SkImageFilter::CropRect ToLegacyCropRect(
+    const PaintFilter::CropRect* crop_rect) {
+  if (crop_rect) {
+    return SkImageFilter::CropRect(*crop_rect);
+  } else {
+    return SkImageFilter::CropRect(SkRect::MakeEmpty(), 0x0);
+  }
+}
+
+SkBlurImageFilter::TileMode ToLegacyBlurTileMode(SkTileMode tile_mode) {
+  switch (tile_mode) {
+    case SkTileMode::kClamp:
+      return SkBlurImageFilter::kClamp_TileMode;
+    case SkTileMode::kRepeat:
+      return SkBlurImageFilter::kRepeat_TileMode;
+    case SkTileMode::kMirror:
+      // The legacy SkBlurImageFilter::TileMode did not expose a mirror tiling
+      // option, so return kRepeat for now, which is consistent with Skia's
+      // deprecated behavior.
+      return SkBlurImageFilter::kRepeat_TileMode;
+    case SkTileMode::kDecal:
+      return SkBlurImageFilter::kClampToBlack_TileMode;
+    default:
+      NOTREACHED();
+      return SkBlurImageFilter::kClamp_TileMode;
+  }
+}
+
+SkMatrixConvolutionImageFilter::TileMode ToLegacyConvolutionTileMode(
+    SkTileMode tile_mode) {
+  switch (tile_mode) {
+    case SkTileMode::kClamp:
+      return SkMatrixConvolutionImageFilter::kClamp_TileMode;
+    case SkTileMode::kRepeat:
+      return SkMatrixConvolutionImageFilter::kRepeat_TileMode;
+    case SkTileMode::kMirror:
+      // The legacy SkMatrixConvolutionImageFilter::TileMode did not expose a
+      // mirror tiling option, so return kRepeat for now, which is consistent
+      // with Skia's deprecated behavior.
+      return SkMatrixConvolutionImageFilter::kRepeat_TileMode;
+    case SkTileMode::kDecal:
+      return SkMatrixConvolutionImageFilter::kClampToBlack_TileMode;
+    default:
+      NOTREACHED();
+      return SkMatrixConvolutionImageFilter::kClamp_TileMode;
+  }
+}
+
+SkDisplacementMapEffect::ChannelSelectorType ToLegacyChannel(
+    SkColorChannel channel) {
+  switch (channel) {
+    case SkColorChannel::kR:
+      return SkDisplacementMapEffect::kR_ChannelSelectorType;
+    case SkColorChannel::kG:
+      return SkDisplacementMapEffect::kG_ChannelSelectorType;
+    case SkColorChannel::kB:
+      return SkDisplacementMapEffect::kB_ChannelSelectorType;
+    case SkColorChannel::kA:
+      return SkDisplacementMapEffect::kA_ChannelSelectorType;
+    default:
+      NOTREACHED();
+      return SkDisplacementMapEffect::kUnknown_ChannelSelectorType;
+  }
+}
+
+SkDropShadowImageFilter::ShadowMode ToLegacyShadowMode(
+    DropShadowPaintFilter::ShadowMode shadow_mode) {
+  switch (shadow_mode) {
+    case DropShadowPaintFilter::ShadowMode::kDrawShadowAndForeground:
+      return SkDropShadowImageFilter::kDrawShadowAndForeground_ShadowMode;
+    case DropShadowPaintFilter::ShadowMode::kDrawShadowOnly:
+      return SkDropShadowImageFilter::kDrawShadowOnly_ShadowMode;
+    default:
+      NOTREACHED();
+      return SkDropShadowImageFilter::kDrawShadowAndForeground_ShadowMode;
+  }
+}
+
 }  // namespace
 
 PaintFilter::PaintFilter(Type type,
@@ -140,8 +220,7 @@
   total_size += sizeof(uint32_t);
   if (crop_rect_) {
     // CropRect.
-    total_size += sizeof(crop_rect_->flags());
-    total_size += sizeof(crop_rect_->rect());
+    total_size += sizeof(*crop_rect_);
   }
   return total_size;
 }
@@ -159,9 +238,7 @@
   if (!!crop_rect_ != !!other.crop_rect_)
     return false;
   if (crop_rect_) {
-    if (crop_rect_->flags() != other.crop_rect_->flags() ||
-        !PaintOp::AreSkRectsEqual(crop_rect_->rect(),
-                                  other.crop_rect_->rect())) {
+    if (!PaintOp::AreSkRectsEqual(*crop_rect_, *other.crop_rect_)) {
       return false;
     }
   }
@@ -248,8 +325,9 @@
       color_filter_(std::move(color_filter)),
       input_(std::move(input)) {
   DCHECK(color_filter_);
+  auto sk_crop_rect = ToLegacyCropRect(crop_rect);
   cached_sk_filter_ = SkColorFilterImageFilter::Make(
-      color_filter_, GetSkFilter(input_.get()), crop_rect);
+      color_filter_, GetSkFilter(input_.get()), &sk_crop_rect);
 }
 
 ColorFilterPaintFilter::~ColorFilterPaintFilter() = default;
@@ -277,7 +355,7 @@
 
 BlurPaintFilter::BlurPaintFilter(SkScalar sigma_x,
                                  SkScalar sigma_y,
-                                 TileMode tile_mode,
+                                 SkTileMode tile_mode,
                                  sk_sp<PaintFilter> input,
                                  const CropRect* crop_rect)
     : PaintFilter(kType, crop_rect, HasDiscardableImages(input)),
@@ -285,8 +363,10 @@
       sigma_y_(sigma_y),
       tile_mode_(tile_mode),
       input_(std::move(input)) {
-  cached_sk_filter_ = SkBlurImageFilter::Make(
-      sigma_x_, sigma_y_, GetSkFilter(input_.get()), crop_rect, tile_mode_);
+  auto sk_crop_rect = ToLegacyCropRect(crop_rect);
+  cached_sk_filter_ =
+      SkBlurImageFilter::Make(sigma_x_, sigma_y_, GetSkFilter(input_.get()),
+                              &sk_crop_rect, ToLegacyBlurTileMode(tile_mode_));
 }
 
 BlurPaintFilter::~BlurPaintFilter() = default;
@@ -329,9 +409,10 @@
       color_(color),
       shadow_mode_(shadow_mode),
       input_(std::move(input)) {
+  auto sk_crop_rect = ToLegacyCropRect(crop_rect);
   cached_sk_filter_ = SkDropShadowImageFilter::Make(
-      dx_, dy_, sigma_x_, sigma_y_, color_, shadow_mode_,
-      GetSkFilter(input_.get()), crop_rect);
+      dx_, dy_, sigma_x_, sigma_y_, color_, ToLegacyShadowMode(shadow_mode_),
+      GetSkFilter(input_.get()), &sk_crop_rect);
 }
 
 DropShadowPaintFilter::~DropShadowPaintFilter() = default;
@@ -369,8 +450,9 @@
       src_rect_(src_rect),
       inset_(inset),
       input_(std::move(input)) {
+  auto sk_crop_rect = ToLegacyCropRect(crop_rect);
   cached_sk_filter_ = SkMagnifierImageFilter::Make(
-      src_rect_, inset_, GetSkFilter(input_.get()), crop_rect);
+      src_rect_, inset_, GetSkFilter(input_.get()), &sk_crop_rect);
 }
 
 MagnifierPaintFilter::~MagnifierPaintFilter() = default;
@@ -435,8 +517,10 @@
       inner_min_(inner_min),
       outer_max_(outer_max),
       input_(std::move(input)) {
-  cached_sk_filter_ = SkAlphaThresholdFilter::Make(
-      region_, inner_min_, outer_max_, GetSkFilter(input_.get()), crop_rect);
+  auto sk_crop_rect = ToLegacyCropRect(crop_rect);
+  cached_sk_filter_ =
+      SkAlphaThresholdFilter::Make(region_, inner_min_, outer_max_,
+                                   GetSkFilter(input_.get()), &sk_crop_rect);
 }
 
 AlphaThresholdPaintFilter::~AlphaThresholdPaintFilter() = default;
@@ -476,9 +560,10 @@
       blend_mode_(blend_mode),
       background_(std::move(background)),
       foreground_(std::move(foreground)) {
-  cached_sk_filter_ =
-      SkXfermodeImageFilter::Make(blend_mode_, GetSkFilter(background_.get()),
-                                  GetSkFilter(foreground_.get()), crop_rect);
+  auto sk_crop_rect = ToLegacyCropRect(crop_rect);
+  cached_sk_filter_ = SkXfermodeImageFilter::Make(
+      blend_mode_, GetSkFilter(background_.get()),
+      GetSkFilter(foreground_.get()), &sk_crop_rect);
 }
 
 XfermodePaintFilter::~XfermodePaintFilter() = default;
@@ -523,9 +608,10 @@
       enforce_pm_color_(enforce_pm_color),
       background_(std::move(background)),
       foreground_(std::move(foreground)) {
+  auto sk_crop_rect = ToLegacyCropRect(crop_rect);
   cached_sk_filter_ = SkArithmeticImageFilter::Make(
       k1_, k2_, k3_, k4_, enforce_pm_color_, GetSkFilter(background_.get()),
-      GetSkFilter(foreground_.get()), crop_rect);
+      GetSkFilter(foreground_.get()), &sk_crop_rect);
 }
 
 ArithmeticPaintFilter::~ArithmeticPaintFilter() = default;
@@ -564,7 +650,7 @@
     SkScalar gain,
     SkScalar bias,
     const SkIPoint& kernel_offset,
-    TileMode tile_mode,
+    SkTileMode tile_mode,
     bool convolve_alpha,
     sk_sp<PaintFilter> input,
     const CropRect* crop_rect)
@@ -582,9 +668,11 @@
   for (size_t i = 0; i < len; ++i)
     kernel_->push_back(kernel[i]);
 
+  auto sk_crop_rect = ToLegacyCropRect(crop_rect);
   cached_sk_filter_ = SkMatrixConvolutionImageFilter::Make(
-      kernel_size_, kernel, gain_, bias_, kernel_offset_, tile_mode_,
-      convolve_alpha_, GetSkFilter(input_.get()), crop_rect);
+      kernel_size_, kernel, gain_, bias_, kernel_offset_,
+      ToLegacyConvolutionTileMode(tile_mode_), convolve_alpha_,
+      GetSkFilter(input_.get()), &sk_crop_rect);
 }
 
 MatrixConvolutionPaintFilter::~MatrixConvolutionPaintFilter() = default;
@@ -619,8 +707,8 @@
 }
 
 DisplacementMapEffectPaintFilter::DisplacementMapEffectPaintFilter(
-    ChannelSelectorType channel_x,
-    ChannelSelectorType channel_y,
+    SkColorChannel channel_x,
+    SkColorChannel channel_y,
     SkScalar scale,
     sk_sp<PaintFilter> displacement,
     sk_sp<PaintFilter> color,
@@ -634,17 +722,19 @@
       scale_(scale),
       displacement_(std::move(displacement)),
       color_(std::move(color)) {
+  auto sk_crop_rect = ToLegacyCropRect(crop_rect);
   cached_sk_filter_ = SkDisplacementMapEffect::Make(
-      channel_x_, channel_y_, scale_, GetSkFilter(displacement_.get()),
-      GetSkFilter(color_.get()), crop_rect);
+      ToLegacyChannel(channel_x_), ToLegacyChannel(channel_y_), scale_,
+      GetSkFilter(displacement_.get()), GetSkFilter(color_.get()),
+      &sk_crop_rect);
 }
 
 DisplacementMapEffectPaintFilter::~DisplacementMapEffectPaintFilter() = default;
 
 size_t DisplacementMapEffectPaintFilter::SerializedSize() const {
   base::CheckedNumeric<size_t> total_size = BaseSerializedSize() +
-                                            sizeof(uint32_t) +
-                                            sizeof(uint32_t) + sizeof(scale_);
+                                            sizeof(channel_x_) +
+                                            sizeof(channel_y_) + sizeof(scale_);
   total_size += GetFilterSize(displacement_.get());
   total_size += GetFilterSize(color_.get());
   return total_size.ValueOrDefault(0u);
@@ -770,8 +860,10 @@
     sk_filters.push_back(GetSkFilter(inputs_->back().get()));
   }
 
+  auto sk_crop_rect = ToLegacyCropRect(crop_rect);
   cached_sk_filter_ = SkMergeImageFilter::Make(
-      static_cast<sk_sp<SkImageFilter>*>(sk_filters.data()), count, crop_rect);
+      static_cast<sk_sp<SkImageFilter>*>(sk_filters.data()), count,
+      &sk_crop_rect);
 }
 
 MergePaintFilter::~MergePaintFilter() = default;
@@ -811,14 +903,15 @@
       radius_x_(radius_x),
       radius_y_(radius_y),
       input_(std::move(input)) {
+  auto sk_crop_rect = ToLegacyCropRect(crop_rect);
   switch (morph_type_) {
     case MorphType::kDilate:
       cached_sk_filter_ = SkDilateImageFilter::Make(
-          radius_x_, radius_y_, GetSkFilter(input_.get()), crop_rect);
+          radius_x_, radius_y_, GetSkFilter(input_.get()), &sk_crop_rect);
       break;
     case MorphType::kErode:
       cached_sk_filter_ = SkErodeImageFilter::Make(
-          radius_x_, radius_y_, GetSkFilter(input_.get()), crop_rect);
+          radius_x_, radius_y_, GetSkFilter(input_.get()), &sk_crop_rect);
       break;
   }
 }
@@ -855,8 +948,9 @@
       dx_(dx),
       dy_(dy),
       input_(std::move(input)) {
-  cached_sk_filter_ =
-      SkOffsetImageFilter::Make(dx_, dy_, GetSkFilter(input_.get()), crop_rect);
+  auto sk_crop_rect = ToLegacyCropRect(crop_rect);
+  cached_sk_filter_ = SkOffsetImageFilter::Make(
+      dx_, dy_, GetSkFilter(input_.get()), &sk_crop_rect);
 }
 
 OffsetPaintFilter::~OffsetPaintFilter() = default;
@@ -942,7 +1036,8 @@
 
   SkPaint paint;
   paint.setShader(std::move(shader));
-  cached_sk_filter_ = SkPaintImageFilter::Make(paint, crop_rect);
+  auto sk_crop_rect = ToLegacyCropRect(crop_rect);
+  cached_sk_filter_ = SkPaintImageFilter::Make(paint, &sk_crop_rect);
 }
 
 TurbulencePaintFilter::~TurbulencePaintFilter() = default;
@@ -984,9 +1079,11 @@
   if (image_provider) {
     raster_flags_.emplace(&flags_, image_provider, SkMatrix::I(), 0, 255u);
   }
+
+  auto sk_crop_rect = ToLegacyCropRect(crop_rect);
   cached_sk_filter_ = SkPaintImageFilter::Make(
       raster_flags_ ? raster_flags_->flags()->ToSkPaint() : flags_.ToSkPaint(),
-      crop_rect);
+      &sk_crop_rect);
 }
 
 PaintFlagsPaintFilter::~PaintFlagsPaintFilter() = default;
@@ -1057,16 +1154,17 @@
       kconstant_(kconstant),
       shininess_(shininess),
       input_(std::move(input)) {
+  auto sk_crop_rect = ToLegacyCropRect(crop_rect);
   switch (lighting_type_) {
     case LightingType::kDiffuse:
       cached_sk_filter_ = SkLightingImageFilter::MakeDistantLitDiffuse(
           direction_, light_color_, surface_scale_, kconstant_,
-          GetSkFilter(input_.get()), crop_rect);
+          GetSkFilter(input_.get()), &sk_crop_rect);
       break;
     case LightingType::kSpecular:
       cached_sk_filter_ = SkLightingImageFilter::MakeDistantLitSpecular(
           direction_, light_color_, surface_scale_, kconstant_, shininess_,
-          GetSkFilter(input_.get()), crop_rect);
+          GetSkFilter(input_.get()), &sk_crop_rect);
       break;
   }
 }
@@ -1116,16 +1214,17 @@
       kconstant_(kconstant),
       shininess_(shininess),
       input_(std::move(input)) {
+  auto sk_crop_rect = ToLegacyCropRect(crop_rect);
   switch (lighting_type_) {
     case LightingType::kDiffuse:
       cached_sk_filter_ = SkLightingImageFilter::MakePointLitDiffuse(
           location_, light_color_, surface_scale_, kconstant_,
-          GetSkFilter(input_.get()), crop_rect);
+          GetSkFilter(input_.get()), &sk_crop_rect);
       break;
     case LightingType::kSpecular:
       cached_sk_filter_ = SkLightingImageFilter::MakePointLitSpecular(
           location_, light_color_, surface_scale_, kconstant_, shininess_,
-          GetSkFilter(input_.get()), crop_rect);
+          GetSkFilter(input_.get()), &sk_crop_rect);
       break;
   }
 }
@@ -1181,17 +1280,18 @@
       kconstant_(kconstant),
       shininess_(shininess),
       input_(std::move(input)) {
+  auto sk_crop_rect = ToLegacyCropRect(crop_rect);
   switch (lighting_type_) {
     case LightingType::kDiffuse:
       cached_sk_filter_ = SkLightingImageFilter::MakeSpotLitDiffuse(
           location_, target_, specular_exponent_, cutoff_angle_, light_color_,
-          surface_scale_, kconstant_, GetSkFilter(input_.get()), crop_rect);
+          surface_scale_, kconstant_, GetSkFilter(input_.get()), &sk_crop_rect);
       break;
     case LightingType::kSpecular:
       cached_sk_filter_ = SkLightingImageFilter::MakeSpotLitSpecular(
           location_, target_, specular_exponent_, cutoff_angle_, light_color_,
           surface_scale_, kconstant_, shininess_, GetSkFilter(input_.get()),
-          crop_rect);
+          &sk_crop_rect);
       break;
   }
 }
diff --git a/cc/paint/paint_filter.h b/cc/paint/paint_filter.h
index 532aca4..882b928 100644
--- a/cc/paint/paint_filter.h
+++ b/cc/paint/paint_filter.h
@@ -70,7 +70,7 @@
   };
 
   using MapDirection = SkImageFilter::MapDirection;
-  using CropRect = SkImageFilter::CropRect;
+  using CropRect = SkRect;
 
   PaintFilter(const PaintFilter&) = delete;
   ~PaintFilter() override;
@@ -186,11 +186,10 @@
 
 class CC_PAINT_EXPORT BlurPaintFilter final : public PaintFilter {
  public:
-  using TileMode = SkBlurImageFilter::TileMode;
   static constexpr Type kType = Type::kBlur;
   BlurPaintFilter(SkScalar sigma_x,
                   SkScalar sigma_y,
-                  TileMode tile_mode,
+                  SkTileMode tile_mode,
                   sk_sp<PaintFilter> input,
                   const CropRect* crop_rect = nullptr);
   ~BlurPaintFilter() override;
@@ -199,7 +198,7 @@
 
   SkScalar sigma_x() const { return sigma_x_; }
   SkScalar sigma_y() const { return sigma_y_; }
-  TileMode tile_mode() const { return tile_mode_; }
+  SkTileMode tile_mode() const { return tile_mode_; }
 
   size_t SerializedSize() const override;
   bool operator==(const BlurPaintFilter& other) const;
@@ -211,13 +210,17 @@
  private:
   SkScalar sigma_x_;
   SkScalar sigma_y_;
-  TileMode tile_mode_;
+  SkTileMode tile_mode_;
   sk_sp<PaintFilter> input_;
 };
 
 class CC_PAINT_EXPORT DropShadowPaintFilter final : public PaintFilter {
  public:
-  using ShadowMode = SkDropShadowImageFilter::ShadowMode;
+  enum class ShadowMode {
+    kDrawShadowAndForeground,
+    kDrawShadowOnly,
+    kMaxValue = kDrawShadowOnly
+  };
   static constexpr Type kType = Type::kDropShadow;
   DropShadowPaintFilter(SkScalar dx,
                         SkScalar dy,
@@ -396,14 +399,13 @@
 
 class CC_PAINT_EXPORT MatrixConvolutionPaintFilter final : public PaintFilter {
  public:
-  using TileMode = SkMatrixConvolutionImageFilter::TileMode;
   static constexpr Type kType = Type::kMatrixConvolution;
   MatrixConvolutionPaintFilter(const SkISize& kernel_size,
                                const SkScalar* kernel,
                                SkScalar gain,
                                SkScalar bias,
                                const SkIPoint& kernel_offset,
-                               TileMode tile_mode,
+                               SkTileMode tile_mode,
                                bool convolve_alpha,
                                sk_sp<PaintFilter> input,
                                const CropRect* crop_rect = nullptr);
@@ -414,7 +416,7 @@
   SkScalar gain() const { return gain_; }
   SkScalar bias() const { return bias_; }
   SkIPoint kernel_offset() const { return kernel_offset_; }
-  TileMode tile_mode() const { return tile_mode_; }
+  SkTileMode tile_mode() const { return tile_mode_; }
   bool convolve_alpha() const { return convolve_alpha_; }
   const sk_sp<PaintFilter>& input() const { return input_; }
 
@@ -431,7 +433,7 @@
   SkScalar gain_;
   SkScalar bias_;
   SkIPoint kernel_offset_;
-  TileMode tile_mode_;
+  SkTileMode tile_mode_;
   bool convolve_alpha_;
   sk_sp<PaintFilter> input_;
 };
@@ -439,18 +441,17 @@
 class CC_PAINT_EXPORT DisplacementMapEffectPaintFilter final
     : public PaintFilter {
  public:
-  using ChannelSelectorType = SkDisplacementMapEffect::ChannelSelectorType;
   static constexpr Type kType = Type::kDisplacementMapEffect;
-  DisplacementMapEffectPaintFilter(ChannelSelectorType channel_x,
-                                   ChannelSelectorType channel_y,
+  DisplacementMapEffectPaintFilter(SkColorChannel channel_x,
+                                   SkColorChannel channel_y,
                                    SkScalar scale,
                                    sk_sp<PaintFilter> displacement,
                                    sk_sp<PaintFilter> color,
                                    const CropRect* crop_rect = nullptr);
   ~DisplacementMapEffectPaintFilter() override;
 
-  ChannelSelectorType channel_x() const { return channel_x_; }
-  ChannelSelectorType channel_y() const { return channel_y_; }
+  SkColorChannel channel_x() const { return channel_x_; }
+  SkColorChannel channel_y() const { return channel_y_; }
   SkScalar scale() const { return scale_; }
   const sk_sp<PaintFilter>& displacement() const { return displacement_; }
   const sk_sp<PaintFilter>& color() const { return color_; }
@@ -463,8 +464,8 @@
       ImageProvider* image_provider) const override;
 
  private:
-  ChannelSelectorType channel_x_;
-  ChannelSelectorType channel_y_;
+  SkColorChannel channel_x_;
+  SkColorChannel channel_y_;
   SkScalar scale_;
   sk_sp<PaintFilter> displacement_;
   sk_sp<PaintFilter> color_;
diff --git a/cc/paint/paint_filter_unittest.cc b/cc/paint/paint_filter_unittest.cc
index df029b8..05d8838 100644
--- a/cc/paint/paint_filter_unittest.cc
+++ b/cc/paint/paint_filter_unittest.cc
@@ -44,7 +44,7 @@
   auto record_filter =
       sk_make_sp<RecordPaintFilter>(record, SkRect::MakeWH(100.f, 100.f));
 
-  SkImageFilter::CropRect crop_rect(SkRect::MakeWH(100.f, 100.f));
+  PaintFilter::CropRect crop_rect(SkRect::MakeWH(100.f, 100.f));
 
   switch (filter_type) {
     case PaintFilter::Type::kNullFilter:
@@ -54,13 +54,12 @@
       return sk_make_sp<ColorFilterPaintFilter>(SkLumaColorFilter::Make(),
                                                 image_filter, &crop_rect);
     case PaintFilter::Type::kBlur:
-      return sk_make_sp<BlurPaintFilter>(0.1f, 0.2f,
-                                         SkBlurImageFilter::kClamp_TileMode,
+      return sk_make_sp<BlurPaintFilter>(0.1f, 0.2f, SkTileMode::kClamp,
                                          record_filter, &crop_rect);
     case PaintFilter::Type::kDropShadow:
       return sk_make_sp<DropShadowPaintFilter>(
           0.1, 0.2f, 0.3f, 0.4f, SK_ColorWHITE,
-          SkDropShadowImageFilter::kDrawShadowOnly_ShadowMode, image_filter,
+          DropShadowPaintFilter::ShadowMode::kDrawShadowOnly, image_filter,
           &crop_rect);
     case PaintFilter::Type::kMagnifier:
       return sk_make_sp<MagnifierPaintFilter>(SkRect::MakeWH(100.f, 100.f),
@@ -82,14 +81,12 @@
       SkScalar scalars[9] = {1.f, 2.f, 3.f, 4.f, 5.f, 6.f, 7.f, 8.f, 9.f};
       return sk_make_sp<MatrixConvolutionPaintFilter>(
           SkISize::Make(3, 3), scalars, 0.1f, 0.2f, SkIPoint::Make(2, 2),
-          SkMatrixConvolutionImageFilter::TileMode::kRepeat_TileMode, false,
-          image_filter, &crop_rect);
+          SkTileMode::kRepeat, false, image_filter, &crop_rect);
     }
     case PaintFilter::Type::kDisplacementMapEffect:
       return sk_make_sp<DisplacementMapEffectPaintFilter>(
-          SkDisplacementMapEffect::ChannelSelectorType::kR_ChannelSelectorType,
-          SkDisplacementMapEffect::ChannelSelectorType::kR_ChannelSelectorType,
-          0.1f, image_filter, record_filter, &crop_rect);
+          SkColorChannel::kR, SkColorChannel::kR, 0.1f, image_filter,
+          record_filter, &crop_rect);
     case PaintFilter::Type::kImage:
       return image_filter;
     case PaintFilter::Type::kPaintRecord:
diff --git a/cc/paint/paint_op_buffer_unittest.cc b/cc/paint/paint_op_buffer_unittest.cc
index 5012427..9df39c8d 100644
--- a/cc/paint/paint_op_buffer_unittest.cc
+++ b/cc/paint/paint_op_buffer_unittest.cc
@@ -37,7 +37,6 @@
 #include "third_party/skia/include/effects/SkColorMatrixFilter.h"
 #include "third_party/skia/include/effects/SkDashPathEffect.h"
 #include "third_party/skia/include/effects/SkLayerDrawLooper.h"
-#include "third_party/skia/include/effects/SkOffsetImageFilter.h"
 #include "third_party/skia/src/core/SkRemoteGlyphCache.h"
 
 using testing::_;
@@ -3320,19 +3319,18 @@
   std::vector<sk_sp<PaintFilter>> filters = {
       sk_sp<PaintFilter>{new ColorFilterPaintFilter(
           SkColorFilters::LinearToSRGBGamma(), nullptr)},
-      sk_sp<PaintFilter>{new BlurPaintFilter(
-          0.5f, 0.3f, SkBlurImageFilter::kRepeat_TileMode, nullptr)},
+      sk_sp<PaintFilter>{
+          new BlurPaintFilter(0.5f, 0.3f, SkTileMode::kRepeat, nullptr)},
       sk_sp<PaintFilter>{new DropShadowPaintFilter(
           5.f, 10.f, 0.1f, 0.3f, SK_ColorBLUE,
-          SkDropShadowImageFilter::kDrawShadowOnly_ShadowMode, nullptr)},
+          DropShadowPaintFilter::ShadowMode::kDrawShadowOnly, nullptr)},
       sk_sp<PaintFilter>{new MagnifierPaintFilter(SkRect::MakeXYWH(5, 6, 7, 8),
                                                   10.5f, nullptr)},
       sk_sp<PaintFilter>{new AlphaThresholdPaintFilter(
           SkRegion(SkIRect::MakeXYWH(0, 0, 100, 200)), 10.f, 20.f, nullptr)},
       sk_sp<PaintFilter>{new MatrixConvolutionPaintFilter(
           SkISize::Make(3, 3), scalars, 30.f, 123.f, SkIPoint::Make(0, 0),
-          SkMatrixConvolutionImageFilter::kClampToBlack_TileMode, true,
-          nullptr)},
+          SkTileMode::kDecal, true, nullptr)},
       sk_sp<PaintFilter>{new MorphologyPaintFilter(
           MorphologyPaintFilter::MorphType::kErode, 15.5f, 30.2f, nullptr)},
       sk_sp<PaintFilter>{new OffsetPaintFilter(-1.f, -2.f, nullptr)},
@@ -3361,12 +3359,10 @@
   filters.emplace_back(new ComposePaintFilter(filters[0], filters[1]));
   filters.emplace_back(
       new XfermodePaintFilter(SkBlendMode::kDst, filters[2], filters[3]));
-  filters.emplace_back(new ArithmeticPaintFilter(
-      1.1f, 2.2f, 3.3f, 4.4f, false, filters[4], filters[5], nullptr));
+  filters.emplace_back(new ArithmeticPaintFilter(1.1f, 2.2f, 3.3f, 4.4f, false,
+                                                 filters[4], filters[5]));
   filters.emplace_back(new DisplacementMapEffectPaintFilter(
-      SkDisplacementMapEffect::kR_ChannelSelectorType,
-      SkDisplacementMapEffect::kG_ChannelSelectorType, 10, filters[6],
-      filters[7]));
+      SkColorChannel::kR, SkColorChannel::kG, 10, filters[6], filters[7]));
   filters.emplace_back(new MergePaintFilter(filters.data(), filters.size()));
   filters.emplace_back(new RecordPaintFilter(
       sk_sp<PaintRecord>{new PaintRecord}, SkRect::MakeXYWH(10, 15, 20, 25)));
diff --git a/cc/paint/paint_op_perftest.cc b/cc/paint/paint_op_perftest.cc
index 4dd80d1c..a9ab9cc6 100644
--- a/cc/paint/paint_op_perftest.cc
+++ b/cc/paint/paint_op_perftest.cc
@@ -15,7 +15,6 @@
 #include "third_party/skia/include/effects/SkColorMatrixFilter.h"
 #include "third_party/skia/include/effects/SkDashPathEffect.h"
 #include "third_party/skia/include/effects/SkLayerDrawLooper.h"
-#include "third_party/skia/include/effects/SkOffsetImageFilter.h"
 
 namespace cc {
 namespace {
diff --git a/cc/paint/paint_op_reader.cc b/cc/paint/paint_op_reader.cc
index ef65cf7..81eb45eb 100644
--- a/cc/paint/paint_op_reader.cc
+++ b/cc/paint/paint_op_reader.cc
@@ -751,11 +751,9 @@
   base::Optional<PaintFilter::CropRect> crop_rect;
   ReadSimple(&has_crop_rect);
   if (has_crop_rect) {
-    uint32_t flags = 0;
     SkRect rect = SkRect::MakeEmpty();
-    ReadSimple(&flags);
     ReadSimple(&rect);
-    crop_rect.emplace(rect, flags);
+    crop_rect.emplace(rect);
   }
 
   AlignMemory(4);
@@ -854,12 +852,12 @@
     const base::Optional<PaintFilter::CropRect>& crop_rect) {
   SkScalar sigma_x = 0.f;
   SkScalar sigma_y = 0.f;
-  BlurPaintFilter::TileMode tile_mode = SkBlurImageFilter::kClamp_TileMode;
+  SkTileMode tile_mode;
   sk_sp<PaintFilter> input;
 
   Read(&sigma_x);
   Read(&sigma_y);
-  ReadSimple(&tile_mode);
+  Read(&tile_mode);
   Read(&input);
   if (!valid_)
     return;
@@ -871,13 +869,12 @@
 void PaintOpReader::ReadDropShadowPaintFilter(
     sk_sp<PaintFilter>* filter,
     const base::Optional<PaintFilter::CropRect>& crop_rect) {
-  using ShadowMode = DropShadowPaintFilter::ShadowMode;
   SkScalar dx = 0.f;
   SkScalar dy = 0.f;
   SkScalar sigma_x = 0.f;
   SkScalar sigma_y = 0.f;
   SkColor color = SK_ColorBLACK;
-  ShadowMode shadow_mode;
+  DropShadowPaintFilter::ShadowMode shadow_mode;
   sk_sp<PaintFilter> input;
 
   Read(&dx);
@@ -885,7 +882,7 @@
   Read(&sigma_x);
   Read(&sigma_y);
   Read(&color);
-  ReadEnum<ShadowMode, ShadowMode::kLast_ShadowMode>(&shadow_mode);
+  ReadEnum(&shadow_mode);
   Read(&input);
 
   if (!valid_)
@@ -992,7 +989,7 @@
   SkScalar gain = 0.f;
   SkScalar bias = 0.f;
   SkIPoint kernel_offset = SkIPoint::Make(0, 0);
-  uint32_t tile_mode_int = 0;
+  SkTileMode tile_mode;
   bool convolve_alpha = false;
   sk_sp<PaintFilter> input;
 
@@ -1011,15 +1008,11 @@
   Read(&gain);
   Read(&bias);
   ReadSimple(&kernel_offset);
-  Read(&tile_mode_int);
+  Read(&tile_mode);
   Read(&convolve_alpha);
   Read(&input);
-  if (tile_mode_int > SkMatrixConvolutionImageFilter::kMax_TileMode)
-    SetInvalid();
   if (!valid_)
     return;
-  MatrixConvolutionPaintFilter::TileMode tile_mode =
-      static_cast<MatrixConvolutionPaintFilter::TileMode>(tile_mode_int);
   filter->reset(new MatrixConvolutionPaintFilter(
       kernel_size, kernel.data(), gain, bias, kernel_offset, tile_mode,
       convolve_alpha, std::move(input), base::OptionalOrNullptr(crop_rect)));
@@ -1028,19 +1021,14 @@
 void PaintOpReader::ReadDisplacementMapEffectPaintFilter(
     sk_sp<PaintFilter>* filter,
     const base::Optional<PaintFilter::CropRect>& crop_rect) {
-  using ChannelSelectorType =
-      DisplacementMapEffectPaintFilter::ChannelSelectorType;
-
-  ChannelSelectorType channel_x;
-  ChannelSelectorType channel_y;
+  SkColorChannel channel_x;
+  SkColorChannel channel_y;
   SkScalar scale = 0.f;
   sk_sp<PaintFilter> displacement;
   sk_sp<PaintFilter> color;
 
-  ReadEnum<ChannelSelectorType, ChannelSelectorType::kLast_ChannelSelectorType>(
-      &channel_x);
-  ReadEnum<ChannelSelectorType, ChannelSelectorType::kLast_ChannelSelectorType>(
-      &channel_y);
+  ReadEnum<SkColorChannel, SkColorChannel::kA>(&channel_x);
+  ReadEnum<SkColorChannel, SkColorChannel::kA>(&channel_y);
   Read(&scale);
   Read(&displacement);
   Read(&color);
@@ -1186,7 +1174,6 @@
 void PaintOpReader::ReadPaintFlagsPaintFilter(
     sk_sp<PaintFilter>* filter,
     const base::Optional<PaintFilter::CropRect>& crop_rect) {
-  AlignMemory(4);
   PaintFlags flags;
   Read(&flags);
   if (!valid_)
diff --git a/cc/paint/paint_op_writer.cc b/cc/paint/paint_op_writer.cc
index 7c032e80..3352bee 100644
--- a/cc/paint/paint_op_writer.cc
+++ b/cc/paint/paint_op_writer.cc
@@ -588,8 +588,7 @@
   auto* crop_rect = filter->crop_rect();
   WriteSimple(static_cast<uint32_t>(!!crop_rect));
   if (crop_rect) {
-    WriteSimple(crop_rect->flags());
-    WriteSimple(crop_rect->rect());
+    WriteSimple(*crop_rect);
   }
 
   if (!valid_)
@@ -677,7 +676,7 @@
 void PaintOpWriter::Write(const BlurPaintFilter& filter) {
   WriteSimple(filter.sigma_x());
   WriteSimple(filter.sigma_y());
-  WriteSimple(filter.tile_mode());
+  Write(filter.tile_mode());
   Write(filter.input().get());
 }
 
@@ -734,7 +733,7 @@
   WriteSimple(filter.gain());
   WriteSimple(filter.bias());
   WriteSimple(filter.kernel_offset());
-  WriteSimple(static_cast<uint32_t>(filter.tile_mode()));
+  Write(filter.tile_mode());
   WriteSimple(filter.convolve_alpha());
   Write(filter.input().get());
 }
diff --git a/cc/paint/render_surface_filters.cc b/cc/paint/render_surface_filters.cc
index 913cd40..d8b243e 100644
--- a/cc/paint/render_surface_filters.cc
+++ b/cc/paint/render_surface_filters.cc
@@ -12,14 +12,7 @@
 #include "cc/paint/filter_operations.h"
 #include "cc/paint/paint_filter.h"
 #include "third_party/skia/include/core/SkColorFilter.h"
-#include "third_party/skia/include/core/SkImageFilter.h"
 #include "third_party/skia/include/core/SkRegion.h"
-#include "third_party/skia/include/effects/SkAlphaThresholdFilter.h"
-#include "third_party/skia/include/effects/SkColorFilterImageFilter.h"
-#include "third_party/skia/include/effects/SkColorMatrixFilter.h"
-#include "third_party/skia/include/effects/SkComposeImageFilter.h"
-#include "third_party/skia/include/effects/SkDropShadowImageFilter.h"
-#include "third_party/skia/include/effects/SkMagnifierImageFilter.h"
 #include "ui/gfx/geometry/size_f.h"
 #include "ui/gfx/skia_util.h"
 
@@ -209,7 +202,7 @@
             SkIntToScalar(op.drop_shadow_offset().y()),
             SkIntToScalar(op.amount()), SkIntToScalar(op.amount()),
             op.drop_shadow_color(),
-            SkDropShadowImageFilter::kDrawShadowAndForeground_ShadowMode,
+            DropShadowPaintFilter::ShadowMode::kDrawShadowAndForeground,
             std::move(image_filter));
         break;
       case FilterOperation::COLOR_MATRIX:
diff --git a/cc/trees/damage_tracker_unittest.cc b/cc/trees/damage_tracker_unittest.cc
index 7120a2d..82590e17 100644
--- a/cc/trees/damage_tracker_unittest.cc
+++ b/cc/trees/damage_tracker_unittest.cc
@@ -904,9 +904,8 @@
   child->SetDrawsContent(true);
 
   FilterOperations filters;
-  filters.Append(
-      FilterOperation::CreateReferenceFilter(sk_make_sp<BlurPaintFilter>(
-          2, 2, BlurPaintFilter::TileMode::kClampToBlack_TileMode, nullptr)));
+  filters.Append(FilterOperation::CreateReferenceFilter(
+      sk_make_sp<BlurPaintFilter>(2, 2, SkTileMode::kDecal, nullptr)));
 
   // Setting the filter will damage the whole surface.
   CreateTransformNode(child).post_translation =
@@ -988,9 +987,8 @@
   child->SetDrawsContent(true);
 
   FilterOperations filters;
-  filters.Append(
-      FilterOperation::CreateReferenceFilter(sk_make_sp<BlurPaintFilter>(
-          2, 2, BlurPaintFilter::TileMode::kClampToBlack_TileMode, nullptr)));
+  filters.Append(FilterOperation::CreateReferenceFilter(
+      sk_make_sp<BlurPaintFilter>(2, 2, SkTileMode::kDecal, nullptr)));
 
   // Setting the filter will damage the whole surface.
   gfx::Transform transform;
diff --git a/cc/trees/layer_tree_host_pixeltest_filters.cc b/cc/trees/layer_tree_host_pixeltest_filters.cc
index 3687e59c..c6193ce 100644
--- a/cc/trees/layer_tree_host_pixeltest_filters.cc
+++ b/cc/trees/layer_tree_host_pixeltest_filters.cc
@@ -59,7 +59,7 @@
     };
 
     FilterOperations filters;
-    SkImageFilter::CropRect cropRect(
+    PaintFilter::CropRect cropRect(
         SkRect::MakeXYWH(-40000, -40000, 80000, 80000));
     filters.Append(FilterOperation::CreateReferenceFilter(
         sk_make_sp<ColorFilterPaintFilter>(SkColorFilters::Matrix(matrix),
@@ -98,8 +98,7 @@
   background->AddChild(blur);
 
   FilterOperations filters;
-  filters.Append(FilterOperation::CreateBlurFilter(
-      2.f, SkBlurImageFilter::kClamp_TileMode));
+  filters.Append(FilterOperation::CreateBlurFilter(2.f, SkTileMode::kClamp));
   blur->SetBackdropFilters(filters);
   gfx::RRectF backdrop_filter_bounds(gfx::RectF(gfx::SizeF(blur->bounds())), 0);
   blur->SetBackdropFilterBounds(backdrop_filter_bounds);
@@ -164,8 +163,7 @@
   background->AddChild(blur);
 
   FilterOperations filters;
-  filters.Append(FilterOperation::CreateBlurFilter(
-      30.f, SkBlurImageFilter::kClamp_TileMode));
+  filters.Append(FilterOperation::CreateBlurFilter(30.f, SkTileMode::kClamp));
   blur->SetBackdropFilters(filters);
   gfx::RRectF backdrop_filter_bounds(gfx::RectF(gfx::SizeF(blur->bounds())), 0);
   blur->SetBackdropFilterBounds(backdrop_filter_bounds);
@@ -206,8 +204,7 @@
   background->AddChild(blur);
 
   FilterOperations filters;
-  filters.Append(FilterOperation::CreateBlurFilter(
-      2.f, SkBlurImageFilter::kClamp_TileMode));
+  filters.Append(FilterOperation::CreateBlurFilter(2.f, SkTileMode::kClamp));
   blur->SetBackdropFilters(filters);
   gfx::RRectF backdrop_filter_bounds(gfx::RectF(gfx::SizeF(blur->bounds())), 14,
                                      16, 18, 20, 22, 30, 40, 50);
@@ -254,8 +251,7 @@
   background->AddChild(blur);
 
   FilterOperations filters;
-  filters.Append(FilterOperation::CreateBlurFilter(
-      5.f, SkBlurImageFilter::kClamp_TileMode));
+  filters.Append(FilterOperation::CreateBlurFilter(5.f, SkTileMode::kClamp));
   blur->SetBackdropFilters(filters);
   gfx::RRectF backdrop_filter_bounds(gfx::RectF(gfx::SizeF(blur->bounds())), 0);
   blur->SetBackdropFilterBounds(backdrop_filter_bounds);
@@ -339,8 +335,7 @@
     EffectNode& blur_effect_node = CreateEffectNode(blur_layers[0].get());
 
     FilterOperations filters;
-    filters.Append(FilterOperation::CreateBlurFilter(
-        2.f, SkBlurImageFilter::kClamp_TileMode));
+    filters.Append(FilterOperation::CreateBlurFilter(2.f, SkTileMode::kClamp));
     blur_effect_node.backdrop_filters = filters;
     blur_effect_node.render_surface_reason =
         RenderSurfaceReason::kBackdropFilter;
@@ -497,7 +492,7 @@
   // Check that a filter with a zero-height crop rect crops out its
   // result completely.
   FilterOperations filters;
-  SkImageFilter::CropRect cropRect(SkRect::MakeXYWH(0, 0, 100, 0));
+  PaintFilter::CropRect cropRect(SkRect::MakeXYWH(0, 0, 100, 0));
   sk_sp<PaintFilter> offset(
       sk_make_sp<OffsetPaintFilter>(0, 0, nullptr, &cropRect));
   filters.Append(FilterOperation::CreateReferenceFilter(offset));
@@ -518,7 +513,7 @@
   // This filter does a red-blue swap, so the foreground becomes blue.
   matrix[2] = matrix[6] = matrix[10] = matrix[18] = 1.0f;
   // We filter only the bottom 200x100 pixels of the foreground.
-  SkImageFilter::CropRect crop_rect(SkRect::MakeXYWH(0, 100, 200, 100));
+  PaintFilter::CropRect crop_rect(SkRect::MakeXYWH(0, 100, 200, 100));
   FilterOperations filters;
   filters.Append(
       FilterOperation::CreateReferenceFilter(sk_make_sp<ColorFilterPaintFilter>(
@@ -644,8 +639,7 @@
 
   // Add a blur filter to the blue layer.
   FilterOperations filters;
-  filters.Append(FilterOperation::CreateBlurFilter(
-      5.0f, SkBlurImageFilter::kClamp_TileMode));
+  filters.Append(FilterOperation::CreateBlurFilter(5.0f, SkTileMode::kClamp));
   filter_layer->SetBackdropFilters(filters);
   gfx::RRectF backdrop_filter_bounds(
       gfx::RectF(gfx::SizeF(filter_layer->bounds())), 0);
@@ -1019,7 +1013,7 @@
   filter_layer->AddChild(child2);
 
   FilterOperations filters;
-  SkImageFilter::CropRect cropRect(SkRect::MakeXYWH(10, 10, 80, 80));
+  PaintFilter::CropRect cropRect(SkRect::MakeXYWH(10, 10, 80, 80));
   filters.Append(FilterOperation::CreateReferenceFilter(
       sk_make_sp<OffsetPaintFilter>(0, 0, nullptr, &cropRect)));
   filter_layer->SetFilters(filters);
@@ -1052,8 +1046,7 @@
   filter_layer->AddChild(child4);
 
   FilterOperations filters;
-  filters.Append(FilterOperation::CreateBlurFilter(
-      2.f, SkBlurImageFilter::kClamp_TileMode));
+  filters.Append(FilterOperation::CreateBlurFilter(2.f, SkTileMode::kClamp));
   filter_layer->SetFilters(filters);
 
   // Force the allocation a larger textures.
diff --git a/cc/trees/layer_tree_host_unittest.cc b/cc/trees/layer_tree_host_unittest.cc
index 4e11711..73f513c 100644
--- a/cc/trees/layer_tree_host_unittest.cc
+++ b/cc/trees/layer_tree_host_unittest.cc
@@ -914,8 +914,7 @@
 
   void AddBackgroundBlurFilter(Layer* layer) {
     FilterOperations filters;
-    filters.Append(FilterOperation::CreateBlurFilter(
-        30, SkBlurImageFilter::kClamp_TileMode));
+    filters.Append(FilterOperation::CreateBlurFilter(30, SkTileMode::kClamp));
     layer->SetBackdropFilters(filters);
   }
 
diff --git a/chrome/VERSION b/chrome/VERSION
index 489bff3..572fe2b 100644
--- a/chrome/VERSION
+++ b/chrome/VERSION
@@ -1,4 +1,4 @@
 MAJOR=89
 MINOR=0
-BUILD=4387
+BUILD=4388
 PATCH=0
diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn
index 61596a3..228d878 100644
--- a/chrome/android/BUILD.gn
+++ b/chrome/android/BUILD.gn
@@ -515,6 +515,7 @@
     "//third_party/android_deps:androidx_mediarouter_mediarouter_java",
     "//third_party/android_deps:androidx_preference_preference_java",
     "//third_party/android_deps:androidx_recyclerview_recyclerview_java",
+    "//third_party/android_deps:androidx_viewpager2_viewpager2_java",
     "//third_party/android_deps:androidx_viewpager_viewpager_java",
     "//third_party/android_deps:chromium_play_services_availability_java",
     "//third_party/android_deps:com_google_code_findbugs_jsr305_java",
@@ -1252,6 +1253,7 @@
     "//third_party/android_deps:androidx_preference_preference_java",
     "//third_party/android_deps:androidx_recyclerview_recyclerview_java",
     "//third_party/android_deps:androidx_test_runner_java",
+    "//third_party/android_deps:androidx_viewpager2_viewpager2_java",
     "//third_party/android_deps:androidx_viewpager_viewpager_java",
     "//third_party/android_deps:com_google_code_findbugs_jsr305_java",
     "//third_party/android_deps:espresso_java",
diff --git a/chrome/android/feed/core/javatests/src/org/chromium/chrome/browser/feed/v2/FeedV2TestHelper.java b/chrome/android/feed/core/javatests/src/org/chromium/chrome/browser/feed/v2/FeedV2TestHelper.java
index 61eed8e..9b29dc7 100644
--- a/chrome/android/feed/core/javatests/src/org/chromium/chrome/browser/feed/v2/FeedV2TestHelper.java
+++ b/chrome/android/feed/core/javatests/src/org/chromium/chrome/browser/feed/v2/FeedV2TestHelper.java
@@ -99,6 +99,9 @@
         enumNames.put("kLoadNotAllowedDisabledByEnterprisePolicy", 17);
         enumNames.put("kNetworkFetchFailed", 18);
         enumNames.put("kCannotLoadMoreNoNextPageToken", 19);
+        enumNames.put("kDataInStoreStaleMissedLastRefresh", 20);
+        enumNames.put("kLoadedStaleDataFromStoreDueToNetworkFailure", 21);
+        enumNames.put("kDataInStoreIsExpired", 22);
         return enumNames;
     }
 }
diff --git a/chrome/android/java/res/layout/clear_browsing_data_tabs.xml b/chrome/android/java/res/layout/clear_browsing_data_tabs.xml
index 62a1414..5dec369f 100644
--- a/chrome/android/java/res/layout/clear_browsing_data_tabs.xml
+++ b/chrome/android/java/res/layout/clear_browsing_data_tabs.xml
@@ -12,16 +12,13 @@
     android:layout_height="match_parent"
     android:orientation="vertical">
 
-    <!-- RTL is handled manually in ClearBrowsingDataTabsFragment because
-         it is not working correctly with a ViewPager -->
     <com.google.android.material.tabs.TabLayout
         android:id="@+id/clear_browsing_data_tabs"
-        android:layoutDirection="ltr"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         style="@style/TabLayoutStyle" />
 
-    <androidx.viewpager.widget.ViewPager
+    <androidx.viewpager2.widget.ViewPager2
         android:id="@+id/clear_browsing_data_viewpager"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/browsing_data/ClearBrowsingDataTabsFragment.java b/chrome/android/java/src/org/chromium/chrome/browser/browsing_data/ClearBrowsingDataTabsFragment.java
index 579a884..0feb906 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/browsing_data/ClearBrowsingDataTabsFragment.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/browsing_data/ClearBrowsingDataTabsFragment.java
@@ -4,7 +4,6 @@
 
 package org.chromium.chrome.browser.browsing_data;
 
-import android.content.Context;
 import android.os.Bundle;
 import android.view.LayoutInflater;
 import android.view.Menu;
@@ -13,18 +12,15 @@
 import android.view.View;
 import android.view.ViewGroup;
 
-import androidx.core.text.TextUtilsCompat;
-import androidx.core.view.ViewCompat;
 import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentActivity;
 import androidx.fragment.app.FragmentManager;
-import androidx.fragment.app.FragmentPagerAdapter;
 import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat;
-// TODO(bjoyce): Need to convert viewpager after fragmentpageradatper.
-import androidx.viewpager.widget.ViewPager;
+import androidx.viewpager2.adapter.FragmentStateAdapter;
+import androidx.viewpager2.widget.ViewPager2;
 
-// TODO(bjoyce): Need to convert tablayout to androidx after viewpager
-// and fragmentpageradatper are converted.
 import com.google.android.material.tabs.TabLayout;
+import com.google.android.material.tabs.TabLayoutMediator;
 
 import org.chromium.base.metrics.RecordUserAction;
 import org.chromium.chrome.R;
@@ -32,8 +28,6 @@
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.settings.SettingsActivity;
 
-import java.util.Locale;
-
 /**
  * Fragment with a {@link TabLayout} containing a basic and an advanced version of the CBD dialog.
  */
@@ -59,21 +53,6 @@
         RecordUserAction.record("ClearBrowsingData_DialogCreated");
     }
 
-    /*
-     * RTL is broken for ViewPager: https://code.google.com/p/android/issues/detail?id=56831
-     * This class works around this issue by inserting the tabs in inverse order if RTL is active.
-     * The TabLayout needs to be set to LTR for this to work.
-     * TODO(dullweber): Extract the RTL code into a wrapper class if other places in Chromium need
-     * it as well.
-     */
-    private static int adjustIndexForDirectionality(int index) {
-        if (TextUtilsCompat.getLayoutDirectionFromLocale(Locale.getDefault())
-                == ViewCompat.LAYOUT_DIRECTION_RTL) {
-            return CBD_TAB_COUNT - 1 - index;
-        }
-        return index;
-    }
-
     @Override
     public View onCreateView(
             LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
@@ -81,15 +60,16 @@
         View view = inflater.inflate(R.layout.clear_browsing_data_tabs, container, false);
 
         // Get the ViewPager and set its PagerAdapter so that it can display items.
-        ViewPager viewPager = view.findViewById(R.id.clear_browsing_data_viewpager);
-        viewPager.setAdapter(
-                new ClearBrowsingDataPagerAdapter(mFetcher, getFragmentManager(), getActivity()));
+        ViewPager2 viewPager = view.findViewById(R.id.clear_browsing_data_viewpager);
+        viewPager.setAdapter(new ClearBrowsingDataPagerAdapter(
+                mFetcher, getFragmentManager(), (FragmentActivity) getActivity()));
 
         // Give the TabLayout the ViewPager.
         TabLayout tabLayout = view.findViewById(R.id.clear_browsing_data_tabs);
-        tabLayout.setupWithViewPager(viewPager);
-        int tabIndex = adjustIndexForDirectionality(
-                BrowsingDataBridge.getInstance().getLastSelectedClearBrowsingDataTab());
+        new TabLayoutMediator(tabLayout, viewPager, (tab, position) -> {
+            tab.setText(getTabTitle(position));
+        }).attach();
+        int tabIndex = BrowsingDataBridge.getInstance().getLastSelectedClearBrowsingDataTab();
         TabLayout.Tab tab = tabLayout.getTabAt(tabIndex);
         if (tab != null) {
             tab.select();
@@ -103,6 +83,17 @@
         return view;
     }
 
+    private String getTabTitle(int position) {
+        switch (position) {
+            case 0:
+                return getActivity().getString(R.string.clear_browsing_data_basic_tab_title);
+            case 1:
+                return getActivity().getString(R.string.prefs_section_advanced);
+            default:
+                throw new RuntimeException("invalid position: " + position);
+        }
+    }
+
     @Override
     public void onSaveInstanceState(Bundle outState) {
         super.onSaveInstanceState(outState);
@@ -111,25 +102,22 @@
         outState.putParcelable(ClearBrowsingDataFragment.CLEAR_BROWSING_DATA_FETCHER, mFetcher);
     }
 
-    private static class ClearBrowsingDataPagerAdapter extends FragmentPagerAdapter {
+    private static class ClearBrowsingDataPagerAdapter extends FragmentStateAdapter {
         private final ClearBrowsingDataFetcher mFetcher;
-        private final Context mContext;
 
         ClearBrowsingDataPagerAdapter(
-                ClearBrowsingDataFetcher fetcher, FragmentManager fm, Context context) {
-            super(fm);
+                ClearBrowsingDataFetcher fetcher, FragmentManager fm, FragmentActivity activity) {
+            super(activity);
             mFetcher = fetcher;
-            mContext = context;
         }
 
         @Override
-        public int getCount() {
+        public int getItemCount() {
             return CBD_TAB_COUNT;
         }
 
         @Override
-        public Fragment getItem(int position) {
-            position = adjustIndexForDirectionality(position);
+        public Fragment createFragment(int position) {
             ClearBrowsingDataFragment fragment;
             switch (position) {
                 case 0:
@@ -144,25 +132,12 @@
             fragment.setClearBrowsingDataFetcher(mFetcher);
             return fragment;
         }
-
-        @Override
-        public CharSequence getPageTitle(int position) {
-            position = adjustIndexForDirectionality(position);
-            switch (position) {
-                case 0:
-                    return mContext.getString(R.string.clear_browsing_data_basic_tab_title);
-                case 1:
-                    return mContext.getString(R.string.prefs_section_advanced);
-                default:
-                    throw new RuntimeException("invalid position: " + position);
-            }
-        }
     }
 
     private static class TabSelectListener implements TabLayout.OnTabSelectedListener {
         @Override
         public void onTabSelected(TabLayout.Tab tab) {
-            int tabIndex = adjustIndexForDirectionality(tab.getPosition());
+            int tabIndex = tab.getPosition();
             BrowsingDataBridge.getInstance().setLastSelectedClearBrowsingDataTab(tabIndex);
             if (tabIndex == ClearBrowsingDataTab.BASIC) {
                 RecordUserAction.record("ClearBrowsingData_SwitchTo_BasicTab");
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/menu_button/MenuButtonMediator.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/menu_button/MenuButtonMediator.java
index 3f5a2bde..6596ead 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/menu_button/MenuButtonMediator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/menu_button/MenuButtonMediator.java
@@ -9,6 +9,7 @@
 import android.app.Activity;
 import android.content.res.ColorStateList;
 import android.content.res.Resources;
+import android.view.View;
 
 import androidx.annotation.Nullable;
 
@@ -120,8 +121,13 @@
             // Defocus here to avoid handling focus in multiple places, e.g., when the
             // forward button is pressed. (see crbug.com/414219)
             mSetUrlBarFocusFunction.setFocus(false, OmniboxFocusReason.UNFOCUS);
-            // Dismiss keyboard in case the user was interacting with an input field on a website.
-            mKeyboardDelegate.hideKeyboard(mActivity.getCurrentFocus());
+
+            View view = mActivity.getCurrentFocus();
+            if (view != null) {
+                // Dismiss keyboard in case the user was interacting with an input field on a
+                // website.
+                mKeyboardDelegate.hideKeyboard(view);
+            }
 
             if (!mIsInOverviewModeSupplier.get() && isShowingAppMenuUpdateBadge()) {
                 // The app menu badge should be removed the first time the menu is opened.
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/browsing_data/ClearBrowsingDataFragmentTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/browsing_data/ClearBrowsingDataFragmentTest.java
index 231b789..37b23c0 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/browsing_data/ClearBrowsingDataFragmentTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/browsing_data/ClearBrowsingDataFragmentTest.java
@@ -25,10 +25,12 @@
 import androidx.preference.CheckBoxPreference;
 import androidx.preference.Preference;
 import androidx.preference.PreferenceScreen;
+import androidx.recyclerview.widget.RecyclerView;
 import androidx.test.filters.LargeTest;
 import androidx.test.filters.MediumTest;
-import androidx.viewpager.widget.PagerAdapter;
-import androidx.viewpager.widget.ViewPager;
+import androidx.viewpager2.widget.ViewPager2;
+
+import com.google.android.material.tabs.TabLayout;
 
 import org.hamcrest.Matchers;
 import org.junit.Assert;
@@ -165,17 +167,18 @@
         verify(mBrowsingDataBridgeMock).getLastClearBrowsingDataTab(any());
 
         TestThreadUtils.runOnUiThreadBlocking(() -> {
-            ViewPager viewPager = (ViewPager) preferences.getView().findViewById(
+            ViewPager2 viewPager = (ViewPager2) preferences.getView().findViewById(
                     R.id.clear_browsing_data_viewpager);
-            PagerAdapter adapter = viewPager.getAdapter();
-            Assert.assertEquals(2, adapter.getCount());
+            RecyclerView.Adapter adapter = viewPager.getAdapter();
+            Assert.assertEquals(2, adapter.getItemCount());
             Assert.assertEquals(1, viewPager.getCurrentItem());
+            TabLayout tabLayout = preferences.getView().findViewById(R.id.clear_browsing_data_tabs);
             Assert.assertEquals(InstrumentationRegistry.getTargetContext().getString(
                                         R.string.clear_browsing_data_basic_tab_title),
-                    adapter.getPageTitle(0));
+                    tabLayout.getTabAt(0).getText());
             Assert.assertEquals(InstrumentationRegistry.getTargetContext().getString(
                                         R.string.prefs_section_advanced),
-                    adapter.getPageTitle(1));
+                    tabLayout.getTabAt(1).getText());
             viewPager.setCurrentItem(0);
         });
         // Verify the tab preference is saved.
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/UndoTabModelTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/UndoTabModelTest.java
index 62af3d2..2a3b486 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/UndoTabModelTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/UndoTabModelTest.java
@@ -11,12 +11,13 @@
 
 import org.hamcrest.Matchers;
 import org.junit.Assert;
-import org.junit.Before;
+import org.junit.ClassRule;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 import org.chromium.base.ApplicationStatus;
+import org.chromium.base.test.util.Batch;
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Criteria;
@@ -24,6 +25,7 @@
 import org.chromium.base.test.util.DisabledTest;
 import org.chromium.base.test.util.FlakyTest;
 import org.chromium.base.test.util.MinAndroidSdkLevel;
+import org.chromium.base.test.util.RequiresRestart;
 import org.chromium.base.test.util.Restriction;
 import org.chromium.base.test.util.UrlUtils;
 import org.chromium.chrome.browser.ChromeTabbedActivity2;
@@ -34,6 +36,7 @@
 import org.chromium.chrome.browser.tab.TabSelectionType;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
+import org.chromium.chrome.test.batch.BlankCTATabInitialStateRule;
 import org.chromium.chrome.test.util.ChromeTabUtils;
 import org.chromium.content_public.browser.LoadUrlParams;
 import org.chromium.content_public.browser.test.util.TestThreadUtils;
@@ -46,19 +49,20 @@
  */
 @RunWith(ChromeJUnit4ClassRunner.class)
 @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
+@Batch(Batch.PER_CLASS)
 public class UndoTabModelTest {
+    @ClassRule
+    public static ChromeTabbedActivityTestRule sActivityTestRule =
+            new ChromeTabbedActivityTestRule();
+
     @Rule
-    public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule();
+    public BlankCTATabInitialStateRule mInitialStateRule =
+            new BlankCTATabInitialStateRule(sActivityTestRule, true);
 
     private static final Tab[] EMPTY = new Tab[] { };
     private static final String TEST_URL_0 = UrlUtils.encodeHtmlDataUri("<html>test_url_0.</html>");
     private static final String TEST_URL_1 = UrlUtils.encodeHtmlDataUri("<html>test_url_1.</html>");
 
-    @Before
-    public void setUp() throws InterruptedException {
-        mActivityTestRule.startMainActivityOnBlankPage();
-    }
-
     private void checkState(
             final TabModel model, final Tab[] tabsList, final Tab selectedTab,
             final Tab[] closingTabs, final Tab[] fullTabsList,
@@ -329,8 +333,8 @@
     @Test
     @MediumTest
     public void testSingleTab() throws TimeoutException {
-        TabModel model = mActivityTestRule.getActivity().getTabModelSelector().getModel(false);
-        ChromeTabCreator tabCreator = mActivityTestRule.getActivity().getTabCreator(false);
+        TabModel model = sActivityTestRule.getActivity().getTabModelSelector().getModel(false);
+        ChromeTabCreator tabCreator = sActivityTestRule.getActivity().getTabCreator(false);
 
         Tab tab0 = model.getTabAt(0);
 
@@ -421,8 +425,8 @@
             message = "Flaky on all Android configurations except Swarming.  See crbug.com/620014.")
     public void
     testTwoTabs() throws TimeoutException {
-        TabModel model = mActivityTestRule.getActivity().getTabModelSelector().getModel(false);
-        ChromeTabCreator tabCreator = mActivityTestRule.getActivity().getTabCreator(false);
+        TabModel model = sActivityTestRule.getActivity().getTabModelSelector().getModel(false);
+        ChromeTabCreator tabCreator = sActivityTestRule.getActivity().getTabCreator(false);
         createTabOnUiThread(tabCreator);
 
         Tab tab0 = model.getTabAt(0);
@@ -568,8 +572,8 @@
     @Test
     @MediumTest
     public void testInOrderRestore() throws TimeoutException {
-        TabModel model = mActivityTestRule.getActivity().getTabModelSelector().getModel(false);
-        ChromeTabCreator tabCreator = mActivityTestRule.getActivity().getTabCreator(false);
+        TabModel model = sActivityTestRule.getActivity().getTabModelSelector().getModel(false);
+        ChromeTabCreator tabCreator = sActivityTestRule.getActivity().getTabCreator(false);
         createTabOnUiThread(tabCreator);
         createTabOnUiThread(tabCreator);
         createTabOnUiThread(tabCreator);
@@ -727,8 +731,8 @@
     @MediumTest
     @Restriction(UiRestriction.RESTRICTION_TYPE_PHONE) // See crbug.com/633607
     public void testReverseOrderRestore() throws TimeoutException {
-        TabModel model = mActivityTestRule.getActivity().getTabModelSelector().getModel(false);
-        ChromeTabCreator tabCreator = mActivityTestRule.getActivity().getTabCreator(false);
+        TabModel model = sActivityTestRule.getActivity().getTabModelSelector().getModel(false);
+        ChromeTabCreator tabCreator = sActivityTestRule.getActivity().getTabCreator(false);
         createTabOnUiThread(tabCreator);
         createTabOnUiThread(tabCreator);
         createTabOnUiThread(tabCreator);
@@ -878,8 +882,8 @@
     @Test
     @MediumTest
     public void testOutOfOrder1() throws TimeoutException {
-        TabModel model = mActivityTestRule.getActivity().getTabModelSelector().getModel(false);
-        ChromeTabCreator tabCreator = mActivityTestRule.getActivity().getTabCreator(false);
+        TabModel model = sActivityTestRule.getActivity().getTabModelSelector().getModel(false);
+        ChromeTabCreator tabCreator = sActivityTestRule.getActivity().getTabCreator(false);
         createTabOnUiThread(tabCreator);
         createTabOnUiThread(tabCreator);
         createTabOnUiThread(tabCreator);
@@ -994,8 +998,8 @@
     @MediumTest
     @FlakyTest(message = "crbug.com/592969")
     public void testOutOfOrder2() throws TimeoutException {
-        TabModel model = mActivityTestRule.getActivity().getTabModelSelector().getModel(false);
-        ChromeTabCreator tabCreator = mActivityTestRule.getActivity().getTabCreator(false);
+        TabModel model = sActivityTestRule.getActivity().getTabModelSelector().getModel(false);
+        ChromeTabCreator tabCreator = sActivityTestRule.getActivity().getTabCreator(false);
         createTabOnUiThread(tabCreator);
         createTabOnUiThread(tabCreator);
         createTabOnUiThread(tabCreator);
@@ -1091,8 +1095,8 @@
     @MediumTest
     @DisabledTest(message = "crbug.com/633607")
     public void testCloseAll() throws TimeoutException {
-        TabModel model = mActivityTestRule.getActivity().getTabModelSelector().getModel(false);
-        ChromeTabCreator tabCreator = mActivityTestRule.getActivity().getTabCreator(false);
+        TabModel model = sActivityTestRule.getActivity().getTabModelSelector().getModel(false);
+        ChromeTabCreator tabCreator = sActivityTestRule.getActivity().getTabCreator(false);
         createTabOnUiThread(tabCreator);
         createTabOnUiThread(tabCreator);
         createTabOnUiThread(tabCreator);
@@ -1161,12 +1165,16 @@
      * 3.  CloseTab(2, allow undo)    [ 0 3s ]           [ 2 1 ]           [ 0 1 2 3s ]
      * 4.  CloseTab(3, disallow undo) [ 0s ]             -                 [ 0s ]
      *
+     *
+     * TODO(crbug.com/1165954) Investigate and resolve failure on testCloseTab when batching.
+     * RequiresRestart is used as a workaround.
      */
     @Test
     @MediumTest
+    @RequiresRestart
     public void testCloseTab() throws TimeoutException {
-        TabModel model = mActivityTestRule.getActivity().getTabModelSelector().getModel(false);
-        ChromeTabCreator tabCreator = mActivityTestRule.getActivity().getTabCreator(false);
+        TabModel model = sActivityTestRule.getActivity().getTabModelSelector().getModel(false);
+        ChromeTabCreator tabCreator = sActivityTestRule.getActivity().getTabCreator(false);
         createTabOnUiThread(tabCreator);
         createTabOnUiThread(tabCreator);
         createTabOnUiThread(tabCreator);
@@ -1211,8 +1219,8 @@
     @Test
     @MediumTest
     public void testMoveTab() throws TimeoutException {
-        TabModel model = mActivityTestRule.getActivity().getTabModelSelector().getModel(false);
-        ChromeTabCreator tabCreator = mActivityTestRule.getActivity().getTabCreator(false);
+        TabModel model = sActivityTestRule.getActivity().getTabModelSelector().getModel(false);
+        ChromeTabCreator tabCreator = sActivityTestRule.getActivity().getTabCreator(false);
         createTabOnUiThread(tabCreator);
         createTabOnUiThread(tabCreator);
         createTabOnUiThread(tabCreator);
@@ -1263,8 +1271,8 @@
     @Test
     @DisabledTest
     public void testAddTab() throws TimeoutException {
-        TabModel model = mActivityTestRule.getActivity().getTabModelSelector().getModel(false);
-        ChromeTabCreator tabCreator = mActivityTestRule.getActivity().getTabCreator(false);
+        TabModel model = sActivityTestRule.getActivity().getTabModelSelector().getModel(false);
+        ChromeTabCreator tabCreator = sActivityTestRule.getActivity().getTabCreator(false);
         createTabOnUiThread(tabCreator);
         createTabOnUiThread(tabCreator);
         createTabOnUiThread(tabCreator);
@@ -1336,8 +1344,8 @@
     @MediumTest
     @DisabledTest(message = "crbug.com/1042168")
     public void testUndoNotSupported() throws TimeoutException {
-        TabModel model = mActivityTestRule.getActivity().getTabModelSelector().getModel(true);
-        ChromeTabCreator tabCreator = mActivityTestRule.getActivity().getTabCreator(true);
+        TabModel model = sActivityTestRule.getActivity().getTabModelSelector().getModel(true);
+        ChromeTabCreator tabCreator = sActivityTestRule.getActivity().getTabCreator(true);
         createTabOnUiThread(tabCreator);
         createTabOnUiThread(tabCreator);
         createTabOnUiThread(tabCreator);
@@ -1383,9 +1391,9 @@
     @MediumTest
     @Restriction(UiRestriction.RESTRICTION_TYPE_PHONE) // See crbug.com/633607
     public void testSaveStateCommitsUndos() throws TimeoutException {
-        TabModelSelector selector = mActivityTestRule.getActivity().getTabModelSelector();
+        TabModelSelector selector = sActivityTestRule.getActivity().getTabModelSelector();
         TabModel model = selector.getModel(false);
-        ChromeTabCreator tabCreator = mActivityTestRule.getActivity().getTabCreator(false);
+        ChromeTabCreator tabCreator = sActivityTestRule.getActivity().getTabCreator(false);
         createTabOnUiThread(tabCreator);
 
         Tab tab0 = model.getTabAt(0);
@@ -1414,9 +1422,9 @@
     @Test
     @MediumTest
     public void testOpenRecentlyClosedTab() throws TimeoutException {
-        TabModelSelector selector = mActivityTestRule.getActivity().getTabModelSelector();
+        TabModelSelector selector = sActivityTestRule.getActivity().getTabModelSelector();
         TabModel model = selector.getModel(false);
-        ChromeTabCreator tabCreator = mActivityTestRule.getActivity().getTabCreator(false);
+        ChromeTabCreator tabCreator = sActivityTestRule.getActivity().getTabCreator(false);
 
         createTabOnUiThread(tabCreator);
 
@@ -1438,14 +1446,14 @@
     @Test
     @MediumTest
     public void testOpenRecentlyClosedTabNative() throws TimeoutException {
-        final TabModelSelector selector = mActivityTestRule.getActivity().getTabModelSelector();
+        final TabModelSelector selector = sActivityTestRule.getActivity().getTabModelSelector();
         final TabModel model = selector.getModel(false);
 
         // Create new tab and wait until it's loaded.
         // Native can only successfully recover the tab after a page load has finished and
         // it has navigation history.
         ChromeTabUtils.fullyLoadUrlInNewTab(InstrumentationRegistry.getInstrumentation(),
-                mActivityTestRule.getActivity(), TEST_URL_0, false);
+                sActivityTestRule.getActivity(), TEST_URL_0, false);
 
         // Close the tab, and commit pending closure.
         Assert.assertEquals(model.getCount(), 2);
@@ -1484,14 +1492,14 @@
     public void testOpenRecentlyClosedTabMultiWindow() throws TimeoutException {
         final ChromeTabbedActivity2 secondActivity =
                 MultiWindowTestHelper.createSecondChromeTabbedActivity(
-                        mActivityTestRule.getActivity());
+                        sActivityTestRule.getActivity());
 
         // Wait for the second window to be fully initialized.
         CriteriaHelper.pollUiThread(
                 () -> secondActivity.getTabModelSelector().isTabStateInitialized());
         // First window context.
         final TabModelSelector firstSelector =
-                mActivityTestRule.getActivity().getTabModelSelector();
+                sActivityTestRule.getActivity().getTabModelSelector();
         final TabModel firstModel = firstSelector.getModel(false);
 
         // Second window context.
@@ -1499,7 +1507,7 @@
 
         // Create tabs.
         ChromeTabUtils.fullyLoadUrlInNewTab(InstrumentationRegistry.getInstrumentation(),
-                mActivityTestRule.getActivity(), TEST_URL_0, false);
+                sActivityTestRule.getActivity(), TEST_URL_0, false);
         ChromeTabUtils.fullyLoadUrlInNewTab(
                 InstrumentationRegistry.getInstrumentation(), secondActivity, TEST_URL_1, false);
 
@@ -1559,14 +1567,14 @@
     public void testOpenRecentlyClosedTabMultiWindowFallback() throws TimeoutException {
         final ChromeTabbedActivity2 secondActivity =
                 MultiWindowTestHelper.createSecondChromeTabbedActivity(
-                        mActivityTestRule.getActivity());
+                        sActivityTestRule.getActivity());
         // Wait for the second window to be fully initialized.
         CriteriaHelper.pollUiThread(
                 () -> secondActivity.getTabModelSelector().isTabStateInitialized());
 
         // First window context.
         final TabModelSelector firstSelector =
-                mActivityTestRule.getActivity().getTabModelSelector();
+                sActivityTestRule.getActivity().getTabModelSelector();
         final TabModel firstModel = firstSelector.getModel(false);
 
         // Second window context.
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/toolbar/menu_button/MenuButtonMediatorTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/toolbar/menu_button/MenuButtonMediatorTest.java
index f210998..2d7ef00 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/toolbar/menu_button/MenuButtonMediatorTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/toolbar/menu_button/MenuButtonMediatorTest.java
@@ -9,11 +9,13 @@
 
 import static org.mockito.Mockito.anyObject;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 
 import android.app.Activity;
 import android.content.res.Resources;
+import android.view.View;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -71,6 +73,8 @@
     private WindowAndroid mWindowAndroid;
     @Mock
     private KeyboardVisibilityDelegate mKeyboardDelegate;
+    @Mock
+    private View mUtilityView;
 
     private UpdateMenuItemHelper.MenuUiState mMenuUiState;
     private OneshotSupplierImpl<AppMenuCoordinator> mAppMenuSupplier;
@@ -217,7 +221,15 @@
 
     @Test
     public void testKeyboardIsDismissedWhenMenuShows() {
+        doReturn(mUtilityView).when(mActivity).getCurrentFocus();
         mMenuButtonMediator.onMenuVisibilityChanged(true);
-        verify(mKeyboardDelegate).hideKeyboard(anyObject());
+        verify(mKeyboardDelegate).hideKeyboard(eq(mUtilityView));
+    }
+
+    @Test
+    public void testKeyboardIsNotDismissedWhenMenuShowsWithNoFocusedViews() {
+        doReturn(null).when(mActivity).getCurrentFocus();
+        mMenuButtonMediator.onMenuVisibilityChanged(true);
+        verify(mKeyboardDelegate, never()).hideKeyboard(anyObject());
     }
 }
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index 362410e..ec6c5ff7 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -5574,7 +5574,7 @@
         Uninstall
       </message>
       <message name="IDS_NTP_CUSTOMIZE_COLOR_PICKER_LABEL" desc="The label for the color picker in the customization menu on the New Tab Page">
-        Select color
+        Custom color
       </message>
       <message name="IDS_NTP_CUSTOMIZE_DEFAULT_LABEL" desc="The label for the Default theme in the customization menu on the New Tab Page">
         Default
@@ -5681,6 +5681,36 @@
       <message name="IDS_NTP_MODULES_CART_HEADER_CHIP_NEW" desc="Text shown on the header chip of chrome cart module.">
         New
       </message>
+      <message name="IDS_NTP_MODULES_CART_TITLE" desc="Title shown in the header of the chrome cart module.">
+        Your carts
+      </message>
+      <message name="IDS_NTP_MODULES_CART_WARM_WELCOME" desc="Text shown in the module header when module shows for the first couple of times. This is served as welcome message and user education.">
+        Find what you added to shopping carts and check out when you're ready
+      </message>
+      <message name="IDS_NTP_MODULES_CART_MODULE_MENU_HIDE" desc="An option in the chrome cart module action menu to temporarily hide the module.">
+        Hide these carts
+      </message>
+      <message name="IDS_NTP_MODULES_CART_MODULE_MENU_HIDE_TOAST_MESSAGE" desc="Text shown in the toast confirming the cart module has been temporarily hidden.">
+        Carts hidden. They’ll reappear when you make changes.
+      </message>
+      <message name="IDS_NTP_MODULES_CART_MODULE_MENU_REMOVE" desc="An option in the chrome cart module action menu to permanently remove the module.">
+        Never show carts
+      </message>
+      <message name="IDS_NTP_MODULES_CART_MODULE_MENU_REMOVE_TOAST_MESSAGE" desc="Text shown in the toast confirming the cart module has been permanently removed.">
+        You won’t see your carts again on this page
+      </message>
+      <message name="IDS_NTP_MODULES_CART_CART_MENU_HIDE_MERCHANT" desc="An option in the action menu of one cart in the chrome cart module. When clicked, cart from that merchant will be temporarily hidden. Note that the MERCHANT field can be either merchant name (e.g. Amazon) or domain (e.g. amazon.com). ">
+        Hide <ph name="MERCHANT">$1<ex>Amazon</ex></ph>
+      </message>
+      <message name="IDS_NTP_MODULES_CART_CART_MENU_HIDE_MERCHANT_TOAST_MESSAGE" desc="Text shown in the toast confirming the cart for a specific merchant has been temporarily hidden. Note that the MERCHANT field can be either merchant name (e.g. Amazon) or domain (e.g. amazon.com).">
+        <ph name="MERCHANT">$1<ex>Amazon</ex> hidden. You’ll see it again when you revisit your cart.</ph>
+      </message>
+      <message name="IDS_NTP_MODULES_CART_CART_MENU_REMOVE_MERCHANT" desc="An option in the action menu of one cart in the chrome cart module. When clicked, cart from that merchant wil be permanently removed. Note that the MERCHANT field can be either merchant name (e.g. Amazon) or domain (e.g. amazon.com).">
+        Never show <ph name="MERCHANT">$1<ex>Amazon</ex></ph>
+      </message>
+      <message name="IDS_NTP_MODULES_CART_CART_MENU_REMOVE_MERCHANT_TOAST_MESSAGE" desc="Text shown in the toast confirming the cart for a specific merchant has been permanently removed. Note that the MERCHANT field can be either merchant name (e.g. Amazon) or domain (e.g. amazon.com).">
+        You won't see <ph name="MERCHANT">$1<ex>Amazon</ex></ph> again
+      </message>
       <!-- Extensions NTP Middle Slot Promo -->
       <message name="IDS_EXTENSIONS_PROMO_PERFORMANCE">
         Web browsing should be fast. Take a moment to <ph name="BEGIN_LINK">&lt;a href="chrome://extensions"&gt;</ph>check your extensions<ph name="END_LINK">&lt;/a&gt;</ph> now.
diff --git a/chrome/app/generated_resources_grd/IDS_NTP_CUSTOMIZE_COLOR_PICKER_LABEL.png.sha1 b/chrome/app/generated_resources_grd/IDS_NTP_CUSTOMIZE_COLOR_PICKER_LABEL.png.sha1
index 979fda5..396e225 100644
--- a/chrome/app/generated_resources_grd/IDS_NTP_CUSTOMIZE_COLOR_PICKER_LABEL.png.sha1
+++ b/chrome/app/generated_resources_grd/IDS_NTP_CUSTOMIZE_COLOR_PICKER_LABEL.png.sha1
@@ -1 +1 @@
-f73e91018684682237fa39464bb6b467411ac6e5
\ No newline at end of file
+25bd90400bbbceaab134f368521d93613c1dae04
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_NTP_MODULES_CART_CART_MENU_HIDE_MERCHANT.png.sha1 b/chrome/app/generated_resources_grd/IDS_NTP_MODULES_CART_CART_MENU_HIDE_MERCHANT.png.sha1
new file mode 100644
index 0000000..26a8d1e
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_NTP_MODULES_CART_CART_MENU_HIDE_MERCHANT.png.sha1
@@ -0,0 +1 @@
+205ecc3cdc1026ec8cc6609ae243b5b90dfc11f4
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_NTP_MODULES_CART_CART_MENU_HIDE_MERCHANT_TOAST_MESSAGE.png.sha1 b/chrome/app/generated_resources_grd/IDS_NTP_MODULES_CART_CART_MENU_HIDE_MERCHANT_TOAST_MESSAGE.png.sha1
new file mode 100644
index 0000000..6b518042
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_NTP_MODULES_CART_CART_MENU_HIDE_MERCHANT_TOAST_MESSAGE.png.sha1
@@ -0,0 +1 @@
+8f5ccbf525e4e402e95defa3c72ed94704649721
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_NTP_MODULES_CART_CART_MENU_REMOVE_MERCHANT.png.sha1 b/chrome/app/generated_resources_grd/IDS_NTP_MODULES_CART_CART_MENU_REMOVE_MERCHANT.png.sha1
new file mode 100644
index 0000000..056630c
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_NTP_MODULES_CART_CART_MENU_REMOVE_MERCHANT.png.sha1
@@ -0,0 +1 @@
+c119d94406ad14f56739fac99c72c7c24974f65c
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_NTP_MODULES_CART_CART_MENU_REMOVE_MERCHANT_TOAST_MESSAGE.png.sha1 b/chrome/app/generated_resources_grd/IDS_NTP_MODULES_CART_CART_MENU_REMOVE_MERCHANT_TOAST_MESSAGE.png.sha1
new file mode 100644
index 0000000..d87b0116
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_NTP_MODULES_CART_CART_MENU_REMOVE_MERCHANT_TOAST_MESSAGE.png.sha1
@@ -0,0 +1 @@
+03a7ed561ab0fbfe8ebdc00d4928d040186af0b9
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_NTP_MODULES_CART_MODULE_MENU_HIDE.png.sha1 b/chrome/app/generated_resources_grd/IDS_NTP_MODULES_CART_MODULE_MENU_HIDE.png.sha1
new file mode 100644
index 0000000..0e49938
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_NTP_MODULES_CART_MODULE_MENU_HIDE.png.sha1
@@ -0,0 +1 @@
+df11d3425075ca0f6e7e9cf78d2a62109889cbc5
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_NTP_MODULES_CART_MODULE_MENU_HIDE_TOAST_MESSAGE.png.sha1 b/chrome/app/generated_resources_grd/IDS_NTP_MODULES_CART_MODULE_MENU_HIDE_TOAST_MESSAGE.png.sha1
new file mode 100644
index 0000000..8e0d11e
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_NTP_MODULES_CART_MODULE_MENU_HIDE_TOAST_MESSAGE.png.sha1
@@ -0,0 +1 @@
+ec548a96a4238fc86061da1c371cf3e6a7cbe752
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_NTP_MODULES_CART_MODULE_MENU_REMOVE.png.sha1 b/chrome/app/generated_resources_grd/IDS_NTP_MODULES_CART_MODULE_MENU_REMOVE.png.sha1
new file mode 100644
index 0000000..bed019e
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_NTP_MODULES_CART_MODULE_MENU_REMOVE.png.sha1
@@ -0,0 +1 @@
+f04554a56498783b474bb86512ae5457820c6d7f
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_NTP_MODULES_CART_MODULE_MENU_REMOVE_TOAST_MESSAGE.png.sha1 b/chrome/app/generated_resources_grd/IDS_NTP_MODULES_CART_MODULE_MENU_REMOVE_TOAST_MESSAGE.png.sha1
new file mode 100644
index 0000000..30e16a8
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_NTP_MODULES_CART_MODULE_MENU_REMOVE_TOAST_MESSAGE.png.sha1
@@ -0,0 +1 @@
+de5e8c64284ec8fc829ef48456bbc7d881101912
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_NTP_MODULES_CART_TITLE.png.sha1 b/chrome/app/generated_resources_grd/IDS_NTP_MODULES_CART_TITLE.png.sha1
new file mode 100644
index 0000000..c7d7bb9
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_NTP_MODULES_CART_TITLE.png.sha1
@@ -0,0 +1 @@
+0e41d6ac7fc3bdeebfcb684017dfa72e725e9681
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_NTP_MODULES_CART_WARM_WELCOME.png.sha1 b/chrome/app/generated_resources_grd/IDS_NTP_MODULES_CART_WARM_WELCOME.png.sha1
new file mode 100644
index 0000000..f0531e9
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_NTP_MODULES_CART_WARM_WELCOME.png.sha1
@@ -0,0 +1 @@
+cc02912defe1e527eb9b6f9aa4e41df5fb99b3d1
\ No newline at end of file
diff --git a/chrome/app/os_settings_strings.grdp b/chrome/app/os_settings_strings.grdp
index 11b36fa..cd0b87d 100644
--- a/chrome/app/os_settings_strings.grdp
+++ b/chrome/app/os_settings_strings.grdp
@@ -940,10 +940,10 @@
     Enable select-to-speak
   </message>
   <message name="IDS_SETTINGS_ACCESSIBILITY_SELECT_TO_SPEAK_DISABLED_DESCRIPTION" desc="In the settings tab, the description of a feature that will read on-screen text out loud. This text is shown when that feature is disabled.">
-    Hear text read aloud
+    Hear selected text
   </message>
   <message name="IDS_SETTINGS_ACCESSIBILITY_SELECT_TO_SPEAK_DESCRIPTION" desc="For devices with a hardware keyboard. In the settings tab, the description of an option to hold a key and click or drag a box with the mouse to speak any on-screen text out loud.">
-    Highlight what you want to hear, then press Search + S. You can also press and hold the Search key, or tap the Select-to-Speak icon near your profile image to make a selection.
+    Highlight what you want to hear, then press Search + S. You can also press and hold the Search key, or tap the Select-to-Speak icon near the status tray to make a selection.
   </message>
   <message name="IDS_SETTINGS_ACCESSIBILITY_SELECT_TO_SPEAK_DESCRIPTION_WITHOUT_KEYBOARD" desc="For devices that do not have a hardware keyboard. In the settings tab, the description of an option to highlight text to be read out loud.">
     Tap the Select-to-Speak icon near your profile image, then select what you want to hear.
diff --git a/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_ACCESSIBILITY_SELECT_TO_SPEAK_DESCRIPTION.png.sha1 b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_ACCESSIBILITY_SELECT_TO_SPEAK_DESCRIPTION.png.sha1
new file mode 100644
index 0000000..b4cb7810
--- /dev/null
+++ b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_ACCESSIBILITY_SELECT_TO_SPEAK_DESCRIPTION.png.sha1
@@ -0,0 +1 @@
+983069cf6fcac3d42c39b8ef62901848e08883db
\ No newline at end of file
diff --git a/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_ACCESSIBILITY_SELECT_TO_SPEAK_DISABLED_DESCRIPTION.png.sha1 b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_ACCESSIBILITY_SELECT_TO_SPEAK_DISABLED_DESCRIPTION.png.sha1
new file mode 100644
index 0000000..5f9c5a2
--- /dev/null
+++ b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_ACCESSIBILITY_SELECT_TO_SPEAK_DISABLED_DESCRIPTION.png.sha1
@@ -0,0 +1 @@
+ebc6324130ba5e8c4a0dc3bca3c414cfe978da38
\ No newline at end of file
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 36106305..28524fb 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -2234,6 +2234,7 @@
     "//components/subresource_filter/content/browser",
     "//components/subresource_filter/core/browser",
     "//components/subresource_filter/core/common",
+    "//components/subresource_redirect/common",
     "//components/suggestions",
     "//components/sync",
     "//components/sync_bookmarks",
@@ -4598,8 +4599,6 @@
       "feedback/show_feedback_page_lacros.cc",
       "first_run/first_run_internal_lacros.cc",
       "lacros/account_manager_facade_factory_lacros.cc",
-      "lacros/account_manager_facade_lacros.cc",
-      "lacros/account_manager_facade_lacros.h",
       "lacros/account_manager_util.cc",
       "lacros/account_manager_util.h",
       "lacros/cert_db_initializer.h",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 863f0d67..89e5fcc 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -3418,7 +3418,7 @@
      FEATURE_VALUE_TYPE(blink::features::kWebAppEnableLinkCapturing)},
     {"enable-desktop-pwas-run-on-os-login",
      flag_descriptions::kDesktopPWAsRunOnOsLoginName,
-     flag_descriptions::kDesktopPWAsRunOnOsLoginDescription, kOsWin | kOsLinux,
+     flag_descriptions::kDesktopPWAsRunOnOsLoginDescription, kOsWin | kOsLinux | kOsMac,
      FEATURE_VALUE_TYPE(features::kDesktopPWAsRunOnOsLogin)},
     {"record-web-app-debug-info", flag_descriptions::kRecordWebAppDebugInfoName,
      flag_descriptions::kRecordWebAppDebugInfoDescription, kOsDesktop,
@@ -3840,10 +3840,6 @@
      kOsCrOS,
      SINGLE_VALUE_TYPE(
          ::switches::kEnableExperimentalAccessibilityChromeVoxAnnotations)},
-    {"enable-experimental-accessibility-cursor-colors",
-     flag_descriptions::kExperimentalAccessibilityCursorColorsName,
-     flag_descriptions::kExperimentalAccessibilityCursorColorsDescription,
-     kOsCrOS, FEATURE_VALUE_TYPE(features::kAccessibilityCursorColor)},
     {"enable-experimental-kernel-vm-support",
      flag_descriptions::kKernelnextVMsName,
      flag_descriptions::kKernelnextVMsDescription, kOsCrOS,
@@ -5763,6 +5759,13 @@
      FEATURE_VALUE_TYPE(
          safe_browsing::kEnhancedProtectionMessageInInterstitials)},
 
+    {"safe-browsing-real-time-url-lookup-enterprise-ga-endpoint",
+     flag_descriptions::kSafeBrowsingRealTimeUrlLookupEnterpriseGaEndpointName,
+     flag_descriptions::
+         kSafeBrowsingRealTimeUrlLookupEnterpriseGaEndpointDescription,
+     kOsAll,
+     FEATURE_VALUE_TYPE(safe_browsing::kRealTimeUrlLookupEnterpriseGaEndpoint)},
+
 #if BUILDFLAG(IS_CHROMEOS_ASH)
     {"gesture-properties-dbus-service",
      flag_descriptions::kEnableGesturePropertiesDBusServiceName,
diff --git a/chrome/browser/android/feed/v2/refresh_task_scheduler_impl.cc b/chrome/browser/android/feed/v2/refresh_task_scheduler_impl.cc
index 815b502..f96627a 100644
--- a/chrome/browser/android/feed/v2/refresh_task_scheduler_impl.cc
+++ b/chrome/browser/android/feed/v2/refresh_task_scheduler_impl.cc
@@ -9,15 +9,11 @@
 #include "components/background_task_scheduler/background_task_scheduler.h"
 #include "components/background_task_scheduler/task_ids.h"
 #include "components/background_task_scheduler/task_info.h"
+#include "components/feed/core/v2/config.h"
 #include "components/feed/core/v2/public/feed_service.h"
 #include "components/feed/core/v2/public/feed_stream_api.h"
 
 namespace feed {
-namespace {
-// TODO(harringtond): This scheduling window is provisional, I'm not sure how
-// this affects task scheduling.
-constexpr base::TimeDelta kSchedulingWindowSize = base::TimeDelta::FromHours(1);
-}  // namespace
 
 RefreshTaskSchedulerImpl::RefreshTaskSchedulerImpl(
     background_task::BackgroundTaskScheduler* scheduler)
@@ -35,7 +31,9 @@
 void RefreshTaskSchedulerImpl::EnsureScheduled(base::TimeDelta delay) {
   background_task::OneOffInfo one_off;
   one_off.window_start_time_ms = delay.InMilliseconds();
-  one_off.window_end_time_ms = (delay + kSchedulingWindowSize).InMilliseconds();
+  one_off.window_end_time_ms =
+      delay.InMilliseconds() +
+      GetFeedConfig().background_refresh_window_length.InMilliseconds();
   one_off.expires_after_window_end_time = true;
   background_task::TaskInfo task_info(
       static_cast<int>(background_task::TaskIds::FEEDV2_REFRESH_JOB_ID),
diff --git a/chrome/browser/apps/app_service/arc_apps.cc b/chrome/browser/apps/app_service/arc_apps.cc
index 8456a8c8..bfcc5a2e 100644
--- a/chrome/browser/apps/app_service/arc_apps.cc
+++ b/chrome/browser/apps/app_service/arc_apps.cc
@@ -1436,8 +1436,11 @@
 
   auto show = ShouldShow(app_info) ? apps::mojom::OptionalBool::kTrue
                                    : apps::mojom::OptionalBool::kFalse;
+  // All published ARC apps are launchable. All launchable apps should be
+  // permitted to be shown on the shelf, and have their pins on the shelf
+  // persisted.
+  app->show_in_shelf = apps::mojom::OptionalBool::kTrue;
   app->show_in_launcher = show;
-  app->show_in_shelf = show;
   app->show_in_search = show;
   app->show_in_management = show;
 
diff --git a/chrome/browser/apps/digital_goods/digital_goods_impl.h b/chrome/browser/apps/digital_goods/digital_goods_impl.h
index 2445730e4..f988b8b3 100644
--- a/chrome/browser/apps/digital_goods/digital_goods_impl.h
+++ b/chrome/browser/apps/digital_goods/digital_goods_impl.h
@@ -41,8 +41,6 @@
   RENDER_DOCUMENT_HOST_USER_DATA_KEY_DECL();
 
   arc::ArcDigitalGoodsBridge* GetArcDigitalGoodsBridge();
-  std::string GetTwaPackageName();
-  std::string GetScope();
 
   content::RenderFrameHost* render_frame_host_;
   mojo::Receiver<payments::mojom::DigitalGoods> receiver_;
diff --git a/chrome/browser/chrome_browser_main_extra_parts_ozone.cc b/chrome/browser/chrome_browser_main_extra_parts_ozone.cc
index 228757d..52d74d6 100644
--- a/chrome/browser/chrome_browser_main_extra_parts_ozone.cc
+++ b/chrome/browser/chrome_browser_main_extra_parts_ozone.cc
@@ -33,7 +33,7 @@
 
 void ChromeBrowserMainExtraPartsOzone::PostMainMessageLoopStart() {
   auto shutdown_cb = base::BindOnce([] {
-#if BUILDFLAG(IS_LACROS)
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
     // Force a crash so that a crash report is generated.
     LOG(FATAL) << "Wayland protocol error.";
 #else
diff --git a/chrome/browser/chromeos/crosapi/ash_chrome_service_impl.cc b/chrome/browser/chromeos/crosapi/ash_chrome_service_impl.cc
index 9bdadfc2..83fc0f2 100644
--- a/chrome/browser/chromeos/crosapi/ash_chrome_service_impl.cc
+++ b/chrome/browser/chromeos/crosapi/ash_chrome_service_impl.cc
@@ -44,12 +44,17 @@
 AshChromeServiceImpl::AshChromeServiceImpl(
     mojo::PendingReceiver<mojom::AshChromeService> pending_receiver)
     : receiver_(this, std::move(pending_receiver)),
+      file_manager_ash_(std::make_unique<FileManagerAsh>()),
+      keystore_service_ash_(std::make_unique<KeystoreServiceAsh>()),
+      message_center_ash_(std::make_unique<MessageCenterAsh>()),
       metrics_reporting_ash_(std::make_unique<MetricsReportingAsh>(
           g_browser_process->local_state())),
       prefs_ash_(std::make_unique<PrefsAsh>(
           g_browser_process->local_state(),
           ProfileManager::GetPrimaryUserProfile()->GetPrefs())),
       screen_manager_ash_(std::make_unique<ScreenManagerAsh>()),
+      select_file_ash_(std::make_unique<SelectFileAsh>()),
+      feedback_ash_(std::make_unique<FeedbackAsh>()),
       cert_database_ash_(std::make_unique<CertDatabaseAsh>()),
       test_controller_ash_(std::make_unique<TestControllerAsh>()),
       clipboard_ash_(std::make_unique<ClipboardAsh>()) {
@@ -98,25 +103,17 @@
 
 void AshChromeServiceImpl::BindFileManager(
     mojo::PendingReceiver<crosapi::mojom::FileManager> receiver) {
-  // TODO(https://crbug.com/1148448): Convert this to allow multiple,
-  // simultaneous crosapi clients. See BindScreenManager for an example.
-  file_manager_ash_ =
-      std::make_unique<crosapi::FileManagerAsh>(std::move(receiver));
+  file_manager_ash_->BindReceiver(std::move(receiver));
 }
 
 void AshChromeServiceImpl::BindKeystoreService(
     mojo::PendingReceiver<crosapi::mojom::KeystoreService> receiver) {
-  // TODO(https://crbug.com/1148448): Convert this to allow multiple,
-  // simultaneous crosapi clients. See BindScreenManager for an example.
-  keystore_service_ash_ =
-      std::make_unique<crosapi::KeystoreServiceAsh>(std::move(receiver));
+  keystore_service_ash_->BindReceiver(std::move(receiver));
 }
 
 void AshChromeServiceImpl::BindMessageCenter(
     mojo::PendingReceiver<mojom::MessageCenter> receiver) {
-  // TODO(https://crbug.com/1148448): Convert this to allow multiple,
-  // simultaneous crosapi clients. See BindScreenManager for an example.
-  message_center_ash_ = std::make_unique<MessageCenterAsh>(std::move(receiver));
+  message_center_ash_->BindReceiver(std::move(receiver));
 }
 
 void AshChromeServiceImpl::BindMetricsReporting(
@@ -126,9 +123,7 @@
 
 void AshChromeServiceImpl::BindSelectFile(
     mojo::PendingReceiver<mojom::SelectFile> receiver) {
-  // TODO(https://crbug.com/1148448): Convert this to allow multiple,
-  // simultaneous crosapi clients. See BindScreenManager for an example.
-  select_file_ash_ = std::make_unique<SelectFileAsh>(std::move(receiver));
+  select_file_ash_->BindReceiver(std::move(receiver));
 }
 
 void AshChromeServiceImpl::BindScreenManager(
@@ -143,9 +138,7 @@
 
 void AshChromeServiceImpl::BindFeedback(
     mojo::PendingReceiver<mojom::Feedback> receiver) {
-  // TODO(https://crbug.com/1148448): Convert this to allow multiple,
-  // simultaneous crosapi clients. See BindScreenManager for an example.
-  feedback_ash_ = std::make_unique<FeedbackAsh>(std::move(receiver));
+  feedback_ash_->BindReceiver(std::move(receiver));
 }
 
 void AshChromeServiceImpl::BindMediaSessionController(
diff --git a/chrome/browser/chromeos/crosapi/feedback_ash.cc b/chrome/browser/chromeos/crosapi/feedback_ash.cc
index 5db2962..c558a86 100644
--- a/chrome/browser/chromeos/crosapi/feedback_ash.cc
+++ b/chrome/browser/chromeos/crosapi/feedback_ash.cc
@@ -23,11 +23,15 @@
 
 }  // namespace
 
-FeedbackAsh::FeedbackAsh(mojo::PendingReceiver<mojom::Feedback> receiver)
-    : receiver_(this, std::move(receiver)) {}
+FeedbackAsh::FeedbackAsh() = default;
 
 FeedbackAsh::~FeedbackAsh() = default;
 
+void FeedbackAsh::BindReceiver(
+    mojo::PendingReceiver<mojom::Feedback> receiver) {
+  receivers_.Add(this, std::move(receiver));
+}
+
 void FeedbackAsh::ShowFeedbackPage(mojom::FeedbackInfoPtr feedback_info) {
   const user_manager::User* user =
       user_manager::UserManager::Get()->GetPrimaryUser();
diff --git a/chrome/browser/chromeos/crosapi/feedback_ash.h b/chrome/browser/chromeos/crosapi/feedback_ash.h
index 10e1cef..5695813 100644
--- a/chrome/browser/chromeos/crosapi/feedback_ash.h
+++ b/chrome/browser/chromeos/crosapi/feedback_ash.h
@@ -7,7 +7,7 @@
 
 #include "chromeos/crosapi/mojom/feedback.mojom.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
-#include "mojo/public/cpp/bindings/receiver.h"
+#include "mojo/public/cpp/bindings/receiver_set.h"
 
 namespace crosapi {
 
@@ -15,16 +15,18 @@
 // UI thread. Shows feedback page in response to mojo IPCs from lacros-chrome.
 class FeedbackAsh : public mojom::Feedback {
  public:
-  explicit FeedbackAsh(mojo::PendingReceiver<mojom::Feedback> receiver);
+  FeedbackAsh();
   FeedbackAsh(const FeedbackAsh&) = delete;
   FeedbackAsh& operator=(const FeedbackAsh&) = delete;
   ~FeedbackAsh() override;
 
+  void BindReceiver(mojo::PendingReceiver<mojom::Feedback> receiver);
+
   // crosapi::mojom::Feedback:
   void ShowFeedbackPage(mojom::FeedbackInfoPtr feedback_info) override;
 
  private:
-  mojo::Receiver<mojom::Feedback> receiver_;
+  mojo::ReceiverSet<mojom::Feedback> receivers_;
 };
 
 }  // namespace crosapi
diff --git a/chrome/browser/chromeos/crosapi/file_manager_ash.cc b/chrome/browser/chromeos/crosapi/file_manager_ash.cc
index 7312bfb..5154446 100644
--- a/chrome/browser/chromeos/crosapi/file_manager_ash.cc
+++ b/chrome/browser/chromeos/crosapi/file_manager_ash.cc
@@ -67,12 +67,15 @@
 
 }  // namespace
 
-FileManagerAsh::FileManagerAsh(
-    mojo::PendingReceiver<mojom::FileManager> receiver)
-    : receiver_(this, std::move(receiver)) {}
+FileManagerAsh::FileManagerAsh() = default;
 
 FileManagerAsh::~FileManagerAsh() = default;
 
+void FileManagerAsh::BindReceiver(
+    mojo::PendingReceiver<mojom::FileManager> receiver) {
+  receivers_.Add(this, std::move(receiver));
+}
+
 void FileManagerAsh::DeprecatedShowItemInFolder(const base::FilePath& path) {
   Profile* primary_profile = ProfileManager::GetPrimaryUserProfile();
   base::FilePath final_path = ExpandPath(primary_profile, path);
diff --git a/chrome/browser/chromeos/crosapi/file_manager_ash.h b/chrome/browser/chromeos/crosapi/file_manager_ash.h
index cf5e325..61e1c41 100644
--- a/chrome/browser/chromeos/crosapi/file_manager_ash.h
+++ b/chrome/browser/chromeos/crosapi/file_manager_ash.h
@@ -7,7 +7,7 @@
 
 #include "chromeos/crosapi/mojom/file_manager.mojom.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
-#include "mojo/public/cpp/bindings/receiver.h"
+#include "mojo/public/cpp/bindings/receiver_set.h"
 
 namespace crosapi {
 
@@ -16,11 +16,13 @@
 // manager, for example to open a folder or highlight a file.
 class FileManagerAsh : public mojom::FileManager {
  public:
-  explicit FileManagerAsh(mojo::PendingReceiver<mojom::FileManager> receiver);
+  FileManagerAsh();
   FileManagerAsh(const FileManagerAsh&) = delete;
   FileManagerAsh& operator=(const FileManagerAsh&) = delete;
   ~FileManagerAsh() override;
 
+  void BindReceiver(mojo::PendingReceiver<mojom::FileManager> receiver);
+
   // crosapi::mojom::FileManager:
   void DeprecatedShowItemInFolder(const base::FilePath& path) override;
   void ShowItemInFolder(const base::FilePath& path,
@@ -30,7 +32,7 @@
   void OpenFile(const base::FilePath& path, OpenFileCallback callback) override;
 
  private:
-  mojo::Receiver<mojom::FileManager> receiver_;
+  mojo::ReceiverSet<mojom::FileManager> receivers_;
 };
 
 }  // namespace crosapi
diff --git a/chrome/browser/chromeos/crosapi/keystore_service_ash.cc b/chrome/browser/chromeos/crosapi/keystore_service_ash.cc
index d6289bce..2c07ac9 100644
--- a/chrome/browser/chromeos/crosapi/keystore_service_ash.cc
+++ b/chrome/browser/chromeos/crosapi/keystore_service_ash.cc
@@ -62,9 +62,7 @@
 
 }  // namespace
 
-KeystoreServiceAsh::KeystoreServiceAsh(
-    mojo::PendingReceiver<mojom::KeystoreService> receiver)
-    : receiver_(this, std::move(receiver)) {
+KeystoreServiceAsh::KeystoreServiceAsh() {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 }
 
@@ -72,6 +70,11 @@
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 }
 
+void KeystoreServiceAsh::BindReceiver(
+    mojo::PendingReceiver<mojom::KeystoreService> receiver) {
+  receivers_.Add(this, std::move(receiver));
+}
+
 void KeystoreServiceAsh::ChallengeAttestationOnlyKeystore(
     const std::string& challenge,
     mojom::KeystoreType type,
diff --git a/chrome/browser/chromeos/crosapi/keystore_service_ash.h b/chrome/browser/chromeos/crosapi/keystore_service_ash.h
index e9f56fc..df5b729 100644
--- a/chrome/browser/chromeos/crosapi/keystore_service_ash.h
+++ b/chrome/browser/chromeos/crosapi/keystore_service_ash.h
@@ -12,7 +12,7 @@
 #include "chrome/browser/chromeos/platform_keys/platform_keys.h"
 #include "chromeos/crosapi/mojom/keystore_service.mojom.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
-#include "mojo/public/cpp/bindings/receiver.h"
+#include "mojo/public/cpp/bindings/receiver_set.h"
 
 namespace chromeos {
 namespace attestation {
@@ -28,12 +28,13 @@
 // system keystores. This class is affine to the UI thread.
 class KeystoreServiceAsh : public mojom::KeystoreService {
  public:
-  explicit KeystoreServiceAsh(
-      mojo::PendingReceiver<mojom::KeystoreService> receiver);
+  KeystoreServiceAsh();
   KeystoreServiceAsh(const KeystoreServiceAsh&) = delete;
   KeystoreServiceAsh& operator=(const KeystoreServiceAsh&) = delete;
   ~KeystoreServiceAsh() override;
 
+  void BindReceiver(mojo::PendingReceiver<mojom::KeystoreService> receiver);
+
   // mojom::KeystoreService:
   using KeystoreType = mojom::KeystoreType;
   void ChallengeAttestationOnlyKeystore(
@@ -81,7 +82,7 @@
   // Container to keep outstanding challenges alive.
   std::vector<std::unique_ptr<chromeos::attestation::TpmChallengeKey>>
       outstanding_challenges_;
-  mojo::Receiver<mojom::KeystoreService> receiver_;
+  mojo::ReceiverSet<mojom::KeystoreService> receivers_;
 
   base::WeakPtrFactory<KeystoreServiceAsh> weak_factory_{this};
 };
diff --git a/chrome/browser/chromeos/crosapi/message_center_ash.cc b/chrome/browser/chromeos/crosapi/message_center_ash.cc
index e9dbf421..2127758 100644
--- a/chrome/browser/chromeos/crosapi/message_center_ash.cc
+++ b/chrome/browser/chromeos/crosapi/message_center_ash.cc
@@ -156,12 +156,15 @@
 
 }  // namespace
 
-MessageCenterAsh::MessageCenterAsh(
-    mojo::PendingReceiver<mojom::MessageCenter> receiver)
-    : receiver_(this, std::move(receiver)) {}
+MessageCenterAsh::MessageCenterAsh() = default;
 
 MessageCenterAsh::~MessageCenterAsh() = default;
 
+void MessageCenterAsh::BindReceiver(
+    mojo::PendingReceiver<mojom::MessageCenter> receiver) {
+  receivers_.Add(this, std::move(receiver));
+}
+
 void MessageCenterAsh::DisplayNotification(
     mojom::NotificationPtr notification,
     mojo::PendingRemote<mojom::NotificationDelegate> delegate) {
diff --git a/chrome/browser/chromeos/crosapi/message_center_ash.h b/chrome/browser/chromeos/crosapi/message_center_ash.h
index 7379e53..2464166 100644
--- a/chrome/browser/chromeos/crosapi/message_center_ash.h
+++ b/chrome/browser/chromeos/crosapi/message_center_ash.h
@@ -7,7 +7,7 @@
 
 #include "chromeos/crosapi/mojom/message_center.mojom.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
-#include "mojo/public/cpp/bindings/receiver.h"
+#include "mojo/public/cpp/bindings/receiver_set.h"
 
 namespace crosapi {
 
@@ -16,12 +16,13 @@
 // Sends reply IPCs when the user interacts with the notifications.
 class MessageCenterAsh : public mojom::MessageCenter {
  public:
-  explicit MessageCenterAsh(
-      mojo::PendingReceiver<mojom::MessageCenter> receiver);
+  MessageCenterAsh();
   MessageCenterAsh(const MessageCenterAsh&) = delete;
   MessageCenterAsh& operator=(const MessageCenterAsh&) = delete;
   ~MessageCenterAsh() override;
 
+  void BindReceiver(mojo::PendingReceiver<mojom::MessageCenter> receiver);
+
   // crosapi::mojom::MessageCenter:
   void DisplayNotification(
       mojom::NotificationPtr notification,
@@ -31,7 +32,7 @@
       GetDisplayedNotificationsCallback callback) override;
 
  private:
-  mojo::Receiver<mojom::MessageCenter> receiver_;
+  mojo::ReceiverSet<mojom::MessageCenter> receivers_;
 };
 
 }  // namespace crosapi
diff --git a/chrome/browser/chromeos/crosapi/message_center_ash_unittest.cc b/chrome/browser/chromeos/crosapi/message_center_ash_unittest.cc
index 27a5b43..a9c98bf 100644
--- a/chrome/browser/chromeos/crosapi/message_center_ash_unittest.cc
+++ b/chrome/browser/chromeos/crosapi/message_center_ash_unittest.cc
@@ -84,7 +84,8 @@
   // testing::Test:
   void SetUp() override {
     message_center::MessageCenter::Initialize();
-    message_center_ash_ = std::make_unique<MessageCenterAsh>(
+    message_center_ash_ = std::make_unique<MessageCenterAsh>();
+    message_center_ash_->BindReceiver(
         message_center_remote_.BindNewPipeAndPassReceiver());
   }
 
diff --git a/chrome/browser/chromeos/crosapi/select_file_ash.cc b/chrome/browser/chromeos/crosapi/select_file_ash.cc
index d109e02..77e36eee8 100644
--- a/chrome/browser/chromeos/crosapi/select_file_ash.cc
+++ b/chrome/browser/chromeos/crosapi/select_file_ash.cc
@@ -167,12 +167,15 @@
 
 }  // namespace
 
-// TODO(https://crbug.com/1090587): Connection error handling.
-SelectFileAsh::SelectFileAsh(mojo::PendingReceiver<mojom::SelectFile> receiver)
-    : receiver_(this, std::move(receiver)) {}
+SelectFileAsh::SelectFileAsh() = default;
 
 SelectFileAsh::~SelectFileAsh() = default;
 
+void SelectFileAsh::BindReceiver(
+    mojo::PendingReceiver<mojom::SelectFile> receiver) {
+  receivers_.Add(this, std::move(receiver));
+}
+
 void SelectFileAsh::Select(mojom::SelectFileOptionsPtr options,
                            SelectCallback callback) {
   aura::Window* owner_window = nullptr;
diff --git a/chrome/browser/chromeos/crosapi/select_file_ash.h b/chrome/browser/chromeos/crosapi/select_file_ash.h
index 28509dc..58c78ce2 100644
--- a/chrome/browser/chromeos/crosapi/select_file_ash.h
+++ b/chrome/browser/chromeos/crosapi/select_file_ash.h
@@ -7,7 +7,7 @@
 
 #include "chromeos/crosapi/mojom/select_file.mojom.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
-#include "mojo/public/cpp/bindings/receiver.h"
+#include "mojo/public/cpp/bindings/receiver_set.h"
 
 namespace crosapi {
 
@@ -16,17 +16,19 @@
 // file manager to provide the dialogs. Lives on the UI thread.
 class SelectFileAsh : public mojom::SelectFile {
  public:
-  explicit SelectFileAsh(mojo::PendingReceiver<mojom::SelectFile> receiver);
+  SelectFileAsh();
   SelectFileAsh(const SelectFileAsh&) = delete;
   SelectFileAsh& operator=(const SelectFileAsh&) = delete;
   ~SelectFileAsh() override;
 
+  void BindReceiver(mojo::PendingReceiver<mojom::SelectFile> receiver);
+
   // crosapi::mojom::SelectFile:
   void Select(mojom::SelectFileOptionsPtr options,
               SelectCallback callback) override;
 
  private:
-  mojo::Receiver<mojom::SelectFile> receiver_;
+  mojo::ReceiverSet<mojom::SelectFile> receivers_;
 };
 
 }  // namespace crosapi
diff --git a/chrome/browser/chromeos/dbus/chrome_features_service_provider.cc b/chrome/browser/chromeos/dbus/chrome_features_service_provider.cc
index 005199c..7a9fabff 100644
--- a/chrome/browser/chromeos/dbus/chrome_features_service_provider.cc
+++ b/chrome/browser/chromeos/dbus/chrome_features_service_provider.cc
@@ -141,6 +141,7 @@
       &arc::kFilePickerExperimentFeature,
       &arc::kNativeBridgeToggleFeature,
       &features::kSessionManagerLongKillTimeout,
+      &features::kCrostiniUseDlc,
   };
 
   dbus::MessageReader reader(method_call);
diff --git a/chrome/browser/chromeos/file_manager/file_manager_jstest.cc b/chrome/browser/chromeos/file_manager/file_manager_jstest.cc
index 5dc1a875..138eab9 100644
--- a/chrome/browser/chromeos/file_manager/file_manager_jstest.cc
+++ b/chrome/browser/chromeos/file_manager/file_manager_jstest.cc
@@ -21,7 +21,7 @@
 };
 
 IN_PROC_BROWSER_TEST_F(FileManagerJsTest, ActionsModelTest) {
-  RunTestURL("foreground/js/actions_model_unittest_gen.html");
+  RunTestURL("foreground/js/actions_model_unittest.m_gen.html");
 }
 
 IN_PROC_BROWSER_TEST_F(FileManagerJsTest, ActionsSubmenuTest) {
@@ -125,15 +125,15 @@
 }
 
 IN_PROC_BROWSER_TEST_F(FileManagerJsTest, FileTableList) {
-  RunTestURL("foreground/js/ui/file_table_list_unittest_gen.html");
+  RunTestURL("foreground/js/ui/file_table_list_unittest.m_gen.html");
 }
 
 IN_PROC_BROWSER_TEST_F(FileManagerJsTest, FileTableTest) {
-  RunTestURL("foreground/js/ui/file_table_unittest_gen.html");
+  RunTestURL("foreground/js/ui/file_table_unittest.m_gen.html");
 }
 
 IN_PROC_BROWSER_TEST_F(FileManagerJsTest, FileTapHandler) {
-  RunTestURL("foreground/js/ui/file_tap_handler_unittest_gen.html");
+  RunTestURL("foreground/js/ui/file_tap_handler_unittest.m_gen.html");
 }
 
 IN_PROC_BROWSER_TEST_F(FileManagerJsTest, FileTasks) {
@@ -141,7 +141,7 @@
 }
 
 IN_PROC_BROWSER_TEST_F(FileManagerJsTest, FileTransferController) {
-  RunTestURL("foreground/js/file_transfer_controller_unittest_gen.html");
+  RunTestURL("foreground/js/file_transfer_controller_unittest.m_gen.html");
 }
 
 IN_PROC_BROWSER_TEST_F(FileManagerJsTest, FileTypeFiltersController) {
@@ -173,7 +173,7 @@
 }
 
 IN_PROC_BROWSER_TEST_F(FileManagerJsTest, ListThumbnailLoader) {
-  RunTestURL("foreground/js/list_thumbnail_loader_unittest_gen.html");
+  RunTestURL("foreground/js/list_thumbnail_loader_unittest.m_gen.html");
 }
 
 IN_PROC_BROWSER_TEST_F(FileManagerJsTest, LRUCacheTest) {
diff --git a/chrome/browser/chromeos/fileapi/file_change_service_factory.cc b/chrome/browser/chromeos/fileapi/file_change_service_factory.cc
index f1fae927..9e282079 100644
--- a/chrome/browser/chromeos/fileapi/file_change_service_factory.cc
+++ b/chrome/browser/chromeos/fileapi/file_change_service_factory.cc
@@ -5,6 +5,7 @@
 #include "chrome/browser/chromeos/fileapi/file_change_service_factory.h"
 
 #include "chrome/browser/chromeos/fileapi/file_change_service.h"
+#include "chrome/browser/profiles/profile.h"
 #include "components/keyed_service/content/browser_context_dependency_manager.h"
 
 namespace chromeos {
@@ -28,13 +29,24 @@
 
 FileChangeServiceFactory::~FileChangeServiceFactory() = default;
 
-KeyedService* FileChangeServiceFactory::BuildServiceInstanceFor(
+content::BrowserContext* FileChangeServiceFactory::GetBrowserContextToUse(
     content::BrowserContext* context) const {
-  return new FileChangeService();
+  Profile* const profile = Profile::FromBrowserContext(context);
+
+  // Guest sessions are supported and guest OTR profiles are allowed.
+  if (profile->IsGuestSession())
+    return profile;
+
+  // Don't create the service for OTR profiles outside of guest sessions.
+  return profile->IsOffTheRecord() ? nullptr : profile;
 }
 
-bool FileChangeServiceFactory::ServiceIsCreatedWithBrowserContext() const {
-  return true;
+KeyedService* FileChangeServiceFactory::BuildServiceInstanceFor(
+    content::BrowserContext* context) const {
+  Profile* const profile = Profile::FromBrowserContext(context);
+  if (profile->IsOffTheRecord())
+    CHECK(profile->IsGuestSession());
+  return new FileChangeService();
 }
 
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/fileapi/file_change_service_factory.h b/chrome/browser/chromeos/fileapi/file_change_service_factory.h
index df554f7d..98c3752 100644
--- a/chrome/browser/chromeos/fileapi/file_change_service_factory.h
+++ b/chrome/browser/chromeos/fileapi/file_change_service_factory.h
@@ -37,9 +37,10 @@
   ~FileChangeServiceFactory() override;
 
   // BrowserContextKeyedServiceFactory:
+  content::BrowserContext* GetBrowserContextToUse(
+      content::BrowserContext* context) const override;
   KeyedService* BuildServiceInstanceFor(
       content::BrowserContext* context) const override;
-  bool ServiceIsCreatedWithBrowserContext() const override;
 };
 
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/fileapi/file_change_service_unittest.cc b/chrome/browser/chromeos/fileapi/file_change_service_unittest.cc
index 5728f00..db4dd930 100644
--- a/chrome/browser/chromeos/fileapi/file_change_service_unittest.cc
+++ b/chrome/browser/chromeos/fileapi/file_change_service_unittest.cc
@@ -278,6 +278,61 @@
   ASSERT_NE(primary_profile_service, secondary_profile_service);
 }
 
+// Verifies service instances are *not* created for OTR profiles.
+TEST_F(FileChangeServiceTest, DoesntCreateServiceInstanceForOTRProfile) {
+  auto* factory = FileChangeServiceFactory::GetInstance();
+  ASSERT_TRUE(factory);
+
+  // `FileChangeService` should be created for non-OTR profile.
+  auto* profile = GetProfile();
+  ASSERT_TRUE(profile);
+  ASSERT_FALSE(profile->IsOffTheRecord());
+  ASSERT_TRUE(factory->GetService(profile));
+
+  // `FileChangeService` should *not* be created for OTR profile.
+  auto* otr_profile =
+      TestingProfile::Builder().BuildIncognito(profile->AsTestingProfile());
+  ASSERT_TRUE(otr_profile);
+  ASSERT_TRUE(otr_profile->IsOffTheRecord());
+  ASSERT_FALSE(factory->GetService(otr_profile));
+}
+
+// Verifies service instance *are* created for guest OTR profiles.
+TEST_F(FileChangeServiceTest, CreatesServiceInstanceForOTRGuestProfile) {
+  auto* factory = FileChangeServiceFactory::GetInstance();
+  ASSERT_TRUE(factory);
+
+  // Construct a guest profile.
+  TestingProfile::Builder guest_profile_builder;
+  guest_profile_builder.SetGuestSession();
+  guest_profile_builder.SetProfileName("guest_profile");
+  std::unique_ptr<TestingProfile> guest_profile = guest_profile_builder.Build();
+
+  // Service instances should be created for guest profiles.
+  ASSERT_TRUE(guest_profile);
+  ASSERT_FALSE(guest_profile->IsOffTheRecord());
+  FileChangeService* const guest_profile_service =
+      factory->GetService(guest_profile.get());
+  ASSERT_TRUE(guest_profile_service);
+
+  // Construct an OTR profile from `guest_profile`.
+  TestingProfile::Builder otr_guest_profile_builder;
+  otr_guest_profile_builder.SetGuestSession();
+  otr_guest_profile_builder.SetProfileName(guest_profile->GetProfileUserName());
+  Profile* const otr_guest_profile =
+      otr_guest_profile_builder.BuildIncognito(guest_profile.get());
+  ASSERT_TRUE(otr_guest_profile);
+  ASSERT_TRUE(otr_guest_profile->IsOffTheRecord());
+
+  // Service instances *should* be created for OTR guest profiles.
+  FileChangeService* const otr_guest_profile_service =
+      factory->GetService(otr_guest_profile);
+  ASSERT_TRUE(otr_guest_profile_service);
+
+  // OTR service instances should be distinct from non-OTR service instances.
+  ASSERT_NE(otr_guest_profile_service, guest_profile_service);
+}
+
 // Verifies `OnFileCopied` events are propagated to observers.
 TEST_F(FileChangeServiceTest, PropagatesOnFileCopiedEvents) {
   auto* profile = GetProfile();
diff --git a/chrome/browser/chromeos/web_applications/help_app_integration_browsertest.cc b/chrome/browser/chromeos/web_applications/help_app_integration_browsertest.cc
index cfb0dc0..4551825 100644
--- a/chrome/browser/chromeos/web_applications/help_app_integration_browsertest.cc
+++ b/chrome/browser/chromeos/web_applications/help_app_integration_browsertest.cc
@@ -31,12 +31,14 @@
 #include "chrome/test/base/interactive_test_utils.h"
 #include "chrome/test/base/ui_test_utils.h"
 #include "chromeos/components/help_app_ui/url_constants.h"
+#include "chromeos/components/local_search_service/shared_structs.h"
 #include "chromeos/components/web_applications/test/sandboxed_web_ui_test_base.h"
 #include "chromeos/constants/chromeos_features.h"
 #include "chromeos/constants/chromeos_switches.h"
 #include "components/user_manager/user_names.h"
 #include "content/public/test/browser_test.h"
 #include "content/public/test/test_navigation_observer.h"
+#include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/display/screen.h"
 #include "ui/display/types/display_constants.h"
@@ -155,6 +157,75 @@
   EXPECT_EQ(1, user_action_tester.GetActionCount("Discover.Help.TabClicked"));
 }
 
+// Test that the Help App logs metrics on search status.
+IN_PROC_BROWSER_TEST_P(HelpAppIntegrationTest, HelpAppV2SearchStatusMetrics) {
+  WaitForTestSystemAppInstall();
+  content::WebContents* web_contents = LaunchApp(web_app::SystemAppType::HELP);
+
+  base::HistogramTester histogram_tester;
+
+  // Use ExecuteScriptAndExtractBool instead of EvalJsInAppFrame because the
+  // script needs to run in the same world as the page's code.
+  bool are_results_null;
+  EXPECT_TRUE(content::ExecuteScriptAndExtractBool(
+      SandboxedWebUiAppTestBase::GetAppFrame(web_contents), R"(
+        (async () => {
+          const res = await DELEGATE.findInSearchIndex('crome');
+          window.domAutomationController.send(res.results === null);
+        })();
+      )",
+      &are_results_null));
+
+  // Since the index has not initialized (which takes ~3.6 seconds), there
+  // should be no results and one histogram count for kEmptyIndex.
+  EXPECT_TRUE(are_results_null);
+  EXPECT_EQ(1,
+            histogram_tester.GetBucketCount(
+                "Discover.Search.SearchStatus",
+                chromeos::local_search_service::ResponseStatus::kEmptyIndex));
+
+  int no_of_results;
+  EXPECT_TRUE(content::ExecuteScriptAndExtractInt(
+      SandboxedWebUiAppTestBase::GetAppFrame(web_contents), R"(
+        (async () => {
+          await DELEGATE.addOrUpdateSearchIndex([{
+            // Title match. No subheadings.
+            id: 'test-id-1',
+            title: 'verycomplicatedsearchtoken',
+            body: 'Body text',
+            mainCategoryName: 'Help',
+            locale: 'en-US',
+          }]);
+          const res = await DELEGATE.findInSearchIndex(
+              'verycomplicatedsearchtoken');
+          window.domAutomationController.send(res.results.length);
+        })();
+      )",
+      &no_of_results));
+  // Now that the index has been initialized with a test item, there should be
+  // 1 result for the above search, and one histogram count for kSuccess.
+  EXPECT_EQ(1, no_of_results);
+  EXPECT_EQ(1, histogram_tester.GetBucketCount(
+                   "Discover.Search.SearchStatus",
+                   chromeos::local_search_service::ResponseStatus::kSuccess));
+
+  EXPECT_TRUE(content::ExecuteScriptAndExtractBool(
+      SandboxedWebUiAppTestBase::GetAppFrame(web_contents), R"(
+        (async () => {
+          const res = await DELEGATE.findInSearchIndex('');
+          window.domAutomationController.send(res.results === null);
+        })();
+      )",
+      &are_results_null));
+  // Searches with empty queries will return {results: null}. Results should be
+  // null and there should be one histogram count for kEmptyQuery.
+  EXPECT_TRUE(are_results_null);
+  EXPECT_EQ(1,
+            histogram_tester.GetBucketCount(
+                "Discover.Search.SearchStatus",
+                chromeos::local_search_service::ResponseStatus::kEmptyQuery));
+}
+
 IN_PROC_BROWSER_TEST_P(HelpAppAllProfilesIntegrationTest, HelpAppV2ShowHelp) {
   WaitForTestSystemAppInstall();
 
diff --git a/chrome/browser/data_saver/DEPS b/chrome/browser/data_saver/DEPS
new file mode 100644
index 0000000..ea44c768
--- /dev/null
+++ b/chrome/browser/data_saver/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+  "+components/subresource_redirect",
+]
diff --git a/chrome/browser/data_saver/subresource_redirect_login_robots_browsertest.cc b/chrome/browser/data_saver/subresource_redirect_login_robots_browsertest.cc
index ba39cfd..753de0f3 100644
--- a/chrome/browser/data_saver/subresource_redirect_login_robots_browsertest.cc
+++ b/chrome/browser/data_saver/subresource_redirect_login_robots_browsertest.cc
@@ -2,13 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include <map>
 #include <string>
 
-#include "base/files/file_path.h"
-#include "base/files/file_util.h"
-#include "base/notreached.h"
-#include "base/path_service.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
 #include "build/build_config.h"
@@ -19,249 +14,19 @@
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/subresource_redirect/https_image_compression_infobar_decider.h"
 #include "chrome/browser/ui/browser.h"
-#include "chrome/common/chrome_paths.h"
 #include "chrome/test/base/in_process_browser_test.h"
 #include "chrome/test/base/ui_test_utils.h"
-#include "components/data_reduction_proxy/proto/robots_rules.pb.h"
-#include "components/metrics/content/subprocess_metrics_provider.h"
+#include "components/subresource_redirect/subresource_redirect_browser_test_util.h"
+#include "components/subresource_redirect/subresource_redirect_test_util.h"
 #include "content/public/test/browser_test.h"
 #include "content/public/test/browser_test_utils.h"
-#include "net/base/url_util.h"
-#include "net/test/embedded_test_server/http_request.h"
-#include "net/test/embedded_test_server/http_response.h"
+#include "net/http/http_status_code.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/common/features.h"
 #include "url/gurl.h"
 
 namespace subresource_redirect {
 
-const bool kRuleTypeAllow = true;
-const bool kRuleTypeDisallow = false;
-
-// Holds one allow or disallow robots rule
-struct RobotsRule {
-  RobotsRule(bool rule_type, const std::string& pattern)
-      : rule_type(rule_type), pattern(pattern) {}
-
-  bool rule_type;
-  std::string pattern;
-};
-
-// Convert robots rules to its proto.
-std::string GetRobotsRulesProtoString(const std::vector<RobotsRule>& patterns) {
-  proto::RobotsRules robots_rules;
-  for (const auto& pattern : patterns) {
-    auto* new_rule = robots_rules.add_image_ordered_rules();
-    if (pattern.rule_type == kRuleTypeAllow) {
-      new_rule->set_allowed_pattern(pattern.pattern);
-    } else {
-      new_rule->set_disallowed_pattern(pattern.pattern);
-    }
-  }
-  return robots_rules.SerializeAsString();
-}
-
-// Retries fetching |histogram_name| until it contains at least |count| samples.
-void RetryForHistogramUntilCountReached(base::HistogramTester* histogram_tester,
-                                        const std::string& histogram_name,
-                                        size_t count) {
-  while (true) {
-    content::FetchHistogramsFromChildProcesses();
-    metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
-
-    const std::vector<base::Bucket> buckets =
-        histogram_tester->GetAllSamples(histogram_name);
-    size_t total_count = 0;
-    for (const auto& bucket : buckets) {
-      total_count += bucket.count;
-    }
-    if (total_count >= count) {
-      break;
-    }
-  }
-}
-
-// Fetches histograms from renderer child processes.
-void FetchHistogramsFromChildProcesses() {
-  content::FetchHistogramsFromChildProcesses();
-  metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
-}
-
-// Embedded test server for the robots rules
-class RobotsRulesTestServer {
- public:
-  // Different failures modes the robots server should return
-  enum FailureMode {
-    kNone = 0,
-    kLoadshed503RetryAfterResponse,
-    kTimeout,
-  };
-
-  RobotsRulesTestServer() : server_(net::EmbeddedTestServer::TYPE_HTTPS) {}
-
-  bool Start() {
-    server_.ServeFilesFromSourceDirectory("chrome/test/data");
-    server_.RegisterRequestHandler(base::BindRepeating(
-        &RobotsRulesTestServer::OnServerRequest, base::Unretained(this)));
-    server_.RegisterRequestMonitor(base::BindRepeating(
-        &RobotsRulesTestServer::OnRequestMonitor, base::Unretained(this)));
-    return server_.Start();
-  }
-
-  std::string GetURL() const {
-    return server_.GetURL("robotsrules.com", "/").spec();
-  }
-
-  void AddRobotsRules(const GURL& origin,
-                      const std::vector<RobotsRule>& robots_rules) {
-    robots_rules_proto_[origin.spec()] =
-        GetRobotsRulesProtoString(robots_rules);
-  }
-
-  void VerifyRequestedOrigins(const std::set<std::string>& requests) {
-    EXPECT_EQ(received_requests_, requests);
-  }
-
-  void set_failure_mode(FailureMode failure_mode) {
-    failure_mode_ = failure_mode;
-  }
-
- private:
-  std::unique_ptr<net::test_server::HttpResponse> OnServerRequest(
-      const net::test_server::HttpRequest& request) {
-    std::unique_ptr<net::test_server::BasicHttpResponse> response =
-        std::make_unique<net::test_server::BasicHttpResponse>();
-    std::string robots_url_str;
-    EXPECT_EQ("/robots", request.GetURL().path());
-    EXPECT_TRUE(
-        net::GetValueForKeyInQuery(request.GetURL(), "u", &robots_url_str));
-    GURL robots_url(robots_url_str);
-    EXPECT_EQ("/robots.txt", GURL(robots_url).path());
-
-    switch (failure_mode_) {
-      case FailureMode::kLoadshed503RetryAfterResponse:
-        response->set_code(net::HTTP_SERVICE_UNAVAILABLE);
-        response->AddCustomHeader("Retry-After", "5");
-        return response;
-      case FailureMode::kTimeout:
-        response = std::make_unique<net::test_server::DelayedHttpResponse>(
-            base::TimeDelta::FromSeconds(2));
-        break;
-      case FailureMode::kNone:
-        break;
-    }
-
-    auto it = robots_rules_proto_.find(robots_url.GetOrigin().spec());
-    if (it != robots_rules_proto_.end())
-      response->set_content(it->second);
-    return std::move(response);
-  }
-
-  // Called on every robots request
-  void OnRequestMonitor(const net::test_server::HttpRequest& request) {
-    std::string robots_url_str;
-    EXPECT_EQ("/robots", request.GetURL().path());
-    EXPECT_TRUE(
-        net::GetValueForKeyInQuery(request.GetURL(), "u", &robots_url_str));
-    std::string robots_origin = GURL(robots_url_str).GetOrigin().spec();
-    EXPECT_TRUE(received_requests_.find(robots_origin) ==
-                received_requests_.end());
-    received_requests_.insert(robots_origin);
-  }
-
-  // Robots rules proto keyed by origin.
-  std::map<std::string, std::string> robots_rules_proto_;
-
-  // Whether the robots server should return failure.
-  FailureMode failure_mode_ = FailureMode::kNone;
-
-  // All the origins the robots rules are requested for.
-  std::set<std::string> received_requests_;
-
-  net::EmbeddedTestServer server_;
-};
-
-class ImageCompressionTestServer {
- public:
-  // Different failures modes the image server should return
-  enum FailureMode {
-    kNone = 0,
-    kLoadshed503RetryAfterResponse,
-  };
-  ImageCompressionTestServer() : server_(net::EmbeddedTestServer::TYPE_HTTPS) {}
-
-  bool Start() {
-    server_.ServeFilesFromSourceDirectory("chrome/test/data");
-    server_.RegisterRequestHandler(base::BindRepeating(
-        &ImageCompressionTestServer::OnServerRequest, base::Unretained(this)));
-    server_.RegisterRequestMonitor(base::BindRepeating(
-        &ImageCompressionTestServer::OnRequestMonitor, base::Unretained(this)));
-    return server_.Start();
-  }
-
-  std::string GetURL() const {
-    return server_.GetURL("imagecompression.com", "/").spec();
-  }
-
-  void VerifyRequestedImagePaths(const std::set<std::string>& paths) {
-    EXPECT_EQ(received_request_paths_, paths);
-  }
-
-  void set_failure_mode(FailureMode failure_mode) {
-    failure_mode_ = failure_mode;
-  }
-
- private:
-  std::unique_ptr<net::test_server::HttpResponse> OnServerRequest(
-      const net::test_server::HttpRequest& request) {
-    std::unique_ptr<net::test_server::BasicHttpResponse> response =
-        std::make_unique<net::test_server::BasicHttpResponse>();
-
-    switch (failure_mode_) {
-      case FailureMode::kLoadshed503RetryAfterResponse:
-        response->set_code(net::HTTP_SERVICE_UNAVAILABLE);
-        response->AddCustomHeader("Retry-After", "5");
-        return response;
-      case FailureMode::kNone:
-        break;
-    }
-
-    // Serve the correct image file.
-    std::string img_url;
-    std::string file_contents;
-    base::FilePath test_data_directory;
-    EXPECT_EQ("/i", request.GetURL().path());
-    EXPECT_TRUE(net::GetValueForKeyInQuery(request.GetURL(), "u", &img_url));
-    base::PathService::Get(chrome::DIR_TEST_DATA, &test_data_directory);
-    if (base::ReadFileToString(
-            test_data_directory.AppendASCII(GURL(img_url).path().substr(1)),
-            &file_contents)) {
-      response->set_content(file_contents);
-      response->set_code(net::HTTP_OK);
-    }
-    return std::move(response);
-  }
-
-  // Called on every subresource request
-  void OnRequestMonitor(const net::test_server::HttpRequest& request) {
-    std::string img_url;
-    EXPECT_EQ("/i", request.GetURL().path());
-    EXPECT_TRUE(net::GetValueForKeyInQuery(request.GetURL(), "u", &img_url));
-    img_url = GURL(img_url).PathForRequest();
-    EXPECT_TRUE(received_request_paths_.find(img_url) ==
-                received_request_paths_.end());
-    received_request_paths_.insert(img_url);
-  }
-
-  // All the URL paths of the requested images.
-  std::set<std::string> received_request_paths_;
-
-  // Whether the subresource server should return failure.
-  FailureMode failure_mode_ = FailureMode::kNone;
-
-  net::EmbeddedTestServer server_;
-};
-
 class SubresourceRedirectLoginRobotsBrowserTest : public InProcessBrowserTest {
  public:
   explicit SubresourceRedirectLoginRobotsBrowserTest(
diff --git a/chrome/browser/devtools/protocol/cast_handler.cc b/chrome/browser/devtools/protocol/cast_handler.cc
index d023d3d..9ba1e8e7 100644
--- a/chrome/browser/devtools/protocol/cast_handler.cc
+++ b/chrome/browser/devtools/protocol/cast_handler.cc
@@ -7,6 +7,7 @@
 #include <utility>
 
 #include "base/bind.h"
+#include "chrome/browser/media/router/media_router_feature.h"
 #include "chrome/browser/ui/media_router/media_router_ui_helper.h"
 #include "components/media_router/browser/media_router.h"
 #include "components/media_router/browser/media_router_factory.h"
@@ -23,9 +24,15 @@
 
 namespace {
 
+constexpr char kMediaRouterErrorMessage[] =
+    "You must enable the Media Router feature in order to use Cast.";
+
 media_router::MediaRouter* GetMediaRouter(content::WebContents* web_contents) {
-  return media_router::MediaRouterFactory::GetApiForBrowserContext(
-      web_contents->GetBrowserContext());
+  if (media_router::MediaRouterEnabled(web_contents->GetBrowserContext())) {
+    return media_router::MediaRouterFactory::GetApiForBrowserContext(
+        web_contents->GetBrowserContext());
+  }
+  return nullptr;
 }
 
 }  // namespace
@@ -83,7 +90,9 @@
 CastHandler::~CastHandler() = default;
 
 Response CastHandler::SetSinkToUse(const std::string& in_sink_name) {
-  EnsureInitialized();
+  Response init_response = EnsureInitialized();
+  if (!init_response.IsSuccess())
+    return init_response;
   media_router::PresentationServiceDelegateImpl::GetOrCreateForWebContents(
       web_contents_)
       ->set_start_presentation_cb(
@@ -95,7 +104,9 @@
 void CastHandler::StartTabMirroring(
     const std::string& in_sink_name,
     std::unique_ptr<StartTabMirroringCallback> callback) {
-  EnsureInitialized();
+  Response init_response = EnsureInitialized();
+  if (!init_response.IsSuccess())
+    callback->sendFailure(init_response);
   const media_router::MediaSink::Id& sink_id = GetSinkIdByName(in_sink_name);
   if (sink_id.empty()) {
     callback->sendFailure(Response::ServerError("Sink not found"));
@@ -116,7 +127,9 @@
 }
 
 Response CastHandler::StopCasting(const std::string& in_sink_name) {
-  EnsureInitialized();
+  Response init_response = EnsureInitialized();
+  if (!init_response.IsSuccess())
+    return init_response;
   const media_router::MediaSink::Id& sink_id = GetSinkIdByName(in_sink_name);
   if (sink_id.empty())
     return Response::ServerError("Sink not found");
@@ -129,7 +142,9 @@
 }
 
 Response CastHandler::Enable(protocol::Maybe<std::string> in_presentation_url) {
-  EnsureInitialized();
+  Response init_response = EnsureInitialized();
+  if (!init_response.IsSuccess())
+    return init_response;
   StartObservingForSinks(std::move(in_presentation_url));
   return Response::Success();
 }
@@ -152,9 +167,11 @@
 CastHandler::CastHandler(content::WebContents* web_contents)
     : web_contents_(web_contents), router_(GetMediaRouter(web_contents)) {}
 
-void CastHandler::EnsureInitialized() {
+Response CastHandler::EnsureInitialized() {
+  if (!router_)
+    return Response::ServerError(kMediaRouterErrorMessage);
   if (query_result_manager_)
-    return;
+    return Response::Success();
 
   query_result_manager_ =
       std::make_unique<media_router::QueryResultManager>(router_);
@@ -165,6 +182,7 @@
   issues_observer_ = std::make_unique<IssuesObserver>(
       router_,
       base::BindRepeating(&CastHandler::OnIssue, base::Unretained(this)));
+  return Response::Success();
 }
 
 void CastHandler::StartPresentation(
diff --git a/chrome/browser/devtools/protocol/cast_handler.h b/chrome/browser/devtools/protocol/cast_handler.h
index 221017cc..6728fa68 100644
--- a/chrome/browser/devtools/protocol/cast_handler.h
+++ b/chrome/browser/devtools/protocol/cast_handler.h
@@ -55,8 +55,9 @@
   // Constructor that does not wire the handler to a dispatcher. Used in tests.
   explicit CastHandler(content::WebContents* web_contents);
 
-  // Initializes the handler if it hasn't been initialized yet.
-  void EnsureInitialized();
+  // Initializes the handler if it hasn't been initialized yet. Returns
+  // Response::Success() if initialization succeeds, otherwise an error.
+  protocol::Response EnsureInitialized();
 
   void StartPresentation(
       const std::string& sink_name,
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index 8f06224e..ec18c76 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -4249,6 +4249,14 @@
     "expiry_milestone": 90
   },
   {
+    "name": "safe-browsing-real-time-url-lookup-enterprise-ga-endpoint",
+    "owners": [
+      "xinghuilu",
+      "chrome-safebrowsing-core@google.com"
+    ],
+    "expiry_milestone": 92
+  },
+  {
     "name": "safe-browsing-security-section-ui-android",
     "owners": [
       "vakh",
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index 8be99d37..9cbb6deb 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -694,7 +694,8 @@
 const char kDesktopPWAsRunOnOsLoginName[] = "Desktop PWAs run on OS login";
 const char kDesktopPWAsRunOnOsLoginDescription[] =
     "Enable installed PWAs to be configured to automatically start when the OS "
-    "user logs in.";
+    "user logs in. Launching a PWA while the browser is not running is known "
+    "to cause a failure to restore sessions. See https://crbug.com/938759.";
 
 const char kEnableMigrateDefaultChromeAppToWebAppsGSuiteName[] =
     "Migrate default G Suite Chrome apps to web apps";
@@ -2006,6 +2007,13 @@
     "will include an enhanced protection message when users are not in "
     "enhanced protection mode.";
 
+const char kSafeBrowsingRealTimeUrlLookupEnterpriseGaEndpointName[] =
+    "Use the new GA endpoint to perform enterprise real time URL check.";
+
+const char kSafeBrowsingRealTimeUrlLookupEnterpriseGaEndpointDescription[] =
+    "If enabled, the enterprise real time URL check will be sent to the new "
+    "endpoint.";
+
 const char kSafetyTipName[] =
     "Show Safety Tip UI when visiting low-reputation websites";
 const char kSafetyTipDescription[] =
@@ -4215,12 +4223,6 @@
     "Enable an in-process feature to select points onscreen with Switch "
     "Access.";
 
-const char kExperimentalAccessibilityCursorColorsName[] =
-    "Enable cursor colors.";
-const char kExperimentalAccessibilityCursorColorsDescription[] =
-    "Enable experimental feature which allows setting cursor color in "
-    "Accessibility settings.";
-
 const char kMagnifierNewFocusFollowingName[] =
     "Enable new focus following in Magnifier";
 const char kMagnifierNewFocusFollowingDescription[] =
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index 40fbe6c..b5deda5 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -1173,6 +1173,10 @@
 extern const char
     kSafeBrowsingEnhancedProtectionMessageInInterstitialsDescription[];
 
+extern const char kSafeBrowsingRealTimeUrlLookupEnterpriseGaEndpointName[];
+extern const char
+    kSafeBrowsingRealTimeUrlLookupEnterpriseGaEndpointDescription[];
+
 extern const char kSafetyTipName[];
 extern const char kSafetyTipDescription[];
 
@@ -2464,9 +2468,6 @@
 extern const char kSwitchAccessPointScanningName[];
 extern const char kSwitchAccessPointScanningDescription[];
 
-extern const char kExperimentalAccessibilityCursorColorsName[];
-extern const char kExperimentalAccessibilityCursorColorsDescription[];
-
 extern const char kMagnifierNewFocusFollowingName[];
 extern const char kMagnifierNewFocusFollowingDescription[];
 
diff --git a/chrome/browser/lacros/account_manager_facade_factory_lacros.cc b/chrome/browser/lacros/account_manager_facade_factory_lacros.cc
index 3b85004..84d4fcb 100644
--- a/chrome/browser/lacros/account_manager_facade_factory_lacros.cc
+++ b/chrome/browser/lacros/account_manager_facade_factory_lacros.cc
@@ -5,15 +5,15 @@
 #include "chrome/browser/account_manager_facade_factory.h"
 
 #include "base/no_destructor.h"
-#include "chrome/browser/lacros/account_manager_facade_lacros.h"
 #include "chromeos/lacros/lacros_chrome_service_impl.h"
 #include "components/account_manager_core/account_manager_facade.h"
+#include "components/account_manager_core/account_manager_facade_impl.h"
 #include "mojo/public/cpp/bindings/remote.h"
 
 account_manager::AccountManagerFacade* GetAccountManagerFacade(
     const std::string& profile_path) {
   // Multi-Login is disabled with Lacros. Always return the same instance.
-  static base::NoDestructor<AccountManagerFacadeLacros> facade([] {
+  static base::NoDestructor<AccountManagerFacadeImpl> facade([] {
     auto* lacros_chrome_service_impl = chromeos::LacrosChromeServiceImpl::Get();
     DCHECK(lacros_chrome_service_impl);
     if (!lacros_chrome_service_impl->IsAccountManagerAvailable()) {
diff --git a/chrome/browser/lookalikes/lookalike_url_service.cc b/chrome/browser/lookalikes/lookalike_url_service.cc
index 01343e8..6029f73 100644
--- a/chrome/browser/lookalikes/lookalike_url_service.cc
+++ b/chrome/browser/lookalikes/lookalike_url_service.cc
@@ -12,7 +12,6 @@
 #include "base/memory/scoped_refptr.h"
 #include "base/memory/singleton.h"
 #include "base/task/post_task.h"
-#include "base/task/thread_pool.h"
 #include "base/time/default_clock.h"
 #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
 #include "chrome/browser/engagement/site_engagement_service_factory.h"
@@ -74,7 +73,9 @@
 }  // namespace
 
 LookalikeUrlService::LookalikeUrlService(Profile* profile)
-    : profile_(profile), clock_(base::DefaultClock::GetInstance()) {}
+    : profile_(profile),
+      clock_(base::DefaultClock::GetInstance()),
+      update_in_progress_(false) {}
 
 LookalikeUrlService::~LookalikeUrlService() {}
 
@@ -83,30 +84,36 @@
   return LookalikeUrlServiceFactory::GetForProfile(profile);
 }
 
-bool LookalikeUrlService::EngagedSitesNeedUpdating() {
+bool LookalikeUrlService::EngagedSitesNeedUpdating() const {
   if (!last_engagement_fetch_time_.is_null()) {
     const base::TimeDelta elapsed = clock_->Now() - last_engagement_fetch_time_;
-    if (elapsed <
-        base::TimeDelta::FromSeconds(kEngagedSiteUpdateIntervalInSeconds)) {
-      return false;
-    }
+    return (elapsed >=
+            base::TimeDelta::FromSeconds(kEngagedSiteUpdateIntervalInSeconds));
   }
   return true;
 }
 
 void LookalikeUrlService::ForceUpdateEngagedSites(
     EngagedSitesCallback callback) {
-  base::ThreadPool::PostTaskAndReplyWithResult(
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  // Queue an update if necessary.
+  if (!update_in_progress_) {
+    update_in_progress_ = true;
+    base::SequencedTaskRunnerHandle::Get()->PostTask(
+        FROM_HERE,
+        base::BindOnce(
+            &LookalikeUrlService::UpdateEngagedSitesInBackground,
+            weak_factory_.GetWeakPtr(),
+            base::WrapRefCounted(
+                HostContentSettingsMapFactory::GetForProfile(profile_))));
+  }
+
+  // Post the callback to the same sequenced task runner, which will run it
+  // after the update is complete.
+  base::SequencedTaskRunnerHandle::Get()->PostTask(
       FROM_HERE,
-      {base::TaskPriority::USER_BLOCKING,
-       base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
-      base::BindOnce(
-          &site_engagement::SiteEngagementService::GetAllDetailsInBackground,
-          clock_->Now(),
-          base::WrapRefCounted(
-              HostContentSettingsMapFactory::GetForProfile(profile_))),
-      base::BindOnce(&LookalikeUrlService::OnFetchEngagedSites,
-                     weak_factory_.GetWeakPtr(), std::move(callback)));
+      base::BindOnce(std::move(callback), std::cref(engaged_sites_)));
 }
 
 const std::vector<DomainInfo> LookalikeUrlService::GetLatestEngagedSites()
@@ -118,10 +125,21 @@
   clock_ = clock;
 }
 
-void LookalikeUrlService::OnFetchEngagedSites(
-    EngagedSitesCallback callback,
-    std::vector<site_engagement::mojom::SiteEngagementDetails> details) {
-  engaged_sites_.clear();
+void LookalikeUrlService::UpdateEngagedSitesInBackground(
+    scoped_refptr<HostContentSettingsMap> map) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK(update_in_progress_);
+
+  // Bail if another update has occurred since this update was scheduled.
+  if (!EngagedSitesNeedUpdating()) {
+    return;
+  }
+
+  auto details =
+      site_engagement::SiteEngagementService::GetAllDetailsInBackground(
+          clock_->Now(), map);
+
+  std::vector<DomainInfo> new_engaged_sites;
   for (const site_engagement::mojom::SiteEngagementDetails& detail : details) {
     if (!detail.origin.SchemeIsHTTPOrHTTPS()) {
       continue;
@@ -135,8 +153,9 @@
     if (domain_info.domain_and_registry.empty()) {
       continue;
     }
-    engaged_sites_.push_back(domain_info);
+    new_engaged_sites.push_back(domain_info);
   }
+  engaged_sites_.swap(new_engaged_sites);
   last_engagement_fetch_time_ = clock_->Now();
-  std::move(callback).Run(engaged_sites_);
+  update_in_progress_ = false;
 }
diff --git a/chrome/browser/lookalikes/lookalike_url_service.h b/chrome/browser/lookalikes/lookalike_url_service.h
index 7ebf2d9a..f5139e7 100644
--- a/chrome/browser/lookalikes/lookalike_url_service.h
+++ b/chrome/browser/lookalikes/lookalike_url_service.h
@@ -11,10 +11,12 @@
 #include "base/callback_forward.h"
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
+#include "base/sequence_checker.h"
+#include "base/task/thread_pool.h"
 #include "base/time/time.h"
+#include "components/content_settings/core/browser/host_content_settings_map.h"
 #include "components/keyed_service/core/keyed_service.h"
 #include "components/lookalikes/core/lookalike_url_util.h"
-#include "components/site_engagement/core/mojom/site_engagement_details.mojom-forward.h"
 #include "components/url_formatter/url_formatter.h"
 
 class Profile;
@@ -37,11 +39,12 @@
 
   static LookalikeUrlService* Get(Profile* profile);
 
-  // Returns whether the engaged site list is recently updated.
-  bool EngagedSitesNeedUpdating();
+  // Returns whether the engaged site list is recently updated. Returns true
+  // even when an update has already been queued or is in progress.
+  bool EngagedSitesNeedUpdating() const;
 
-  // Triggers an update to the engaged sites list and calls |callback| with the
-  // new list once available.
+  // Triggers an update to the engaged site list if one is not already inflight,
+  // then schedules |callback| to be called with the new list once available.
   void ForceUpdateEngagedSites(EngagedSitesCallback callback);
 
   // Returns the _current_ list of engaged sites, without updating them if
@@ -51,16 +54,22 @@
   void SetClockForTesting(base::Clock* clock);
 
  private:
-  void OnFetchEngagedSites(
-      EngagedSitesCallback callback,
-      std::vector<site_engagement::mojom::SiteEngagementDetails> details);
+  void UpdateEngagedSitesInBackground(
+      scoped_refptr<HostContentSettingsMap> map);
 
   Profile* profile_;
   base::Clock* clock_;
   base::Time last_engagement_fetch_time_;
   std::vector<DomainInfo> engaged_sites_;
-  base::WeakPtrFactory<LookalikeUrlService> weak_factory_{this};
 
+  // Indicates that an update to the engaged sites list has been queued. Serves
+  // to prevent enqueuing excessive updates.
+  bool update_in_progress_;
+  scoped_refptr<base::SequencedTaskRunner> task_runner_;
+
+  SEQUENCE_CHECKER(sequence_checker_);
+
+  base::WeakPtrFactory<LookalikeUrlService> weak_factory_{this};
   DISALLOW_COPY_AND_ASSIGN(LookalikeUrlService);
 };
 
diff --git a/chrome/browser/media/router/chrome_media_router_factory.cc b/chrome/browser/media/router/chrome_media_router_factory.cc
index f2ef8c6..b38c3de 100644
--- a/chrome/browser/media/router/chrome_media_router_factory.cc
+++ b/chrome/browser/media/router/chrome_media_router_factory.cc
@@ -5,6 +5,7 @@
 #include "chrome/browser/media/router/chrome_media_router_factory.h"
 
 #include "build/build_config.h"
+#include "chrome/browser/media/router/media_router_feature.h"
 #include "chrome/browser/profiles/incognito_helpers.h"
 #include "components/keyed_service/content/browser_context_dependency_manager.h"
 #include "components/media_router/browser/media_router_dialog_controller.h"
@@ -70,6 +71,10 @@
 
 KeyedService* ChromeMediaRouterFactory::BuildServiceInstanceFor(
     BrowserContext* context) const {
+  if (!MediaRouterEnabled(context)) {
+    NOTREACHED();
+    return nullptr;
+  }
   MediaRouterBase* media_router = nullptr;
 #if defined(OS_ANDROID)
   media_router = new MediaRouterAndroid();
diff --git a/chrome/browser/net/profile_network_context_service.cc b/chrome/browser/net/profile_network_context_service.cc
index 8c9945e..0ccb745 100644
--- a/chrome/browser/net/profile_network_context_service.cc
+++ b/chrome/browser/net/profile_network_context_service.cc
@@ -97,7 +97,7 @@
 #include "extensions/common/constants.h"
 #endif
 
-#if BUILDFLAG(IS_LACROS)
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
 #include "chrome/browser/lacros/cert_db_initializer_factory.h"
 #include "chrome/browser/lacros/client_cert_store_lacros.h"
 #endif
@@ -584,7 +584,7 @@
       std::make_unique<net::ClientCertStoreNSS>(
           base::BindRepeating(&CreateCryptoModuleBlockingPasswordDelegate,
                               kCryptoModulePasswordClientAuth));
-#if BUILDFLAG(IS_LACROS)
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
   CertDbInitializer* cert_db_initializer =
       CertDbInitializerFactory::GetForProfileIfExists(profile_);
   if (!cert_db_initializer || !profile_->IsMainProfile()) {
@@ -595,7 +595,7 @@
 
   store = std::make_unique<ClientCertStoreLacros>(cert_db_initializer,
                                                   std::move(store));
-#endif  // BUILDFLAG(IS_LACROS)
+#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
 
   return store;
 #elif defined(OS_WIN)
diff --git a/chrome/browser/renderer_context_menu/render_view_context_menu.cc b/chrome/browser/renderer_context_menu/render_view_context_menu.cc
index 0542492..90666b52 100644
--- a/chrome/browser/renderer_context_menu/render_view_context_menu.cc
+++ b/chrome/browser/renderer_context_menu/render_view_context_menu.cc
@@ -2501,7 +2501,8 @@
         source_type = ui::MENU_SOURCE_KEYBOARD;
 
       ash::ClipboardHistoryController::Get()->ShowMenu(
-          gfx::Rect(anchor_point_in_screen, gfx::Size()), source_type);
+          gfx::Rect(anchor_point_in_screen, gfx::Size()), source_type,
+          ash::ClipboardHistoryController::ShowSource::kRenderViewContextMenu);
 #else
       NOTREACHED();
 #endif
diff --git a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_feature_item.html b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_feature_item.html
index 84ded70..af7245a 100644
--- a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_feature_item.html
+++ b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_feature_item.html
@@ -45,10 +45,11 @@
                feature, pageContentData, subpageRoute)]]"
            on-click="handleItemClick_">
         <slot name="icon">
-          <iron-icon id="feature-icon" icon="[[getIconName(feature)]]">
+          <iron-icon id="feature-icon" icon="[[getIconName(feature)]]"
+              aria-hidden="true">
           </iron-icon>
         </slot>
-        <div id="item-text-container" class="middle">
+        <div id="item-text-container" class="middle" aria-hidden="true">
           <div id="featureName">[[getFeatureName(feature)]]</div>
           <slot name="feature-summary">
             <settings-localized-link
@@ -68,8 +69,11 @@
           </cr-icon-button>
         </template>
         <template is="dom-if" if="[[infoTooltip]]" restamp>
-          <iron-icon id="help-icon" icon="cr:help-outline"></iron-icon>
-          <paper-tooltip for="help-icon" position="top" fit-to-visible-bounds>
+          <iron-icon id="help-icon" tabindex="0" icon="cr:help-outline"
+              aria-labelledby="tooltip">
+          </iron-icon>
+          <paper-tooltip id="tooltip" for="help-icon" position="top"
+              aria-hidden="true" fit-to-visible-bounds>
             [[infoTooltip]]
           </paper-tooltip>
         </template>
@@ -86,12 +90,19 @@
           restamp>
         <cr-policy-indicator indicator-type="userPolicy"></cr-policy-indicator>
       </template>
-      <div class="margin-matches-padding">
+      <div class="margin-matches-padding" aria-labelledby="featureName"
+          aria-describedby="featureSecondary">
+        <!-- The aria-labelledby and aria-describedby are used by custom slotted
+        content, without which ChromeVox will not announce the #featureName
+        or #featureSummary. Note that the default slotted content still needs
+        their own aria-labelledby and aria-describedby attributes. -->
         <slot name="feature-controller">
           <!-- This settings-multidevice-feature-toggle is the default
           controller. If an element with slot="feature-controller" is attached,
           it will replace this one. -->
           <settings-multidevice-feature-toggle
+              aria-labelledby="featureName"
+              aria-describedby="featureSecondary"
               feature="[[feature]]"
               page-content-data="[[pageContentData]]">
           </settings-multidevice-feature-toggle>
diff --git a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_subpage.html b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_subpage.html
index 5ab03a8..8ac44f2 100644
--- a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_subpage.html
+++ b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_subpage.html
@@ -152,17 +152,18 @@
       </div>
     </template>
     <div class="settings-box two-line">
-        <div id="forget-device-label" class="start">
-            $i18n{multideviceForgetDevice}
-            <div class="secondary">
-                $i18n{multideviceForgetDeviceSummary}
-            </div>
+      <div id="forgetDeviceLabel" class="start" aria-hidden="true">
+        $i18n{multideviceForgetDevice}
+        <div id="forgetDeviceSummary" class="secondary" aria-hidden="true">
+            $i18n{multideviceForgetDeviceSummary}
         </div>
-        <cr-button on-click="handleForgetDeviceClick_"
+      </div>
+      <cr-button on-click="handleForgetDeviceClick_"
           aria-labelledby="forgetDeviceLabel"
+          aria-describedby="forgetDeviceSummary"
           deep-link-focus-id$="[[Setting.kForgetPhone]]">
-           $i18n{multideviceForgetDeviceDisconnect}
-        </cr-button>
+        $i18n{multideviceForgetDeviceDisconnect}
+      </cr-button>
     </div>
     <cr-dialog id="forgetDeviceDialog">
       <div slot="title">$i18n{multideviceForgetDevice}</div>
diff --git a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_tether_item.html b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_tether_item.html
index 9727517..fec27f66 100644
--- a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_tether_item.html
+++ b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_tether_item.html
@@ -21,6 +21,7 @@
         subpage-route-url-search-params=
             "[[getTetherNetworkUrlSearchParams_()]]">
       <network-icon slot="icon"
+          aria-hidden="true"
           show-technology-badge="[[showTechnologyBadge_]]"
           network-state="[[activeNetworkState_]]"
           device-state="[[deviceState_]]">
diff --git a/chrome/browser/resources/settings/chromeos/os_a11y_page/manage_a11y_page.html b/chrome/browser/resources/settings/chromeos/os_a11y_page/manage_a11y_page.html
index 8cffc49..f8f0c68 100644
--- a/chrome/browser/resources/settings/chromeos/os_a11y_page/manage_a11y_page.html
+++ b/chrome/browser/resources/settings/chromeos/os_a11y_page/manage_a11y_page.html
@@ -272,19 +272,17 @@
           label-max="$i18n{largeMouseCursorSizeLargeLabel}">
       </settings-slider>
     </div>
-    <template is="dom-if" if="[[shouldShowExperimentalCursorColor_]]">
-      <div class="settings-box">
-        <div class="start settings-box-text" id="cursorColorOptionsLabel">
-          $i18n{cursorColorOptionsLabel}
-        </div>
-        <settings-dropdown-menu aria-labeledby="cursorColorOptionsLabel"
-            pref="{{prefs.settings.a11y.cursor_color}}"
-            menu-options="[[cursorColorOptions_]]"
-            on-settings-control-change="onA11yCursorColorChange_"
-            deep-link-focus-id$="[[Setting.kEnableCursorColor]]">
-        </settings-dropdown-menu>
+    <div class="settings-box">
+      <div class="start settings-box-text" id="cursorColorOptionsLabel">
+        $i18n{cursorColorOptionsLabel}
       </div>
-    </template>
+      <settings-dropdown-menu aria-labeledby="cursorColorOptionsLabel"
+          pref="{{prefs.settings.a11y.cursor_color}}"
+          menu-options="[[cursorColorOptions_]]"
+          on-settings-control-change="onA11yCursorColorChange_"
+          deep-link-focus-id$="[[Setting.kEnableCursorColor]]">
+      </settings-dropdown-menu>
+    </div>
     <settings-toggle-button
         class="hr"
         pref="{{prefs.settings.a11y.cursor_highlight}}"
diff --git a/chrome/browser/resources/settings/chromeos/os_a11y_page/manage_a11y_page.js b/chrome/browser/resources/settings/chromeos/os_a11y_page/manage_a11y_page.js
index eac72c35..b5cebcc3 100644
--- a/chrome/browser/resources/settings/chromeos/os_a11y_page/manage_a11y_page.js
+++ b/chrome/browser/resources/settings/chromeos/os_a11y_page/manage_a11y_page.js
@@ -150,15 +150,6 @@
     },
 
     /** @private */
-    shouldShowExperimentalCursorColor_: {
-      type: Boolean,
-      value() {
-        return loadTimeData.getBoolean(
-            'showExperimentalAccessibilityCursorColor');
-      },
-    },
-
-    /** @private */
     isMagnifierPanningImprovementsEnabled_: {
       type: Boolean,
       value() {
diff --git a/chrome/browser/resources/settings/site_settings/chooser_exception_list.js b/chrome/browser/resources/settings/site_settings/chooser_exception_list.js
index 89328e6..bbab832 100644
--- a/chrome/browser/resources/settings/site_settings/chooser_exception_list.js
+++ b/chrome/browser/resources/settings/site_settings/chooser_exception_list.js
@@ -197,7 +197,7 @@
 
     if (!this.updateList(
             'chooserExceptions', x => x.displayName, exceptions,
-            true /* uidBasedUpdate */)) {
+            true /* identityBasedUpdate= */)) {
       // The chooser objects have not been changed, so check if their site
       // permissions have changed. The |exceptions| and |this.chooserExceptions|
       // arrays should be the same length.
diff --git a/chrome/browser/resources/tab_search/BUILD.gn b/chrome/browser/resources/tab_search/BUILD.gn
index a32940b..ab330aa 100644
--- a/chrome/browser/resources/tab_search/BUILD.gn
+++ b/chrome/browser/resources/tab_search/BUILD.gn
@@ -182,6 +182,7 @@
   deps = [
     "//third_party/polymer/v3_0/components-chromium/iron-selector:iron-selector",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
+    "//ui/webui/resources/js:list_property_update_behavior.m",
   ]
 }
 
diff --git a/chrome/browser/resources/tab_search/app.html b/chrome/browser/resources/tab_search/app.html
index c054c21..78cc37b 100644
--- a/chrome/browser/resources/tab_search/app.html
+++ b/chrome/browser/resources/tab_search/app.html
@@ -58,7 +58,7 @@
     search-result-text="[[searchResultText_]]">
 </tab-search-search-field>
 <div hidden="[[!filteredOpenTabs_.length]]">
-  <infinite-list id="tabsList" items="[[filteredOpenTabs_]]" >
+  <infinite-list id="tabsList" items="[[filteredOpenTabs_]]">
     <template is="dom-repeat">
       <tab-search-item id="[[item.tab.tabId]]" aria-label="[[ariaLabel_(item)]]"
           class="mwb-list-item" data="[[item]]" on-click="onItemClick_"
diff --git a/chrome/browser/resources/tab_search/app.js b/chrome/browser/resources/tab_search/app.js
index 6f154ac1..3d28fb4 100644
--- a/chrome/browser/resources/tab_search/app.js
+++ b/chrome/browser/resources/tab_search/app.js
@@ -40,10 +40,9 @@
         value: '',
       },
 
-      /** @private {?Array<!WindowTabs>} */
+      /** @private {?Array<!TabData>}*/
       openTabs_: {
         type: Array,
-        observer: 'openTabsChanged_',
       },
 
       /** @private {!Array<!TabData>} */
@@ -169,7 +168,7 @@
           'Tabs.TabSearch.WebUI.TabListDataReceived',
           Math.round(Date.now() - getTabsStartTimestamp));
 
-      this.openTabs_ = profileTabs.windows;
+      this.openTabsChanged_(profileTabs.windows);
     });
   }
 
@@ -178,19 +177,13 @@
    * @private
    */
   onTabUpdated_(updatedTab) {
-    const updatedTabId = updatedTab.tabId;
-    const windows = this.openTabs_;
-    if (windows) {
-      for (const window of windows) {
-        const {tabs} = window;
-        for (let i = 0; i < tabs.length; ++i) {
-          // Replace the tab with the same tabId and trigger rerender.
-          if (tabs[i].tabId === updatedTabId) {
-            tabs[i] = updatedTab;
-            this.openTabs_ = windows.concat();
-            return;
-          }
-        }
+    // Replace the tab with the same tabId and trigger rerender.
+    for (let i = 0; i < this.openTabs_.length; ++i) {
+      if (this.openTabs_[i].tab.tabId === updatedTab.tabId) {
+        this.openTabs_[i] =
+            this.tabData_(updatedTab, this.openTabs_[i].inActiveWindow);
+        this.updateFilteredTabs_(this.openTabs_);
+        return;
       }
     }
   }
@@ -200,14 +193,21 @@
    * @private
    */
   onTabsRemoved_(tabIds) {
-    const windows = this.openTabs_;
-    if (windows) {
-      const ids = new Set(tabIds);
-      for (const window of windows) {
-        window.tabs = window.tabs.filter(tab => (!ids.has(tab.tabId)));
-      }
-      this.openTabs_ = windows.concat();
+    if (!this.openTabs_) {
+      return;
     }
+
+    const ids = new Set(tabIds);
+    // Splicing in descending index order to avoid affecting preceding indices
+    // that are to be removed.
+    for (let i = this.openTabs_.length - 1; i >= 0; i--) {
+      if (ids.has(this.openTabs_[i].tab.tabId)) {
+        this.openTabs_.splice(i, 1);
+      }
+    }
+
+    this.filteredOpenTabs_ =
+        this.filteredOpenTabs_.filter(tabData => !ids.has(tabData.tab.tabId));
   }
 
   /**
@@ -304,11 +304,17 @@
   }
 
   /**
-   * @param {!Array<!WindowTabs>} newOpenTabs
+   * @param {!Array<!WindowTabs>} newOpenWindowTabs
    * @private
    */
-  openTabsChanged_(newOpenTabs) {
-    this.updateFilteredTabs_(newOpenTabs);
+  openTabsChanged_(newOpenWindowTabs) {
+    this.openTabs_ = [];
+    newOpenWindowTabs.forEach(({active, tabs}) => {
+      tabs.forEach(tab => {
+        this.openTabs_.push(this.tabData_(tab, active));
+      });
+    });
+    this.updateFilteredTabs_(this.openTabs_);
 
     // If there was no previously selected index, set the first item as
     // selected; else retain the currently selected index. If the list
@@ -415,19 +421,22 @@
   }
 
   /**
-   * @param {!Array<!WindowTabs>} windowTabs
+   * @param {!Tab} tab
+   * @param {boolean} inActiveWindow
+   * @return {!TabData}
    * @private
    */
-  updateFilteredTabs_(windowTabs) {
-    const result = [];
-    windowTabs.forEach(window => {
-      window.tabs.forEach(tab => {
-        const hostname = new URL(tab.url).hostname;
-        const inActiveWindow = window.active;
-        result.push({hostname, inActiveWindow, tab});
-      });
-    });
-    result.sort((a, b) => {
+  tabData_(tab, inActiveWindow) {
+    const hostname = new URL(tab.url).hostname;
+    return /** @type {!TabData} */ ({hostname, inActiveWindow, tab});
+  }
+
+  /**
+   * @param {!Array<!TabData>} tabs
+   * @private
+   */
+  updateFilteredTabs_(tabs) {
+    tabs.sort((a, b) => {
       // Move the active tab to the bottom of the list
       // because it's not likely users want to click on it.
       if (this.moveActiveTabToBottom_) {
@@ -444,8 +453,9 @@
               a.tab.lastActiveTimeTicks.internalValue) :
           0;
     });
+
     this.filteredOpenTabs_ =
-        fuzzySearch(this.searchText_, result, this.fuzzySearchOptions_);
+        fuzzySearch(this.searchText_, tabs, this.fuzzySearchOptions_);
     this.searchResultText_ = this.getA11ySearchResultText_();
   }
 
diff --git a/chrome/browser/resources/tab_search/fuzzy_search.js b/chrome/browser/resources/tab_search/fuzzy_search.js
index 88f08205..b2a6950 100644
--- a/chrome/browser/resources/tab_search/fuzzy_search.js
+++ b/chrome/browser/resources/tab_search/fuzzy_search.js
@@ -11,11 +11,12 @@
  * @param {string} input
  * @param {!Array<!TabData>} records
  * @param {!Object} options
- * @return {!Array<!TabData>}
+ * @return {!Array<!TabData>} A new array of entries satisfying the input. If no
+ *     search input is present, returns a shallow copy of the records.
  */
 export function fuzzySearch(input, records, options) {
   if (input.length === 0) {
-    return records;
+    return [...records];
   }
   // Fuse does not handle exact match searches well. It indiscriminately
   // searches for direct matches that appear anywhere in the string. This
diff --git a/chrome/browser/resources/tab_search/infinite_list.html b/chrome/browser/resources/tab_search/infinite_list.html
index 2778c25bc..55b5dfe2 100644
--- a/chrome/browser/resources/tab_search/infinite_list.html
+++ b/chrome/browser/resources/tab_search/infinite_list.html
@@ -9,7 +9,6 @@
 </style>
 <div id="items">
   <iron-selector id="selector" on-keydown="onKeyDown_"
-      on-iron-items-changed="updateScrollerSize_"
       on-iron-select="onSelectedChanged_" role="listbox"
       selected-class="selected">
     <slot></slot>
diff --git a/chrome/browser/resources/tab_search/infinite_list.js b/chrome/browser/resources/tab_search/infinite_list.js
index 96e232d..d6f6edb0 100644
--- a/chrome/browser/resources/tab_search/infinite_list.js
+++ b/chrome/browser/resources/tab_search/infinite_list.js
@@ -20,6 +20,7 @@
 import 'chrome://resources/polymer/v3_0/iron-selector/iron-selector.js';
 
 import {assert, assertInstanceof} from 'chrome://resources/js/assert.m.js';
+import {updateListProperty} from 'chrome://resources/js/list_property_update_behavior.m.js';
 import {listenOnce} from 'chrome://resources/js/util.m.js';
 import {afterNextRender, DomRepeat, html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
@@ -92,9 +93,9 @@
 
   /** @private */
   getDomItems_() {
-    const selectorChildren = this.$.selector.children;
+    const selector = /** @type {!IronSelectorElement} */ (this.$.selector);
     return Array.prototype.slice.call(
-        selectorChildren, 0, selectorChildren.length - 1);
+        selector.children, 0, selector.children.length - 1);
   }
 
   /**
@@ -155,6 +156,7 @@
       if (aboveScrollTopItemCount + this.chunkItemThreshold >
           this.domRepeat_.items.length) {
         this.ensureDomItemsAvailableStartingAt_(aboveScrollTopItemCount);
+        this.updateScrollerSize_();
       }
     }
   }
@@ -189,12 +191,13 @@
    * @private
    */
   domItemAverageHeight_() {
-    if (!this.$.selector.items || this.$.selector.items.length === 0) {
+    const selector = /** @type {!IronSelectorElement} */ (this.$.selector);
+    if (!selector.items || selector.items.length === 0) {
       return 0;
     }
 
-    const domItemCount = this.$.selector.items.length;
-    const lastDomItem = this.$.selector.items[domItemCount - 1];
+    const domItemCount = selector.items.length;
+    const lastDomItem = selector.items[domItemCount - 1];
     return (lastDomItem.offsetTop + lastDomItem.offsetHeight) / domItemCount;
   }
 
@@ -202,17 +205,36 @@
    * Ensures that when the items property changes, only a chunk of the items
    * needed to fill the current scroll position view are added to the DOM, thus
    * improving rendering performance.
+   *
+   * @param {!Array} newItems
+   * @param {!Array} oldItems
    * @private
    */
-  onItemsChanged_() {
-    if (this.domRepeat_ && this.items) {
-      const domItemAvgHeight = this.domItemAverageHeight_();
-      const aboveScrollTopItemCount = domItemAvgHeight !== 0 ?
-          Math.round(this.scrollTop / domItemAvgHeight) :
-          0;
+  onItemsChanged_(newItems, oldItems) {
+    if (!this.domRepeat_) {
+      return;
+    }
 
+    if (!oldItems || oldItems.length === 0) {
       this.domRepeat_.set('items', []);
-      this.ensureDomItemsAvailableStartingAt_(aboveScrollTopItemCount);
+      this.ensureDomItemsAvailableStartingAt_(0);
+      listenOnce(this.$.selector, 'iron-items-changed', () => {
+        this.updateScrollerSize_();
+      });
+
+      return;
+    }
+
+    updateListProperty(
+        this.domRepeat_, 'items', tabData => tabData,
+        newItems.slice(
+            0,
+            Math.min(
+                Math.max(this.domRepeat_.items.length, this.chunkItemCount),
+                newItems.length)),
+        true /* identityBasedUpdate= */);
+
+    if (newItems.length !== oldItems.length) {
       this.updateScrollerSize_();
     }
   }
diff --git a/chrome/browser/safe_browsing/chrome_enterprise_url_lookup_service.cc b/chrome/browser/safe_browsing/chrome_enterprise_url_lookup_service.cc
index 4b07c4b..15206c12 100644
--- a/chrome/browser/safe_browsing/chrome_enterprise_url_lookup_service.cc
+++ b/chrome/browser/safe_browsing/chrome_enterprise_url_lookup_service.cc
@@ -85,6 +85,17 @@
   return GetDMToken().value();
 }
 
+GURL ChromeEnterpriseRealTimeUrlLookupService::GetRealTimeLookupUrl() const {
+  bool is_ga_endpoint_enabled =
+      base::FeatureList::IsEnabled(kRealTimeUrlLookupEnterpriseGaEndpoint);
+  std::string endpoint = is_ga_endpoint_enabled
+                             ? "https://enterprise-safebrowsing.googleapis.com/"
+                               "safebrowsing/clientreport/realtime"
+                             : "https://safebrowsing.google.com/safebrowsing/"
+                               "clientreport/realtime";
+  return GURL(endpoint);
+}
+
 net::NetworkTrafficAnnotationTag
 ChromeEnterpriseRealTimeUrlLookupService::GetTrafficAnnotationTag() const {
   // Safe Browsing Zwieback cookies are not sent for enterprise users, because
diff --git a/chrome/browser/safe_browsing/chrome_enterprise_url_lookup_service.h b/chrome/browser/safe_browsing/chrome_enterprise_url_lookup_service.h
index 0f86df6c..23c6bcb 100644
--- a/chrome/browser/safe_browsing/chrome_enterprise_url_lookup_service.h
+++ b/chrome/browser/safe_browsing/chrome_enterprise_url_lookup_service.h
@@ -59,6 +59,7 @@
 
  private:
   // RealTimeUrlLookupServiceBase:
+  GURL GetRealTimeLookupUrl() const override;
   net::NetworkTrafficAnnotationTag GetTrafficAnnotationTag() const override;
   bool CanPerformFullURLLookupWithToken() const override;
   void GetAccessToken(const GURL& url,
diff --git a/chrome/browser/subresource_redirect/DEPS b/chrome/browser/subresource_redirect/DEPS
new file mode 100644
index 0000000..8665069
--- /dev/null
+++ b/chrome/browser/subresource_redirect/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+  "+components/subresource_redirect/common",
+]
diff --git a/chrome/browser/subresource_redirect/subresource_redirect_observer.cc b/chrome/browser/subresource_redirect/subresource_redirect_observer.cc
index f3aa2e7c..27a00bc 100644
--- a/chrome/browser/subresource_redirect/subresource_redirect_observer.cc
+++ b/chrome/browser/subresource_redirect/subresource_redirect_observer.cc
@@ -14,6 +14,7 @@
 #include "chrome/browser/subresource_redirect/subresource_redirect_util.h"
 #include "components/data_reduction_proxy/core/browser/data_reduction_proxy_settings.h"
 #include "components/optimization_guide/proto/public_image_metadata.pb.h"
+#include "components/subresource_redirect/common/subresource_redirect_features.h"
 #include "content/public/browser/navigation_handle.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/render_process_host.h"
diff --git a/chrome/browser/subresource_redirect/subresource_redirect_util.cc b/chrome/browser/subresource_redirect/subresource_redirect_util.cc
index 2b07407..fe3d9c2 100644
--- a/chrome/browser/subresource_redirect/subresource_redirect_util.cc
+++ b/chrome/browser/subresource_redirect/subresource_redirect_util.cc
@@ -12,6 +12,7 @@
 #include "chrome/browser/subresource_redirect/litepages_service_bypass_decider.h"
 #include "chrome/browser/subresource_redirect/origin_robots_rules_cache.h"
 #include "components/data_reduction_proxy/core/browser/data_reduction_proxy_settings.h"
+#include "components/subresource_redirect/common/subresource_redirect_features.h"
 #include "content/public/browser/web_contents.h"
 #include "net/base/escape.h"
 #include "third_party/blink/public/common/features.h"
@@ -24,10 +25,6 @@
 
 namespace {
 
-bool IsSubresourceRedirectEnabled() {
-  return base::FeatureList::IsEnabled(blink::features::kSubresourceRedirect);
-}
-
 DataReductionProxyChromeSettings* GetDataReductionProxyChromeSettings(
     content::WebContents* web_contents) {
   DCHECK(base::FeatureList::IsEnabled(blink::features::kSubresourceRedirect));
@@ -55,38 +52,6 @@
          data_reduction_proxy_settings->IsDataReductionProxyEnabled();
 }
 
-bool ShouldEnablePublicImageHintsBasedCompression() {
-  bool is_enabled = IsSubresourceRedirectEnabled() &&
-                    base::GetFieldTrialParamByFeatureAsBool(
-                        blink::features::kSubresourceRedirect,
-                        "enable_public_image_hints_based_compression", true);
-  // Only one of the public image hints or login and robots based image
-  // compression should be active.
-  DCHECK(!is_enabled || !ShouldEnableLoginRobotsCheckedCompression());
-  return is_enabled;
-}
-
-bool ShouldEnableLoginRobotsCheckedCompression() {
-  bool is_enabled = IsSubresourceRedirectEnabled() &&
-                    base::GetFieldTrialParamByFeatureAsBool(
-                        blink::features::kSubresourceRedirect,
-                        "enable_login_robots_based_compression", false);
-  // Only one of the public image hints or login and robots based image
-  // compression should be active.
-  DCHECK(!is_enabled || !ShouldEnablePublicImageHintsBasedCompression());
-  return is_enabled;
-}
-
-// Should the subresource be redirected to its compressed version. This returns
-// false if only coverage metrics need to be recorded and actual redirection
-// should not happen.
-bool ShouldCompressRedirectSubresource() {
-  return base::FeatureList::IsEnabled(blink::features::kSubresourceRedirect) &&
-         base::GetFieldTrialParamByFeatureAsBool(
-             blink::features::kSubresourceRedirect,
-             "enable_subresource_server_redirect", true);
-}
-
 bool ShowInfoBarAndGetImageCompressionState(
     content::WebContents* web_contents,
     content::NavigationHandle* navigation_handle) {
diff --git a/chrome/browser/subresource_redirect/subresource_redirect_util.h b/chrome/browser/subresource_redirect/subresource_redirect_util.h
index 73ebeb1..6aa798d 100644
--- a/chrome/browser/subresource_redirect/subresource_redirect_util.h
+++ b/chrome/browser/subresource_redirect/subresource_redirect_util.h
@@ -23,19 +23,6 @@
 // |web_contents|.
 bool IsLiteModeEnabled(content::WebContents* web_contents);
 
-// Returns if the public image hints based subresource compression is enabled.
-bool ShouldEnablePublicImageHintsBasedCompression();
-
-// Returns if the login and robots checks based subresource compression is
-// enabled. This compresses non logged-in pages and subresources allowed by
-// robots.txt rules.
-bool ShouldEnableLoginRobotsCheckedCompression();
-
-// Should the subresource be redirected to its compressed version. This returns
-// false if only coverage metrics need to be recorded and actual redirection
-// should not happen.
-bool ShouldCompressRedirectSubresource();
-
 // Returns whether image compression should be applied for this web_contents.
 // Also shows an one-time InfoBar on Android if needed.
 bool ShowInfoBarAndGetImageCompressionState(
diff --git a/chrome/browser/ui/app_list/app_service/app_service_app_icon_loader.cc b/chrome/browser/ui/app_list/app_service/app_service_app_icon_loader.cc
index f64124d..9e3ab1d 100644
--- a/chrome/browser/ui/app_list/app_service/app_service_app_icon_loader.cc
+++ b/chrome/browser/ui/app_list/app_service/app_service_app_icon_loader.cc
@@ -32,6 +32,33 @@
 
 }  // namespace
 
+// static
+bool AppServiceAppIconLoader::CanLoadImage(Profile* profile,
+                                           const std::string& id) {
+  const std::string app_id = GetAppId(profile, id);
+
+  // Skip the ARC intent helper, the system Android app that proxies links to
+  // Chrome, which should be hidden.
+  if (app_id == kArcIntentHelperAppId) {
+    return false;
+  }
+
+  if (!apps::AppServiceProxyFactory::IsAppServiceAvailableForProfile(profile)) {
+    return false;
+  }
+
+  // Support icon loading for apps registered in AppService or Crostini apps
+  // with the prefix "crostini:".
+  if (apps::AppServiceProxyFactory::GetForProfile(profile)
+              ->AppRegistryCache()
+              .GetAppType(app_id) != apps::mojom::AppType::kUnknown ||
+      crostini::IsUnmatchedCrostiniShelfAppId(app_id)) {
+    return true;
+  }
+
+  return false;
+}
+
 AppServiceAppIconLoader::AppServiceAppIconLoader(
     Profile* profile,
     int resource_size_in_dip,
@@ -45,26 +72,7 @@
 AppServiceAppIconLoader::~AppServiceAppIconLoader() = default;
 
 bool AppServiceAppIconLoader::CanLoadImageForApp(const std::string& id) {
-  const std::string app_id = GetAppId(profile(), id);
-
-  // Skip the ARC intent helper, the system Android app that proxies links to
-  // Chrome, which should be hidden.
-  if (app_id == kArcIntentHelperAppId) {
-    return false;
-  }
-
-  apps::AppServiceProxy* proxy =
-      apps::AppServiceProxyFactory::GetForProfile(profile());
-
-  // Support icon loading for apps registered in AppService or Crostini apps
-  // with the prefix "crostini:".
-  if (proxy->AppRegistryCache().GetAppType(app_id) !=
-          apps::mojom::AppType::kUnknown ||
-      crostini::IsUnmatchedCrostiniShelfAppId(app_id)) {
-    return true;
-  }
-
-  return false;
+  return AppServiceAppIconLoader::CanLoadImage(profile(), id);
 }
 
 void AppServiceAppIconLoader::FetchImage(const std::string& id) {
diff --git a/chrome/browser/ui/app_list/app_service/app_service_app_icon_loader.h b/chrome/browser/ui/app_list/app_service/app_service_app_icon_loader.h
index 7996b07..9d8f75a 100644
--- a/chrome/browser/ui/app_list/app_service/app_service_app_icon_loader.h
+++ b/chrome/browser/ui/app_list/app_service/app_service_app_icon_loader.h
@@ -22,6 +22,8 @@
 class AppServiceAppIconLoader : public AppIconLoader,
                                 private apps::AppRegistryCache::Observer {
  public:
+  static bool CanLoadImage(Profile* profile, const std::string& id);
+
   AppServiceAppIconLoader(Profile* profile,
                           int resource_size_in_dip,
                           AppIconLoaderDelegate* delegate);
diff --git a/chrome/browser/ui/app_list/search/files/drive_zero_state_provider.cc b/chrome/browser/ui/app_list/search/files/drive_zero_state_provider.cc
index 9ac064f..47a33f4 100644
--- a/chrome/browser/ui/app_list/search/files/drive_zero_state_provider.cc
+++ b/chrome/browser/ui/app_list/search/files/drive_zero_state_provider.cc
@@ -72,7 +72,9 @@
       drive_service_(
           drive::DriveIntegrationServiceFactory::GetForProfile(profile)),
       item_suggest_cache_(profile, std::move(url_loader_factory)),
-      suggested_files_enabled_(app_list_features::IsSuggestedFilesEnabled()) {
+      suggested_files_enabled_(app_list_features::IsSuggestedFilesEnabled() &&
+                               profile->GetPrefs()->GetBoolean(
+                                   chromeos::prefs::kSuggestedContentEnabled)) {
   DCHECK(profile_);
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   task_runner_ = base::ThreadPool::CreateSequencedTaskRunner(
diff --git a/chrome/browser/ui/app_list/search/files/drive_zero_state_provider.h b/chrome/browser/ui/app_list/search/files/drive_zero_state_provider.h
index c5e38810..ae47ecf 100644
--- a/chrome/browser/ui/app_list/search/files/drive_zero_state_provider.h
+++ b/chrome/browser/ui/app_list/search/files/drive_zero_state_provider.h
@@ -68,7 +68,8 @@
 
   base::TimeTicks query_start_time_;
 
-  // Whether the suggested files experiment is enabled.
+  // Whether suggested files feature is enabled. True if both the experiment is
+  // enabled, and the suggested content toggle is enabled.
   const bool suggested_files_enabled_;
 
   // The normalizer normalizes the relevance scores of Results
diff --git a/chrome/browser/ui/ash/assistant/assistant_browsertest.cc b/chrome/browser/ui/ash/assistant/assistant_browsertest.cc
index a2379d0..a45ae75 100644
--- a/chrome/browser/ui/ash/assistant/assistant_browsertest.cc
+++ b/chrome/browser/ui/ash/assistant/assistant_browsertest.cc
@@ -8,6 +8,7 @@
 #include "base/time/time.h"
 #include "chrome/browser/ui/ash/assistant/assistant_test_mixin.h"
 #include "chrome/test/base/mixin_based_in_process_browser_test.h"
+#include "chromeos/assistant/test_support/expect_utils.h"
 #include "chromeos/audio/cras_audio_handler.h"
 #include "chromeos/dbus/power_manager/backlight.pb.h"
 #include "chromeos/services/assistant/public/cpp/features.h"
@@ -36,6 +37,8 @@
 
 }  // namespace
 
+using chromeos::assistant::test::ExpectResult;
+
 class AssistantBrowserTest : public MixinBasedInProcessBrowserTest {
  public:
   AssistantBrowserTest() = default;
@@ -64,7 +67,7 @@
     chromeos::PowerManagerClient::Get()->SetScreenBrightness(request);
 
     // Wait for the initial value to settle.
-    tester()->ExpectResult(
+    ExpectResult(
         true, base::BindLambdaForTesting([&]() {
           constexpr double kEpsilon = 0.1;
           auto current_brightness = tester()->SyncCall(base::BindOnce(
@@ -79,7 +82,7 @@
   void ExpectBrightnessUp() {
     auto* power_manager = chromeos::PowerManagerClient::Get();
     // Check the brightness changes
-    tester()->ExpectResult(
+    ExpectResult(
         true, base::BindLambdaForTesting([&]() {
           constexpr double kEpsilon = 1;
           auto current_brightness = tester()->SyncCall(base::BindOnce(
@@ -94,7 +97,7 @@
   void ExpectBrightnessDown() {
     auto* power_manager = chromeos::PowerManagerClient::Get();
     // Check the brightness changes
-    tester()->ExpectResult(
+    ExpectResult(
         true, base::BindLambdaForTesting([&]() {
           constexpr double kEpsilon = 1;
           auto current_brightness = tester()->SyncCall(base::BindOnce(
@@ -163,12 +166,12 @@
 
   tester()->SendTextQuery("turn up volume");
 
-  tester()->ExpectResult(true, base::BindRepeating(
-                                   [](chromeos::CrasAudioHandler* cras) {
-                                     return cras->GetOutputVolumePercent() >
-                                            kStartVolumePercent;
-                                   },
-                                   cras));
+  ExpectResult(true, base::BindRepeating(
+                         [](chromeos::CrasAudioHandler* cras) {
+                           return cras->GetOutputVolumePercent() >
+                                  kStartVolumePercent;
+                         },
+                         cras));
 }
 
 IN_PROC_BROWSER_TEST_F(AssistantBrowserTest, ShouldTurnDownVolume) {
@@ -185,12 +188,12 @@
 
   tester()->SendTextQuery("turn down volume");
 
-  tester()->ExpectResult(true, base::BindRepeating(
-                                   [](chromeos::CrasAudioHandler* cras) {
-                                     return cras->GetOutputVolumePercent() <
-                                            kStartVolumePercent;
-                                   },
-                                   cras));
+  ExpectResult(true, base::BindRepeating(
+                         [](chromeos::CrasAudioHandler* cras) {
+                           return cras->GetOutputVolumePercent() <
+                                  kStartVolumePercent;
+                         },
+                         cras));
 }
 
 IN_PROC_BROWSER_TEST_F(AssistantBrowserTest, ShouldTurnUpBrightness) {
diff --git a/chrome/browser/ui/ash/assistant/assistant_test_mixin.cc b/chrome/browser/ui/ash/assistant/assistant_test_mixin.cc
index 9cade257..ed64aec 100644
--- a/chrome/browser/ui/ash/assistant/assistant_test_mixin.cc
+++ b/chrome/browser/ui/ash/assistant/assistant_test_mixin.cc
@@ -260,23 +260,6 @@
   const std::string class_name_;
 };
 
-template <typename T>
-void CheckResult(base::OnceClosure quit,
-                 T expected_value,
-                 base::RepeatingCallback<T()> value_callback) {
-  if (expected_value == value_callback.Run()) {
-    std::move(quit).Run();
-    return;
-  }
-
-  // Check again in the future
-  base::SequencedTaskRunnerHandle::Get()->PostDelayedTask(
-      FROM_HERE,
-      base::BindOnce(CheckResult<T>, std::move(quit), expected_value,
-                     value_callback),
-      base::TimeDelta::FromMilliseconds(10));
-}
-
 // Calls a callback when the view hierarchy changes.
 class CallbackViewHierarchyChangedObserver : views::ViewObserver {
  public:
@@ -455,27 +438,6 @@
 }
 
 template <typename T>
-void AssistantTestMixin::ExpectResult(
-    T expected_value,
-    base::RepeatingCallback<T()> value_callback) {
-  const base::test::ScopedRunLoopTimeout run_timeout(FROM_HERE,
-                                                     kDefaultWaitTimeout);
-
-  // Wait until we're ready or we hit the timeout.
-  base::RunLoop run_loop;
-  CheckResult(run_loop.QuitClosure(), expected_value, value_callback);
-
-  EXPECT_NO_FATAL_FAILURE(run_loop.Run())
-      << "Failed waiting for expected result.\n"
-      << "Expected \"" << expected_value << "\"\n"
-      << "Got \"" << value_callback.Run() << "\"";
-}
-
-template void AssistantTestMixin::ExpectResult<bool>(
-    bool expected_value,
-    base::RepeatingCallback<bool()> value_callback);
-
-template <typename T>
 T AssistantTestMixin::SyncCall(
     base::OnceCallback<void(base::OnceCallback<void(T)>)> func) {
   const base::test::ScopedRunLoopTimeout run_timeout(FROM_HERE,
diff --git a/chrome/browser/ui/ash/assistant/assistant_test_mixin.h b/chrome/browser/ui/ash/assistant/assistant_test_mixin.h
index 4bcdf22..4ef8e935 100644
--- a/chrome/browser/ui/ash/assistant/assistant_test_mixin.h
+++ b/chrome/browser/ui/ash/assistant/assistant_test_mixin.h
@@ -74,17 +74,6 @@
   // displaying the query input text field.
   void SendTextQuery(const std::string& query);
 
-  // Check if the |expected_value| is equal to the result of running
-  // |value_callback|.  This method will block and continuously try the
-  // comparison above until it succeeds, or timeout.
-  //
-  // NOTE: This is a template method. If you need to use it with a new type,
-  // you may see a link error. You will need to manually instantiate for the
-  // new type.  Please see .cc file for examples.
-  template <typename T>
-  void ExpectResult(T expected_value,
-                    base::RepeatingCallback<T()> value_callback);
-
   // Synchronize an async method call to make testing simpler. |func| is the
   // async method to be invoked, the inner callback is the result callback. The
   // result with type |T| will be the return value.
diff --git a/chrome/browser/ui/ash/cast_config_controller_media_router.cc b/chrome/browser/ui/ash/cast_config_controller_media_router.cc
index eabaaed..61b7bab 100644
--- a/chrome/browser/ui/ash/cast_config_controller_media_router.cc
+++ b/chrome/browser/ui/ash/cast_config_controller_media_router.cc
@@ -11,7 +11,6 @@
 #include "base/callback.h"
 #include "base/callback_helpers.h"
 #include "base/macros.h"
-#include "base/optional.h"
 #include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversions.h"
 #include "chrome/browser/chrome_notification_types.h"
@@ -32,12 +31,7 @@
 
 base::Optional<media_router::MediaRouter*> media_router_for_test_;
 
-// Returns the MediaRouter instance for the current primary profile, if there is
-// one.
-media_router::MediaRouter* GetMediaRouter() {
-  if (media_router_for_test_)
-    return *media_router_for_test_;
-
+Profile* GetProfile() {
   if (!user_manager::UserManager::IsInitialized())
     return nullptr;
 
@@ -45,7 +39,16 @@
   if (!user)
     return nullptr;
 
-  Profile* profile = chromeos::ProfileHelper::Get()->GetProfileByUser(user);
+  return chromeos::ProfileHelper::Get()->GetProfileByUser(user);
+}
+
+// Returns the MediaRouter instance for the current primary profile, if there is
+// one.
+media_router::MediaRouter* GetMediaRouter() {
+  if (media_router_for_test_)
+    return *media_router_for_test_;
+
+  Profile* profile = GetProfile();
   if (!profile)
     return nullptr;
 
@@ -144,11 +147,6 @@
 ////////////////////////////////////////////////////////////////////////////////
 // CastConfigControllerMediaRouter:
 
-void CastConfigControllerMediaRouter::SetMediaRouterForTest(
-    media_router::MediaRouter* media_router) {
-  media_router_for_test_ = media_router;
-}
-
 CastConfigControllerMediaRouter::CastConfigControllerMediaRouter() {
   // TODO(jdufault): This should use a callback interface once there is an
   // equivalent. See crbug.com/666005.
@@ -158,6 +156,20 @@
 
 CastConfigControllerMediaRouter::~CastConfigControllerMediaRouter() = default;
 
+// static
+bool CastConfigControllerMediaRouter::MediaRouterEnabled() {
+  if (media_router_for_test_)
+    return true;
+  Profile* profile = GetProfile();
+  return profile ? media_router::MediaRouterEnabled(profile) : false;
+}
+
+// static
+void CastConfigControllerMediaRouter::SetMediaRouterForTest(
+    media_router::MediaRouter* media_router) {
+  media_router_for_test_ = media_router;
+}
+
 CastDeviceCache* CastConfigControllerMediaRouter::device_cache() {
   // The CastDeviceCache instance is lazily allocated because the MediaRouter
   // component is not ready when the constructor is invoked.
@@ -243,15 +255,18 @@
 }
 
 void CastConfigControllerMediaRouter::CastToSink(const std::string& sink_id) {
-  // TODO(imcheng): Pass in tab casting timeout.
-  GetMediaRouter()->CreateRoute(
-      media_router::MediaSource::ForUnchosenDesktop().id(), sink_id,
-      url::Origin::Create(GURL("http://cros-cast-origin/")), nullptr,
-      base::DoNothing(), base::TimeDelta(), false);
+  if (GetMediaRouter()) {
+    // TODO(imcheng): Pass in tab casting timeout.
+    GetMediaRouter()->CreateRoute(
+        media_router::MediaSource::ForUnchosenDesktop().id(), sink_id,
+        url::Origin::Create(GURL("http://cros-cast-origin/")), nullptr,
+        base::DoNothing(), base::TimeDelta(), false);
+  }
 }
 
 void CastConfigControllerMediaRouter::StopCasting(const std::string& route_id) {
-  GetMediaRouter()->TerminateRoute(route_id);
+  if (GetMediaRouter())
+    GetMediaRouter()->TerminateRoute(route_id);
 }
 
 void CastConfigControllerMediaRouter::Observe(
diff --git a/chrome/browser/ui/ash/cast_config_controller_media_router.h b/chrome/browser/ui/ash/cast_config_controller_media_router.h
index cf0e3a5..3d81b33b 100644
--- a/chrome/browser/ui/ash/cast_config_controller_media_router.h
+++ b/chrome/browser/ui/ash/cast_config_controller_media_router.h
@@ -26,6 +26,7 @@
   CastConfigControllerMediaRouter();
   ~CastConfigControllerMediaRouter() override;
 
+  static bool MediaRouterEnabled();
   static void SetMediaRouterForTest(media_router::MediaRouter* media_router);
 
  private:
diff --git a/chrome/browser/ui/ash/chrome_browser_main_extra_parts_ash.cc b/chrome/browser/ui/ash/chrome_browser_main_extra_parts_ash.cc
index 00535ca..3f27e3e 100644
--- a/chrome/browser/ui/ash/chrome_browser_main_extra_parts_ash.cc
+++ b/chrome/browser/ui/ash/chrome_browser_main_extra_parts_ash.cc
@@ -120,8 +120,10 @@
       std::make_unique<NetworkConnectDelegateChromeOS>();
   chromeos::NetworkConnect::Initialize(network_connect_delegate_.get());
 
-  cast_config_controller_media_router_ =
-      std::make_unique<CastConfigControllerMediaRouter>();
+  if (CastConfigControllerMediaRouter::MediaRouterEnabled()) {
+    cast_config_controller_media_router_ =
+        std::make_unique<CastConfigControllerMediaRouter>();
+  }
 
   // Needed by AmbientController in ash.
   if (chromeos::features::IsAmbientModeEnabled())
diff --git a/chrome/browser/ui/ash/clipboard_history_browsertest.cc b/chrome/browser/ui/ash/clipboard_history_browsertest.cc
index 0238539..a19005a 100644
--- a/chrome/browser/ui/ash/clipboard_history_browsertest.cc
+++ b/chrome/browser/ui/ash/clipboard_history_browsertest.cc
@@ -275,9 +275,14 @@
   SetClipboardText("B");
   SetClipboardText("C");
 
+  base::HistogramTester histogram_tester;
+
   ShowContextMenuViaAccelerator(/*wait_for_selection=*/true);
   ASSERT_TRUE(GetClipboardHistoryController()->IsMenuShowing());
   ASSERT_EQ(3, GetContextMenu()->GetMenuItemsCount());
+  histogram_tester.ExpectUniqueSample(
+      "Ash.ClipboardHistory.ContextMenu.ShowMenu",
+      ash::ClipboardHistoryController::ShowSource::kAccelerator, 1);
 
   // The history menu's first item should be selected as default after the menu
   // shows. Meanwhile, its delete button should not show.
diff --git a/chrome/browser/ui/ash/holding_space/holding_space_keyed_service_factory.cc b/chrome/browser/ui/ash/holding_space/holding_space_keyed_service_factory.cc
index 85d3e9f..c949efc 100644
--- a/chrome/browser/ui/ash/holding_space/holding_space_keyed_service_factory.cc
+++ b/chrome/browser/ui/ash/holding_space/holding_space_keyed_service_factory.cc
@@ -40,19 +40,13 @@
 content::BrowserContext*
 HoldingSpaceKeyedServiceFactory::GetBrowserContextToUse(
     content::BrowserContext* context) const {
-  Profile* profile = Profile::FromBrowserContext(context);
+  Profile* const profile = Profile::FromBrowserContext(context);
 
-  user_manager::User* user =
-      chromeos::ProfileHelper::Get()->GetUserByProfile(profile);
-  if (!user)
-    return nullptr;
+  // Guest sessions are supported but redirect to the primary OTR profile.
+  if (profile->IsGuestSession())
+    return profile->GetPrimaryOTRProfile();
 
-  // Guest users are supported but should redirect to create the holding space
-  // service for the original (e.g. non-incognito) profile.
-  if (user->GetType() == user_manager::USER_TYPE_GUEST)
-    return profile->GetOriginalProfile();
-
-  // Don't create the service for off the record profiles of other user types.
+  // Don't create the service for OTR profiles outside of guest sessions.
   return profile->IsOffTheRecord() ? nullptr : context;
 }
 
@@ -61,8 +55,8 @@
   if (!features::IsTemporaryHoldingSpaceEnabled())
     return nullptr;
 
-  Profile* profile = Profile::FromBrowserContext(context);
-  DCHECK(!profile->IsOffTheRecord());
+  Profile* const profile = Profile::FromBrowserContext(context);
+  DCHECK_EQ(profile->IsGuestSession(), profile->IsOffTheRecord());
 
   user_manager::User* user =
       chromeos::ProfileHelper::Get()->GetUserByProfile(profile);
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 2a9c22e..0c6221a 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
@@ -305,20 +305,6 @@
              base::BindRepeating(&BuildVolumeManager)}});
   }
 
-  TestingProfile* CreateGuestProfile() {
-    user_manager::User* guest_user = fake_user_manager_->AddGuestUser();
-    fake_user_manager_->LoginUser(fake_user_manager_->GetGuestAccountId());
-
-    chromeos::ProfileHelper::Get()->SetProfileToUserMappingForTesting(
-        guest_user);
-
-    return profile_manager()->CreateTestingProfile(
-        guest_user->GetAccountId().GetUserEmail(),
-        /*testing_factories=*/{
-            {file_manager::VolumeManagerFactory::GetInstance(),
-             base::BindRepeating(&BuildVolumeManager)}});
-  }
-
   TestingProfile* CreateSecondaryProfile(
       std::unique_ptr<sync_preferences::PrefServiceSyncable> prefs = nullptr) {
     const std::string kSecondaryProfileName = "secondary_profile";
@@ -547,30 +533,56 @@
 }
 
 TEST_F(HoldingSpaceKeyedServiceTest, GuestUserProfile) {
-  // Service instances should be created for guest users.
-  TestingProfile* const guest_profile = CreateGuestProfile();
+  // Construct a guest session profile.
+  TestingProfile::Builder guest_profile_builder;
+  guest_profile_builder.SetGuestSession();
+  guest_profile_builder.SetProfileName("guest_profile");
+  guest_profile_builder.AddTestingFactories(
+      {{file_manager::VolumeManagerFactory::GetInstance(),
+        base::BindRepeating(&BuildVolumeManager)}});
+  std::unique_ptr<TestingProfile> guest_profile = guest_profile_builder.Build();
+
+  // Service instances should be created for guest sessions but note that the
+  // service factory will redirect to use the primary OTR profile.
   ASSERT_TRUE(guest_profile);
   ASSERT_FALSE(guest_profile->IsOffTheRecord());
   HoldingSpaceKeyedService* const guest_profile_service =
-      HoldingSpaceKeyedServiceFactory::GetInstance()->GetService(guest_profile);
+      HoldingSpaceKeyedServiceFactory::GetInstance()->GetService(
+          guest_profile.get());
   ASSERT_TRUE(guest_profile_service);
 
-  // Construct an incognito profile from `guest_profile`.
-  TestingProfile::Builder incognito_guest_profile_builder;
-  incognito_guest_profile_builder.SetGuestSession();
-  incognito_guest_profile_builder.SetProfileName(
-      guest_profile->GetProfileUserName());
-  Profile* const incognito_guest_profile =
-      incognito_guest_profile_builder.BuildIncognito(guest_profile);
-  ASSERT_TRUE(incognito_guest_profile);
-  ASSERT_TRUE(incognito_guest_profile->IsOffTheRecord());
-
-  // Service instances should be created for guest users w/ OTR profiles but
-  // should redirect to use the original (e.g. non-incognito) profile.
-  HoldingSpaceKeyedService* const incognito_guest_profile_service =
+  // Since the service factory redirects to use the primary OTR profile in the
+  // case of guest sessions, retrieving the service instance for the primary OTR
+  // profile should yield the same result as retrieving the service instance for
+  // a non-OTR guest session profile.
+  ASSERT_TRUE(guest_profile->GetPrimaryOTRProfile());
+  HoldingSpaceKeyedService* const primary_otr_guest_profile_service =
       HoldingSpaceKeyedServiceFactory::GetInstance()->GetService(
-          incognito_guest_profile);
-  ASSERT_EQ(incognito_guest_profile_service, guest_profile_service);
+          guest_profile->GetPrimaryOTRProfile());
+  ASSERT_EQ(guest_profile_service, primary_otr_guest_profile_service);
+
+  // Construct a second OTR profile from `guest_profile`.
+  TestingProfile::Builder secondary_otr_guest_profile_builder;
+  secondary_otr_guest_profile_builder.SetGuestSession();
+  secondary_otr_guest_profile_builder.SetProfileName(
+      guest_profile->GetProfileUserName());
+  TestingProfile* const secondary_otr_guest_profile =
+      secondary_otr_guest_profile_builder.BuildOffTheRecord(
+          guest_profile.get(), Profile::OTRProfileID("profile::secondary_otr"));
+  ASSERT_TRUE(secondary_otr_guest_profile);
+  ASSERT_TRUE(secondary_otr_guest_profile->IsOffTheRecord());
+
+  // Service instances should be created for non-primary OTR guest session
+  // profiles but as stated earlier the service factory will redirect to use the
+  // primary OTR profile. This means that the secondary OTR profile service
+  // instance should be equal to that explicitly created for the primary OTR
+  // profile.
+  HoldingSpaceKeyedService* const secondary_otr_guest_profile_service =
+      HoldingSpaceKeyedServiceFactory::GetInstance()->GetService(
+          secondary_otr_guest_profile);
+  ASSERT_TRUE(secondary_otr_guest_profile_service);
+  ASSERT_EQ(primary_otr_guest_profile_service,
+            secondary_otr_guest_profile_service);
 }
 
 TEST_F(HoldingSpaceKeyedServiceTest, OffTheRecordProfile) {
diff --git a/chrome/browser/ui/ash/launcher/chrome_launcher_controller.cc b/chrome/browser/ui/ash/launcher/chrome_launcher_controller.cc
index 91dd75d..76e39e6 100644
--- a/chrome/browser/ui/ash/launcher/chrome_launcher_controller.cc
+++ b/chrome/browser/ui/ash/launcher/chrome_launcher_controller.cc
@@ -39,6 +39,7 @@
 #include "chrome/browser/ui/app_list/app_list_syncable_service_factory.h"
 #include "chrome/browser/ui/app_list/app_service/app_service_app_icon_loader.h"
 #include "chrome/browser/ui/app_list/arc/arc_app_utils.h"
+#include "chrome/browser/ui/app_list/icon_standardizer.h"
 #include "chrome/browser/ui/app_list/md_icon_normalizer.h"
 #include "chrome/browser/ui/apps/app_info_dialog.h"
 #include "chrome/browser/ui/ash/chrome_launcher_prefs.h"
@@ -928,6 +929,10 @@
 
 void ChromeLauncherController::OnAppImageUpdated(const std::string& app_id,
                                                  const gfx::ImageSkia& image) {
+  bool is_standard_icon = true;
+  if (!AppServiceAppIconLoader::CanLoadImage(latest_active_profile_, app_id))
+    is_standard_icon = false;
+
   // TODO: need to get this working for shortcuts.
   for (int index = 0; index < model_->item_count(); ++index) {
     ash::ShelfItem item = model_->items()[index];
@@ -936,7 +941,8 @@
         item.id.app_id != app_id) {
       continue;
     }
-    item.image = image;
+    item.image =
+        is_standard_icon ? image : app_list::CreateStandardIconImage(image);
     shelf_spinner_controller_->MaybeApplySpinningEffect(app_id, &item.image);
     model_->Set(index, item);
     // It's possible we're waiting on more than one item, so don't break.
diff --git a/chrome/browser/ui/ash/launcher/chrome_launcher_controller_unittest.cc b/chrome/browser/ui/ash/launcher/chrome_launcher_controller_unittest.cc
index bf6698d..c2dded7 100644
--- a/chrome/browser/ui/ash/launcher/chrome_launcher_controller_unittest.cc
+++ b/chrome/browser/ui/ash/launcher/chrome_launcher_controller_unittest.cc
@@ -169,6 +169,17 @@
 // pin model with default apps that can affect some tests.
 constexpr char kDummyAppId[] = "dummyappid_dummyappid_dummyappid";
 
+std::vector<arc::mojom::AppInfoPtr> GetArcSettingsAppInfo() {
+  std::vector<arc::mojom::AppInfoPtr> apps;
+  arc::mojom::AppInfoPtr app(arc::mojom::AppInfo::New());
+  app->name = "settings";
+  app->package_name = "com.android.settings";
+  app->activity = "com.android.settings.Settings";
+  app->sticky = true;
+  apps.push_back(std::move(app));
+  return apps;
+}
+
 // Test implementation of AppIconLoader.
 class TestAppIconLoaderImpl : public AppIconLoader {
  public:
@@ -827,6 +838,8 @@
             result += "Play Store";
           } else if (app == crostini::kCrostiniTerminalSystemAppId) {
             result += "Terminal";
+          } else if (app == arc::kSettingsAppId) {
+            result += "Android Settings";
           } else {
             bool arc_app_found = false;
             for (const auto& arc_app : arc_test_.fake_apps()) {
@@ -1525,6 +1538,24 @@
   EXPECT_EQ("Chrome, Files, Gmail, Doc, Play Store", GetPinnedAppStatus());
 }
 
+TEST_F(ChromeLauncherControllerWithArcTest,
+       ArcAppsHiddenFromLaunchCanBePinned) {
+  InitLauncherController();
+
+  // Register Android Settings.
+  arc::mojom::AppHost* app_host = arc_test_.arc_app_list_prefs();
+  app_host->OnAppListRefreshed(GetArcSettingsAppInfo());
+  app_service_test().WaitForAppService();
+
+  // Pin Android settings.
+  launcher_controller_->PinAppWithID(arc::kSettingsAppId);
+  EXPECT_EQ("Chrome, Android Settings", GetPinnedAppStatus());
+
+  // The pin should remain after syncing prefs. Play Store should now appear.
+  StartPrefSyncService(syncer::SyncDataList());
+  EXPECT_EQ("Chrome, Play Store, Android Settings", GetPinnedAppStatus());
+}
+
 TEST_F(ChromeLauncherControllerWithArcTest, ArcAppPinCrossPlatformWorkflow) {
   // Work on ARC disabled platform first.
   const std::string arc_app_id1 =
diff --git a/chrome/browser/ui/ash/system_tray_client.cc b/chrome/browser/ui/ash/system_tray_client.cc
index 642e2eb6..70c5317 100644
--- a/chrome/browser/ui/ash/system_tray_client.cc
+++ b/chrome/browser/ui/ash/system_tray_client.cc
@@ -370,6 +370,11 @@
 
 void SystemTrayClient::ShowNetworkCreate(const std::string& type) {
   if (type == ::onc::network_type::kCellular) {
+    if (base::FeatureList::IsEnabled(
+            chromeos::features::kUpdatedCellularActivationUi)) {
+      ShowSettingsCellularSetupFlow();
+      return;
+    }
     const chromeos::NetworkState* cellular =
         chromeos::NetworkHandler::Get()
             ->network_state_handler()
@@ -382,6 +387,13 @@
   chromeos::InternetConfigDialog::ShowDialogForNetworkType(type);
 }
 
+void SystemTrayClient::ShowSettingsCellularSetupFlow() {
+  // TODO(crbug.com/1093185) Add metrics action recorder
+  std::string page = chromeos::settings::mojom::kCellularNetworksSubpagePath;
+  page += "&showCellularSetup=true";
+  ShowSettingsSubPageForActiveUser(page);
+}
+
 void SystemTrayClient::ShowThirdPartyVpnCreate(
     const std::string& extension_id) {
   Profile* profile = ProfileManager::GetPrimaryUserProfile();
diff --git a/chrome/browser/ui/ash/system_tray_client.h b/chrome/browser/ui/ash/system_tray_client.h
index 5d426ec3..b0b61f2 100644
--- a/chrome/browser/ui/ash/system_tray_client.h
+++ b/chrome/browser/ui/ash/system_tray_client.h
@@ -83,6 +83,9 @@
   void SetLocaleAndExit(const std::string& locale_iso_code) override;
 
  private:
+  // Opens cellular setup dialog in os settings
+  void ShowSettingsCellularSetupFlow();
+
   // Helper function shared by ShowNetworkSettings() and ShowNetworkConfigure().
   void ShowNetworkSettingsHelper(const std::string& network_id,
                                  bool show_configure);
diff --git a/chrome/browser/ui/global_media_controls/media_notification_service.cc b/chrome/browser/ui/global_media_controls/media_notification_service.cc
index 3f09f3d..ac63fa6 100644
--- a/chrome/browser/ui/global_media_controls/media_notification_service.cc
+++ b/chrome/browser/ui/global_media_controls/media_notification_service.cc
@@ -86,9 +86,11 @@
 
 base::WeakPtr<media_router::WebContentsPresentationManager>
 GetPresentationManager(content::WebContents* web_contents) {
-  return web_contents
-             ? media_router::WebContentsPresentationManager::Get(web_contents)
-             : nullptr;
+  if (!web_contents ||
+      !media_router::MediaRouterEnabled(web_contents->GetBrowserContext())) {
+    return nullptr;
+  }
+  return media_router::WebContentsPresentationManager::Get(web_contents);
 }
 
 }  // anonymous namespace
@@ -316,18 +318,19 @@
 MediaNotificationService::MediaNotificationService(Profile* profile,
                                                    bool show_from_all_profiles)
     : overlay_media_notifications_manager_(this) {
-  if (base::FeatureList::IsEnabled(media::kGlobalMediaControlsForCast) &&
-      media_router::MediaRouterEnabled(profile)) {
-    cast_notification_provider_ =
-        std::make_unique<CastMediaNotificationProvider>(
-            profile, this,
-            base::BindRepeating(
-                &MediaNotificationService::OnCastNotificationsChanged,
-                base::Unretained(this)));
-  }
-  if (media_router::GlobalMediaControlsCastStartStopEnabled()) {
-    presentation_request_notification_provider_ =
-        std::make_unique<PresentationRequestNotificationProvider>(this);
+  if (media_router::MediaRouterEnabled(profile)) {
+    if (base::FeatureList::IsEnabled(media::kGlobalMediaControlsForCast)) {
+      cast_notification_provider_ =
+          std::make_unique<CastMediaNotificationProvider>(
+              profile, this,
+              base::BindRepeating(
+                  &MediaNotificationService::OnCastNotificationsChanged,
+                  base::Unretained(this)));
+    }
+    if (media_router::GlobalMediaControlsCastStartStopEnabled()) {
+      presentation_request_notification_provider_ =
+          std::make_unique<PresentationRequestNotificationProvider>(this);
+    }
   }
 
   // Connect to the controller manager so we can create media controllers for
diff --git a/chrome/browser/ui/views/autofill/autofill_popup_base_view.cc b/chrome/browser/ui/views/autofill/autofill_popup_base_view.cc
index a4b3422..00702d4 100644
--- a/chrome/browser/ui/views/autofill/autofill_popup_base_view.cc
+++ b/chrome/browser/ui/views/autofill/autofill_popup_base_view.cc
@@ -24,7 +24,6 @@
 #include "ui/accessibility/platform/ax_platform_node.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/gfx/color_palette.h"
-#include "ui/native_theme/native_theme.h"
 #include "ui/views/accessibility/view_accessibility.h"
 #include "ui/views/border.h"
 #include "ui/views/bubble/bubble_border.h"
@@ -39,23 +38,37 @@
 }
 
 SkColor AutofillPopupBaseView::GetBackgroundColor() {
-  return GetNativeTheme()->GetSystemColor(
-      ui::NativeTheme::kColorId_DropdownBackgroundColor);
+  return GetHighContrastAwareColor(
+      ui::NativeTheme::kColorId_DropdownBackgroundColor,
+      ui::NativeTheme::SystemThemeColor::kButtonFace);
 }
 
 SkColor AutofillPopupBaseView::GetForegroundColor() {
-  return GetNativeTheme()->GetSystemColor(
-      ui::NativeTheme::kColorId_DropdownForegroundColor);
+  return GetHighContrastAwareColor(
+      ui::NativeTheme::kColorId_DropdownForegroundColor,
+      ui::NativeTheme::SystemThemeColor::kButtonText);
 }
 
 SkColor AutofillPopupBaseView::GetSelectedBackgroundColor() {
-  return GetNativeTheme()->GetSystemColor(
-      ui::NativeTheme::kColorId_DropdownSelectedBackgroundColor);
+  return GetHighContrastAwareColor(
+      ui::NativeTheme::kColorId_DropdownSelectedBackgroundColor,
+      ui::NativeTheme::SystemThemeColor::kButtonText);
 }
 
 SkColor AutofillPopupBaseView::GetSelectedForegroundColor() {
-  return GetNativeTheme()->GetSystemColor(
-      ui::NativeTheme::kColorId_DropdownSelectedForegroundColor);
+  return GetHighContrastAwareColor(
+      ui::NativeTheme::kColorId_DropdownSelectedForegroundColor,
+      ui::NativeTheme::SystemThemeColor::kButtonFace);
+}
+
+SkColor AutofillPopupBaseView::GetHighContrastAwareColor(
+    ui::NativeTheme::ColorId non_hc_color,
+    ui::NativeTheme::SystemThemeColor hc_color) {
+  if (ui::NativeTheme::GetInstanceForWeb()->UserHasContrastPreference()) {
+    return *GetNativeTheme()->GetSystemThemeColor(hc_color);
+  } else {
+    return GetNativeTheme()->GetSystemColor(non_hc_color);
+  }
 }
 
 SkColor AutofillPopupBaseView::GetFooterBackgroundColor() {
diff --git a/chrome/browser/ui/views/autofill/autofill_popup_base_view.h b/chrome/browser/ui/views/autofill/autofill_popup_base_view.h
index 5876a6c4..e19bfe2 100644
--- a/chrome/browser/ui/views/autofill/autofill_popup_base_view.h
+++ b/chrome/browser/ui/views/autofill/autofill_popup_base_view.h
@@ -12,6 +12,7 @@
 #include "base/time/time.h"
 #include "build/build_config.h"
 #include "chrome/browser/ui/autofill/autofill_popup_view_delegate.h"
+#include "ui/native_theme/native_theme.h"
 #include "ui/views/focus/widget_focus_manager.h"
 #include "ui/views/widget/widget.h"
 #include "ui/views/widget/widget_delegate.h"
@@ -48,6 +49,8 @@
   SkColor GetForegroundColor();
   SkColor GetSelectedBackgroundColor();
   SkColor GetSelectedForegroundColor();
+  SkColor GetHighContrastAwareColor(ui::NativeTheme::ColorId non_hc_color,
+                                    ui::NativeTheme::SystemThemeColor hc_color);
   SkColor GetFooterBackgroundColor();
   SkColor GetSeparatorColor();
   SkColor GetWarningColor();
diff --git a/chrome/browser/ui/views/task_manager_view.cc b/chrome/browser/ui/views/task_manager_view.cc
index 84f9e5c..47d7153 100644
--- a/chrome/browser/ui/views/task_manager_view.cc
+++ b/chrome/browser/ui/views/task_manager_view.cc
@@ -38,6 +38,7 @@
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 #include "ash/public/cpp/shelf_item.h"
 #include "ash/public/cpp/window_properties.h"
+#include "chrome/browser/ui/app_list/icon_standardizer.h"
 #include "chrome/grit/theme_resources.h"
 #include "ui/aura/window.h"
 #include "ui/base/resource/resource_bundle.h"
@@ -171,8 +172,11 @@
 
 gfx::ImageSkia TaskManagerView::GetWindowIcon() {
 #if BUILDFLAG(IS_CHROMEOS_ASH)
-  return *ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
-      IDR_ASH_SHELF_ICON_TASK_MANAGER);
+  // TODO(crbug.com/1162514): Move app_list::CreateStandardIconImage to some
+  // where lower in the stack.
+  return app_list::CreateStandardIconImage(
+      *ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
+          IDR_ASH_SHELF_ICON_TASK_MANAGER));
 #else
   return views::DialogDelegateView::GetWindowIcon();
 #endif
diff --git a/chrome/browser/ui/views/user_education/feature_promo_bubble_view.cc b/chrome/browser/ui/views/user_education/feature_promo_bubble_view.cc
index 8c8c41e..51b49e3 100644
--- a/chrome/browser/ui/views/user_education/feature_promo_bubble_view.cc
+++ b/chrome/browser/ui/views/user_education/feature_promo_bubble_view.cc
@@ -47,6 +47,9 @@
 // user stops hovering over it.
 constexpr base::TimeDelta kDelayShort = base::TimeDelta::FromSeconds(3);
 
+// Maximum width of the bubble. Longer strings will cause wrapping.
+constexpr int kBubbleMaxWidthDip = 340;
+
 // The insets from the bubble border to the text inside.
 constexpr gfx::Insets kBubbleContentsInsets(12, 16);
 
@@ -205,9 +208,7 @@
   body_label->SetBackgroundColor(background_color);
   body_label->SetEnabledColor(text_color);
   body_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
-
-  if (params.preferred_width.has_value())
-    body_label->SetMultiLine(true);
+  body_label->SetMultiLine(true);
 
   if (snoozable_) {
     auto* button_container = AddChildView(std::make_unique<views::View>());
@@ -330,9 +331,16 @@
   if (preferred_width_.has_value()) {
     return gfx::Size(preferred_width_.value(),
                      GetHeightForWidth(preferred_width_.value()));
-  } else {
-    return View::CalculatePreferredSize();
   }
+
+  gfx::Size layout_manager_preferred_size = View::CalculatePreferredSize();
+
+  // Wrap if the width is larger than |kBubbleMaxWidthDip|.
+  if (layout_manager_preferred_size.width() > kBubbleMaxWidthDip) {
+    return gfx::Size(kBubbleMaxWidthDip, GetHeightForWidth(kBubbleMaxWidthDip));
+  }
+
+  return layout_manager_preferred_size;
 }
 
 views::Button* FeaturePromoBubbleView::GetDismissButtonForTesting() const {
diff --git a/chrome/browser/ui/webui/new_tab_page/new_tab_page_ui.cc b/chrome/browser/ui/webui/new_tab_page/new_tab_page_ui.cc
index a8ed29b288..a3a9532 100644
--- a/chrome/browser/ui/webui/new_tab_page/new_tab_page_ui.cc
+++ b/chrome/browser/ui/webui/new_tab_page/new_tab_page_ui.cc
@@ -203,6 +203,22 @@
       {"modulesTasksInfoTitle", IDS_NTP_MODULES_SHOPPING_TASKS_INFO_TITLE},
       {"modulesTasksInfoClose", IDS_NTP_MODULES_SHOPPING_TASKS_INFO_CLOSE},
       {"modulesCartHeaderNew", IDS_NTP_MODULES_CART_HEADER_CHIP_NEW},
+      {"modulesCartTitle", IDS_NTP_MODULES_CART_TITLE},
+      {"modulesCartWarmWelcome", IDS_NTP_MODULES_CART_WARM_WELCOME},
+      {"modulesCartModuleMenuHide", IDS_NTP_MODULES_CART_MODULE_MENU_HIDE},
+      {"modulesCartModuleMenuHideToastMessage",
+       IDS_NTP_MODULES_CART_MODULE_MENU_HIDE_TOAST_MESSAGE},
+      {"modulesCartModuleMenuRemove", IDS_NTP_MODULES_CART_MODULE_MENU_REMOVE},
+      {"modulesCartModuleMenuRemoveToastMessage",
+       IDS_NTP_MODULES_CART_MODULE_MENU_REMOVE_TOAST_MESSAGE},
+      {"modulesCartCartMenuHideMerchant",
+       IDS_NTP_MODULES_CART_CART_MENU_HIDE_MERCHANT},
+      {"modulesCartCartMenuHideMerchantToastMessage",
+       IDS_NTP_MODULES_CART_CART_MENU_HIDE_MERCHANT_TOAST_MESSAGE},
+      {"modulesCartCartMenuRemoveMerchant",
+       IDS_NTP_MODULES_CART_CART_MENU_REMOVE_MERCHANT},
+      {"modulesCartCartMenuRemoveMerchantToastMessage",
+       IDS_NTP_MODULES_CART_CART_MENU_REMOVE_MERCHANT_TOAST_MESSAGE},
   };
   AddLocalizedStringsBulk(source, kStrings);
 
diff --git a/chrome/browser/ui/webui/settings/chromeos/accessibility_section.cc b/chrome/browser/ui/webui/settings/chromeos/accessibility_section.cc
index 79d131d..2639c5b 100644
--- a/chrome/browser/ui/webui/settings/chromeos/accessibility_section.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/accessibility_section.cc
@@ -202,6 +202,12 @@
        mojom::SearchResultDefaultRank::kMedium,
        mojom::SearchResultType::kSetting,
        {.setting = mojom::Setting::kEnableSwitchAccess}},
+      {IDS_OS_SETTINGS_TAG_A11Y_CURSOR_COLOR,
+       mojom::kManageAccessibilitySubpagePath,
+       mojom::SearchResultIcon::kA11y,
+       mojom::SearchResultDefaultRank::kMedium,
+       mojom::SearchResultType::kSetting,
+       {.setting = mojom::Setting::kEnableCursorColor}},
   });
   return *tags;
 }
@@ -303,18 +309,6 @@
   return *tags;
 }
 
-const std::vector<SearchConcept>& GetA11yCursorColorSearchConcepts() {
-  static const base::NoDestructor<std::vector<SearchConcept>> tags({
-      {IDS_OS_SETTINGS_TAG_A11Y_CURSOR_COLOR,
-       mojom::kManageAccessibilitySubpagePath,
-       mojom::SearchResultIcon::kA11y,
-       mojom::SearchResultDefaultRank::kMedium,
-       mojom::SearchResultType::kSetting,
-       {.setting = mojom::Setting::kEnableCursorColor}},
-  });
-  return *tags;
-}
-
 const std::vector<SearchConcept>&
 GetA11yFullscreenMagnifierFocusFollowingSearchConcepts() {
   static const base::NoDestructor<std::vector<SearchConcept>> tags({
@@ -337,10 +331,6 @@
   return base::FeatureList::IsEnabled(media::kLiveCaption);
 }
 
-bool IsCursorColorAllowed() {
-  return features::IsAccessibilityCursorColorEnabled();
-}
-
 bool IsMagnifierPanningImprovementsEnabled() {
   return features::IsMagnifierPanningImprovementsEnabled();
 }
@@ -652,9 +642,6 @@
   html_source->AddString("tabletModeShelfNavigationButtonsLearnMoreUrl",
                          chrome::kTabletModeGesturesLearnMoreURL);
 
-  html_source->AddBoolean("showExperimentalAccessibilityCursorColor",
-                          IsCursorColorAllowed());
-
   html_source->AddBoolean("isMagnifierPanningImprovementsEnabled",
                           IsMagnifierPanningImprovementsEnabled());
 
@@ -839,12 +826,6 @@
     updater.RemoveSearchTags(GetA11yLiveCaptionSearchConcepts());
   }
 
-  if (IsCursorColorAllowed()) {
-    updater.AddSearchTags(GetA11yCursorColorSearchConcepts());
-  } else {
-    updater.RemoveSearchTags(GetA11yCursorColorSearchConcepts());
-  }
-
   if (IsMagnifierPanningImprovementsEnabled()) {
     updater.AddSearchTags(
         GetA11yFullscreenMagnifierFocusFollowingSearchConcepts());
diff --git a/chrome/browser/ui/webui/settings/chromeos/constants/routes.mojom b/chrome/browser/ui/webui/settings/chromeos/constants/routes.mojom
index 24378fa..4cc604f 100644
--- a/chrome/browser/ui/webui/settings/chromeos/constants/routes.mojom
+++ b/chrome/browser/ui/webui/settings/chromeos/constants/routes.mojom
@@ -146,6 +146,7 @@
 const string kWifiNetworksSubpagePath = "networks?type=WiFi";
 const string kWifiDetailsSubpagePath = "networkDetail";
 const string kKnownNetworksSubpagePath = "knownNetworks";
+const string kCellularNetworksSubpagePath = "networks?type=Cellular";
 const string kMobileDataNetworksSubpagePath = "networks?type=Tether";
 const string kCellularDetailsSubpagePath = "networkDetail";
 const string kTetherDetailsSubpagePath = "networkDetail";
diff --git a/chrome/browser/web_applications/components/BUILD.gn b/chrome/browser/web_applications/components/BUILD.gn
index 905f217..9db650a 100644
--- a/chrome/browser/web_applications/components/BUILD.gn
+++ b/chrome/browser/web_applications/components/BUILD.gn
@@ -115,6 +115,7 @@
       "app_shim_registry_mac.cc",
       "app_shim_registry_mac.h",
       "web_app_file_handler_registration_mac.cc",
+      "web_app_run_on_os_login_mac.mm",
       "web_app_shortcut_mac.h",
       "web_app_shortcut_mac.mm",
     ]
@@ -195,6 +196,7 @@
   if (is_mac) {
     sources += [
       "app_shim_registry_mac_unittest.cc",
+      "web_app_run_on_os_login_mac_unittest.mm",
       "web_app_shortcut_mac_unittest.mm",
     ]
   }
diff --git a/chrome/browser/web_applications/components/web_app_run_on_os_login.cc b/chrome/browser/web_applications/components/web_app_run_on_os_login.cc
index 5f92f955..6e4348e 100644
--- a/chrome/browser/web_applications/components/web_app_run_on_os_login.cc
+++ b/chrome/browser/web_applications/components/web_app_run_on_os_login.cc
@@ -34,17 +34,17 @@
 
 // TODO(crbug.com/1052397): Revisit once build flag switch of lacros-chrome is
 // complete.
-#if !(defined(OS_WIN) || (defined(OS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS)))
+#if !(defined(OS_WIN) || defined(OS_MAC) || (defined(OS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS)))
 // TODO(crbug.com/897302): This boilerplate function is used for platforms
 // that don't support Run On OS Login. Currently the feature is supported on
-// Windows and Linux.
+// Windows, Linux and MacOS.
 bool RegisterRunOnOsLogin(const ShortcutInfo& shortcut_info) {
   return false;
 }
 
 // TODO(crbug.com/897302): This boilerplate function is used for platforms
 // that don't support Run On OS Login. Currently the feature is supported on
-// Windows and Linux.
+// Windows, Linux and MacOS.
 bool UnregisterRunOnOsLogin(const std::string& app_id,
                             const base::FilePath& profile_path,
                             const base::string16& shortcut_title) {
diff --git a/chrome/browser/web_applications/components/web_app_run_on_os_login_mac.mm b/chrome/browser/web_applications/components/web_app_run_on_os_login_mac.mm
new file mode 100644
index 0000000..5cefaddd3
--- /dev/null
+++ b/chrome/browser/web_applications/components/web_app_run_on_os_login_mac.mm
@@ -0,0 +1,32 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/web_applications/components/web_app_run_on_os_login.h"
+
+#import "chrome/browser/web_applications/components/web_app_shortcut_mac.h"
+
+namespace web_app {
+
+namespace internals {
+
+bool RegisterRunOnOsLogin(const ShortcutInfo& shortcut_info) {
+  base::FilePath shortcut_data_dir = GetShortcutDataDir(shortcut_info);
+
+  ShortcutLocations locations;
+  locations.in_startup = true;
+
+  return CreatePlatformShortcuts(shortcut_data_dir, locations,
+                                 SHORTCUT_CREATION_AUTOMATED, shortcut_info);
+}
+
+bool UnregisterRunOnOsLogin(const std::string& app_id,
+                            const base::FilePath& profile_path,
+                            const base::string16& shortcut_title) {
+  RemoveAppShimFromLoginItems(app_id);
+  return true;
+}
+
+}  // namespace internals
+
+}  // namespace web_app
diff --git a/chrome/browser/web_applications/components/web_app_run_on_os_login_mac_unittest.mm b/chrome/browser/web_applications/components/web_app_run_on_os_login_mac_unittest.mm
new file mode 100644
index 0000000..7083421
--- /dev/null
+++ b/chrome/browser/web_applications/components/web_app_run_on_os_login_mac_unittest.mm
@@ -0,0 +1,166 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/web_applications/components/web_app_run_on_os_login.h"
+
+#import <Cocoa/Cocoa.h>
+#include <errno.h>
+#include <stddef.h>
+#include <sys/xattr.h>
+
+#include <memory>
+
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/mac/foundation_util.h"
+#include "base/path_service.h"
+#include "base/strings/utf_string_conversions.h"
+#include "chrome/browser/web_applications/components/web_app_shortcut.h"
+#include "chrome/browser/web_applications/components/web_app_shortcut_mac.h"
+#include "chrome/browser/web_applications/test/web_app_test.h"
+#include "chrome/common/chrome_paths.h"
+#include "components/version_info/version_info.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#import "testing/gtest_mac.h"
+
+namespace web_app {
+
+namespace {
+
+constexpr char kFakeChromeBundleId[] = {"fake.cfbundleidentifier"};
+constexpr char kAppTitle[] = {"app"};
+
+class WebAppAutoLoginUtilMock : public WebAppAutoLoginUtil {
+ public:
+  WebAppAutoLoginUtilMock() = default;
+  WebAppAutoLoginUtilMock(const WebAppAutoLoginUtilMock&) = delete;
+  WebAppAutoLoginUtilMock& operator=(const WebAppAutoLoginUtilMock&) = delete;
+
+  void AddToLoginItems(const base::FilePath& app_bundle_path,
+                       bool hide_on_startup) override {
+    EXPECT_TRUE(base::PathExists(app_bundle_path));
+    EXPECT_FALSE(hide_on_startup);
+    add_to_login_items_called_count_++;
+  }
+
+  void RemoveFromLoginItems(const base::FilePath& app_bundle_path) override {
+    EXPECT_TRUE(base::PathExists(app_bundle_path));
+    remove_from_login_items_called_count_++;
+  }
+
+  void ResetCounts() {
+    add_to_login_items_called_count_ = 0;
+    remove_from_login_items_called_count_ = 0;
+  }
+
+  int GetAddToLoginItemsCalledCount() const {
+    return add_to_login_items_called_count_;
+  }
+
+  int GetRemoveFromLoginItemsCalledCount() const {
+    return remove_from_login_items_called_count_;
+  }
+
+ private:
+  int add_to_login_items_called_count_ = 0;
+  int remove_from_login_items_called_count_ = 0;
+};
+
+}  // namespace
+
+class WebAppRunOnOsLoginMacTest : public WebAppTest {
+ public:
+  void SetUp() override {
+    WebAppTest::SetUp();
+    base::mac::SetBaseBundleID(kFakeChromeBundleId);
+
+    EXPECT_TRUE(temp_destination_dir_.CreateUniqueTempDir());
+    EXPECT_TRUE(temp_user_data_dir_.CreateUniqueTempDir());
+    destination_dir_ = temp_destination_dir_.GetPath();
+    user_data_dir_ = temp_user_data_dir_.GetPath();
+    // Recreate the directory structure as it would be created for the
+    // ShortcutInfo created in the above GetShortcutInfo.
+    app_data_dir_ = user_data_dir_.Append("Profile 1")
+                        .Append("Web Applications")
+                        .Append("_crx_app-id");
+    EXPECT_TRUE(base::CreateDirectory(app_data_dir_));
+
+    // When using base::PathService::Override, it calls
+    // base::MakeAbsoluteFilePath. On Mac this prepends "/private" to the path,
+    // but points to the same directory in the file system.
+    EXPECT_TRUE(
+        base::PathService::Override(chrome::DIR_USER_DATA, user_data_dir_));
+    user_data_dir_ = base::MakeAbsoluteFilePath(user_data_dir_);
+    app_data_dir_ = base::MakeAbsoluteFilePath(app_data_dir_);
+
+    SetChromeAppsFolderForTesting(destination_dir_);
+
+    info_ = GetShortcutInfo();
+    base::FilePath shim_base_name =
+        base::FilePath(base::UTF16ToUTF8(info_->title) + ".app");
+    shim_path_ = destination_dir_.Append(shim_base_name);
+
+    auto_login_util_mock_ = std::make_unique<WebAppAutoLoginUtilMock>();
+    WebAppAutoLoginUtil::SetInstanceForTesting(auto_login_util_mock_.get());
+  }
+
+  void TearDown() override {
+    WebAppAutoLoginUtil::SetInstanceForTesting(nullptr);
+    SetChromeAppsFolderForTesting(base::FilePath());
+    WebAppTest::TearDown();
+  }
+
+  std::unique_ptr<ShortcutInfo> GetShortcutInfo() {
+    std::unique_ptr<ShortcutInfo> info(new ShortcutInfo);
+    info->extension_id = "app-id";
+    info->title = base::UTF8ToUTF16(kAppTitle);
+    info->url = GURL("http://example.com/");
+    info->profile_path = user_data_dir_.Append("Profile 1");
+    info->profile_name = "profile name";
+    info->version_for_display = "stable 1.0";
+    info->is_multi_profile = true;
+    return info;
+  }
+
+ protected:
+  base::ScopedTempDir temp_destination_dir_;
+  base::ScopedTempDir temp_user_data_dir_;
+  base::FilePath app_data_dir_;
+  base::FilePath destination_dir_;
+  base::FilePath user_data_dir_;
+
+  std::unique_ptr<WebAppAutoLoginUtilMock> auto_login_util_mock_;
+  std::unique_ptr<ShortcutInfo> info_;
+  base::FilePath shim_path_;
+};
+
+TEST_F(WebAppRunOnOsLoginMacTest, Register) {
+  auto_login_util_mock_->ResetCounts();
+  EXPECT_FALSE(base::PathExists(shim_path_));
+  EXPECT_TRUE(internals::RegisterRunOnOsLogin(*info_));
+  EXPECT_TRUE(base::PathExists(shim_path_));
+  EXPECT_EQ(auto_login_util_mock_->GetAddToLoginItemsCalledCount(), 1);
+  EXPECT_TRUE(base::PathExists(shim_path_));
+  EXPECT_TRUE(base::DeletePathRecursively(shim_path_));
+}
+
+TEST_F(WebAppRunOnOsLoginMacTest, Unregister) {
+  auto_login_util_mock_->ResetCounts();
+  EXPECT_FALSE(base::PathExists(shim_path_));
+  EXPECT_TRUE(internals::RegisterRunOnOsLogin(*info_));
+  EXPECT_TRUE(base::PathExists(shim_path_));
+  EXPECT_EQ(auto_login_util_mock_->GetAddToLoginItemsCalledCount(), 1);
+  EXPECT_EQ(auto_login_util_mock_->GetRemoveFromLoginItemsCalledCount(), 0);
+
+  auto_login_util_mock_->ResetCounts();
+  EXPECT_TRUE(internals::UnregisterRunOnOsLogin(
+      info_->extension_id, info_->profile_path, info_->title));
+  EXPECT_EQ(auto_login_util_mock_->GetRemoveFromLoginItemsCalledCount(), 1);
+  EXPECT_TRUE(base::PathExists(shim_path_));
+  EXPECT_TRUE(base::DeletePathRecursively(shim_path_));
+}
+
+}  // namespace web_app
\ No newline at end of file
diff --git a/chrome/browser/web_applications/components/web_app_shortcut_mac.h b/chrome/browser/web_applications/components/web_app_shortcut_mac.h
index 19d5cf9..9cf04023 100644
--- a/chrome/browser/web_applications/components/web_app_shortcut_mac.h
+++ b/chrome/browser/web_applications/components/web_app_shortcut_mac.h
@@ -60,6 +60,30 @@
 // Testing method to override calls to GetChromeAppsFolder.
 void SetChromeAppsFolderForTesting(const base::FilePath& path);
 
+// Remove the specified app from the OS login item list.
+void RemoveAppShimFromLoginItems(const std::string& app_id);
+
+class WebAppAutoLoginUtil {
+ public:
+  WebAppAutoLoginUtil() = default;
+  WebAppAutoLoginUtil(const WebAppAutoLoginUtil&) = delete;
+  WebAppAutoLoginUtil& operator=(const WebAppAutoLoginUtil&) = delete;
+
+  static WebAppAutoLoginUtil* GetInstance();
+
+  static void SetInstanceForTesting(WebAppAutoLoginUtil* auto_login_util);
+
+  // Adds the specified app to the list of login items.
+  virtual void AddToLoginItems(const base::FilePath& app_bundle_path,
+                               bool hide_on_startup);
+
+  // Removes the specified app from the list of login items.
+  virtual void RemoveFromLoginItems(const base::FilePath& app_bundle_path);
+
+ protected:
+  virtual ~WebAppAutoLoginUtil() = default;
+};
+
 // Creates a shortcut for a web application. The shortcut is a stub app
 // that simply loads the browser framework and runs the given app.
 class WebAppShortcutCreator {
diff --git a/chrome/browser/web_applications/components/web_app_shortcut_mac.mm b/chrome/browser/web_applications/components/web_app_shortcut_mac.mm
index 2fc0d6b..383f329 100644
--- a/chrome/browser/web_applications/components/web_app_shortcut_mac.mm
+++ b/chrome/browser/web_applications/components/web_app_shortcut_mac.mm
@@ -159,6 +159,8 @@
 
 namespace {
 
+WebAppAutoLoginUtil* g_auto_login_util_for_testing = nullptr;
+
 // UMA metric name for creating shortcut result.
 constexpr const char* kCreateShortcutResult = "Apps.CreateShortcuts.Mac.Result";
 
@@ -768,6 +770,31 @@
   *GetOverriddenApplicationsFolder() = path;
 }
 
+// static
+WebAppAutoLoginUtil* WebAppAutoLoginUtil::GetInstance() {
+  if (g_auto_login_util_for_testing)
+    return g_auto_login_util_for_testing;
+
+  static base::NoDestructor<WebAppAutoLoginUtil> instance;
+  return instance.get();
+}
+
+// static
+void WebAppAutoLoginUtil::SetInstanceForTesting(
+    WebAppAutoLoginUtil* auto_login_util) {
+  g_auto_login_util_for_testing = auto_login_util;
+}
+
+void WebAppAutoLoginUtil::AddToLoginItems(const base::FilePath& app_bundle_path,
+                                          bool hide_on_startup) {
+  base::mac::AddToLoginItems(app_bundle_path, hide_on_startup);
+}
+
+void WebAppAutoLoginUtil::RemoveFromLoginItems(
+    const base::FilePath& app_bundle_path) {
+  base::mac::RemoveFromLoginItems(app_bundle_path);
+}
+
 WebAppShortcutCreator::WebAppShortcutCreator(const base::FilePath& app_data_dir,
                                              const ShortcutInfo* shortcut_info)
     : app_data_dir_(app_data_dir), info_(shortcut_info) {
@@ -1001,6 +1028,11 @@
   std::vector<base::FilePath> updated_app_paths;
   if (!UpdateShortcuts(true /* create_if_needed */, &updated_app_paths))
     return false;
+  if (creation_locations.in_startup) {
+    // Only add the first app to run at OS login.
+    WebAppAutoLoginUtil::GetInstance()->AddToLoginItems(updated_app_paths[0],
+                                                        false);
+  }
   if (creation_reason == SHORTCUT_CREATION_BY_USER)
     RevealAppShimInFinder(updated_app_paths[0]);
   RecordCreateShortcut(CreateShortcutResult::kSuccess);
@@ -1313,6 +1345,18 @@
       std::move(shortcut_info));
 }
 
+// Removes the app shim from the list of Login Items.
+void RemoveAppShimFromLoginItems(const std::string& app_id) {
+  base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
+                                                base::BlockingType::MAY_BLOCK);
+  const std::string bundle_id = GetBundleIdentifier(app_id);
+  auto bundle_infos = SearchForBundlesById(bundle_id);
+  for (const auto& bundle_info : bundle_infos) {
+    WebAppAutoLoginUtil::GetInstance()->RemoveFromLoginItems(
+        bundle_info.bundle_path());
+  }
+}
+
 namespace internals {
 
 bool CreatePlatformShortcuts(const base::FilePath& app_data_path,
@@ -1337,6 +1381,8 @@
   auto bundle_infos = SearchForBundlesById(bundle_id);
   bool result = true;
   for (const auto& bundle_info : bundle_infos) {
+    WebAppAutoLoginUtil::GetInstance()->RemoveFromLoginItems(
+        bundle_info.bundle_path());
     if (!base::DeletePathRecursively(bundle_info.bundle_path()))
       result = false;
   }
@@ -1349,6 +1395,8 @@
   const std::string bundle_id = GetBundleIdentifier(app_id);
   auto bundle_infos = SearchForBundlesById(bundle_id);
   for (const auto& bundle_info : bundle_infos) {
+    WebAppAutoLoginUtil::GetInstance()->RemoveFromLoginItems(
+        bundle_info.bundle_path());
     base::DeletePathRecursively(bundle_info.bundle_path());
   }
 }
@@ -1381,6 +1429,8 @@
       continue;
     if (!info.IsForProfile(profile_path))
       continue;
+    WebAppAutoLoginUtil::GetInstance()->RemoveFromLoginItems(
+        info.bundle_path());
     base::DeletePathRecursively(info.bundle_path());
   }
 }
diff --git a/chrome/browser/web_applications/components/web_app_shortcut_mac_unittest.mm b/chrome/browser/web_applications/components/web_app_shortcut_mac_unittest.mm
index 8d1fc5c7..43b9a8d 100644
--- a/chrome/browser/web_applications/components/web_app_shortcut_mac_unittest.mm
+++ b/chrome/browser/web_applications/components/web_app_shortcut_mac_unittest.mm
@@ -70,6 +70,42 @@
       const WebAppShortcutCreatorSortingMock&) = delete;
 };
 
+class WebAppAutoLoginUtilMock : public WebAppAutoLoginUtil {
+ public:
+  WebAppAutoLoginUtilMock() = default;
+  WebAppAutoLoginUtilMock(const WebAppAutoLoginUtilMock&) = delete;
+  WebAppAutoLoginUtilMock& operator=(const WebAppAutoLoginUtilMock&) = delete;
+
+  void AddToLoginItems(const base::FilePath& app_bundle_path,
+                       bool hide_on_startup) override {
+    EXPECT_TRUE(base::PathExists(app_bundle_path));
+    EXPECT_FALSE(hide_on_startup);
+    add_to_login_items_called_count_++;
+  }
+
+  void RemoveFromLoginItems(const base::FilePath& app_bundle_path) override {
+    EXPECT_TRUE(base::PathExists(app_bundle_path));
+    remove_from_login_items_called_count_++;
+  }
+
+  void ResetCounts() {
+    add_to_login_items_called_count_ = 0;
+    remove_from_login_items_called_count_ = 0;
+  }
+
+  int GetAddToLoginItemsCalledCount() const {
+    return add_to_login_items_called_count_;
+  }
+
+  int GetRemoveFromLoginItemsCalledCount() const {
+    return remove_from_login_items_called_count_;
+  }
+
+ private:
+  int add_to_login_items_called_count_ = 0;
+  int remove_from_login_items_called_count_ = 0;
+};
+
 std::unique_ptr<ShortcutInfo> GetShortcutInfo() {
   std::unique_ptr<ShortcutInfo> info(new ShortcutInfo);
   info->extension_id = "extensionid";
@@ -120,9 +156,13 @@
 
     shim_base_name_ = base::FilePath(base::UTF16ToUTF8(info_->title) + ".app");
     shim_path_ = destination_dir_.Append(shim_base_name_);
+
+    auto_login_util_mock_ = std::make_unique<WebAppAutoLoginUtilMock>();
+    WebAppAutoLoginUtil::SetInstanceForTesting(auto_login_util_mock_.get());
   }
 
   void TearDown() override {
+    WebAppAutoLoginUtil::SetInstanceForTesting(nullptr);
     SetChromeAppsFolderForTesting(base::FilePath());
     testing::Test::TearDown();
   }
@@ -136,6 +176,7 @@
   base::FilePath destination_dir_;
   base::FilePath user_data_dir_;
 
+  std::unique_ptr<WebAppAutoLoginUtilMock> auto_login_util_mock_;
   std::unique_ptr<ShortcutInfo> info_;
   base::FilePath fallback_shim_base_name_;
   base::FilePath shim_base_name_;
@@ -169,11 +210,14 @@
   // Delete it here, just to test that it is not recreated.
   EXPECT_TRUE(base::DeletePathRecursively(strings_file));
 
+  auto_login_util_mock_->ResetCounts();
+
   // Ensure the strings file wasn't recreated. It's not needed for any other
   // tests.
   EXPECT_TRUE(shortcut_creator.CreateShortcuts(SHORTCUT_CREATION_AUTOMATED,
                                                ShortcutLocations()));
   EXPECT_FALSE(base::PathExists(strings_file));
+  EXPECT_EQ(auto_login_util_mock_->GetAddToLoginItemsCalledCount(), 0);
 
   base::FilePath plist_path =
       shim_path_.Append("Contents").Append("Info.plist");
@@ -339,6 +383,22 @@
   EXPECT_TRUE(base::PathExists(destination_dir_));
 }
 
+TEST_F(WebAppShortcutCreatorTest, CreateShortcutsStartup) {
+  WebAppShortcutCreatorMock shortcut_creator(app_data_dir_, info_.get());
+
+  ShortcutLocations locations;
+  locations.in_startup = true;
+
+  auto_login_util_mock_->ResetCounts();
+  EXPECT_FALSE(base::PathExists(shim_path_));
+  EXPECT_CALL(shortcut_creator, RevealAppShimInFinder(_)).Times(0);
+  EXPECT_TRUE(
+      shortcut_creator.CreateShortcuts(SHORTCUT_CREATION_AUTOMATED, locations));
+  EXPECT_TRUE(base::PathExists(shim_path_));
+  EXPECT_EQ(auto_login_util_mock_->GetAddToLoginItemsCalledCount(), 1);
+  EXPECT_TRUE(base::DeletePathRecursively(shim_path_));
+}
+
 TEST_F(WebAppShortcutCreatorTest, NormalizeTitle) {
   NiceMock<WebAppShortcutCreatorMock> shortcut_creator(app_data_dir_,
                                                        info_.get());
@@ -454,10 +514,15 @@
   // Ensure the paths were created, and that they are destroyed.
   EXPECT_TRUE(base::PathExists(shim_path_));
   EXPECT_TRUE(base::PathExists(other_shim_path));
+  auto_login_util_mock_->ResetCounts();
   internals::DeleteMultiProfileShortcutsForApp(info_->extension_id);
+  EXPECT_EQ(auto_login_util_mock_->GetRemoveFromLoginItemsCalledCount(), 0);
+
   EXPECT_TRUE(base::PathExists(shim_path_));
   EXPECT_TRUE(base::PathExists(other_shim_path));
+  auto_login_util_mock_->ResetCounts();
   internals::DeletePlatformShortcuts(app_data_dir_, *info_);
+  EXPECT_EQ(auto_login_util_mock_->GetRemoveFromLoginItemsCalledCount(), 2);
   EXPECT_FALSE(base::PathExists(shim_path_));
   EXPECT_FALSE(base::PathExists(other_shim_path));
 }
@@ -481,10 +546,14 @@
   // Ensure the paths were created, and that they are destroyed.
   EXPECT_TRUE(base::PathExists(shim_path_));
   EXPECT_TRUE(base::PathExists(other_shim_path));
+  auto_login_util_mock_->ResetCounts();
   internals::DeletePlatformShortcuts(app_data_dir_, *info_);
+  EXPECT_EQ(auto_login_util_mock_->GetRemoveFromLoginItemsCalledCount(), 0);
   EXPECT_TRUE(base::PathExists(shim_path_));
   EXPECT_TRUE(base::PathExists(other_shim_path));
+  auto_login_util_mock_->ResetCounts();
   internals::DeleteMultiProfileShortcutsForApp(info_->extension_id);
+  EXPECT_EQ(auto_login_util_mock_->GetRemoveFromLoginItemsCalledCount(), 2);
   EXPECT_FALSE(base::PathExists(shim_path_));
   EXPECT_FALSE(base::PathExists(other_shim_path));
 }
@@ -503,10 +572,14 @@
                                                ShortcutLocations()));
   EXPECT_TRUE(base::PathExists(shim_path_));
 
+  auto_login_util_mock_->ResetCounts();
   internals::DeleteAllShortcutsForProfile(other_profile_path);
+  EXPECT_EQ(auto_login_util_mock_->GetRemoveFromLoginItemsCalledCount(), 0);
   EXPECT_TRUE(base::PathExists(shim_path_));
 
+  auto_login_util_mock_->ResetCounts();
   internals::DeleteAllShortcutsForProfile(profile_path);
+  EXPECT_EQ(auto_login_util_mock_->GetRemoveFromLoginItemsCalledCount(), 1);
   EXPECT_FALSE(base::PathExists(shim_path_));
 }
 
@@ -582,4 +655,29 @@
   EXPECT_EQ(result, sorted);
 }
 
+TEST_F(WebAppShortcutCreatorTest, RemoveAppShimFromLoginItems) {
+  WebAppShortcutCreatorMock shortcut_creator(app_data_dir_, info_.get());
+
+  ShortcutLocations locations;
+  locations.in_startup = true;
+
+  auto_login_util_mock_->ResetCounts();
+  EXPECT_FALSE(base::PathExists(shim_path_));
+  EXPECT_CALL(shortcut_creator, RevealAppShimInFinder(_)).Times(0);
+  EXPECT_TRUE(
+      shortcut_creator.CreateShortcuts(SHORTCUT_CREATION_AUTOMATED, locations));
+  EXPECT_TRUE(base::PathExists(shim_path_));
+  EXPECT_EQ(auto_login_util_mock_->GetAddToLoginItemsCalledCount(), 1);
+
+  auto_login_util_mock_->ResetCounts();
+  RemoveAppShimFromLoginItems("does-not-exist-app");
+  EXPECT_EQ(auto_login_util_mock_->GetRemoveFromLoginItemsCalledCount(), 0);
+
+  auto_login_util_mock_->ResetCounts();
+  RemoveAppShimFromLoginItems(info_->extension_id);
+  EXPECT_EQ(auto_login_util_mock_->GetRemoveFromLoginItemsCalledCount(), 1);
+
+  EXPECT_TRUE(base::DeletePathRecursively(shim_path_));
+}
+
 }  // namespace web_app
diff --git a/chrome/build/linux.pgo.txt b/chrome/build/linux.pgo.txt
index dcdf088..54b287e24 100644
--- a/chrome/build/linux.pgo.txt
+++ b/chrome/build/linux.pgo.txt
@@ -1 +1 @@
-chrome-linux-master-1610474333-25cc4688b5885ba387e87620f7adfe156a967401.profdata
+chrome-linux-master-1610495996-ac73e18e1b2efb1cd1bfbf2a2aaf90e9e3476b5a.profdata
diff --git a/chrome/build/mac.pgo.txt b/chrome/build/mac.pgo.txt
index 7e608c9..b392018b 100644
--- a/chrome/build/mac.pgo.txt
+++ b/chrome/build/mac.pgo.txt
@@ -1 +1 @@
-chrome-mac-master-1610474333-6e9ea86fde1168dfc05bd82ef581aacfe157db52.profdata
+chrome-mac-master-1610495996-38f3d5c57ab4be3d52eaefe38d45ce16ec0addd8.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index a081a0a..4f696ae 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-master-1610474333-c40f8988181199af819c164a2a0a3aef267d7bde.profdata
+chrome-win32-master-1610495996-5598384ad8a00037e18a56fd00981ea765e31cd1.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index c978b2c..435afb8 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-master-1610474333-dd150f7e1f8853577c8d1e97bbf4737322a5d0b4.profdata
+chrome-win64-master-1610495996-7189a1d14c9908600535ebce9352fe55a59722e6.profdata
diff --git a/chrome/renderer/BUILD.gn b/chrome/renderer/BUILD.gn
index f1302b65b..66d90eb 100644
--- a/chrome/renderer/BUILD.gn
+++ b/chrome/renderer/BUILD.gn
@@ -161,7 +161,6 @@
     "//components/content_settings/renderer",
     "//components/contextual_search/content:renderer",
     "//components/data_reduction_proxy/core/common",
-    "//components/data_reduction_proxy/proto:subresource_redirect_proto",
     "//components/dom_distiller/content/renderer",
     "//components/network_hints/renderer",
     "//components/no_state_prefetch/common",
@@ -185,6 +184,8 @@
     "//components/spellcheck:buildflags",
     "//components/subresource_filter/content/renderer",
     "//components/subresource_filter/core/common",
+    "//components/subresource_redirect/common",
+    "//components/subresource_redirect/proto",
     "//components/sync/driver",
     "//components/translate/content/renderer",
     "//components/translate/core/common",
diff --git a/chrome/renderer/DEPS b/chrome/renderer/DEPS
index df1b550..96fbe68d 100644
--- a/chrome/renderer/DEPS
+++ b/chrome/renderer/DEPS
@@ -51,6 +51,7 @@
   "+components/subresource_filter/content/common",
   "+components/subresource_filter/content/renderer",
   "+components/subresource_filter/core/common/common_features.h",
+  "+components/subresource_redirect/common",
   "+components/sync/driver/sync_driver_switches.h",
   "+components/sync/engine/sync_engine_switches.h",
   "+components/translate/content/common",
diff --git a/chrome/renderer/chrome_content_renderer_client.cc b/chrome/renderer/chrome_content_renderer_client.cc
index b5b5090..9b410da 100644
--- a/chrome/renderer/chrome_content_renderer_client.cc
+++ b/chrome/renderer/chrome_content_renderer_client.cc
@@ -101,6 +101,7 @@
 #include "components/subresource_filter/content/renderer/subresource_filter_agent.h"
 #include "components/subresource_filter/content/renderer/unverified_ruleset_dealer.h"
 #include "components/subresource_filter/core/common/common_features.h"
+#include "components/subresource_redirect/common/subresource_redirect_features.h"
 #include "components/sync/engine/sync_engine_switches.h"
 #include "components/translate/content/renderer/per_frame_translate_agent.h"
 #include "components/translate/core/common/translate_util.h"
@@ -604,10 +605,11 @@
 
   new previews::ResourceLoadingHintsAgent(associated_interfaces, render_frame);
 
-  if (subresource_redirect::IsPublicImageHintsBasedCompressionEnabled()) {
+  if (subresource_redirect::ShouldEnablePublicImageHintsBasedCompression()) {
     new subresource_redirect::PublicImageHintsDeciderAgent(
         associated_interfaces, render_frame);
-  } else if (subresource_redirect::IsLoginRobotsCheckedCompressionEnabled()) {
+  } else if (subresource_redirect::
+                 ShouldEnableLoginRobotsCheckedCompression()) {
     new subresource_redirect::LoginRobotsDeciderAgent(associated_interfaces,
                                                       render_frame);
   }
diff --git a/chrome/renderer/subresource_redirect/DEPS b/chrome/renderer/subresource_redirect/DEPS
index 054120c..1a04cce 100644
--- a/chrome/renderer/subresource_redirect/DEPS
+++ b/chrome/renderer/subresource_redirect/DEPS
@@ -1,6 +1,20 @@
 include_rules = [
   "+components/base32",
   "+services/metrics/public/cpp",
-  "+components/data_reduction_proxy/proto/robots_rules.pb.h",
+  "+components/subresource_redirect",
+  "+components/subresource_redirect/proto/robots_rules.pb.h",
   "+services/network/test/test_utils.h",
 ]
+
+# These are browser tests that access both code from the browser and the
+# renderer.
+specific_include_rules = {
+  "subresource_redirect_renderer_browsertest\.cc": [
+    "+chrome/browser/data_reduction_proxy",
+    "+chrome/browser/login_detection",
+    "+chrome/browser/profiles",
+    "+chrome/browser/subresource_redirect",
+    "+chrome/browser/ui",
+    "+components/subresource_redirect",
+  ],
+}
diff --git a/chrome/renderer/subresource_redirect/login_robots_decider_agent.cc b/chrome/renderer/subresource_redirect/login_robots_decider_agent.cc
index dea688c..9054a33ae 100644
--- a/chrome/renderer/subresource_redirect/login_robots_decider_agent.cc
+++ b/chrome/renderer/subresource_redirect/login_robots_decider_agent.cc
@@ -12,6 +12,7 @@
 #include "chrome/renderer/subresource_redirect/robots_rules_parser.h"
 #include "chrome/renderer/subresource_redirect/robots_rules_parser_cache.h"
 #include "chrome/renderer/subresource_redirect/subresource_redirect_params.h"
+#include "components/subresource_redirect/common/subresource_redirect_features.h"
 #include "content/public/renderer/render_frame.h"
 
 namespace subresource_redirect {
@@ -52,7 +53,7 @@
     blink::AssociatedInterfaceRegistry* associated_interfaces,
     content::RenderFrame* render_frame)
     : PublicResourceDeciderAgent(associated_interfaces, render_frame) {
-  DCHECK(IsLoginRobotsCheckedCompressionEnabled());
+  DCHECK(ShouldEnableLoginRobotsCheckedCompression());
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
 }
 
@@ -136,7 +137,7 @@
     mojom::CompressPublicImagesHintsPtr images_hints) {
   // This mojo from browser process should not be called for robots rules based
   // subresource compression on non logged-in pages.
-  DCHECK(IsLoginRobotsCheckedCompressionEnabled());
+  DCHECK(ShouldEnableLoginRobotsCheckedCompression());
   NOTREACHED();
 }
 
diff --git a/chrome/renderer/subresource_redirect/login_robots_decider_agent_browsertest.cc b/chrome/renderer/subresource_redirect/login_robots_decider_agent_browsertest.cc
index 9fdd1b46..3b7afdd7 100644
--- a/chrome/renderer/subresource_redirect/login_robots_decider_agent_browsertest.cc
+++ b/chrome/renderer/subresource_redirect/login_robots_decider_agent_browsertest.cc
@@ -5,11 +5,11 @@
 #include "base/memory/weak_ptr.h"
 #include "base/test/scoped_feature_list.h"
 #include "chrome/renderer/subresource_redirect/login_robots_decider_agent.h"
-#include "chrome/renderer/subresource_redirect/login_robots_decider_test_util.h"
 #include "chrome/renderer/subresource_redirect/subresource_redirect_url_loader_throttle.h"
 #include "chrome/renderer/subresource_redirect/subresource_redirect_util.h"
 #include "chrome/test/base/chrome_render_view_test.h"
 #include "chrome/test/base/ui_test_utils.h"
+#include "components/subresource_redirect/subresource_redirect_test_util.h"
 #include "content/public/renderer/render_frame.h"
 #include "content/public/renderer/render_view.h"
 #include "services/network/public/cpp/resource_request.h"
@@ -53,7 +53,7 @@
     : public ChromeRenderViewTest {
  public:
   void SetUpRobotsRules(const std::string& origin,
-                        const std::vector<Rule>& patterns) {
+                        const std::vector<RobotsRule>& patterns) {
     login_robots_decider_agent_->UpdateRobotsRulesForTesting(
         url::Origin::Create(GURL(origin)), GetRobotsRulesProtoString(patterns));
   }
diff --git a/chrome/renderer/subresource_redirect/login_robots_decider_test_util.h b/chrome/renderer/subresource_redirect/login_robots_decider_test_util.h
deleted file mode 100644
index 439e0cb0..0000000
--- a/chrome/renderer/subresource_redirect/login_robots_decider_test_util.h
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_RENDERER_SUBRESOURCE_REDIRECT_LOGIN_ROBOTS_DECIDER_TEST_UTIL_H_
-#define CHROME_RENDERER_SUBRESOURCE_REDIRECT_LOGIN_ROBOTS_DECIDER_TEST_UTIL_H_
-
-#include <string>
-
-namespace subresource_redirect {
-
-const bool kRuleTypeAllow = true;
-const bool kRuleTypeDisallow = false;
-
-struct Rule {
-  Rule(bool rule_type, std::string pattern)
-      : rule_type(rule_type), pattern(pattern) {}
-
-  bool rule_type;
-  std::string pattern;
-};
-
-std::string GetRobotsRulesProtoString(const std::vector<Rule>& patterns);
-
-}  // namespace subresource_redirect
-
-#endif  // CHROME_RENDERER_SUBRESOURCE_REDIRECT_LOGIN_ROBOTS_DECIDER_TEST_UTIL_H_
diff --git a/chrome/renderer/subresource_redirect/login_robots_url_loader_throttle_browsertest.cc b/chrome/renderer/subresource_redirect/login_robots_url_loader_throttle_browsertest.cc
index b9e5fd7..36b7f0f9 100644
--- a/chrome/renderer/subresource_redirect/login_robots_url_loader_throttle_browsertest.cc
+++ b/chrome/renderer/subresource_redirect/login_robots_url_loader_throttle_browsertest.cc
@@ -5,11 +5,11 @@
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
 #include "chrome/renderer/subresource_redirect/login_robots_decider_agent.h"
-#include "chrome/renderer/subresource_redirect/login_robots_decider_test_util.h"
 #include "chrome/renderer/subresource_redirect/subresource_redirect_url_loader_throttle.h"
 #include "chrome/renderer/subresource_redirect/subresource_redirect_util.h"
 #include "chrome/test/base/chrome_render_view_test.h"
 #include "chrome/test/base/ui_test_utils.h"
+#include "components/subresource_redirect/subresource_redirect_test_util.h"
 #include "content/public/renderer/render_frame.h"
 #include "content/public/renderer/render_view.h"
 #include "services/network/public/cpp/resource_request.h"
@@ -118,7 +118,7 @@
   }
 
   void SetUpRobotsRules(const std::string& origin,
-                        const std::vector<Rule>& patterns) {
+                        const std::vector<RobotsRule>& patterns) {
     login_robots_decider_agent_->UpdateRobotsRulesForTesting(
         url::Origin::Create(GURL(origin)), GetRobotsRulesProtoString(patterns));
   }
diff --git a/chrome/renderer/subresource_redirect/public_image_hints_decider_agent.cc b/chrome/renderer/subresource_redirect/public_image_hints_decider_agent.cc
index 39eee6f..5999e043 100644
--- a/chrome/renderer/subresource_redirect/public_image_hints_decider_agent.cc
+++ b/chrome/renderer/subresource_redirect/public_image_hints_decider_agent.cc
@@ -6,6 +6,7 @@
 
 #include "base/metrics/field_trial_params.h"
 #include "chrome/renderer/subresource_redirect/subresource_redirect_params.h"
+#include "components/subresource_redirect/common/subresource_redirect_features.h"
 #include "content/public/renderer/render_frame.h"
 #include "content/public/renderer/render_thread.h"
 #include "services/metrics/public/cpp/metrics_utils.h"
@@ -34,7 +35,7 @@
     blink::AssociatedInterfaceRegistry* associated_interfaces,
     content::RenderFrame* render_frame)
     : PublicResourceDeciderAgent(associated_interfaces, render_frame) {
-  DCHECK(IsPublicImageHintsBasedCompressionEnabled());
+  DCHECK(ShouldEnablePublicImageHintsBasedCompression());
 }
 
 PublicImageHintsDeciderAgent::~PublicImageHintsDeciderAgent() = default;
diff --git a/chrome/renderer/subresource_redirect/robots_rules_parser.cc b/chrome/renderer/subresource_redirect/robots_rules_parser.cc
index f8aa4d81..aec3c36 100644
--- a/chrome/renderer/subresource_redirect/robots_rules_parser.cc
+++ b/chrome/renderer/subresource_redirect/robots_rules_parser.cc
@@ -12,7 +12,7 @@
 #include "base/time/time.h"
 #include "base/timer/elapsed_timer.h"
 #include "chrome/renderer/subresource_redirect/subresource_redirect_params.h"
-#include "components/data_reduction_proxy/proto/robots_rules.pb.h"
+#include "components/subresource_redirect/proto/robots_rules.pb.h"
 
 namespace subresource_redirect {
 
diff --git a/chrome/renderer/subresource_redirect/robots_rules_parser_unittest.cc b/chrome/renderer/subresource_redirect/robots_rules_parser_unittest.cc
index 8df3d73..2ae736a 100644
--- a/chrome/renderer/subresource_redirect/robots_rules_parser_unittest.cc
+++ b/chrome/renderer/subresource_redirect/robots_rules_parser_unittest.cc
@@ -9,9 +9,9 @@
 #include "base/test/scoped_feature_list.h"
 #include "base/test/task_environment.h"
 #include "base/time/time.h"
-#include "chrome/renderer/subresource_redirect/login_robots_decider_test_util.h"
 #include "chrome/renderer/subresource_redirect/robots_rules_parser.h"
-#include "components/data_reduction_proxy/proto/robots_rules.pb.h"
+#include "components/subresource_redirect/proto/robots_rules.pb.h"
+#include "components/subresource_redirect/subresource_redirect_test_util.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/common/features.h"
 
@@ -50,7 +50,7 @@
         {{blink::features::kSubresourceRedirect, {{}}}}, {});
   }
 
-  void SetUpRobotsRules(const std::vector<Rule>& patterns) {
+  void SetUpRobotsRules(const std::vector<RobotsRule>& patterns) {
     robots_rules_parser_.UpdateRobotsRules(GetRobotsRulesProtoString(patterns));
     VerifyRulesReceiveState(RobotsRulesParser::RulesReceiveState::kSuccess);
   }
diff --git a/chrome/renderer/subresource_redirect/subresource_redirect_params.cc b/chrome/renderer/subresource_redirect/subresource_redirect_params.cc
index 4fc8f4d..d8626561 100644
--- a/chrome/renderer/subresource_redirect/subresource_redirect_params.cc
+++ b/chrome/renderer/subresource_redirect/subresource_redirect_params.cc
@@ -17,9 +17,6 @@
 // Default timeout for the hints to be received from the time navigation starts.
 const int64_t kHintsReceiveDefaultTimeoutSeconds = 5;
 
-bool IsSubresourceRedirectEnabled() {
-  return base::FeatureList::IsEnabled(blink::features::kSubresourceRedirect);
-}
 }  // namespace
 
 url::Origin GetSubresourceRedirectOrigin() {
@@ -30,34 +27,6 @@
   return url::Origin::Create(GURL(lite_page_subresource_origin));
 }
 
-bool IsPublicImageHintsBasedCompressionEnabled() {
-  bool is_enabled = IsSubresourceRedirectEnabled() &&
-                    base::GetFieldTrialParamByFeatureAsBool(
-                        blink::features::kSubresourceRedirect,
-                        "enable_public_image_hints_based_compression", true);
-  // Only one of the public image hints or login and robots based image
-  // compression should be active.
-  DCHECK(!is_enabled || !IsLoginRobotsCheckedCompressionEnabled());
-  return is_enabled;
-}
-
-bool IsLoginRobotsCheckedCompressionEnabled() {
-  bool is_enabled = IsSubresourceRedirectEnabled() &&
-                    base::GetFieldTrialParamByFeatureAsBool(
-                        blink::features::kSubresourceRedirect,
-                        "enable_login_robots_based_compression", false);
-  // Only one of the public image hints or login and robots based image
-  // compression should be active.
-  DCHECK(!is_enabled || !IsPublicImageHintsBasedCompressionEnabled());
-  return is_enabled;
-}
-
-bool ShouldCompressionServerRedirectSubresource() {
-  return base::GetFieldTrialParamByFeatureAsBool(
-      blink::features::kSubresourceRedirect,
-      "enable_subresource_server_redirect", true);
-}
-
 base::TimeDelta GetCompressionRedirectTimeout() {
   return base::TimeDelta::FromMilliseconds(
       base::GetFieldTrialParamByFeatureAsInt(
diff --git a/chrome/renderer/subresource_redirect/subresource_redirect_params.h b/chrome/renderer/subresource_redirect/subresource_redirect_params.h
index d63cc3c..1ff032aa 100644
--- a/chrome/renderer/subresource_redirect/subresource_redirect_params.h
+++ b/chrome/renderer/subresource_redirect/subresource_redirect_params.h
@@ -16,19 +16,6 @@
 // default.
 url::Origin GetSubresourceRedirectOrigin();
 
-// Returns if the public image hints based subresource compression is enabled.
-bool IsPublicImageHintsBasedCompressionEnabled();
-
-// Returns if the login and robots checks based subresource compression is
-// enabled. This compresses non logged-in pages and subresources allowed by
-// robots.txt rules.
-bool IsLoginRobotsCheckedCompressionEnabled();
-
-// Should the subresource be redirected to its compressed version. This returns
-// false if only coverage metrics need to be recorded and actual redirection
-// should not happen.
-bool ShouldCompressionServerRedirectSubresource();
-
 // Returns the timeout for the compressed subresource redirect, after which the
 // subresource should be fetched directly from the origin.
 base::TimeDelta GetCompressionRedirectTimeout();
diff --git a/chrome/renderer/subresource_redirect/subresource_redirect_renderer_browsertest.cc b/chrome/renderer/subresource_redirect/subresource_redirect_renderer_browsertest.cc
new file mode 100644
index 0000000..f809f2d
--- /dev/null
+++ b/chrome/renderer/subresource_redirect/subresource_redirect_renderer_browsertest.cc
@@ -0,0 +1,186 @@
+// 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 "base/test/metrics/histogram_tester.h"
+#include "base/test/scoped_feature_list.h"
+#include "build/build_config.h"
+#include "chrome/browser/data_reduction_proxy/data_reduction_proxy_chrome_settings.h"
+#include "chrome/browser/data_reduction_proxy/data_reduction_proxy_chrome_settings_factory.h"
+#include "chrome/browser/login_detection/login_detection_type.h"
+#include "chrome/browser/login_detection/login_detection_util.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/subresource_redirect/https_image_compression_infobar_decider.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/renderer/subresource_redirect/redirect_result.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "components/subresource_redirect/subresource_redirect_browser_test_util.h"
+#include "components/subresource_redirect/subresource_redirect_test_util.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "net/http/http_status_code.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "third_party/blink/public/common/features.h"
+
+namespace subresource_redirect {
+
+// Test class that sets up logged-in sites from field trial.
+class SubresourceRedirectLoggedInSitesBrowserTest
+    : public InProcessBrowserTest {
+ public:
+  SubresourceRedirectLoggedInSitesBrowserTest()
+      : https_test_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}
+
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    command_line->AppendSwitchASCII("host-rules", "MAP * 127.0.0.1");
+    command_line->AppendSwitch("enable-spdy-proxy-auth");
+
+    // Disable infobar shown check to actually compress the pages.
+    command_line->AppendSwitch("override-https-image-compression-infobar");
+  }
+
+  void SetUp() override {
+    ASSERT_TRUE(robots_rules_server_.Start());
+    ASSERT_TRUE(image_compression_server_.Start());
+    https_test_server_.ServeFilesFromSourceDirectory("chrome/test/data");
+    ASSERT_TRUE(https_test_server_.Start());
+
+    std::vector<base::test::ScopedFeatureList::FeatureAndParams>
+        enabled_features;
+    base::FieldTrialParams params, login_detection_params;
+    params["enable_public_image_hints_based_compression"] = "false";
+    params["enable_login_robots_based_compression"] = "true";
+    params["lite_page_robots_origin"] = robots_rules_server_.GetURL();
+    params["lite_page_subresource_origin"] = image_compression_server_.GetURL();
+    // This rules fetch timeout is chosen such that the tests would have
+    // enough time to fetch the rules without causing a timeout.
+    params["robots_rules_receive_timeout"] = "1000";
+    enabled_features.emplace_back(blink::features::kSubresourceRedirect,
+                                  params);
+
+    login_detection_params["logged_in_sites"] = "https://loggedin.com";
+    enabled_features.emplace_back(login_detection::kLoginDetection,
+                                  login_detection_params);
+
+    scoped_feature_list_.InitWithFeaturesAndParameters(enabled_features, {});
+    InProcessBrowserTest::SetUp();
+  }
+
+  GURL GetHttpsTestURL(const std::string& path) const {
+    return https_test_server_.GetURL("test_https_server.com", path);
+  }
+
+  void NavigateAndWaitForLoad(Browser* browser, const GURL& url) {
+    ui_test_utils::NavigateToURL(browser, url);
+    EXPECT_EQ(true, EvalJs(browser->tab_strip_model()->GetActiveWebContents(),
+                           "checkImage()"));
+    FetchHistogramsFromChildProcesses();
+  }
+
+  bool RunScriptExtractBool(const std::string& script,
+                            content::WebContents* web_contents = nullptr) {
+    if (!web_contents)
+      web_contents = browser()->tab_strip_model()->GetActiveWebContents();
+    return EvalJs(web_contents, script).ExtractBool();
+  }
+
+ protected:
+  base::test::ScopedFeatureList scoped_feature_list_;
+
+  // Simulates the LitePages servers that return the robots rules and compress
+  // images.
+  RobotsRulesTestServer robots_rules_server_;
+  ImageCompressionTestServer image_compression_server_;
+  net::EmbeddedTestServer https_test_server_;
+
+  base::HistogramTester histogram_tester_;
+};
+
+// Enable tests for linux since LiteMode is enabled only for Android.
+#if defined(OS_WIN) || defined(OS_MAC) || defined(OS_CHROMEOS)
+#define DISABLE_ON_WIN_MAC_CHROMEOS(x) DISABLED_##x
+#else
+#define DISABLE_ON_WIN_MAC_CHROMEOS(x) x
+#endif
+
+// Verify that when image load gets canceled due to subsequent page load, the
+// subresource redirect for the image is canceled as well.
+IN_PROC_BROWSER_TEST_F(SubresourceRedirectLoggedInSitesBrowserTest,
+                       DISABLE_ON_WIN_MAC_CHROMEOS(TestCancelBeforeImageLoad)) {
+  robots_rules_server_.set_failure_mode(
+      RobotsRulesTestServer::FailureMode::kTimeout);
+  robots_rules_server_.AddRobotsRules(GetHttpsTestURL("/"),
+                                      {{kRuleTypeAllow, ""}});
+
+  ui_test_utils::NavigateToURL(browser(),
+                               GetHttpsTestURL("/load_image/image.html"));
+
+  // Wait for the image request to start and its robots rules to be requested.
+  while (robots_rules_server_.received_requests().empty()) {
+    base::RunLoop().RunUntilIdle();
+  }
+
+  ui_test_utils::NavigateToURL(browser(),
+                               GetHttpsTestURL("/load_image/simple.html"));
+  FetchHistogramsFromChildProcesses();
+
+  RetryForHistogramUntilCountReached(
+      &histogram_tester_,
+      "SubresourceRedirect.LoginRobotsDeciderAgent.RedirectResult", 1);
+  histogram_tester_.ExpectUniqueSample(
+      "SubresourceRedirect.LoginRobotsDeciderAgent.RedirectResult",
+      RedirectResult::kIneligibleRobotsTimeout, 1);
+  histogram_tester_.ExpectUniqueSample(
+      "SubresourceRedirect.CompressionAttempt.ResponseCode",
+      net::HTTP_TEMPORARY_REDIRECT, 1);
+  histogram_tester_.ExpectTotalCount(
+      "SubresourceRedirect.CompressionAttempt.ServerResponded", 0);
+
+  robots_rules_server_.VerifyRequestedOrigins({GetHttpsTestURL("/").spec()});
+  image_compression_server_.VerifyRequestedImagePaths({});
+}
+
+// Verify that when image load gets canceled due to subsequent navigation to a
+// logged-in page, the subresource redirect for the image is disabled as well.
+IN_PROC_BROWSER_TEST_F(
+    SubresourceRedirectLoggedInSitesBrowserTest,
+    DISABLE_ON_WIN_MAC_CHROMEOS(TestCancelBeforeImageLoadForLoggedInSite)) {
+  robots_rules_server_.set_failure_mode(
+      RobotsRulesTestServer::FailureMode::kTimeout);
+  robots_rules_server_.AddRobotsRules(GetHttpsTestURL("/"),
+                                      {{kRuleTypeAllow, ""}});
+
+  ui_test_utils::NavigateToURL(browser(),
+                               GetHttpsTestURL("/load_image/image.html"));
+
+  // Wait for the image request to start and its robots rules to be requested.
+  while (robots_rules_server_.received_requests().empty()) {
+    base::RunLoop().RunUntilIdle();
+  }
+
+  ui_test_utils::NavigateToURL(
+      browser(),
+      https_test_server_.GetURL("loggedin.com", "/load_image/simple.html"));
+  FetchHistogramsFromChildProcesses();
+  histogram_tester_.ExpectBucketCount(
+      "Login.PageLoad.DetectionType",
+      login_detection::LoginDetectionType::kFieldTrialLoggedInSite, 1);
+
+  RetryForHistogramUntilCountReached(
+      &histogram_tester_,
+      "SubresourceRedirect.LoginRobotsDeciderAgent.RedirectResult", 1);
+  histogram_tester_.ExpectUniqueSample(
+      "SubresourceRedirect.LoginRobotsDeciderAgent.RedirectResult",
+      RedirectResult::kIneligibleRobotsTimeout, 1);
+  histogram_tester_.ExpectUniqueSample(
+      "SubresourceRedirect.CompressionAttempt.ResponseCode",
+      net::HTTP_TEMPORARY_REDIRECT, 1);
+  histogram_tester_.ExpectTotalCount(
+      "SubresourceRedirect.CompressionAttempt.ServerResponded", 0);
+
+  robots_rules_server_.VerifyRequestedOrigins({GetHttpsTestURL("/").spec()});
+  image_compression_server_.VerifyRequestedImagePaths({});
+}
+
+}  // namespace subresource_redirect
diff --git a/chrome/renderer/subresource_redirect/subresource_redirect_url_loader_throttle.cc b/chrome/renderer/subresource_redirect/subresource_redirect_url_loader_throttle.cc
index a4d4790..7fdff6c 100644
--- a/chrome/renderer/subresource_redirect/subresource_redirect_url_loader_throttle.cc
+++ b/chrome/renderer/subresource_redirect/subresource_redirect_url_loader_throttle.cc
@@ -13,6 +13,7 @@
 #include "chrome/renderer/subresource_redirect/subresource_redirect_params.h"
 #include "chrome/renderer/subresource_redirect/subresource_redirect_util.h"
 #include "components/data_reduction_proxy/core/common/data_reduction_proxy_headers.h"
+#include "components/subresource_redirect/common/subresource_redirect_features.h"
 #include "content/public/renderer/render_frame.h"
 #include "net/base/escape.h"
 #include "net/base/load_flags.h"
@@ -50,8 +51,8 @@
 SubresourceRedirectURLLoaderThrottle::MaybeCreateThrottle(
     const blink::WebURLRequest& request,
     int render_frame_id) {
-  if (!IsPublicImageHintsBasedCompressionEnabled() &&
-      !IsLoginRobotsCheckedCompressionEnabled()) {
+  if (!ShouldEnablePublicImageHintsBasedCompression() &&
+      !ShouldEnableLoginRobotsCheckedCompression()) {
     return nullptr;
   }
   if (request.GetRequestDestination() ==
@@ -72,8 +73,8 @@
     int render_frame_id,
     bool allowed_to_redirect)
     : render_frame_id_(render_frame_id) {
-  DCHECK(IsPublicImageHintsBasedCompressionEnabled() ||
-         IsLoginRobotsCheckedCompressionEnabled());
+  DCHECK(ShouldEnablePublicImageHintsBasedCompression() ||
+         ShouldEnableLoginRobotsCheckedCompression());
   redirect_result_ = allowed_to_redirect
                          ? RedirectResult::kRedirectable
                          : RedirectResult::kIneligibleBlinkDisallowed;
@@ -95,7 +96,7 @@
   if (IsCompressionServerOrigin(request->url))
     return;
 
-  if (!ShouldCompressionServerRedirectSubresource())
+  if (!ShouldCompressRedirectSubresource())
     return;
 
   auto* public_resource_decider_agent =
@@ -188,7 +189,7 @@
          redirect_state_ == RedirectState::kRedirectFailed);
   if (redirect_state_ != RedirectState::kRedirectAttempted)
     return;
-  DCHECK(ShouldCompressionServerRedirectSubresource());
+  DCHECK(ShouldCompressRedirectSubresource());
   // If response was not from the compression server, don't restart it.
   if (!response_url.is_valid())
     return;
@@ -261,7 +262,7 @@
 
   if (redirect_state_ != RedirectState::kRedirectAttempted)
     return;
-  DCHECK(ShouldCompressionServerRedirectSubresource());
+  DCHECK(ShouldCompressRedirectSubresource());
 
   // Record that the server responded.
   UMA_HISTOGRAM_BOOLEAN(
@@ -292,7 +293,7 @@
     bool* defer) {
   if (redirect_state_ != RedirectState::kRedirectAttempted)
     return;
-  DCHECK(ShouldCompressionServerRedirectSubresource());
+  DCHECK(ShouldCompressRedirectSubresource());
   redirect_result_ = RedirectResult::kIneligibleRedirectFailed;
 
   // If the server fails, restart the request to the original resource, and
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 7af02fa..f37bd0cf 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -1576,10 +1576,9 @@
       "../renderer/chrome_render_frame_observer_browsertest.cc",
       "../renderer/lite_video/lite_video_hint_agent_browsertest.cc",
       "../renderer/subresource_redirect/login_robots_decider_agent_browsertest.cc",
-      "../renderer/subresource_redirect/login_robots_decider_test_util.cc",
-      "../renderer/subresource_redirect/login_robots_decider_test_util.h",
       "../renderer/subresource_redirect/login_robots_url_loader_throttle_browsertest.cc",
       "../renderer/subresource_redirect/public_image_hints_decider_agent_browsertest.cc",
+      "../renderer/subresource_redirect/subresource_redirect_renderer_browsertest.cc",
       "../renderer/translate/per_frame_translate_agent_browsertest.cc",
       "../renderer/translate/translate_agent_browsertest.cc",
       "../renderer/translate/translate_script_browsertest.cc",
@@ -1910,6 +1909,8 @@
       }
     }
 
+    deps += [ "//components/subresource_redirect:test_support" ]
+
     if (enable_extensions) {
       sources += [
         "../browser/apps/platform_apps/api/browser/browser_apitest.cc",
@@ -2887,6 +2888,7 @@
         deps += [
           "//chrome/browser/ui/ash/assistant/test_support",
           "//chromeos/assistant/internal:internal",
+          "//chromeos/assistant/test_support",
         ]
 
         data += [ "//chromeos/assistant/internal/test_data/" ]
@@ -3849,8 +3851,6 @@
     "../renderer/media/flash_embed_rewrite_unittest.cc",
     "../renderer/net/net_error_helper_core_unittest.cc",
     "../renderer/plugins/plugin_uma_unittest.cc",
-    "../renderer/subresource_redirect/login_robots_decider_test_util.cc",
-    "../renderer/subresource_redirect/login_robots_decider_test_util.h",
     "../renderer/subresource_redirect/robots_rules_parser_unittest.cc",
     "../renderer/subresource_redirect/subresource_redirect_util_unittest.cc",
     "../renderer/v8_unwinder_unittest.cc",
@@ -4230,6 +4230,7 @@
     "//components/spellcheck:buildflags",
     "//components/strings",
     "//components/subresource_filter/core/browser:test_support",
+    "//components/subresource_redirect:test_support",
     "//components/sync:test_support",
     "//components/sync_device_info:test_support",
     "//components/sync_sessions:test_support",
@@ -4939,7 +4940,6 @@
   if (is_chromeos_lacros) {
     assert(enable_native_notifications)
     sources += [
-      "../browser/lacros/account_manager_facade_lacros_unittest.cc",
       "../browser/lacros/client_cert_store_lacros_unittest.cc",
       "../browser/lacros/lacros_chrome_service_delegate_impl_unittest.cc",
       "../browser/lacros/metrics_reporting_observer_unittest.cc",
diff --git a/chrome/test/android/javatests/src/org/chromium/chrome/test/batch/BlankCTATabInitialStateRule.java b/chrome/test/android/javatests/src/org/chromium/chrome/test/batch/BlankCTATabInitialStateRule.java
index 6f6ada94..510d56d 100644
--- a/chrome/test/android/javatests/src/org/chromium/chrome/test/batch/BlankCTATabInitialStateRule.java
+++ b/chrome/test/android/javatests/src/org/chromium/chrome/test/batch/BlankCTATabInitialStateRule.java
@@ -54,6 +54,9 @@
                             () -> { FirstRunStatus.setFirstRunFlowComplete(true); });
                     mActivityTestRule.startMainActivityOnBlankPage();
                     sActivity = mActivityTestRule.getActivity();
+
+                    // Previous tests may have left tabs open and finished the Activity.
+                    if (regularTabCount() > 1) resetTabStateFast();
                 } else {
                     mActivityTestRule.setActivity(sActivity);
                     if (shouldPerformFastReset()) {
@@ -68,15 +71,22 @@
                     // If the activity was relaunched during the test, update the reference to use
                     // the most up to date Activity.
                     sActivity = mActivityTestRule.getActivity();
+                    if (sActivity.isActivityFinishingOrDestroyed()) {
+                        sActivity = null;
+                    }
                 }
             }
         };
     }
 
+    private int regularTabCount() {
+        return TestThreadUtils.runOnUiThreadBlockingNoException(
+                () -> { return sActivity.getTabModelSelector().getModel(false).getCount(); });
+    }
+
     private boolean shouldPerformFastReset() {
         if (mClearAllTabState) return false;
-        return TestThreadUtils.runOnUiThreadBlockingNoException(
-                () -> { return sActivity.getTabModelSelector().getModel(false).getCount() > 0; });
+        return regularTabCount() > 0;
     }
 
     // Avoids closing the primary tab (and killing the renderer) in order to reset tab state
diff --git a/chrome/test/data/webui/resources/list_property_update_behavior_tests.js b/chrome/test/data/webui/resources/list_property_update_behavior_tests.js
index 8b571cd..a11be43 100644
--- a/chrome/test/data/webui/resources/list_property_update_behavior_tests.js
+++ b/chrome/test/data/webui/resources/list_property_update_behavior_tests.js
@@ -4,7 +4,7 @@
 
 /** @fileoverview Suite of tests for the ListPropertyUpdateBehavior.  */
 
-// #import {ListPropertyUpdateBehavior} from 'chrome://resources/js/list_property_update_behavior.m.js';
+// #import {ListPropertyUpdateBehavior, updateListProperty} from 'chrome://resources/js/list_property_update_behavior.m.js';
 // #import {Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 suite('ListPropertyUpdateBehavior', function() {
@@ -76,7 +76,7 @@
       updateComplexArray(newArray) {
         if (this.updateList(
                 'complexArray', x => x.letter, newArray,
-                true /* uidBasedUpdate */)) {
+                true /* identityBasedUpdate */)) {
           return {topArrayChanged: true, wordsArrayChanged: false};
         }
 
@@ -305,4 +305,48 @@
     assertTrue(testElement.updateList('complexArray', x => x.letter, newArray));
     assertDeepEquals(['apricot'], testElement.complexArray[0].words);
   });
+
+  test('updateListProperty() function triggers notifySplices()', () => {
+    // Ensure that the array is updated when an element is removed from the
+    // end.
+    let elementRemoved = testElement.complexArray.slice(0, 2);
+    updateListProperty(
+        testElement, 'complexArray', obj => obj, elementRemoved, true);
+    assertComplexArrayEquals(testElement.complexArray, elementRemoved);
+
+    // Ensure that the array is updated when an element is removed from the
+    // beginning.
+    testElement.resetComplexArray();
+    elementRemoved = testElement.complexArray.slice(1);
+    updateListProperty(
+        testElement, 'complexArray', obj => obj, elementRemoved, true);
+    assertComplexArrayEquals(testElement.complexArray, elementRemoved);
+
+    // Ensure that the array is updated when an element is added to the end.
+    testElement.resetComplexArray();
+    let elementAdded = testElement.complexArray.slice();
+    elementAdded.push({letter: 'd', words: ['door', 'dice']});
+    updateListProperty(
+        testElement, 'complexArray', obj => obj, elementAdded, true);
+    assertComplexArrayEquals(testElement.complexArray, elementAdded);
+
+    // Ensure that the array is updated when an element is added to the
+    // beginning.
+    testElement.resetComplexArray();
+    elementAdded = [{letter: 'A', words: ['Alphabet']}];
+    elementAdded.push(...testElement.complexArray);
+    updateListProperty(
+        testElement, 'complexArray', obj => obj, elementAdded, true);
+    assertComplexArrayEquals(testElement.complexArray, elementAdded);
+
+    // Ensure that the array is updated when the entire array is different.
+    testElement.resetComplexArray();
+    const newArray = [
+      {letter: 'w', words: ['water', 'woods']},
+      {letter: 'x', words: ['xylophone']}, {letter: 'y', words: ['yo-yo']},
+      {letter: 'z', words: ['zebra', 'zephyr']}
+    ];
+    updateListProperty(testElement, 'complexArray', obj => obj, newArray, true);
+    assertComplexArrayEquals(testElement.complexArray, newArray);
+  });
 });
diff --git a/chrome/test/data/webui/tab_search/infinite_list_test.js b/chrome/test/data/webui/tab_search/infinite_list_test.js
index f924aed..e36a04e9 100644
--- a/chrome/test/data/webui/tab_search/infinite_list_test.js
+++ b/chrome/test/data/webui/tab_search/infinite_list_test.js
@@ -87,6 +87,7 @@
   test('ScrollHeight', async () => {
     const tabItems = sampleTabItems(sampleSiteNames());
     await setupTest(tabItems);
+    await waitAfterNextRender(infiniteList);
 
     assertEquals(0, infiniteList.scrollTop);
 
@@ -99,6 +100,28 @@
     assertEquals(tabItemHeight * tabItems.length, infiniteList.scrollHeight);
   });
 
+  test('ListUpdates', async () => {
+    let siteNames = Array.from({length: 1}, (_, i) => 'site' + (i + 1));
+    const tabItems = sampleTabItems(siteNames);
+    await setupTest(tabItems);
+    assertEquals(1, queryRows().length);
+
+    // Ensure that on updating the list with an array smaller in size
+    // than the chunkItemCount property, all the array items are rendered.
+    siteNames = Array.from({length: 3}, (_, i) => 'site' + (i + 1));
+    infiniteList.items = sampleTabItems(siteNames);
+    await waitAfterNextRender(infiniteList);
+    assertEquals(3, queryRows().length);
+
+    // Ensure that on updating the list with an array greater in size than
+    // the chunkItemCount property, only a chunk of array items are rendered.
+    siteNames =
+        Array.from({length: 2 * CHUNK_ITEM_COUNT}, (_, i) => 'site' + (i + 1));
+    infiniteList.items = sampleTabItems(siteNames);
+    await waitAfterNextRender(infiniteList);
+    assertEquals(CHUNK_ITEM_COUNT, queryRows().length);
+  });
+
   test('SelectedIndex', async () => {
     const siteNames = Array.from({length: 50}, (_, i) => 'site' + (i + 1));
     const tabItems = sampleTabItems(siteNames);
diff --git a/chrome/updater/BUILD.gn b/chrome/updater/BUILD.gn
index b6477455..7a8085b 100644
--- a/chrome/updater/BUILD.gn
+++ b/chrome/updater/BUILD.gn
@@ -166,6 +166,7 @@
         "app/server/win/service_main.cc",
         "app/server/win/service_main.h",
         "external_constants_win.cc",
+        "installer_win.cc",
         "lib_util_win.cc",
         "prefs_win.cc",
         "service_factory_win.cc",
@@ -180,6 +181,7 @@
       ":version_header",
       "//base",
       "//base:i18n",
+      "//chrome/updater/device_management",
       "//components/crash/core/common:crash_key",
       "//components/crx_file:crx_file",
       "//components/prefs",
@@ -190,17 +192,13 @@
     ]
 
     if (is_win) {
-      # //chrome/updater:lib and //chrome/updater/win:lib behave as one unit
-      # for linking purposes. Targets depending on the latter must use
-      # the former as a dependency.
-      allow_circular_includes_from = [ "//chrome/updater/win:lib" ]
       deps += [
         "//chrome/updater/app/server/win:updater_idl_idl",
         "//chrome/updater/app/server/win:updater_internal_idl_idl",
         "//chrome/updater/app/server/win:updater_legacy_idl_idl",
         "//chrome/updater/win:constants",
+        "//chrome/updater/win:lib",
       ]
-      public_deps = [ "//chrome/updater/win:lib" ]
     }
 
     if (is_mac) {
@@ -372,6 +370,7 @@
         "//chrome/updater/app/server/win:updater_internal_idl_idl",
         "//chrome/updater/app/server/win:updater_legacy_idl_idl",
         "//chrome/updater/win:constants",
+        "//chrome/updater/win:lib",
         "//chrome/updater/win:updater_tests",
       ]
 
diff --git a/chrome/updater/device_management/BUILD.gn b/chrome/updater/device_management/BUILD.gn
index c3dce30..66957b7 100644
--- a/chrome/updater/device_management/BUILD.gn
+++ b/chrome/updater/device_management/BUILD.gn
@@ -45,8 +45,8 @@
   if (is_win) {
     sources += [ "dm_storage_win.cc" ]
     deps += [
-      "//chrome/updater:lib",
       "//chrome/updater/win:constants",
+      "//chrome/updater/win:lib",
     ]
   }
 }
@@ -81,6 +81,6 @@
   }
 
   if (is_win) {
-    deps += [ "//chrome/updater:lib" ]
+    deps += [ "//chrome/updater/win:lib" ]
   }
 }
diff --git a/chrome/updater/win/installer.cc b/chrome/updater/installer_win.cc
similarity index 100%
rename from chrome/updater/win/installer.cc
rename to chrome/updater/installer_win.cc
diff --git a/chrome/updater/test/test_app/BUILD.gn b/chrome/updater/test/test_app/BUILD.gn
index 2c70d29..1dea265b 100644
--- a/chrome/updater/test/test_app/BUILD.gn
+++ b/chrome/updater/test/test_app/BUILD.gn
@@ -87,8 +87,8 @@
       "update_client_win.h",
     ]
     deps += [
-      "//chrome/updater:lib",
       "//chrome/updater/app/server/win:updater_idl_idl",
+      "//chrome/updater/win:lib",
     ]
   }
 }
diff --git a/chrome/updater/win/BUILD.gn b/chrome/updater/win/BUILD.gn
index 8123880b..87c0a12 100644
--- a/chrome/updater/win/BUILD.gn
+++ b/chrome/updater/win/BUILD.gn
@@ -34,6 +34,7 @@
 
   deps = [
     ":app_install_controller",
+    ":lib",
     ":version_resources",
     "//build/win:default_exe_manifest",
     "//chrome/updater:lib",
@@ -73,15 +74,10 @@
 }
 
 source_set("lib") {
-  # This build target and "//chrome/updater:lib" act as a unit. Depending
-  # targets must use "//chrome/updater:lib" as a dependency.
-  visibility = [ "//chrome/updater:lib" ]
-
   sources = [
     "action_handler.cc",
     "group_policy_manager.cc",
     "group_policy_manager.h",
-    "installer.cc",
     "net/net_util.cc",
     "net/net_util.h",
     "net/network.h",
@@ -157,22 +153,25 @@
 # compilation units outside the UI itself.
 # TODO(sorin): https://crbug.com/1014311
 source_set("app_install_controller") {
-  visibility = [ "//chrome/updater/win/*" ]
+  if (is_win) {
+    visibility = [ "//chrome/updater/win/*" ]
+    allow_circular_includes_from = [ "//chrome/updater:lib" ]
 
-  cflags_cc = [ "-Wno-missing-braces" ]
+    cflags_cc = [ "-Wno-missing-braces" ]
 
-  sources = [ "app_install_controller.cc" ]
+    sources = [ "app_install_controller.cc" ]
 
-  allow_circular_includes_from = [ "//chrome/updater:lib" ]
-  deps = [
-    ":install_progress_observer",
-    "//base",
-    "//base:i18n",
-    "//chrome/updater:base",
-    "//chrome/updater:lib",
-    "//chrome/updater:version_header",
-    "//chrome/updater/win/ui",
-  ]
+    deps = [
+      ":install_progress_observer",
+      ":lib",
+      "//base",
+      "//base:i18n",
+      "//chrome/updater:base",
+      "//chrome/updater:lib",
+      "//chrome/updater:version_header",
+      "//chrome/updater/win/ui",
+    ]
+  }
 }
 
 source_set("tag_extractor") {
@@ -209,9 +208,9 @@
   deps = [
     ":app_install_controller",
     ":constants",
+    ":lib",
     ":tag_extractor",
     "//base/test:test_support",
-    "//chrome/updater:lib",
     "//testing/gtest",
     "//url:url",
   ]
@@ -242,10 +241,10 @@
 
   deps = [
     ":app_install_controller",
+    ":lib",
     "//base",
     "//base/test:test_support",
     "//chrome/updater:branding_header",
-    "//chrome/updater:lib",
     "//chrome/updater/win/test:test_executables",
     "//chrome/updater/win/test:test_strings",
     "//testing/gtest",
diff --git a/chrome/updater/win/test/BUILD.gn b/chrome/updater/win/test/BUILD.gn
index fd7f1dc..a964ea9 100644
--- a/chrome/updater/win/test/BUILD.gn
+++ b/chrome/updater/win/test/BUILD.gn
@@ -26,8 +26,8 @@
   deps = [
     "//base",
     "//chrome/updater:base",
-    "//chrome/updater:lib",
     "//chrome/updater/win:app_install_controller",
+    "//chrome/updater/win:lib",
   ]
 }
 
diff --git a/chrome/updater/win/ui/BUILD.gn b/chrome/updater/win/ui/BUILD.gn
index d6f92e7..e105be4 100644
--- a/chrome/updater/win/ui/BUILD.gn
+++ b/chrome/updater/win/ui/BUILD.gn
@@ -41,8 +41,8 @@
     "//base:i18n",
     "//chrome/updater:base",
     "//chrome/updater:branding_header",
-    "//chrome/updater:lib",
     "//chrome/updater/win:install_progress_observer",
+    "//chrome/updater/win:lib",
     "//third_party/wtl",
   ]
 
diff --git a/chromeos/assistant/test_support/BUILD.gn b/chromeos/assistant/test_support/BUILD.gn
new file mode 100644
index 0000000..a31514eb
--- /dev/null
+++ b/chromeos/assistant/test_support/BUILD.gn
@@ -0,0 +1,10 @@
+static_library("test_support") {
+  testonly = true
+  sources = [ "expect_utils.h" ]
+  deps = [
+    "//base",
+    "//base/test:test_support",
+    "//testing/gmock",
+    "//testing/gtest",
+  ]
+}
diff --git a/chromeos/assistant/test_support/expect_utils.h b/chromeos/assistant/test_support/expect_utils.h
new file mode 100644
index 0000000..e888582
--- /dev/null
+++ b/chromeos/assistant/test_support/expect_utils.h
@@ -0,0 +1,70 @@
+// 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_ASSISTANT_TEST_SUPPORT_EXPECT_UTILS_H_
+#define CHROMEOS_ASSISTANT_TEST_SUPPORT_EXPECT_UTILS_H_
+
+#include "base/callback_forward.h"
+#include "base/run_loop.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace chromeos {
+namespace assistant {
+namespace test {
+
+namespace {
+
+template <typename T>
+void CheckResult(base::OnceClosure quit,
+                 T expected_value,
+                 base::RepeatingCallback<T()> value_callback) {
+  if (expected_value == value_callback.Run()) {
+    std::move(quit).Run();
+    return;
+  }
+
+  // Check again in the future
+  base::SequencedTaskRunnerHandle::Get()->PostDelayedTask(
+      FROM_HERE,
+      base::BindOnce(CheckResult<T>, std::move(quit), expected_value,
+                     value_callback),
+      base::TimeDelta::FromMilliseconds(10));
+}
+
+}  // namespace
+
+// Check if the |expected_value| is equal to the result of running
+// |value_callback|.  This method will block and continuously try the
+// comparison above until it succeeds, or timeout.
+template <typename T>
+void ExpectResult(T expected_value,
+                  base::RepeatingCallback<T()> value_callback,
+                  const std::string& tag = std::string()) {
+  // Wait until we're ready or we hit the default task environment timeout.
+  base::RunLoop run_loop;
+  CheckResult(run_loop.QuitClosure(), expected_value, value_callback);
+
+  EXPECT_NO_FATAL_FAILURE(run_loop.Run())
+      << (tag.empty() ? tag : tag + ": ")
+      << "Failed waiting for expected result.\n"
+      << "Expected \"" << expected_value << "\"\n"
+      << "Got \"" << value_callback.Run() << "\"";
+}
+
+#define WAIT_FOR_CALL(mock_obj, call)                                        \
+  {                                                                          \
+    base::RunLoop run_loop;                                                  \
+    EXPECT_CALL(mock_obj, call).WillOnce([quit = run_loop.QuitClosure()]() { \
+      std::move(quit).Run();                                                 \
+    });                                                                      \
+    EXPECT_NO_FATAL_FAILURE(run_loop.Run())                                  \
+        << #mock_obj << "::" << #call << " is NOT called.";                  \
+  }
+
+}  // namespace test
+}  // namespace assistant
+}  // namespace chromeos
+
+#endif  // CHROMEOS_ASSISTANT_TEST_SUPPORT_EXPECT_UTILS_H_
diff --git a/chromeos/components/camera_app_ui/resources/css/main.css b/chromeos/components/camera_app_ui/resources/css/main.css
index 41adc66..88f72cd 100644
--- a/chromeos/components/camera_app_ui/resources/css/main.css
+++ b/chromeos/components/camera_app_ui/resources/css/main.css
@@ -955,7 +955,7 @@
   transform: translateY(-50%);
 }
 
-body.grid-4x4 #preview-grid-horizontal::before {
+body.grid.grid-4x4 #preview-grid-horizontal::before {
   border-bottom: 1px solid white;
   content: '';
   height: 0;
@@ -965,7 +965,7 @@
   top: 0;
 }
 
-body.grid-3x3 #preview-grid-horizontal,
+body.grid.grid-3x3 #preview-grid-horizontal,
 body.view-grid-settings.grid-3x3 #preview-grid-horizontal {
   height: 33.333%;
 }
diff --git a/chromeos/components/diagnostics_ui/backend/BUILD.gn b/chromeos/components/diagnostics_ui/backend/BUILD.gn
index f052f1d..46552ad 100644
--- a/chromeos/components/diagnostics_ui/backend/BUILD.gn
+++ b/chromeos/components/diagnostics_ui/backend/BUILD.gn
@@ -16,6 +16,8 @@
     "histogram_util.h",
     "power_manager_client_conversions.cc",
     "power_manager_client_conversions.h",
+    "routine_log.cc",
+    "routine_log.h",
     "system_data_provider.cc",
     "system_data_provider.h",
     "system_routine_controller.cc",
@@ -41,6 +43,7 @@
   sources = [
     "cpu_usage_data_unittest.cc",
     "power_manager_client_conversions_unittest.cc",
+    "routine_log_unittest.cc",
     "system_data_provider_unittest.cc",
     "system_routine_controller_unittest.cc",
   ]
diff --git a/chromeos/components/diagnostics_ui/backend/routine_log.cc b/chromeos/components/diagnostics_ui/backend/routine_log.cc
new file mode 100644
index 0000000..814c8b9
--- /dev/null
+++ b/chromeos/components/diagnostics_ui/backend/routine_log.cc
@@ -0,0 +1,61 @@
+// 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/components/diagnostics_ui/backend/routine_log.h"
+
+#include <sstream>
+#include <string>
+
+#include "base/files/file_util.h"
+#include "base/i18n/time_formatting.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/time/time.h"
+
+namespace chromeos {
+namespace diagnostics {
+namespace {
+
+const char kNewline[] = "\n";
+const char kSeparator[] = " - ";
+const char kStartedDescription[] = "Started";
+
+std::string GetCurrentTimeAsString() {
+  return base::UTF16ToUTF8(
+      base::TimeFormatTimeOfDayWithMilliseconds(base::Time::Now()));
+}
+
+}  // namespace
+
+RoutineLog::RoutineLog(const base::FilePath& routine_log_file_path)
+    : routine_log_file_path_(routine_log_file_path) {}
+
+RoutineLog::~RoutineLog() = default;
+
+void RoutineLog::LogRoutineStarted(mojom::RoutineType type) {
+  if (!base::PathExists(routine_log_file_path_)) {
+    base::WriteFile(routine_log_file_path_, "");
+  }
+
+  std::stringstream log_line;
+  log_line << GetCurrentTimeAsString() << kSeparator << type << kSeparator
+           << kStartedDescription << kNewline;
+  AppendToLog(log_line.str());
+}
+
+void RoutineLog::LogRoutineCompleted(mojom::RoutineType type,
+                                     mojom::StandardRoutineResult result) {
+  DCHECK(base::PathExists(routine_log_file_path_));
+
+  std::stringstream log_line;
+  log_line << GetCurrentTimeAsString() << kSeparator << type << kSeparator
+           << result << kNewline;
+  AppendToLog(log_line.str());
+}
+
+void RoutineLog::AppendToLog(const std::string& content) {
+  base::AppendToFile(routine_log_file_path_, content.data(), content.size());
+}
+
+}  // namespace diagnostics
+}  // namespace chromeos
diff --git a/chromeos/components/diagnostics_ui/backend/routine_log.h b/chromeos/components/diagnostics_ui/backend/routine_log.h
new file mode 100644
index 0000000..718464c
--- /dev/null
+++ b/chromeos/components/diagnostics_ui/backend/routine_log.h
@@ -0,0 +1,40 @@
+// 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_COMPONENTS_DIAGNOSTICS_UI_BACKEND_ROUTINE_LOG_H_
+#define CHROMEOS_COMPONENTS_DIAGNOSTICS_UI_BACKEND_ROUTINE_LOG_H_
+
+#include "base/files/file_path.h"
+#include "chromeos/components/diagnostics_ui/mojom/system_routine_controller.mojom.h"
+
+namespace chromeos {
+namespace diagnostics {
+
+// RoutineLog is used to record the status and outcome of Diagnostics Routines.
+// Each time `LogRoutineStarted()` or `LogRoutineCompleted()` is called, a new
+// line is appended to `routine_log_file_path`. The file is created before the
+// first write if it does not exist.
+class RoutineLog {
+ public:
+  explicit RoutineLog(const base::FilePath& routine_log_file_path);
+  ~RoutineLog();
+
+  RoutineLog(const RoutineLog&) = delete;
+  RoutineLog& operator=(const RoutineLog&) = delete;
+
+  // Adds an entry in the RoutineLog.
+  void LogRoutineStarted(mojom::RoutineType type);
+  void LogRoutineCompleted(mojom::RoutineType type,
+                           mojom::StandardRoutineResult result);
+
+ private:
+  void AppendToLog(const std::string& content);
+
+  const base::FilePath routine_log_file_path_;
+};
+
+}  // namespace diagnostics
+}  // namespace chromeos
+
+#endif  // CHROMEOS_COMPONENTS_DIAGNOSTICS_UI_BACKEND_ROUTINE_LOG_H_
diff --git a/chromeos/components/diagnostics_ui/backend/routine_log_unittest.cc b/chromeos/components/diagnostics_ui/backend/routine_log_unittest.cc
new file mode 100644
index 0000000..1221257
--- /dev/null
+++ b/chromeos/components/diagnostics_ui/backend/routine_log_unittest.cc
@@ -0,0 +1,117 @@
+// 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/components/diagnostics_ui/backend/routine_log.h"
+
+#include <string>
+#include <vector>
+
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/strings/string_split.h"
+#include "base/test/task_environment.h"
+#include "base/time/clock.h"
+#include "base/time/time.h"
+#include "chromeos/components/diagnostics_ui/mojom/system_routine_controller.mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace chromeos {
+namespace diagnostics {
+namespace {
+
+const char kLogFileName[] = "diagnostic_routine_log";
+const char kSeparator[] = " - ";
+const char kNewline[] = "\n";
+
+// Returns the lines of the log as a vector of strings.
+std::vector<std::string> GetLogLines(const std::string& log) {
+  return base::SplitString(log, kNewline,
+                           base::WhitespaceHandling::TRIM_WHITESPACE,
+                           base::SplitResult::SPLIT_WANT_NONEMPTY);
+}
+
+// Splits a single line of the log at `kSeparator`. It is expected that each log
+// line contains exactly 3 components: 1) timestamp, 2) routine name, 3) status.
+std::vector<std::string> GetLogLineContents(const std::string& log_line) {
+  const std::vector<std::string> result = base::SplitString(
+      log_line, kSeparator, base::WhitespaceHandling::TRIM_WHITESPACE,
+      base::SplitResult::SPLIT_WANT_NONEMPTY);
+  DCHECK_EQ(3u, result.size());
+  return result;
+}
+
+}  // namespace
+
+class RoutineLogTest : public testing::Test {
+ public:
+  RoutineLogTest() {
+    EXPECT_TRUE(temp_dir_.CreateUniqueTempDir());
+    log_path_ = temp_dir_.GetPath().AppendASCII(kLogFileName);
+  }
+
+  ~RoutineLogTest() override = default;
+
+ protected:
+  base::test::TaskEnvironment task_environment_{
+      base::test::TaskEnvironment::TimeSource::MOCK_TIME};
+
+  base::ScopedTempDir temp_dir_;
+  base::FilePath log_path_;
+};
+
+TEST_F(RoutineLogTest, Empty) {
+  RoutineLog log(log_path_);
+
+  EXPECT_FALSE(base::PathExists(log_path_));
+}
+
+TEST_F(RoutineLogTest, Basic) {
+  RoutineLog log(log_path_);
+
+  log.LogRoutineStarted(mojom::RoutineType::kCpuStress);
+
+  EXPECT_TRUE(base::PathExists(log_path_));
+
+  std::string contents;
+  base::ReadFileToString(log_path_, &contents);
+  const std::string first_line = GetLogLines(contents)[0];
+  const std::vector<std::string> first_line_contents =
+      GetLogLineContents(first_line);
+
+  ASSERT_EQ(3u, first_line_contents.size());
+  EXPECT_EQ("RoutineType::kCpuStress", first_line_contents[1]);
+  EXPECT_EQ("Started", first_line_contents[2]);
+}
+
+TEST_F(RoutineLogTest, TwoLine) {
+  RoutineLog log(log_path_);
+
+  log.LogRoutineStarted(mojom::RoutineType::kMemory);
+  log.LogRoutineCompleted(mojom::RoutineType::kMemory,
+                          mojom::StandardRoutineResult::kTestPassed);
+  EXPECT_TRUE(base::PathExists(log_path_));
+
+  std::string contents;
+  base::ReadFileToString(log_path_, &contents);
+  const std::vector<std::string> log_lines = GetLogLines(contents);
+
+  const std::string first_line = log_lines[0];
+  const std::vector<std::string> first_line_contents =
+      GetLogLineContents(first_line);
+
+  ASSERT_EQ(3u, first_line_contents.size());
+  EXPECT_EQ("RoutineType::kMemory", first_line_contents[1]);
+  EXPECT_EQ("Started", first_line_contents[2]);
+
+  const std::string second_line = log_lines[1];
+  const std::vector<std::string> second_line_contents =
+      GetLogLineContents(second_line);
+
+  ASSERT_EQ(3u, second_line_contents.size());
+  EXPECT_EQ("RoutineType::kMemory", second_line_contents[1]);
+  EXPECT_EQ("StandardRoutineResult::kTestPassed", second_line_contents[2]);
+}
+
+}  // namespace diagnostics
+}  // namespace chromeos
diff --git a/chromeos/components/help_app_ui/resources/BUILD.gn b/chromeos/components/help_app_ui/resources/BUILD.gn
index 8c6b463b..817ae35 100644
--- a/chromeos/components/help_app_ui/resources/BUILD.gn
+++ b/chromeos/components/help_app_ui/resources/BUILD.gn
@@ -29,6 +29,7 @@
     # The privileged context can't access the app, but shares struct definitions
     # passed over postMessage.
     "help_app.externs.js",
+    "//third_party/closure_compiler/externs/metrics_private.js",
   ]
   deps = [
     ":message_types",
diff --git a/chromeos/components/help_app_ui/resources/browser_proxy.js b/chromeos/components/help_app_ui/resources/browser_proxy.js
index 32ff8bc..7f55aa5 100644
--- a/chromeos/components/help_app_ui/resources/browser_proxy.js
+++ b/chromeos/components/help_app_ui/resources/browser_proxy.js
@@ -129,6 +129,12 @@
       const response = await indexRemote.find(
           toString16((/** @type {{query: string}} */ (message)).query),
           /*max_results=*/ 100);
+
+      // Record the search status in the trusted frame.
+      chrome.metricsPrivate.recordEnumerationValue(
+          'Discover.Search.SearchStatus', response.status,
+          chromeos.localSearchService.mojom.ResponseStatus.MAX_VALUE);
+
       if (response.status !==
               chromeos.localSearchService.mojom.ResponseStatus.kSuccess ||
           !response.results) {
diff --git a/chromeos/components/phonehub/tether_controller_impl.cc b/chromeos/components/phonehub/tether_controller_impl.cc
index 670cc78..0e9a9041 100644
--- a/chromeos/components/phonehub/tether_controller_impl.cc
+++ b/chromeos/components/phonehub/tether_controller_impl.cc
@@ -122,6 +122,7 @@
   user_action_recorder_->RecordTetherConnectionAttempt();
   util::LogTetherConnectionResult(
       util::TetherConnectionResult::kAttemptConnection);
+  is_attempting_connection_ = true;
 
   FeatureState feature_state =
       multidevice_setup_client_->GetFeatureState(Feature::kInstantTethering);
@@ -393,11 +394,13 @@
   PA_LOG(INFO) << "TetherController status update: " << status_ << " => "
                << status;
 
-  // Log the connection attempt result if it has succeed.
-  if (status == Status::kConnected)
+  status_ = status;
+
+  if (is_attempting_connection_ && status_ == Status::kConnected)
     util::LogTetherConnectionResult(util::TetherConnectionResult::kSuccess);
 
-  status_ = status;
+  if (status_ != Status::kConnecting)
+    is_attempting_connection_ = false;
 
   NotifyStatusChanged();
 }
diff --git a/chromeos/components/phonehub/tether_controller_impl.h b/chromeos/components/phonehub/tether_controller_impl.h
index 8f4c8e51..c8651cf5 100644
--- a/chromeos/components/phonehub/tether_controller_impl.h
+++ b/chromeos/components/phonehub/tether_controller_impl.h
@@ -164,6 +164,9 @@
       ConnectDisconnectStatus::kIdle;
   Status status_ = Status::kIneligibleForFeature;
 
+  // Whether this class is attempting a tether connection.
+  bool is_attempting_connection_ = false;
+
   network_config::mojom::NetworkStatePropertiesPtr tether_network_;
 
   std::unique_ptr<TetherNetworkConnector> connector_;
diff --git a/chromeos/services/assistant/BUILD.gn b/chromeos/services/assistant/BUILD.gn
index 6b9c2d5..ef51bf3f 100644
--- a/chromeos/services/assistant/BUILD.gn
+++ b/chromeos/services/assistant/BUILD.gn
@@ -207,6 +207,7 @@
       "//chromeos/assistant/internal:test_support",
       "//chromeos/assistant/internal:tests",
       "//chromeos/assistant/internal/proto/google3",
+      "//chromeos/assistant/test_support",
       "//chromeos/dbus",
       "//chromeos/services/assistant/proxy",
       "//chromeos/services/assistant/public/cpp/migration",
diff --git a/chromeos/services/assistant/assistant_manager_service_impl.cc b/chromeos/services/assistant/assistant_manager_service_impl.cc
index 357e038..3e8984d 100644
--- a/chromeos/services/assistant/assistant_manager_service_impl.cc
+++ b/chromeos/services/assistant/assistant_manager_service_impl.cc
@@ -451,6 +451,7 @@
 void AssistantManagerServiceImpl::StartVoiceInteraction() {
   DCHECK(assistant_manager());
   DVLOG(1) << __func__;
+  MaybeStopPreviousInteraction();
 
   audio_input_host_->SetMicState(true);
   assistant_manager()->StartAssistantInteraction();
@@ -465,8 +466,27 @@
     VLOG(1) << "Stopping interaction without assistant manager.";
     return;
   }
-  assistant_manager_internal()->StopAssistantInteractionInternal(
-      cancel_conversation);
+
+  // We do not stop the interaction immediately, but instead we give
+  // Libassistant a bit of time to stop on its own accord. This improves
+  // stability as Libassistant might misbehave when it's forcefully stopped.
+  auto stop_callback = [](base::WeakPtr<AssistantManagerServiceImpl> weak_this,
+                          bool cancel_conversation) {
+    if (!weak_this || !weak_this->assistant_manager_internal()) {
+      return;
+    }
+    VLOG(1) << "Stopping interaction.";
+    weak_this->assistant_manager_internal()->StopAssistantInteractionInternal(
+        cancel_conversation);
+  };
+
+  stop_interaction_closure_ =
+      std::make_unique<base::CancelableOnceClosure>(base::BindOnce(
+          stop_callback, weak_factory_.GetWeakPtr(), cancel_conversation));
+
+  main_task_runner()->PostDelayedTask(FROM_HERE,
+                                      stop_interaction_closure_->callback(),
+                                      stop_interactioin_delay_);
 }
 
 void AssistantManagerServiceImpl::StartEditReminderInteraction(
@@ -508,6 +528,8 @@
     bool allow_tts) {
   DVLOG(1) << __func__;
 
+  MaybeStopPreviousInteraction();
+
   conversation_controller_proxy().SendTextQuery(
       query, allow_tts,
       NewPendingInteraction(AssistantInteractionType::kText, source, query));
@@ -569,6 +591,8 @@
       &AssistantManagerServiceImpl::OnConversationTurnStartedInternal,
       metadata);
 
+  stop_interaction_closure_.reset();
+
   audio_input_host_->OnConversationTurnStarted();
 
   // Retrieve the cached interaction metadata associated with this conversation
@@ -594,6 +618,8 @@
   ENSURE_MAIN_THREAD(&AssistantManagerServiceImpl::OnConversationTurnFinished,
                      resolution);
 
+  stop_interaction_closure_.reset();
+
   // TODO(updowndota): Find a better way to handle the edge cases.
   if (resolution != Resolution::NORMAL_WITH_FOLLOW_ON &&
       resolution != Resolution::CANCELLED &&
@@ -1229,6 +1255,14 @@
       interaction, description, voiceless_options, [](auto) {});
 }
 
+void AssistantManagerServiceImpl::MaybeStopPreviousInteraction() {
+  if (!stop_interaction_closure_ || stop_interaction_closure_->IsCancelled()) {
+    return;
+  }
+
+  stop_interaction_closure_->callback().Run();
+}
+
 std::string AssistantManagerServiceImpl::GetLastSearchSource() {
   return ConsumeLastTriggerSource();
 }
diff --git a/chromeos/services/assistant/assistant_manager_service_impl.h b/chromeos/services/assistant/assistant_manager_service_impl.h
index e4f358cd..6cfe80b7 100644
--- a/chromeos/services/assistant/assistant_manager_service_impl.h
+++ b/chromeos/services/assistant/assistant_manager_service_impl.h
@@ -11,6 +11,7 @@
 #include <vector>
 
 #include "ash/public/cpp/assistant/controller/assistant_screen_context_controller.h"
+#include "base/cancelable_callback.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/observer_list.h"
 #include "base/optional.h"
@@ -293,6 +294,8 @@
                                 const std::string& description,
                                 bool is_user_initiated);
 
+  void MaybeStopPreviousInteraction();
+
   ash::AssistantAlarmTimerController* assistant_alarm_timer_controller();
   ash::AssistantNotificationController* assistant_notification_controller();
   ash::AssistantScreenContextController* assistant_screen_context_controller();
@@ -305,6 +308,9 @@
   ServiceControllerProxy& service_controller();
   const ServiceControllerProxy& service_controller() const;
   base::Thread& background_thread();
+  void set_stop_interaction_delay_for_testing(base::TimeDelta delay) {
+    stop_interactioin_delay_ = delay;
+  }
 
   void SetStateAndInformObservers(State new_state);
 
@@ -357,6 +363,10 @@
   // Configuration passed to libassistant.
   std::string libassistant_config_;
 
+  base::TimeDelta stop_interactioin_delay_ =
+      base::TimeDelta::FromMilliseconds(500);
+  std::unique_ptr<base::CancelableOnceClosure> stop_interaction_closure_;
+
   base::ScopedObservation<DeviceActions,
                           AppListEventSubscriber,
                           &DeviceActions::AddAndFireAppListEventSubscriber,
diff --git a/chromeos/services/assistant/assistant_manager_service_impl_unittest.cc b/chromeos/services/assistant/assistant_manager_service_impl_unittest.cc
index 13d4b23d..8ae4e39 100644
--- a/chromeos/services/assistant/assistant_manager_service_impl_unittest.cc
+++ b/chromeos/services/assistant/assistant_manager_service_impl_unittest.cc
@@ -18,6 +18,7 @@
 #include "chromeos/assistant/internal/test_support/fake_alarm_timer_manager.h"
 #include "chromeos/assistant/internal/test_support/fake_assistant_manager.h"
 #include "chromeos/assistant/internal/test_support/fake_assistant_manager_internal.h"
+#include "chromeos/assistant/test_support/expect_utils.h"
 #include "chromeos/dbus/power/fake_power_manager_client.h"
 #include "chromeos/services/assistant/assistant_manager_service.h"
 #include "chromeos/services/assistant/proxy/libassistant_service_host.h"
@@ -62,17 +63,15 @@
   EXPECT_EQ(_state, assistant_manager_service()->GetState());
 
 // Adds an AlarmTimerEvent of the given |type| to |events|.
-static void AddAlarmTimerEvent(
-    std::vector<assistant_client::AlarmTimerEvent>* events,
-    assistant_client::AlarmTimerEvent::Type type) {
+void AddAlarmTimerEvent(std::vector<assistant_client::AlarmTimerEvent>* events,
+                        assistant_client::AlarmTimerEvent::Type type) {
   events->push_back(assistant_client::AlarmTimerEvent());
   events->back().type = type;
 }
 
 // Adds an AlarmTimerEvent of type TIMER with the given |state| to |events|.
-static void AddTimerEvent(
-    std::vector<assistant_client::AlarmTimerEvent>* events,
-    assistant_client::Timer::State state) {
+void AddTimerEvent(std::vector<assistant_client::AlarmTimerEvent>* events,
+                   assistant_client::Timer::State state) {
   AddAlarmTimerEvent(events, assistant_client::AlarmTimerEvent::TIMER);
   events->back().timer_data.state = state;
 }
@@ -81,7 +80,7 @@
 // authentication errors. This list is created on demand as there is no clear
 // enum that defines these, and we don't want to hard code this list in the
 // test.
-static std::vector<int> GetAuthenticationErrorCodes() {
+std::vector<int> GetAuthenticationErrorCodes() {
   const int kMinErrorCode = GetLowestErrorCode();
   const int kMaxErrorCode = GetHighestErrorCode();
 
@@ -97,58 +96,10 @@
 // Return a list of some libassistant error codes that are not considered to be
 // authentication errors.  Note we do not return all such codes as there are
 // simply too many and testing them all significantly slows down the tests.
-static std::vector<int> GetNonAuthenticationErrorCodes() {
+std::vector<int> GetNonAuthenticationErrorCodes() {
   return {-99999, 0, 1};
 }
 
-// Waits until the AssistantManagerService is in the given state, or until the
-// timeout is hit.
-class StateWaiter : private AssistantManagerService::StateObserver {
- public:
-  const base::TimeDelta kDefaultTimeout = base::TimeDelta::FromSeconds(5);
-
-  explicit StateWaiter(AssistantManagerService* service) : service_(service) {
-    service_->AddAndFireStateObserver(this);
-  }
-
-  ~StateWaiter() override { service_->RemoveStateObserver(this); }
-
-  void RunUntilState(AssistantManagerService::State expected_state) {
-    if (current_state() == expected_state)
-      return;
-
-    expected_state_ = expected_state;
-
-    // Ensure we time out after kDefaultTimeout.
-    base::test::ScopedRunLoopTimeout timeout(FROM_HERE, kDefaultTimeout);
-
-    base::RunLoop run_loop;
-    base::AutoReset<base::OnceClosure> quit_loop(&quit_loop_,
-                                                 run_loop.QuitClosure());
-
-    // And wait until we hit the expected state.
-    EXPECT_NO_FATAL_FAILURE(run_loop.Run())
-        << "Failed waiting for AssistantManagerService::State change."
-        << " Expected state " << expected_state_ << " but have "
-        << current_state();
-  }
-
- private:
-  // StateObserver implementation:
-  void OnStateChanged(AssistantManagerService::State new_state) override {
-    if (quit_loop_ && (new_state == expected_state_))
-      std::move(quit_loop_).Run();
-  }
-
-  AssistantManagerService::State current_state() {
-    return service_->GetState();
-  }
-
-  AssistantManagerService* const service_;
-  AssistantManagerService::State expected_state_;
-  base::OnceClosure quit_loop_;
-};
-
 class AssistantAlarmTimerControllerMock
     : public ash::AssistantAlarmTimerController {
  public:
@@ -218,6 +169,13 @@
   DISALLOW_COPY_AND_ASSIGN(StateObserverMock);
 };
 
+class FakeLibassistantV1Api : public LibassistantV1Api {
+ public:
+  explicit FakeLibassistantV1Api(FakeAssistantManager* assistant_manager)
+      : LibassistantV1Api(assistant_manager,
+                          &assistant_manager->assistant_manager_internal()) {}
+};
+
 class AssistantManagerServiceImplTest : public testing::Test {
  public:
   AssistantManagerServiceImplTest() = default;
@@ -238,11 +196,11 @@
 
     service_context_ = std::make_unique<FakeServiceContext>();
     service_context_
-        ->set_main_task_runner(task_environment.GetMainThreadTaskRunner())
+        ->set_main_task_runner(task_environment().GetMainThreadTaskRunner())
         .set_power_manager_client(PowerManagerClient::Get())
         .set_assistant_state(&assistant_state_);
 
-    CreateAssistantManagerServiceImpl(/*libassistant_config=*/{});
+    CreateAssistantManagerServiceImpl();
   }
 
   void CreateAssistantManagerServiceImpl(
@@ -274,7 +232,9 @@
 
   ash::AssistantState* assistant_state() { return &assistant_state_; }
 
-  FakeAssistantManager* fake_assistant_manager() { return &assistant_manager_; }
+  FakeAssistantManager* fake_assistant_manager() {
+    return assistant_manager_.get();
+  }
 
   FakeAssistantManagerInternal* fake_assistant_manager_internal() {
     return &fake_assistant_manager()->assistant_manager_internal();
@@ -291,6 +251,8 @@
     return assistant_manager_service_->action_module_for_testing();
   }
 
+  base::test::TaskEnvironment& task_environment() { return task_environment_; }
+
   void Start() {
     assistant_manager_service()->Start(UserInfo("<user-id>", "<access-token>"),
                                        /*enable_hotword=*/false);
@@ -307,8 +269,11 @@
   }
 
   void WaitForState(AssistantManagerService::State expected_state) {
-    StateWaiter waiter(assistant_manager_service());
-    waiter.RunUntilState(expected_state);
+    test::ExpectResult(
+        expected_state,
+        base::BindRepeating(&AssistantManagerServiceImpl::GetState,
+                            base::Unretained(assistant_manager_service())),
+        "AssistantManagerStateImpl");
   }
 
   // Raise all the |libassistant_error_codes| as communication errors from
@@ -338,8 +303,25 @@
     }
   }
 
+  void SetAssistantManagerInternal(std::unique_ptr<FakeAssistantManagerInternal>
+                                       assistant_manager_internal) {
+    assistant_manager_->set_assistant_manager_internal(
+        std::move(assistant_manager_internal));
+    libassistant_v1_api_.reset();
+    libassistant_v1_api_ =
+        std::make_unique<FakeLibassistantV1Api>(assistant_manager_.get());
+  }
+
+  void SetAssistantManager(
+      std::unique_ptr<FakeAssistantManager> assistant_manager) {
+    assistant_manager_ = std::move(assistant_manager);
+    libassistant_v1_api_.reset();
+    libassistant_v1_api_ =
+        std::make_unique<FakeLibassistantV1Api>(assistant_manager_.get());
+  }
+
  private:
-  base::test::SingleThreadTaskEnvironment task_environment;
+  base::test::SingleThreadTaskEnvironment task_environment_;
 
   ScopedAssistantClient assistant_client_;
   ScopedDeviceActions device_actions_;
@@ -348,9 +330,10 @@
   // Fake implementation of the Libassistant Mojom service.
   FakeLibassistantService libassistant_service_;
 
-  FakeAssistantManager assistant_manager_;
-  LibassistantV1Api libassistant_v1_api_{
-      &assistant_manager_, &assistant_manager_.assistant_manager_internal()};
+  std::unique_ptr<FakeAssistantManager> assistant_manager_{
+      std::make_unique<FakeAssistantManager>()};
+  std::unique_ptr<FakeLibassistantV1Api> libassistant_v1_api_{
+      std::make_unique<FakeLibassistantV1Api>(assistant_manager_.get())};
 
   std::unique_ptr<FakeServiceContext> service_context_;
 
@@ -749,5 +732,71 @@
   assistant_manager_service()->OnStartFinished();
 }
 
+class AssistantManagerMock : public FakeAssistantManager {
+ public:
+  AssistantManagerMock() = default;
+  ~AssistantManagerMock() override = default;
+
+  MOCK_METHOD(void, StartAssistantInteraction, (), (override));
+};
+
+class AssistantManagerInternalMock : public FakeAssistantManagerInternal {
+ public:
+  AssistantManagerInternalMock() = default;
+  ~AssistantManagerInternalMock() override = default;
+
+  MOCK_METHOD(void, StopAssistantInteractionInternal, (bool), (override));
+};
+
+TEST_F(AssistantManagerServiceImplTest, ShouldStopInteractionAfterDelay) {
+  // Start LibAssistant.
+  Start();
+  WaitForState(AssistantManagerService::STARTED);
+
+  auto assistant_manager_internal_mock =
+      std::make_unique<AssistantManagerInternalMock>();
+  auto* mock_ptr = assistant_manager_internal_mock.get();
+  SetAssistantManagerInternal(std::move(assistant_manager_internal_mock));
+
+  EXPECT_CALL(*mock_ptr, StopAssistantInteractionInternal).Times(0);
+
+  assistant_manager_service()->StopActiveInteraction(true);
+  testing::Mock::VerifyAndClearExpectations(mock_ptr);
+
+  WAIT_FOR_CALL(*mock_ptr, StopAssistantInteractionInternal);
+}
+
+TEST_F(AssistantManagerServiceImplTest,
+       ShouldStopInteractionImmediatelyBeforeNewInteraction) {
+  // Start LibAssistant.
+  Start();
+  WaitForState(AssistantManagerService::STARTED);
+
+  auto assistant_manager_mock = std::make_unique<AssistantManagerMock>();
+  auto assistant_manager_internal_mock =
+      std::make_unique<AssistantManagerInternalMock>();
+  auto* assistant_manager_mock_ptr = assistant_manager_mock.get();
+  auto* assistant_manager_internal_mock_ptr =
+      assistant_manager_internal_mock.get();
+
+  assistant_manager_mock->set_assistant_manager_internal(
+      std::move(assistant_manager_internal_mock));
+  SetAssistantManager(std::move(assistant_manager_mock));
+
+  EXPECT_CALL(*assistant_manager_internal_mock_ptr,
+              StopAssistantInteractionInternal)
+      .Times(0);
+
+  assistant_manager_service()->StopActiveInteraction(true);
+  testing::Mock::VerifyAndClearExpectations(
+      assistant_manager_internal_mock_ptr);
+
+  EXPECT_CALL(*assistant_manager_internal_mock_ptr,
+              StopAssistantInteractionInternal)
+      .Times(1);
+  EXPECT_CALL(*assistant_manager_mock_ptr, StartAssistantInteraction).Times(1);
+  assistant_manager_service()->StartVoiceInteraction();
+}
+
 }  // namespace assistant
 }  // namespace chromeos
diff --git a/components/BUILD.gn b/components/BUILD.gn
index 91fb415..5f98b4e6 100644
--- a/components/BUILD.gn
+++ b/components/BUILD.gn
@@ -427,6 +427,10 @@
     ]
   }
 
+  if (is_chromeos_ash || is_chromeos_lacros) {
+    deps += [ "//components/account_manager_core:unit_tests" ]
+  }
+
   if (is_mac) {
     deps += [ "//components/metal_util:unit_tests" ]
   }
diff --git a/components/account_manager_core/BUILD.gn b/components/account_manager_core/BUILD.gn
index b16801c..0d3219b 100644
--- a/components/account_manager_core/BUILD.gn
+++ b/components/account_manager_core/BUILD.gn
@@ -13,6 +13,8 @@
     "account.h",
     "account_manager_facade.cc",
     "account_manager_facade.h",
+    "account_manager_facade_impl.cc",
+    "account_manager_facade_impl.h",
     "account_manager_util.cc",
     "account_manager_util.h",
   ]
@@ -21,6 +23,7 @@
     "//base",
     "//chromeos/crosapi/mojom",
     "//google_apis:google_apis",
+    "//mojo/public/cpp/bindings",
   ]
 
   defines = [ "IS_ACCOUNT_MANAGER_CORE_IMPL" ]
@@ -36,3 +39,17 @@
 
   deps = [ ":account_manager_core" ]
 }
+
+source_set("unit_tests") {
+  testonly = true
+
+  sources = [ "account_manager_facade_impl_unittest.cc" ]
+
+  deps = [
+    ":account_manager_core",
+    ":test_support",
+    "//base/test:test_support",
+    "//chromeos/crosapi/mojom",
+    "//testing/gtest",
+  ]
+}
diff --git a/components/account_manager_core/DEPS b/components/account_manager_core/DEPS
index 1709b49..5941fce 100644
--- a/components/account_manager_core/DEPS
+++ b/components/account_manager_core/DEPS
@@ -1,4 +1,5 @@
 include_rules = [
   "+chromeos/crosapi/mojom/account_manager.mojom.h",
   "+google_apis/gaia/google_service_auth_error.h",
+  "+mojo/public",
 ]
diff --git a/chrome/browser/lacros/account_manager_facade_lacros.cc b/components/account_manager_core/account_manager_facade_impl.cc
similarity index 68%
rename from chrome/browser/lacros/account_manager_facade_lacros.cc
rename to components/account_manager_core/account_manager_facade_impl.cc
index dbaa6f30..fd788cf2 100644
--- a/chrome/browser/lacros/account_manager_facade_lacros.cc
+++ b/components/account_manager_core/account_manager_facade_impl.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/lacros/account_manager_facade_lacros.h"
+#include "components/account_manager_core/account_manager_facade_impl.h"
 
 #include <memory>
 #include <utility>
@@ -17,7 +17,7 @@
 
 }  // namespace
 
-AccountManagerFacadeLacros::AccountManagerFacadeLacros(
+AccountManagerFacadeImpl::AccountManagerFacadeImpl(
     mojo::Remote<crosapi::mojom::AccountManager> account_manager_remote,
     base::OnceClosure init_finished)
     : account_manager_remote_(std::move(account_manager_remote)),
@@ -29,39 +29,39 @@
   }
 
   account_manager_remote_.QueryVersion(base::BindOnce(
-      &AccountManagerFacadeLacros::OnVersionCheck, weak_factory_.GetWeakPtr()));
+      &AccountManagerFacadeImpl::OnVersionCheck, weak_factory_.GetWeakPtr()));
 }
 
-AccountManagerFacadeLacros::~AccountManagerFacadeLacros() = default;
+AccountManagerFacadeImpl::~AccountManagerFacadeImpl() = default;
 
-bool AccountManagerFacadeLacros::IsInitialized() {
+bool AccountManagerFacadeImpl::IsInitialized() {
   return is_initialized_;
 }
 
-void AccountManagerFacadeLacros::ShowAddAccountDialog(
+void AccountManagerFacadeImpl::ShowAddAccountDialog(
     const AccountAdditionSource& source,
     base::OnceCallback<void(const AccountAdditionResult& result)> callback) {
   // TODO(crbug.com/1140469): implement this.
 }
 
-void AccountManagerFacadeLacros::ShowReauthAccountDialog(
+void AccountManagerFacadeImpl::ShowReauthAccountDialog(
     const AccountAdditionSource& source,
     const std::string& email) {
   // TODO(crbug.com/1140469): implement this.
 }
 
-void AccountManagerFacadeLacros::OnVersionCheck(uint32_t version) {
+void AccountManagerFacadeImpl::OnVersionCheck(uint32_t version) {
   if (version < kMinVersionWithObserver) {
     std::move(init_finished_).Run();
     return;
   }
 
   account_manager_remote_->AddObserver(
-      base::BindOnce(&AccountManagerFacadeLacros::OnReceiverReceived,
+      base::BindOnce(&AccountManagerFacadeImpl::OnReceiverReceived,
                      weak_factory_.GetWeakPtr()));
 }
 
-void AccountManagerFacadeLacros::OnReceiverReceived(
+void AccountManagerFacadeImpl::OnReceiverReceived(
     mojo::PendingReceiver<AccountManagerObserver> receiver) {
   receiver_ =
       std::make_unique<mojo::Receiver<crosapi::mojom::AccountManagerObserver>>(
@@ -69,19 +69,19 @@
   // At this point (|receiver_| exists), we are subscribed to Account Manager.
 
   account_manager_remote_->IsInitialized(base::BindOnce(
-      &AccountManagerFacadeLacros::OnInitialized, weak_factory_.GetWeakPtr()));
+      &AccountManagerFacadeImpl::OnInitialized, weak_factory_.GetWeakPtr()));
 }
-void AccountManagerFacadeLacros::OnInitialized(bool is_initialized) {
+void AccountManagerFacadeImpl::OnInitialized(bool is_initialized) {
   if (is_initialized)
     is_initialized_ = true;
   // else: We will receive a notification in |OnTokenUpserted|.
   std::move(init_finished_).Run();
 }
 
-void AccountManagerFacadeLacros::OnTokenUpserted(
+void AccountManagerFacadeImpl::OnTokenUpserted(
     crosapi::mojom::AccountPtr account) {
   is_initialized_ = true;
 }
 
-void AccountManagerFacadeLacros::OnAccountRemoved(
+void AccountManagerFacadeImpl::OnAccountRemoved(
     crosapi::mojom::AccountPtr account) {}
diff --git a/chrome/browser/lacros/account_manager_facade_lacros.h b/components/account_manager_core/account_manager_facade_impl.h
similarity index 68%
rename from chrome/browser/lacros/account_manager_facade_lacros.h
rename to components/account_manager_core/account_manager_facade_impl.h
index 6103c2f..c61c1a20 100644
--- a/chrome/browser/lacros/account_manager_facade_lacros.h
+++ b/components/account_manager_core/account_manager_facade_impl.h
@@ -2,30 +2,30 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_LACROS_ACCOUNT_MANAGER_FACADE_LACROS_H_
-#define CHROME_BROWSER_LACROS_ACCOUNT_MANAGER_FACADE_LACROS_H_
+#ifndef COMPONENTS_ACCOUNT_MANAGER_CORE_ACCOUNT_MANAGER_FACADE_IMPL_H_
+#define COMPONENTS_ACCOUNT_MANAGER_CORE_ACCOUNT_MANAGER_FACADE_IMPL_H_
 
 #include <memory>
 
+#include "base/component_export.h"
 #include "base/memory/weak_ptr.h"
 #include "chromeos/crosapi/mojom/account_manager.mojom.h"
 #include "components/account_manager_core/account_manager_facade.h"
 #include "mojo/public/cpp/bindings/receiver.h"
 #include "mojo/public/cpp/bindings/remote.h"
 
-// Lacros specific implementation of |AccountManagerFacade| that talks to
-// |chromeos::AccountManager|, residing in ash-chrome, over Mojo.
-class AccountManagerFacadeLacros
+// ChromeOS-specific implementation of |AccountManagerFacade| that talks to
+// |chromeos::AccountManager| over Mojo. Used by both Lacros and Ash.
+class COMPONENT_EXPORT(ACCOUNT_MANAGER_CORE) AccountManagerFacadeImpl
     : public account_manager::AccountManagerFacade,
       public crosapi::mojom::AccountManagerObserver {
  public:
-  AccountManagerFacadeLacros(
+  AccountManagerFacadeImpl(
       mojo::Remote<crosapi::mojom::AccountManager> account_manager_remote,
       base::OnceClosure init_finished = base::DoNothing());
-  AccountManagerFacadeLacros(const AccountManagerFacadeLacros&) = delete;
-  AccountManagerFacadeLacros& operator=(const AccountManagerFacadeLacros&) =
-      delete;
-  ~AccountManagerFacadeLacros() override;
+  AccountManagerFacadeImpl(const AccountManagerFacadeImpl&) = delete;
+  AccountManagerFacadeImpl& operator=(const AccountManagerFacadeImpl&) = delete;
+  ~AccountManagerFacadeImpl() override;
 
   // AccountManagerFacade overrides:
   bool IsInitialized() override;
@@ -52,7 +52,7 @@
   std::unique_ptr<mojo::Receiver<crosapi::mojom::AccountManagerObserver>>
       receiver_;
 
-  base::WeakPtrFactory<AccountManagerFacadeLacros> weak_factory_{this};
+  base::WeakPtrFactory<AccountManagerFacadeImpl> weak_factory_{this};
 };
 
-#endif  // CHROME_BROWSER_LACROS_ACCOUNT_MANAGER_FACADE_LACROS_H_
+#endif  // COMPONENTS_ACCOUNT_MANAGER_CORE_ACCOUNT_MANAGER_FACADE_IMPL_H_
diff --git a/chrome/browser/lacros/account_manager_facade_lacros_unittest.cc b/components/account_manager_core/account_manager_facade_impl_unittest.cc
similarity index 77%
rename from chrome/browser/lacros/account_manager_facade_lacros_unittest.cc
rename to components/account_manager_core/account_manager_facade_impl_unittest.cc
index 7934e5d3..59ecea9 100644
--- a/chrome/browser/lacros/account_manager_facade_lacros_unittest.cc
+++ b/components/account_manager_core/account_manager_facade_impl_unittest.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/lacros/account_manager_facade_lacros.h"
+#include "components/account_manager_core/account_manager_facade_impl.h"
 
 #include "base/test/task_environment.h"
 #include "chromeos/crosapi/mojom/account_manager.mojom.h"
@@ -58,21 +58,20 @@
 
 }  // namespace
 
-class AccountManagerFacadeLacrosTest : public testing::Test {
+class AccountManagerFacadeImplTest : public testing::Test {
  public:
-  AccountManagerFacadeLacrosTest() = default;
-  AccountManagerFacadeLacrosTest(const AccountManagerFacadeLacrosTest&) =
+  AccountManagerFacadeImplTest() = default;
+  AccountManagerFacadeImplTest(const AccountManagerFacadeImplTest&) = delete;
+  AccountManagerFacadeImplTest& operator=(const AccountManagerFacadeImplTest&) =
       delete;
-  AccountManagerFacadeLacrosTest& operator=(
-      const AccountManagerFacadeLacrosTest&) = delete;
-  ~AccountManagerFacadeLacrosTest() override = default;
+  ~AccountManagerFacadeImplTest() override = default;
 
  protected:
   FakeAccountManager& account_manager() { return account_manager_; }
 
-  std::unique_ptr<AccountManagerFacadeLacros> CreateFacade() {
+  std::unique_ptr<AccountManagerFacadeImpl> CreateFacade() {
     base::RunLoop run_loop;
-    auto result = std::make_unique<AccountManagerFacadeLacros>(
+    auto result = std::make_unique<AccountManagerFacadeImpl>(
         account_manager().CreateRemote(), run_loop.QuitClosure());
     run_loop.Run();
     return result;
@@ -83,17 +82,17 @@
   FakeAccountManager account_manager_;
 };
 
-TEST_F(AccountManagerFacadeLacrosTest,
+TEST_F(AccountManagerFacadeImplTest,
        FacadeIsInitializedOnConnectIfAccountManagerIsInitialized) {
   account_manager().SetIsInitialized(true);
 
-  std::unique_ptr<AccountManagerFacadeLacros> account_manager_facade =
+  std::unique_ptr<AccountManagerFacadeImpl> account_manager_facade =
       CreateFacade();
   EXPECT_TRUE(account_manager_facade->IsInitialized());
 }
 
-TEST_F(AccountManagerFacadeLacrosTest, FacadeIsUninitializedByDefault) {
-  std::unique_ptr<AccountManagerFacadeLacros> account_manager_facade =
+TEST_F(AccountManagerFacadeImplTest, FacadeIsUninitializedByDefault) {
+  std::unique_ptr<AccountManagerFacadeImpl> account_manager_facade =
       CreateFacade();
   EXPECT_FALSE(account_manager_facade->IsInitialized());
 }
diff --git a/components/autofill/core/browser/payments/OWNERS b/components/autofill/core/browser/payments/OWNERS
index c3fbe35..8b40deb1 100644
--- a/components/autofill/core/browser/payments/OWNERS
+++ b/components/autofill/core/browser/payments/OWNERS
@@ -1 +1,2 @@
 jsaul@google.com
+siyua@chromium.org
diff --git a/components/data_reduction_proxy/proto/BUILD.gn b/components/data_reduction_proxy/proto/BUILD.gn
index 7d97e46..6ed094b 100644
--- a/components/data_reduction_proxy/proto/BUILD.gn
+++ b/components/data_reduction_proxy/proto/BUILD.gn
@@ -10,7 +10,3 @@
     "data_store.proto",
   ]
 }
-
-proto_library("subresource_redirect_proto") {
-  sources = [ "robots_rules.proto" ]
-}
diff --git a/components/exo/wayland/clients/blur.cc b/components/exo/wayland/clients/blur.cc
index 3b3cfe5f..d941feb 100644
--- a/components/exo/wayland/clients/blur.cc
+++ b/components/exo/wayland/clients/blur.cc
@@ -13,7 +13,7 @@
 #include "third_party/skia/include/core/SkCanvas.h"
 #include "third_party/skia/include/core/SkImage.h"
 #include "third_party/skia/include/core/SkSurface.h"
-#include "third_party/skia/include/effects/SkBlurImageFilter.h"
+#include "third_party/skia/include/effects/SkImageFilters.h"
 #include "third_party/skia/include/gpu/GrDirectContext.h"
 #include "ui/gl/gl_bindings.h"
 
@@ -131,8 +131,8 @@
   if (sigma_x > 0.0 || sigma_y > 0.0) {
     sigma_x = AdjustSigma(sigma_x, max_sigma, &blur_scale_factor_x);
     sigma_y = AdjustSigma(sigma_y, max_sigma, &blur_scale_factor_y);
-    blur_filter = SkBlurImageFilter::Make(sigma_x, sigma_y, nullptr, nullptr,
-                                          SkBlurImageFilter::kClamp_TileMode);
+    blur_filter = SkImageFilters::Blur(sigma_x, sigma_y, SkTileMode::kClamp,
+                                       nullptr, nullptr);
     auto size = SkISize::Make(size_.width() / blur_scale_factor_x,
                               size_.height() / blur_scale_factor_y);
     do {
diff --git a/components/feed/core/v2/config.cc b/components/feed/core/v2/config.cc
index 309e458..4ff2188 100644
--- a/components/feed/core/v2/config.cc
+++ b/components/feed/core/v2/config.cc
@@ -44,10 +44,20 @@
           kInterestFeedV2, "stale_content_threshold_seconds",
           config->stale_content_threshold.InSecondsF()));
 
+  config->content_expiration_threshold =
+      base::TimeDelta::FromSecondsD(base::GetFieldTrialParamByFeatureAsDouble(
+          kInterestFeedV2, "content_expiration_threshold_seconds",
+          config->content_expiration_threshold.InSecondsF()));
+
+  config->background_refresh_window_length =
+      base::TimeDelta::FromSecondsD(base::GetFieldTrialParamByFeatureAsDouble(
+          kInterestFeedV2, "background_refresh_window_length_seconds",
+          config->background_refresh_window_length.InSecondsF()));
+
   config->default_background_refresh_interval =
       base::TimeDelta::FromSecondsD(base::GetFieldTrialParamByFeatureAsDouble(
           kInterestFeedV2, "default_background_refresh_interval_seconds",
-          config->stale_content_threshold.InSecondsF()));
+          config->default_background_refresh_interval.InSecondsF()));
 
   config->max_action_upload_attempts = base::GetFieldTrialParamByFeatureAsInt(
       kInterestFeedV2, "max_action_upload_attempts",
diff --git a/components/feed/core/v2/config.h b/components/feed/core/v2/config.h
index 025b287..e710facc 100644
--- a/components/feed/core/v2/config.h
+++ b/components/feed/core/v2/config.h
@@ -17,8 +17,14 @@
   // Maximum number of FeedQuery or action upload requests per day.
   int max_feed_query_requests_per_day = 20;
   int max_action_upload_requests_per_day = 20;
+  // We'll always attempt to refresh content older than this.
+  base::TimeDelta stale_content_threshold = base::TimeDelta::FromHours(24);
   // Content older than this threshold will not be shown to the user.
-  base::TimeDelta stale_content_threshold = base::TimeDelta::FromHours(48);
+  base::TimeDelta content_expiration_threshold = base::TimeDelta::FromHours(48);
+  // How long the window is for background refresh tasks. If the task cannot be
+  // scheduled in the window, the background refresh is aborted.
+  base::TimeDelta background_refresh_window_length =
+      base::TimeDelta::FromHours(24);
   // The time between background refresh attempts. Ignored if a server-defined
   // fetch schedule has been assigned.
   base::TimeDelta default_background_refresh_interval =
diff --git a/components/feed/core/v2/enums.cc b/components/feed/core/v2/enums.cc
index b502b1b1..3567958 100644
--- a/components/feed/core/v2/enums.cc
+++ b/components/feed/core/v2/enums.cc
@@ -55,6 +55,12 @@
       return out << "kNetworkFetchFailed";
     case LoadStreamStatus::kCannotLoadMoreNoNextPageToken:
       return out << "kCannotLoadMoreNoNextPageToken";
+    case LoadStreamStatus::kDataInStoreStaleMissedLastRefresh:
+      return out << "kDataInStoreStaleMissedLastRefresh";
+    case LoadStreamStatus::kLoadedStaleDataFromStoreDueToNetworkFailure:
+      return out << "kLoadedStaleDataFromStoreDueToNetworkFailure";
+    case LoadStreamStatus::kDataInStoreIsExpired:
+      return out << "kDataInStoreIsExpired";
   }
 #else
   return out << (static_cast<int>(value));
diff --git a/components/feed/core/v2/enums.h b/components/feed/core/v2/enums.h
index 3a394525..3137f9b 100644
--- a/components/feed/core/v2/enums.h
+++ b/components/feed/core/v2/enums.h
@@ -17,20 +17,30 @@
 };
 
 // This must be kept in sync with FeedLoadStreamStatus in enums.xml.
+// These values are persisted to logs. Entries should not be renumbered and
+// numeric values should never be reused.
 enum class LoadStreamStatus {
   // Loading was not attempted.
   kNoStatus = 0,
+
+  // Final loading statuses where loading succeeds. :
   kLoadedFromStore = 1,
   kLoadedFromNetwork = 2,
+  kLoadedStaleDataFromStoreDueToNetworkFailure = 21,
+
+  // Statuses where data is loaded from the persistent store, but is stale.
+  kDataInStoreIsStale = 8,
+  // The timestamp for stored data is in the future, so we're treating stored
+  // data as it it is stale.
+  kDataInStoreIsStaleTimestampInFuture = 9,
+  kDataInStoreStaleMissedLastRefresh = 20,
+
+  // Failure statuses where content is not loaded.
   kFailedWithStoreError = 3,
   kNoStreamDataInStore = 4,
   kModelAlreadyLoaded = 5,
   kNoResponseBody = 6,
   kProtoTranslationFailed = 7,
-  kDataInStoreIsStale = 8,
-  // The timestamp for stored data is in the future, so we're treating stored
-  // data as it it is stale.
-  kDataInStoreIsStaleTimestampInFuture = 9,
   kCannotLoadFromNetworkSupressedForHistoryDelete_DEPRECATED = 10,
   kCannotLoadFromNetworkOffline = 11,
   kCannotLoadFromNetworkThrottled = 12,
@@ -41,7 +51,9 @@
   kLoadNotAllowedDisabledByEnterprisePolicy = 17,
   kNetworkFetchFailed = 18,
   kCannotLoadMoreNoNextPageToken = 19,
-  kMaxValue = kCannotLoadMoreNoNextPageToken,
+  kDataInStoreIsExpired = 22,
+
+  kMaxValue = kDataInStoreIsExpired,
 };
 
 std::ostream& operator<<(std::ostream& out, LoadStreamStatus value);
diff --git a/components/feed/core/v2/feed_stream.cc b/components/feed/core/v2/feed_stream.cc
index cc73904..5e5095d 100644
--- a/components/feed/core/v2/feed_stream.cc
+++ b/components/feed/core/v2/feed_stream.cc
@@ -237,9 +237,10 @@
 
 void FeedStream::InitialStreamLoadComplete(LoadStreamTask::Result result) {
   PopulateDebugStreamData(result, *profile_prefs_);
-  metrics_reporter_->OnLoadStream(result.load_from_store_status,
-                                  result.final_status,
-                                  std::move(result.latencies));
+  metrics_reporter_->OnLoadStream(
+      result.load_from_store_status, result.final_status,
+      result.loaded_new_content_from_network, result.stored_content_age,
+      std::move(result.latencies));
   UpdateIsActivityLoggingEnabled();
 
   model_loading_in_progress_ = false;
@@ -575,6 +576,15 @@
   return LoadStreamStatus::kNoStatus;
 }
 
+bool FeedStream::MissedLastRefresh() {
+  RequestSchedule schedule = prefs::GetRequestSchedule(*profile_prefs_);
+  if (schedule.refresh_offsets.empty())
+    return false;
+  base::Time scheduled_time =
+      schedule.anchor_time + schedule.refresh_offsets[0];
+  return scheduled_time < base::Time::Now();
+}
+
 LoadStreamStatus FeedStream::ShouldMakeFeedQueryRequest(bool is_load_more,
                                                         bool consume_quota) {
   if (!is_load_more) {
diff --git a/components/feed/core/v2/feed_stream.h b/components/feed/core/v2/feed_stream.h
index e40d7a9..d4e20c9 100644
--- a/components/feed/core/v2/feed_stream.h
+++ b/components/feed/core/v2/feed_stream.h
@@ -234,6 +234,9 @@
   // Returns |LoadStreamStatus::kNoStatus| if loading may be attempted.
   LoadStreamStatus ShouldAttemptLoad(bool model_loading = false);
 
+  // Whether the last scheduled refresh was missed.
+  bool MissedLastRefresh();
+
   // Determines if a FeedQuery request can be made. If successful,
   // returns |LoadStreamStatus::kNoStatus| and acquires throttler quota.
   // Otherwise returns the reason. If |consume_quota| is false, no quota is
diff --git a/components/feed/core/v2/feed_stream_unittest.cc b/components/feed/core/v2/feed_stream_unittest.cc
index ccb9683..c71c26e 100644
--- a/components/feed/core/v2/feed_stream_unittest.cc
+++ b/components/feed/core/v2/feed_stream_unittest.cc
@@ -72,7 +72,7 @@
   };
   LoadStreamFromStoreTask load_task(
       LoadStreamFromStoreTask::LoadType::kFullLoad, store,
-      base::BindLambdaForTesting(complete));
+      /*missed_last_refresh=*/false, base::BindLambdaForTesting(complete));
   // We want to load the data no matter how stale.
   load_task.IgnoreStalenessForTesting();
 
@@ -450,12 +450,16 @@
   }
   void OnLoadStream(LoadStreamStatus load_from_store_status,
                     LoadStreamStatus final_status,
+                    bool loaded_new_content_from_network,
+                    base::TimeDelta stored_content_age,
                     std::unique_ptr<LoadLatencyTimes> latencies) override {
+    load_stream_from_store_status = load_from_store_status;
     load_stream_status = final_status;
     LOG(INFO) << "OnLoadStream: " << final_status
               << " (store status: " << load_from_store_status << ")";
     MetricsReporter::OnLoadStream(load_from_store_status, final_status,
-                                  std::move(latencies));
+                                  loaded_new_content_from_network,
+                                  stored_content_age, std::move(latencies));
   }
   void OnLoadMoreBegin(SurfaceId surface_id) override {
     load_more_surface_id = surface_id;
@@ -482,6 +486,7 @@
 
   base::Optional<int> slice_viewed_index;
   base::Optional<LoadStreamStatus> load_stream_status;
+  base::Optional<LoadStreamStatus> load_stream_from_store_status;
   base::Optional<SurfaceId> load_more_surface_id;
   base::Optional<LoadStreamStatus> load_more_status;
   base::Optional<LoadStreamStatus> background_refresh_status;
@@ -1029,8 +1034,9 @@
 
   // Verify |RefreshTaskComplete()| was called and next refresh was scheduled.
   EXPECT_TRUE(refresh_scheduler_.refresh_task_complete);
+  ASSERT_TRUE(refresh_scheduler_.scheduled_run_time);
   EXPECT_EQ(base::TimeDelta::FromSeconds(48 - 12),
-            refresh_scheduler_.scheduled_run_time);
+            *refresh_scheduler_.scheduled_run_time);
 
   // Simulate executing the background task again.
   refresh_scheduler_.Clear();
@@ -1045,6 +1051,46 @@
             *refresh_scheduler_.scheduled_run_time);
 }
 
+TEST_F(FeedStreamTest, ForceRefreshIfMissedScheduledRefresh) {
+  // Inject a typical network response, with a server-defined request schedule.
+  {
+    RequestSchedule schedule;
+    schedule.anchor_time = kTestTimeEpoch;
+    schedule.refresh_offsets = {base::TimeDelta::FromSeconds(12),
+                                base::TimeDelta::FromSeconds(48)};
+    RefreshResponseData response_data;
+    response_data.model_update_request = MakeTypicalInitialModelState();
+    response_data.request_schedule = schedule;
+
+    response_translator_.InjectResponse(std::move(response_data));
+  }
+  TestSurface surface(stream_.get());
+  WaitForIdleTaskQueue();
+  ASSERT_EQ(1, network_.send_query_call_count);
+  surface.Detach();
+  stream_->UnloadModel();
+
+  // Ensure a refresh is foreced only after a scheduled refresh was missed.
+  // First, load the stream after 11 seconds.
+  response_translator_.InjectResponse(MakeTypicalInitialModelState());
+  task_environment_.AdvanceClock(base::TimeDelta::FromSeconds(11));
+  surface.Attach(stream_.get());
+  WaitForIdleTaskQueue();
+  ASSERT_EQ(1, network_.send_query_call_count);  // no refresh yet
+
+  // Load the stream after 13 seconds. We missed the scheduled refresh at
+  // 12 seconds.
+  surface.Detach();
+  stream_->UnloadModel();
+  task_environment_.AdvanceClock(base::TimeDelta::FromSeconds(2));
+  surface.Attach(stream_.get());
+  WaitForIdleTaskQueue();
+
+  ASSERT_EQ(2, network_.send_query_call_count);
+  EXPECT_EQ(LoadStreamStatus::kDataInStoreStaleMissedLastRefresh,
+            metrics_reporter_->load_stream_from_store_status);
+}
+
 TEST_F(FeedStreamTest, LoadFromNetworkBecauseStoreIsStale) {
   // Fill the store with stream data that is just barely stale, and verify we
   // fetch new data over the network.
@@ -1070,6 +1116,85 @@
   ASSERT_TRUE(surface.initial_state);
 }
 
+// Same as LoadFromNetworkBecauseStoreIsStale, but with expired content.
+TEST_F(FeedStreamTest, LoadFromNetworkBecauseStoreIsExpired) {
+  base::HistogramTester histograms;
+  const base::TimeDelta kContentAge =
+      GetFeedConfig().content_expiration_threshold +
+      base::TimeDelta::FromMinutes(1);
+  store_->OverwriteStream(
+      MakeTypicalInitialModelState(
+          /*first_cluster_id=*/0, kTestTimeEpoch - kContentAge),
+      base::DoNothing());
+  stream_->GetMetadata()->SetConsistencyToken("token-1");
+
+  // Store is stale, so we should fallback to a network request.
+  response_translator_.InjectResponse(MakeTypicalInitialModelState());
+  TestSurface surface(stream_.get());
+  WaitForIdleTaskQueue();
+
+  ASSERT_TRUE(network_.query_request_sent);
+  // The stored continutation token should be sent.
+  EXPECT_EQ(
+      "token-1",
+      network_.query_request_sent->feed_request().consistency_token().token());
+  EXPECT_TRUE(response_translator_.InjectedResponseConsumed());
+  ASSERT_TRUE(surface.initial_state);
+  EXPECT_EQ(LoadStreamStatus::kDataInStoreIsExpired,
+            metrics_reporter_->load_stream_from_store_status);
+  histograms.ExpectUniqueTimeSample(
+      "ContentSuggestions.Feed.ContentAgeOnLoad.BlockingRefresh", kContentAge,
+      1);
+}
+
+TEST_F(FeedStreamTest, LoadStaleDataBecauseNetworkRequestFails) {
+  // Fill the store with stream data that is just barely stale.
+  base::HistogramTester histograms;
+  const base::TimeDelta kContentAge =
+      GetFeedConfig().stale_content_threshold + base::TimeDelta::FromMinutes(1);
+  store_->OverwriteStream(
+      MakeTypicalInitialModelState(
+          /*first_cluster_id=*/0, kTestTimeEpoch - kContentAge),
+      base::DoNothing());
+  stream_->GetMetadata()->SetConsistencyToken("token-1");
+
+  // Store is stale, so we should fallback to a network request. Since we didn't
+  // inject a network response, the network update will fail.
+  TestSurface surface(stream_.get());
+  WaitForIdleTaskQueue();
+
+  ASSERT_TRUE(network_.query_request_sent);
+  EXPECT_EQ("loading -> 2 slices", surface.DescribeUpdates());
+  EXPECT_EQ(LoadStreamStatus::kDataInStoreIsStale,
+            metrics_reporter_->load_stream_from_store_status);
+  EXPECT_EQ(LoadStreamStatus::kLoadedStaleDataFromStoreDueToNetworkFailure,
+            metrics_reporter_->load_stream_status);
+  histograms.ExpectUniqueTimeSample(
+      "ContentSuggestions.Feed.ContentAgeOnLoad.NotRefreshed", kContentAge, 1);
+}
+
+TEST_F(FeedStreamTest, LoadFailsStoredDataIsExpired) {
+  // Fill the store with stream data that is just barely expired.
+  store_->OverwriteStream(
+      MakeTypicalInitialModelState(
+          /*first_cluster_id=*/0,
+          kTestTimeEpoch - GetFeedConfig().content_expiration_threshold -
+              base::TimeDelta::FromMinutes(1)),
+      base::DoNothing());
+
+  // Store contains expired content, so we should fallback to a network request.
+  // Since we didn't inject a network response, the network update will fail.
+  TestSurface surface(stream_.get());
+  WaitForIdleTaskQueue();
+
+  ASSERT_TRUE(network_.query_request_sent);
+  EXPECT_EQ("loading -> cant-refresh", surface.DescribeUpdates());
+  EXPECT_EQ(LoadStreamStatus::kDataInStoreIsExpired,
+            metrics_reporter_->load_stream_from_store_status);
+  EXPECT_EQ(LoadStreamStatus::kProtoTranslationFailed,
+            metrics_reporter_->load_stream_status);
+}
+
 TEST_F(FeedStreamTest, LoadFromNetworkFailsDueToProtoTranslation) {
   // No data in the store, so we should fetch from the network.
   // The network will respond with an empty response, which should fail proto
@@ -1560,9 +1685,12 @@
   // should have already been scheduled/consumed, leaving only the second
   // entry still in the the refresh_offsets vector.
   RequestSchedule schedule = prefs::GetRequestSchedule(profile_prefs_);
-  EXPECT_EQ(
-      std::vector<base::TimeDelta>({base::TimeDelta::FromSeconds(120000)}),
-      schedule.refresh_offsets);
+  EXPECT_EQ(std::vector<base::TimeDelta>({
+                base::TimeDelta::FromSeconds(86308) +
+                    base::TimeDelta::FromNanoseconds(822963644),
+                base::TimeDelta::FromSeconds(120000),
+            }),
+            schedule.refresh_offsets);
 
   // The stream's user attributes are set, so activity logging is enabled.
   EXPECT_TRUE(stream_->IsActivityLoggingEnabled());
diff --git a/components/feed/core/v2/metrics_reporter.cc b/components/feed/core/v2/metrics_reporter.cc
index 43dd693..df3bdc6 100644
--- a/components/feed/core/v2/metrics_reporter.cc
+++ b/components/feed/core/v2/metrics_reporter.cc
@@ -415,6 +415,8 @@
 void MetricsReporter::OnLoadStream(
     LoadStreamStatus load_from_store_status,
     LoadStreamStatus final_status,
+    bool loaded_new_content_from_network,
+    base::TimeDelta stored_content_age,
     std::unique_ptr<LoadLatencyTimes> load_latencies) {
   DVLOG(1) << "OnLoadStream load_from_store_status=" << load_from_store_status
            << " final_status=" << final_status;
@@ -426,6 +428,26 @@
         "ContentSuggestions.Feed.LoadStreamStatus.InitialFromStore",
         load_from_store_status);
   }
+
+  // For stored_content_age, the zero-value means there was no content loaded
+  // from the store. A negative value means there was content loaded, but it had
+  // a timestamp from the future. In either case, we'll avoid recording the
+  // content age.
+  if (stored_content_age > base::TimeDelta()) {
+    if (loaded_new_content_from_network) {
+      base::UmaHistogramCustomTimes(
+          "ContentSuggestions.Feed.ContentAgeOnLoad.BlockingRefresh",
+          stored_content_age, base::TimeDelta::FromMinutes(5),
+          base::TimeDelta::FromDays(7),
+          /*buckets=*/50);
+    } else {
+      base::UmaHistogramCustomTimes(
+          "ContentSuggestions.Feed.ContentAgeOnLoad.NotRefreshed",
+          stored_content_age, base::TimeDelta::FromSeconds(5),
+          base::TimeDelta::FromDays(7),
+          /*buckets=*/50);
+    }
+  }
 }
 
 void MetricsReporter::OnBackgroundRefresh(LoadStreamStatus final_status) {
diff --git a/components/feed/core/v2/metrics_reporter.h b/components/feed/core/v2/metrics_reporter.h
index b0779b7..241805b1 100644
--- a/components/feed/core/v2/metrics_reporter.h
+++ b/components/feed/core/v2/metrics_reporter.h
@@ -70,6 +70,8 @@
 
   virtual void OnLoadStream(LoadStreamStatus load_from_store_status,
                             LoadStreamStatus final_status,
+                            bool loaded_new_content_from_network,
+                            base::TimeDelta stored_content_age,
                             std::unique_ptr<LoadLatencyTimes> load_latencies);
   virtual void OnBackgroundRefresh(LoadStreamStatus final_status);
   virtual void OnLoadMoreBegin(SurfaceId surface_id);
diff --git a/components/feed/core/v2/metrics_reporter_unittest.cc b/components/feed/core/v2/metrics_reporter_unittest.cc
index 03425936..b7d501a 100644
--- a/components/feed/core/v2/metrics_reporter_unittest.cc
+++ b/components/feed/core/v2/metrics_reporter_unittest.cc
@@ -160,6 +160,8 @@
 TEST_F(MetricsReporterTest, ReportsLoadStreamStatus) {
   reporter_->OnLoadStream(LoadStreamStatus::kDataInStoreIsStale,
                           LoadStreamStatus::kLoadedFromNetwork,
+                          /*loaded_new_content_from_network=*/true,
+                          /*stored_content_age=*/base::TimeDelta::FromDays(5),
                           std::make_unique<LoadLatencyTimes>());
 
   histogram_.ExpectUniqueSample(
@@ -173,6 +175,8 @@
 TEST_F(MetricsReporterTest, ReportsLoadStreamStatusIgnoresNoStatusFromStore) {
   reporter_->OnLoadStream(LoadStreamStatus::kNoStatus,
                           LoadStreamStatus::kLoadedFromNetwork,
+                          /*loaded_new_content_from_network=*/true,
+                          /*stored_content_age=*/base::TimeDelta(),
                           std::make_unique<LoadLatencyTimes>());
 
   histogram_.ExpectUniqueSample(
@@ -182,6 +186,47 @@
       "ContentSuggestions.Feed.LoadStreamStatus.InitialFromStore", 0);
 }
 
+TEST_F(MetricsReporterTest, ReportsContentAgeBlockingRefresh) {
+  reporter_->OnLoadStream(LoadStreamStatus::kDataInStoreIsStale,
+                          LoadStreamStatus::kLoadedFromNetwork,
+                          /*loaded_new_content_from_network=*/true,
+                          /*stored_content_age=*/base::TimeDelta::FromDays(5),
+                          std::make_unique<LoadLatencyTimes>());
+
+  histogram_.ExpectUniqueTimeSample(
+      "ContentSuggestions.Feed.ContentAgeOnLoad.BlockingRefresh",
+      base::TimeDelta::FromDays(5), 1);
+}
+
+TEST_F(MetricsReporterTest, ReportsContentAgeNoRefresh) {
+  reporter_->OnLoadStream(LoadStreamStatus::kDataInStoreIsStale,
+                          LoadStreamStatus::kLoadedFromStore,
+                          /*loaded_new_content_from_network=*/false,
+                          /*stored_content_age=*/base::TimeDelta::FromDays(5),
+                          std::make_unique<LoadLatencyTimes>());
+
+  histogram_.ExpectUniqueTimeSample(
+      "ContentSuggestions.Feed.ContentAgeOnLoad.NotRefreshed",
+      base::TimeDelta::FromDays(5), 1);
+}
+
+TEST_F(MetricsReporterTest, DoNotReportContentAgeWhenNotPositive) {
+  reporter_->OnLoadStream(
+      LoadStreamStatus::kDataInStoreIsStale, LoadStreamStatus::kLoadedFromStore,
+      /*loaded_new_content_from_network=*/false,
+      /*stored_content_age=*/-base::TimeDelta::FromSeconds(1),
+      std::make_unique<LoadLatencyTimes>());
+  reporter_->OnLoadStream(LoadStreamStatus::kDataInStoreIsStale,
+                          LoadStreamStatus::kLoadedFromStore,
+                          /*loaded_new_content_from_network=*/false,
+                          /*stored_content_age=*/base::TimeDelta(),
+                          std::make_unique<LoadLatencyTimes>());
+  histogram_.ExpectTotalCount(
+      "ContentSuggestions.Feed.ContentAgeOnLoad.NotRefreshed", 0);
+  histogram_.ExpectTotalCount(
+      "ContentSuggestions.Feed.ContentAgeOnLoad.BlockingRefresh", 0);
+}
+
 TEST_F(MetricsReporterTest, ReportsLoadStepLatenciesOnFirstView) {
   {
     auto latencies = std::make_unique<LoadLatencyTimes>();
@@ -189,9 +234,10 @@
     latencies->StepComplete(LoadLatencyTimes::kLoadFromStore);
     task_environment_.FastForwardBy(base::TimeDelta::FromMilliseconds(50));
     latencies->StepComplete(LoadLatencyTimes::kUploadActions);
-    reporter_->OnLoadStream(LoadStreamStatus::kNoStatus,
-                            LoadStreamStatus::kLoadedFromNetwork,
-                            std::move(latencies));
+    reporter_->OnLoadStream(
+        LoadStreamStatus::kNoStatus, LoadStreamStatus::kLoadedFromNetwork,
+        /*loaded_new_content_from_network=*/true,
+        /*stored_content_age=*/base::TimeDelta(), std::move(latencies));
   }
   task_environment_.FastForwardBy(base::TimeDelta::FromMilliseconds(300));
   reporter_->FeedViewed(kSurfaceId);
diff --git a/components/feed/core/v2/scheduling.cc b/components/feed/core/v2/scheduling.cc
index a198062..e1c3496b 100644
--- a/components/feed/core/v2/scheduling.cc
+++ b/components/feed/core/v2/scheduling.cc
@@ -72,11 +72,11 @@
   while (!schedule->refresh_offsets.empty()) {
     base::Time request_time =
         schedule->anchor_time + schedule->refresh_offsets[0];
-    schedule->refresh_offsets.erase(schedule->refresh_offsets.begin());
-    if (request_time < now) {
-      // The schedule time is in the past. This is most likely to happen if we
-      // fail to run one of our scheduled fetches. Just ignore this fetch so
-      // that we don't risk multiple fetches at a time.
+    if (request_time <= now) {
+      // The schedule time is in the past. This can happen if the scheduled
+      // request already ran, or if the scheduled task was missed. Just ignore
+      // this fetch so that we don't risk multiple fetches at a time.
+      schedule->refresh_offsets.erase(schedule->refresh_offsets.begin());
       continue;
     }
     return request_time;
diff --git a/components/feed/core/v2/scheduling_unittest.cc b/components/feed/core/v2/scheduling_unittest.cc
index a3669b4..5146e5d 100644
--- a/components/feed/core/v2/scheduling_unittest.cc
+++ b/components/feed/core/v2/scheduling_unittest.cc
@@ -64,8 +64,10 @@
   base::Time kNow = kAnchorTime + TimeDelta::FromMinutes(12);
   EXPECT_EQ(kAnchorTime + TimeDelta::FromHours(1),
             NextScheduledRequestTime(kNow, &schedule));
+  kNow += TimeDelta::FromHours(1);
   EXPECT_EQ(kAnchorTime + TimeDelta::FromHours(6),
             NextScheduledRequestTime(kNow, &schedule));
+  kNow += TimeDelta::FromHours(6);
   EXPECT_EQ(kNow + kDefaultScheduleInterval,
             NextScheduledRequestTime(kNow, &schedule));
 }
@@ -78,6 +80,7 @@
   base::Time kNow = kAnchorTime + TimeDelta::FromMinutes(61);
   EXPECT_EQ(kAnchorTime + TimeDelta::FromHours(6),
             NextScheduledRequestTime(kNow, &schedule));
+  kNow += TimeDelta::FromHours(6);
   EXPECT_EQ(kNow + kDefaultScheduleInterval,
             NextScheduledRequestTime(kNow, &schedule));
 }
@@ -101,10 +104,7 @@
   base::Time kNow = kAnchorTime - TimeDelta::FromMinutes(12);
   EXPECT_EQ(kNow + TimeDelta::FromHours(1),
             NextScheduledRequestTime(kNow, &schedule));
-  EXPECT_EQ(kNow + TimeDelta::FromHours(6),
-            NextScheduledRequestTime(kNow, &schedule));
-  EXPECT_EQ(kNow + kDefaultScheduleInterval,
-            NextScheduledRequestTime(kNow, &schedule));
+  EXPECT_EQ(kNow, schedule.anchor_time);
 }
 
 TEST_F(NextScheduledRequestTimeTest, NowInFarFuture) {
@@ -116,10 +116,7 @@
   base::Time kNow = kAnchorTime + TimeDelta::FromDays(12);
   EXPECT_EQ(kNow + TimeDelta::FromHours(1),
             NextScheduledRequestTime(kNow, &schedule));
-  EXPECT_EQ(kNow + TimeDelta::FromHours(6),
-            NextScheduledRequestTime(kNow, &schedule));
-  EXPECT_EQ(kNow + kDefaultScheduleInterval,
-            NextScheduledRequestTime(kNow, &schedule));
+  EXPECT_EQ(kNow, schedule.anchor_time);
 }
 
 }  // namespace
diff --git a/components/feed/core/v2/surface_updater.cc b/components/feed/core/v2/surface_updater.cc
index b9fb0482..58e23ef 100644
--- a/components/feed/core/v2/surface_updater.cc
+++ b/components/feed/core/v2/surface_updater.cc
@@ -154,6 +154,10 @@
     case LoadStreamStatus::kLoadMoreModelIsNotLoaded:
     case LoadStreamStatus::kLoadNotAllowedDisabledByEnterprisePolicy:
     case LoadStreamStatus::kCannotLoadMoreNoNextPageToken:
+    case LoadStreamStatus::kDataInStoreStaleMissedLastRefresh:
+    case LoadStreamStatus::kLoadedStaleDataFromStoreDueToNetworkFailure:
+      break;
+    case LoadStreamStatus::kDataInStoreIsExpired:
       break;
   }
   return feedui::ZeroStateSlice::NO_CARDS_AVAILABLE;
diff --git a/components/feed/core/v2/tasks/get_prefetch_suggestions_task.cc b/components/feed/core/v2/tasks/get_prefetch_suggestions_task.cc
index 645143a..69cf3fb4 100644
--- a/components/feed/core/v2/tasks/get_prefetch_suggestions_task.cc
+++ b/components/feed/core/v2/tasks/get_prefetch_suggestions_task.cc
@@ -57,6 +57,7 @@
 
   load_from_store_task_ = std::make_unique<LoadStreamFromStoreTask>(
       LoadStreamFromStoreTask::LoadType::kFullLoad, stream_->GetStore(),
+      /*missed_last_refresh=*/false,
       base::BindOnce(&GetPrefetchSuggestionsTask::LoadStreamComplete,
                      base::Unretained(this)));
 
diff --git a/components/feed/core/v2/tasks/load_stream_from_store_task.cc b/components/feed/core/v2/tasks/load_stream_from_store_task.cc
index 032356b..ce26f37 100644
--- a/components/feed/core/v2/tasks/load_stream_from_store_task.cc
+++ b/components/feed/core/v2/tasks/load_stream_from_store_task.cc
@@ -9,7 +9,9 @@
 
 #include "base/time/time.h"
 #include "components/feed/core/proto/v2/store.pb.h"
+#include "components/feed/core/v2/config.h"
 #include "components/feed/core/v2/feed_store.h"
+#include "components/feed/core/v2/feed_stream.h"
 #include "components/feed/core/v2/proto_util.h"
 #include "components/feed/core/v2/protocol_translator.h"
 #include "components/feed/core/v2/scheduling.h"
@@ -26,9 +28,11 @@
 LoadStreamFromStoreTask::LoadStreamFromStoreTask(
     LoadType load_type,
     FeedStore* store,
+    bool missed_last_refresh,
     base::OnceCallback<void(Result)> callback)
     : load_type_(load_type),
       store_(store),
+      missed_last_refresh_(missed_last_refresh),
       result_callback_(std::move(callback)),
       update_request_(std::make_unique<StreamModelUpdateRequest>()) {}
 
@@ -57,14 +61,18 @@
     return;
   }
   if (!ignore_staleness_) {
-    const base::TimeDelta content_age =
+    content_age_ =
         base::Time::Now() - feedstore::GetLastAddedTime(result.stream_data);
-    if (content_age < base::TimeDelta()) {
-      Complete(LoadStreamStatus::kDataInStoreIsStaleTimestampInFuture);
+    if (content_age_ > GetFeedConfig().content_expiration_threshold) {
+      Complete(LoadStreamStatus::kDataInStoreIsExpired);
       return;
-    } else if (ShouldWaitForNewContent(true, content_age)) {
-      Complete(LoadStreamStatus::kDataInStoreIsStale);
-      return;
+    }
+    if (content_age_ < base::TimeDelta()) {
+      stale_reason_ = LoadStreamStatus::kDataInStoreIsStaleTimestampInFuture;
+    } else if (ShouldWaitForNewContent(true, content_age_)) {
+      stale_reason_ = LoadStreamStatus::kDataInStoreIsStale;
+    } else if (missed_last_refresh_) {
+      stale_reason_ = LoadStreamStatus::kDataInStoreStaleMissedLastRefresh;
     }
   }
 
@@ -119,12 +127,19 @@
 
 void LoadStreamFromStoreTask::Complete(LoadStreamStatus status) {
   Result task_result;
-  task_result.status = status;
+
   task_result.pending_actions = std::move(pending_actions_);
   if (status == LoadStreamStatus::kLoadedFromStore &&
       load_type_ == LoadType::kFullLoad) {
     task_result.update_request = std::move(update_request_);
   }
+  if (status == LoadStreamStatus::kLoadedFromStore &&
+      stale_reason_ != LoadStreamStatus::kNoStatus) {
+    task_result.status = stale_reason_;
+  } else {
+    task_result.status = status;
+  }
+  task_result.content_age = content_age_;
   std::move(result_callback_).Run(std::move(task_result));
   TaskComplete();
 }
diff --git a/components/feed/core/v2/tasks/load_stream_from_store_task.h b/components/feed/core/v2/tasks/load_stream_from_store_task.h
index 95a8aa16..8fb144b 100644
--- a/components/feed/core/v2/tasks/load_stream_from_store_task.h
+++ b/components/feed/core/v2/tasks/load_stream_from_store_task.h
@@ -35,6 +35,9 @@
     // Pending actions to be uploaded if the stream is to be loaded from the
     // network.
     std::vector<feedstore::StoredAction> pending_actions;
+    // How long since the loaded content was fetched from the server.
+    // May be zero if content is not loaded.
+    base::TimeDelta content_age;
   };
 
   enum class LoadType {
@@ -44,6 +47,7 @@
 
   LoadStreamFromStoreTask(LoadType load_type,
                           FeedStore* store,
+                          bool missed_last_refresh,
                           base::OnceCallback<void(Result)> callback);
   ~LoadStreamFromStoreTask() override;
   LoadStreamFromStoreTask(const LoadStreamFromStoreTask&) = delete;
@@ -63,14 +67,17 @@
     return weak_ptr_factory_.GetWeakPtr();
   }
 
+  LoadStreamStatus stale_reason_ = LoadStreamStatus::kNoStatus;
   LoadType load_type_;
   FeedStore* store_;  // Unowned.
   bool ignore_staleness_ = false;
+  bool missed_last_refresh_ = false;
   base::OnceCallback<void(Result)> result_callback_;
 
   // Data to be stuffed into the Result when the task is complete.
   std::unique_ptr<StreamModelUpdateRequest> update_request_;
   std::vector<feedstore::StoredAction> pending_actions_;
+  base::TimeDelta content_age_;
 
   base::WeakPtrFactory<LoadStreamFromStoreTask> weak_ptr_factory_{this};
 };
diff --git a/components/feed/core/v2/tasks/load_stream_task.cc b/components/feed/core/v2/tasks/load_stream_task.cc
index 0af2358..5eb66ed6 100644
--- a/components/feed/core/v2/tasks/load_stream_task.cc
+++ b/components/feed/core/v2/tasks/load_stream_task.cc
@@ -77,7 +77,7 @@
           : LoadStreamFromStoreTask::LoadType::kPendingActionsOnly;
 
   load_from_store_task_ = std::make_unique<LoadStreamFromStoreTask>(
-      load_from_store_type, stream_->GetStore(),
+      load_from_store_type, stream_->GetStore(), stream_->MissedLastRefresh(),
       base::BindOnce(&LoadStreamTask::LoadFromStoreComplete, GetWeakPtr()));
   load_from_store_task_->Execute(base::DoNothing());
 }
@@ -86,6 +86,7 @@
     LoadStreamFromStoreTask::Result result) {
   load_from_store_status_ = result.status;
   latencies_->StepComplete(LoadLatencyTimes::kLoadFromStore);
+  stored_content_age_ = result.content_age;
 
   // Phase 2.
   //  - If loading from store works, update the model.
@@ -100,6 +101,16 @@
     return;
   }
 
+  // If data in store is stale, we'll continue with a network request, but keep
+  // the stale model data in case we fail to load a fresh feed.
+  if (load_type_ == LoadType::kInitialLoad &&
+      (result.status == LoadStreamStatus::kDataInStoreStaleMissedLastRefresh ||
+       result.status == LoadStreamStatus::kDataInStoreIsStale ||
+       result.status ==
+           LoadStreamStatus::kDataInStoreIsStaleTimestampInFuture)) {
+    stale_store_state_ = std::move(result.update_request);
+  }
+
   LoadStreamStatus final_status = stream_->ShouldMakeFeedQueryRequest();
   if (final_status != LoadStreamStatus::kNoStatus) {
     Done(final_status);
@@ -181,8 +192,17 @@
 }
 
 void LoadStreamTask::Done(LoadStreamStatus status) {
+  // If the network load fails, but there is stale content in the store, use
+  // that stale content.
+  if (stale_store_state_ && status != LoadStreamStatus::kLoadedFromNetwork) {
+    auto model = std::make_unique<StreamModel>();
+    model->Update(std::move(stale_store_state_));
+    stream_->LoadModel(std::move(model));
+    status = LoadStreamStatus::kLoadedStaleDataFromStoreDueToNetworkFailure;
+  }
   Result result;
   result.load_from_store_status = load_from_store_status_;
+  result.stored_content_age = stored_content_age_;
   result.final_status = status;
   result.load_type = load_type_;
   result.network_response_info = network_response_info_;
diff --git a/components/feed/core/v2/tasks/load_stream_task.h b/components/feed/core/v2/tasks/load_stream_task.h
index dd4c210..12d5e62 100644
--- a/components/feed/core/v2/tasks/load_stream_task.h
+++ b/components/feed/core/v2/tasks/load_stream_task.h
@@ -48,6 +48,8 @@
     // Status of just loading the stream from the persistent store, if that
     // was attempted.
     LoadStreamStatus load_from_store_status = LoadStreamStatus::kNoStatus;
+    // Age of content loaded from local storage. Zero if none was loaded.
+    base::TimeDelta stored_content_age;
     LoadType load_type;
 
     // Information about the network request, if one was made.
@@ -80,11 +82,13 @@
   LoadType load_type_;
   FeedStream* stream_;  // Unowned.
   std::unique_ptr<LoadStreamFromStoreTask> load_from_store_task_;
+  std::unique_ptr<StreamModelUpdateRequest> stale_store_state_;
 
   // Information to be stuffed in |Result|.
   LoadStreamStatus load_from_store_status_ = LoadStreamStatus::kNoStatus;
   base::Optional<NetworkResponseInfo> network_response_info_;
   bool loaded_new_content_from_network_ = false;
+  base::TimeDelta stored_content_age_;
 
   std::unique_ptr<LoadLatencyTimes> latencies_;
   base::TimeTicks task_creation_time_;
diff --git a/components/feed/core/v2/tasks/prefetch_images_task.cc b/components/feed/core/v2/tasks/prefetch_images_task.cc
index e4df3d1..7131229 100644
--- a/components/feed/core/v2/tasks/prefetch_images_task.cc
+++ b/components/feed/core/v2/tasks/prefetch_images_task.cc
@@ -45,6 +45,7 @@
 
   load_from_store_task_ = std::make_unique<LoadStreamFromStoreTask>(
       LoadStreamFromStoreTask::LoadType::kFullLoad, stream_->GetStore(),
+      /*missed_last_refresh=*/false,
       base::BindOnce(&PrefetchImagesTask::LoadStreamComplete,
                      base::Unretained(this)));
 
diff --git a/components/metrics/generate_expired_histograms_array.gni b/components/metrics/generate_expired_histograms_array.gni
index c9b4562..b3e2894 100644
--- a/components/metrics/generate_expired_histograms_array.gni
+++ b/components/metrics/generate_expired_histograms_array.gni
@@ -77,6 +77,7 @@
       "//tools/metrics/histograms/histograms_xml/google/histograms.xml",
       "//tools/metrics/histograms/histograms_xml/gpu/histograms.xml",
       "//tools/metrics/histograms/histograms_xml/hang_watcher/histograms.xml",
+      "//tools/metrics/histograms/histograms_xml/help_app/histograms.xml",
       "//tools/metrics/histograms/histograms_xml/histogram_suffixes_list.xml",
       "//tools/metrics/histograms/histograms_xml/history/histograms.xml",
       "//tools/metrics/histograms/histograms_xml/holding_space/histograms.xml",
diff --git a/components/mirroring/service/rtp_stream.cc b/components/mirroring/service/rtp_stream.cc
index 85e3d23..637ebd2 100644
--- a/components/mirroring/service/rtp_stream.cc
+++ b/components/mirroring/service/rtp_stream.cc
@@ -52,12 +52,12 @@
 void VideoRtpStream::InsertVideoFrame(
     scoped_refptr<media::VideoFrame> video_frame) {
   DCHECK(client_);
-  if (!video_frame->metadata()->reference_time.has_value()) {
+  if (!video_frame->metadata().reference_time.has_value()) {
     client_->OnError("Missing REFERENCE_TIME.");
     return;
   }
 
-  base::TimeTicks reference_time = *video_frame->metadata()->reference_time;
+  base::TimeTicks reference_time = *video_frame->metadata().reference_time;
   DCHECK(!reference_time.is_null());
   if (expecting_a_refresh_frame_) {
     // There is uncertainty as to whether the video frame was in response to a
diff --git a/components/mirroring/service/rtp_stream_unittest.cc b/components/mirroring/service/rtp_stream_unittest.cc
index d0052fac22..6effab0 100644
--- a/components/mirroring/service/rtp_stream_unittest.cc
+++ b/components/mirroring/service/rtp_stream_unittest.cc
@@ -86,7 +86,7 @@
   scoped_refptr<media::VideoFrame> video_frame = media::VideoFrame::CreateFrame(
       media::PIXEL_FORMAT_I420, size, gfx::Rect(size), size, base::TimeDelta());
   media::cast::PopulateVideoFrame(video_frame.get(), 1);
-  video_frame->metadata()->reference_time = testing_clock_.NowTicks();
+  video_frame->metadata().reference_time = testing_clock_.NowTicks();
 
   auto video_sender = std::make_unique<media::cast::VideoSender>(
       cast_environment_, media::cast::GetDefaultVideoSenderConfig(),
diff --git a/components/safe_browsing/core/features.cc b/components/safe_browsing/core/features.cc
index 287d9f9..6536391 100644
--- a/components/safe_browsing/core/features.cc
+++ b/components/safe_browsing/core/features.cc
@@ -92,6 +92,10 @@
     "SafeBrowsingRealTimeUrlLookupEnabledForEnterprise",
     base::FEATURE_ENABLED_BY_DEFAULT};
 
+const base::Feature kRealTimeUrlLookupEnterpriseGaEndpoint{
+    "SafeBrowsingkRealTimeUrlLookupEnterpriseGaEndpoint",
+    base::FEATURE_DISABLED_BY_DEFAULT};
+
 const base::Feature kRealTimeUrlLookupEnabledWithToken{
     "SafeBrowsingRealTimeUrlLookupEnabledWithToken",
     base::FEATURE_ENABLED_BY_DEFAULT};
@@ -145,6 +149,7 @@
     {&kPromptAppForDeepScanning, true},
     {&kRealTimeUrlLookupEnabled, true},
     {&kRealTimeUrlLookupEnabledForEnterprise, true},
+    {&kRealTimeUrlLookupEnterpriseGaEndpoint, true},
     {&kRealTimeUrlLookupEnabledWithToken, true},
     {&kSafeBrowsingSeparateNetworkContexts, true},
     {&kSafeBrowsingSectionUIAndroid, true},
diff --git a/components/safe_browsing/core/features.h b/components/safe_browsing/core/features.h
index 2c329a2..f9408e88 100644
--- a/components/safe_browsing/core/features.h
+++ b/components/safe_browsing/core/features.h
@@ -93,6 +93,9 @@
 // disabled.
 extern const base::Feature kRealTimeUrlLookupEnabledForEnterprise;
 
+// Controls whether to use the GA endpoint for enterprise real time URL lookup.
+extern const base::Feature kRealTimeUrlLookupEnterpriseGaEndpoint;
+
 // Controls whether the GAIA-keyed real time URL lookup is enabled.
 extern const base::Feature kRealTimeUrlLookupEnabledWithToken;
 
diff --git a/components/safe_browsing/core/realtime/url_lookup_service.cc b/components/safe_browsing/core/realtime/url_lookup_service.cc
index 543a38db..c0f71cb 100644
--- a/components/safe_browsing/core/realtime/url_lookup_service.cc
+++ b/components/safe_browsing/core/realtime/url_lookup_service.cc
@@ -110,6 +110,11 @@
   return true;
 }
 
+GURL RealTimeUrlLookupService::GetRealTimeLookupUrl() const {
+  return GURL(
+      "https://safebrowsing.google.com/safebrowsing/clientreport/realtime");
+}
+
 net::NetworkTrafficAnnotationTag
 RealTimeUrlLookupService::GetTrafficAnnotationTag() const {
   return net::DefineNetworkTrafficAnnotation(
diff --git a/components/safe_browsing/core/realtime/url_lookup_service.h b/components/safe_browsing/core/realtime/url_lookup_service.h
index 2eebf4a..f0579b4 100644
--- a/components/safe_browsing/core/realtime/url_lookup_service.h
+++ b/components/safe_browsing/core/realtime/url_lookup_service.h
@@ -74,6 +74,7 @@
 
  private:
   // RealTimeUrlLookupServiceBase:
+  GURL GetRealTimeLookupUrl() const override;
   net::NetworkTrafficAnnotationTag GetTrafficAnnotationTag() const override;
   bool CanPerformFullURLLookupWithToken() const override;
   void GetAccessToken(const GURL& url,
diff --git a/components/safe_browsing/core/realtime/url_lookup_service_base.cc b/components/safe_browsing/core/realtime/url_lookup_service_base.cc
index bf849653..97368fc 100644
--- a/components/safe_browsing/core/realtime/url_lookup_service_base.cc
+++ b/components/safe_browsing/core/realtime/url_lookup_service_base.cc
@@ -174,12 +174,6 @@
   return url.ReplaceComponents(replacements);
 }
 
-// static
-GURL RealTimeUrlLookupServiceBase::GetRealTimeLookupUrl() {
-  return GURL(
-      "https://safebrowsing.google.com/safebrowsing/clientreport/realtime");
-}
-
 base::WeakPtr<RealTimeUrlLookupServiceBase>
 RealTimeUrlLookupServiceBase::GetWeakPtr() {
   return weak_factory_.GetWeakPtr();
diff --git a/components/safe_browsing/core/realtime/url_lookup_service_base.h b/components/safe_browsing/core/realtime/url_lookup_service_base.h
index 26fc2c0..eb89be0 100644
--- a/components/safe_browsing/core/realtime/url_lookup_service_base.h
+++ b/components/safe_browsing/core/realtime/url_lookup_service_base.h
@@ -121,7 +121,7 @@
       base::flat_map<network::SimpleURLLoader*, RTLookupResponseCallback>;
 
   // Returns the endpoint that the URL lookup will be sent to.
-  static GURL GetRealTimeLookupUrl();
+  virtual GURL GetRealTimeLookupUrl() const = 0;
 
   // Returns the traffic annotation tag that is attached in the simple URL
   // loader.
diff --git a/components/spellcheck/common/spellcheck_common.cc b/components/spellcheck/common/spellcheck_common.cc
index ef00b71..e6596b1 100644
--- a/components/spellcheck/common/spellcheck_common.cc
+++ b/components/spellcheck/common/spellcheck_common.cc
@@ -62,6 +62,7 @@
     {"hy", "hy"},
     {"id", "id-ID"},
     {"it", "it-IT"},
+    {"it-IT", "it-IT"},
     {"ko", "ko"},
     {"lt", "lt-LT"},
     {"lv", "lv-LV"},
diff --git a/components/subresource_redirect/BUILD.gn b/components/subresource_redirect/BUILD.gn
new file mode 100644
index 0000000..c4629b5
--- /dev/null
+++ b/components/subresource_redirect/BUILD.gn
@@ -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.
+
+static_library("test_support") {
+  testonly = true
+
+  sources = [
+    "subresource_redirect_browser_test_util.cc",
+    "subresource_redirect_browser_test_util.h",
+    "subresource_redirect_test_util.cc",
+    "subresource_redirect_test_util.h",
+  ]
+
+  deps = [
+    "//base/test:test_support",
+    "//chrome/common:constants",
+    "//components/metrics:content",
+    "//components/subresource_redirect/proto:proto",
+    "//content/test:test_support",
+    "//net:test_support",
+    "//testing/gtest:gtest",
+    "//url:url",
+  ]
+
+  if (!is_android) {
+    deps += [ "//chrome/test:test_support_ui" ]
+  }
+}
diff --git a/components/subresource_redirect/DEPS b/components/subresource_redirect/DEPS
new file mode 100644
index 0000000..2b1c72b
--- /dev/null
+++ b/components/subresource_redirect/DEPS
@@ -0,0 +1,9 @@
+include_rules = [
+  "+chrome/common",
+  "+components/data_reduction_proxy",
+  "+components/metrics",
+  "+content/public/test",
+  "+net/base",
+  "+net/test/embedded_test_server",
+  "+third_party/blink/public/common/features.h",
+]
diff --git a/components/subresource_redirect/DIR_METADATA b/components/subresource_redirect/DIR_METADATA
new file mode 100644
index 0000000..3d3ad077
--- /dev/null
+++ b/components/subresource_redirect/DIR_METADATA
@@ -0,0 +1,3 @@
+monorail {
+  component: "Internals>Network>DataUse"
+}
diff --git a/components/subresource_redirect/OWNERS b/components/subresource_redirect/OWNERS
new file mode 100644
index 0000000..2783dea
--- /dev/null
+++ b/components/subresource_redirect/OWNERS
@@ -0,0 +1 @@
+file://components/data_reduction_proxy/OWNERS
diff --git a/components/subresource_redirect/common/BUILD.gn b/components/subresource_redirect/common/BUILD.gn
new file mode 100644
index 0000000..d67109d
--- /dev/null
+++ b/components/subresource_redirect/common/BUILD.gn
@@ -0,0 +1,12 @@
+# Copyright 2021 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+source_set("common") {
+  sources = [
+    "subresource_redirect_features.cc",
+    "subresource_redirect_features.h",
+  ]
+
+  deps = [ "//third_party/blink/public/common" ]
+}
diff --git a/components/subresource_redirect/common/subresource_redirect_features.cc b/components/subresource_redirect/common/subresource_redirect_features.cc
new file mode 100644
index 0000000..4147808
--- /dev/null
+++ b/components/subresource_redirect/common/subresource_redirect_features.cc
@@ -0,0 +1,52 @@
+// 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/subresource_redirect/common/subresource_redirect_features.h"
+
+#include "base/metrics/field_trial_params.h"
+#include "third_party/blink/public/common/features.h"
+
+namespace subresource_redirect {
+
+namespace {
+
+bool IsSubresourceRedirectEnabled() {
+  return base::FeatureList::IsEnabled(blink::features::kSubresourceRedirect);
+}
+
+}  // namespace
+
+bool ShouldEnablePublicImageHintsBasedCompression() {
+  bool is_enabled = IsSubresourceRedirectEnabled() &&
+                    base::GetFieldTrialParamByFeatureAsBool(
+                        blink::features::kSubresourceRedirect,
+                        "enable_public_image_hints_based_compression", true);
+  // Only one of the public image hints or login and robots based image
+  // compression should be active.
+  DCHECK(!is_enabled || !ShouldEnableLoginRobotsCheckedCompression());
+  return is_enabled;
+}
+
+bool ShouldEnableLoginRobotsCheckedCompression() {
+  bool is_enabled = IsSubresourceRedirectEnabled() &&
+                    base::GetFieldTrialParamByFeatureAsBool(
+                        blink::features::kSubresourceRedirect,
+                        "enable_login_robots_based_compression", false);
+  // Only one of the public image hints or login and robots based image
+  // compression should be active.
+  DCHECK(!is_enabled || !ShouldEnablePublicImageHintsBasedCompression());
+  return is_enabled;
+}
+
+// Should the subresource be redirected to its compressed version. This returns
+// false if only coverage metrics need to be recorded and actual redirection
+// should not happen.
+bool ShouldCompressRedirectSubresource() {
+  return base::FeatureList::IsEnabled(blink::features::kSubresourceRedirect) &&
+         base::GetFieldTrialParamByFeatureAsBool(
+             blink::features::kSubresourceRedirect,
+             "enable_subresource_server_redirect", true);
+}
+
+}  // namespace subresource_redirect
diff --git a/components/subresource_redirect/common/subresource_redirect_features.h b/components/subresource_redirect/common/subresource_redirect_features.h
new file mode 100644
index 0000000..58908c6
--- /dev/null
+++ b/components/subresource_redirect/common/subresource_redirect_features.h
@@ -0,0 +1,25 @@
+// 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_SUBRESOURCE_REDIRECT_COMMON_SUBRESOURCE_REDIRECT_FEATURES_H_
+#define COMPONENTS_SUBRESOURCE_REDIRECT_COMMON_SUBRESOURCE_REDIRECT_FEATURES_H_
+
+namespace subresource_redirect {
+
+// Returns if the public image hints based subresource compression is enabled.
+bool ShouldEnablePublicImageHintsBasedCompression();
+
+// Returns if the login and robots checks based subresource compression is
+// enabled. This compresses non logged-in pages and subresources allowed by
+// robots.txt rules.
+bool ShouldEnableLoginRobotsCheckedCompression();
+
+// Should the subresource be redirected to its compressed version. This returns
+// false if only coverage metrics need to be recorded and actual redirection
+// should not happen.
+bool ShouldCompressRedirectSubresource();
+
+}  // namespace subresource_redirect
+
+#endif  // COMPONENTS_SUBRESOURCE_REDIRECT_COMMON_SUBRESOURCE_REDIRECT_FEATURES_H_
diff --git a/components/subresource_redirect/proto/BUILD.gn b/components/subresource_redirect/proto/BUILD.gn
new file mode 100644
index 0000000..b54caebd
--- /dev/null
+++ b/components/subresource_redirect/proto/BUILD.gn
@@ -0,0 +1,9 @@
+# Copyright 2021 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//third_party/protobuf/proto_library.gni")
+
+proto_library("proto") {
+  sources = [ "robots_rules.proto" ]
+}
diff --git a/components/data_reduction_proxy/proto/robots_rules.proto b/components/subresource_redirect/proto/robots_rules.proto
similarity index 100%
rename from components/data_reduction_proxy/proto/robots_rules.proto
rename to components/subresource_redirect/proto/robots_rules.proto
diff --git a/components/subresource_redirect/subresource_redirect_browser_test_util.cc b/components/subresource_redirect/subresource_redirect_browser_test_util.cc
new file mode 100644
index 0000000..8579326
--- /dev/null
+++ b/components/subresource_redirect/subresource_redirect_browser_test_util.cc
@@ -0,0 +1,174 @@
+// 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/subresource_redirect/subresource_redirect_browser_test_util.h"
+
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/path_service.h"
+#include "chrome/common/chrome_paths.h"
+#include "components/metrics/content/subprocess_metrics_provider.h"
+#include "components/subresource_redirect/proto/robots_rules.pb.h"
+#include "content/public/test/browser_test_utils.h"
+#include "net/base/url_util.h"
+#include "net/test/embedded_test_server/http_request.h"
+#include "net/test/embedded_test_server/http_response.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace subresource_redirect {
+
+void RetryForHistogramUntilCountReached(base::HistogramTester* histogram_tester,
+                                        const std::string& histogram_name,
+                                        size_t count) {
+  while (true) {
+    FetchHistogramsFromChildProcesses();
+
+    const std::vector<base::Bucket> buckets =
+        histogram_tester->GetAllSamples(histogram_name);
+    size_t total_count = 0;
+    for (const auto& bucket : buckets) {
+      total_count += bucket.count;
+    }
+    if (total_count >= count) {
+      break;
+    }
+  }
+}
+
+void FetchHistogramsFromChildProcesses() {
+  content::FetchHistogramsFromChildProcesses();
+  metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
+}
+
+RobotsRulesTestServer::RobotsRulesTestServer()
+    : server_(net::EmbeddedTestServer::TYPE_HTTPS) {}
+
+RobotsRulesTestServer::~RobotsRulesTestServer() = default;
+
+bool RobotsRulesTestServer::Start() {
+  server_.ServeFilesFromSourceDirectory("chrome/test/data");
+  server_.RegisterRequestHandler(base::BindRepeating(
+      &RobotsRulesTestServer::OnServerRequest, base::Unretained(this)));
+  server_.RegisterRequestMonitor(base::BindRepeating(
+      &RobotsRulesTestServer::OnRequestMonitor, base::Unretained(this)));
+  return server_.Start();
+}
+
+void RobotsRulesTestServer::AddRobotsRules(
+    const GURL& origin,
+    const std::vector<RobotsRule>& robots_rules) {
+  robots_rules_proto_[origin.spec()] = GetRobotsRulesProtoString(robots_rules);
+}
+
+void RobotsRulesTestServer::VerifyRequestedOrigins(
+    const std::set<std::string>& requests) {
+  EXPECT_EQ(received_requests_, requests);
+}
+
+std::unique_ptr<net::test_server::HttpResponse>
+RobotsRulesTestServer::OnServerRequest(
+    const net::test_server::HttpRequest& request) {
+  std::unique_ptr<net::test_server::BasicHttpResponse> response =
+      std::make_unique<net::test_server::BasicHttpResponse>();
+  std::string robots_url_str;
+  EXPECT_EQ("/robots", request.GetURL().path());
+  EXPECT_TRUE(
+      net::GetValueForKeyInQuery(request.GetURL(), "u", &robots_url_str));
+  GURL robots_url(robots_url_str);
+  EXPECT_EQ("/robots.txt", GURL(robots_url).path());
+
+  switch (failure_mode_) {
+    case FailureMode::kLoadshed503RetryAfterResponse:
+      response->set_code(net::HTTP_SERVICE_UNAVAILABLE);
+      response->AddCustomHeader("Retry-After", "5");
+      return response;
+    case FailureMode::kTimeout:
+      response = std::make_unique<net::test_server::DelayedHttpResponse>(
+          base::TimeDelta::FromSeconds(2));
+      break;
+    case FailureMode::kNone:
+      break;
+  }
+
+  auto it = robots_rules_proto_.find(robots_url.GetOrigin().spec());
+  if (it != robots_rules_proto_.end())
+    response->set_content(it->second);
+  return std::move(response);
+}
+
+void RobotsRulesTestServer::OnRequestMonitor(
+    const net::test_server::HttpRequest& request) {
+  std::string robots_url_str;
+  EXPECT_EQ("/robots", request.GetURL().path());
+  EXPECT_TRUE(
+      net::GetValueForKeyInQuery(request.GetURL(), "u", &robots_url_str));
+  std::string robots_origin = GURL(robots_url_str).GetOrigin().spec();
+  EXPECT_TRUE(received_requests_.find(robots_origin) ==
+              received_requests_.end());
+  received_requests_.insert(robots_origin);
+}
+
+ImageCompressionTestServer::ImageCompressionTestServer()
+    : server_(net::EmbeddedTestServer::TYPE_HTTPS) {}
+
+ImageCompressionTestServer::~ImageCompressionTestServer() = default;
+
+bool ImageCompressionTestServer::Start() {
+  server_.ServeFilesFromSourceDirectory("chrome/test/data");
+  server_.RegisterRequestHandler(base::BindRepeating(
+      &ImageCompressionTestServer::OnServerRequest, base::Unretained(this)));
+  server_.RegisterRequestMonitor(base::BindRepeating(
+      &ImageCompressionTestServer::OnRequestMonitor, base::Unretained(this)));
+  return server_.Start();
+}
+
+void ImageCompressionTestServer::VerifyRequestedImagePaths(
+    const std::set<std::string>& paths) {
+  EXPECT_EQ(received_request_paths_, paths);
+}
+
+std::unique_ptr<net::test_server::HttpResponse>
+ImageCompressionTestServer::OnServerRequest(
+    const net::test_server::HttpRequest& request) {
+  std::unique_ptr<net::test_server::BasicHttpResponse> response =
+      std::make_unique<net::test_server::BasicHttpResponse>();
+
+  switch (failure_mode_) {
+    case FailureMode::kLoadshed503RetryAfterResponse:
+      response->set_code(net::HTTP_SERVICE_UNAVAILABLE);
+      response->AddCustomHeader("Retry-After", "5");
+      return response;
+    case FailureMode::kNone:
+      break;
+  }
+
+  // Serve the correct image file.
+  std::string img_url;
+  std::string file_contents;
+  base::FilePath test_data_directory;
+  EXPECT_EQ("/i", request.GetURL().path());
+  EXPECT_TRUE(net::GetValueForKeyInQuery(request.GetURL(), "u", &img_url));
+  base::PathService::Get(chrome::DIR_TEST_DATA, &test_data_directory);
+  if (base::ReadFileToString(
+          test_data_directory.AppendASCII(GURL(img_url).path().substr(1)),
+          &file_contents)) {
+    response->set_content(file_contents);
+    response->set_code(net::HTTP_OK);
+  }
+  return std::move(response);
+}
+
+// Called on every subresource request
+void ImageCompressionTestServer::OnRequestMonitor(
+    const net::test_server::HttpRequest& request) {
+  std::string img_url;
+  EXPECT_EQ("/i", request.GetURL().path());
+  EXPECT_TRUE(net::GetValueForKeyInQuery(request.GetURL(), "u", &img_url));
+  img_url = GURL(img_url).PathForRequest();
+  EXPECT_TRUE(received_request_paths_.find(img_url) ==
+              received_request_paths_.end());
+  received_request_paths_.insert(img_url);
+}
+
+}  // namespace subresource_redirect
diff --git a/components/subresource_redirect/subresource_redirect_browser_test_util.h b/components/subresource_redirect/subresource_redirect_browser_test_util.h
new file mode 100644
index 0000000..4165a2b
--- /dev/null
+++ b/components/subresource_redirect/subresource_redirect_browser_test_util.h
@@ -0,0 +1,118 @@
+// 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_SUBRESOURCE_REDIRECT_SUBRESOURCE_REDIRECT_BROWSER_TEST_UTIL_H_
+#define COMPONENTS_SUBRESOURCE_REDIRECT_SUBRESOURCE_REDIRECT_BROWSER_TEST_UTIL_H_
+
+#include <map>
+#include <string>
+
+#include "base/test/metrics/histogram_tester.h"
+#include "components/subresource_redirect/subresource_redirect_test_util.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "url/gurl.h"
+
+namespace subresource_redirect {
+
+// Retries fetching |histogram_name| until it contains at least |count| samples.
+void RetryForHistogramUntilCountReached(base::HistogramTester* histogram_tester,
+                                        const std::string& histogram_name,
+                                        size_t count);
+
+// Fetches histograms from renderer child processes.
+void FetchHistogramsFromChildProcesses();
+
+// Embedded test server for the robots rules.
+class RobotsRulesTestServer {
+ public:
+  // Different failures modes the robots server should return.
+  enum FailureMode {
+    kNone = 0,
+    kLoadshed503RetryAfterResponse,
+    kTimeout,
+  };
+
+  RobotsRulesTestServer();
+  ~RobotsRulesTestServer();
+
+  // Start the server.
+  bool Start();
+
+  std::string GetURL() const {
+    return server_.GetURL("robotsrules.com", "/").spec();
+  }
+
+  void AddRobotsRules(const GURL& origin,
+                      const std::vector<RobotsRule>& robots_rules);
+
+  void VerifyRequestedOrigins(const std::set<std::string>& requests);
+
+  std::set<std::string> received_requests() const { return received_requests_; }
+
+  void set_failure_mode(FailureMode failure_mode) {
+    failure_mode_ = failure_mode;
+  }
+
+ private:
+  std::unique_ptr<net::test_server::HttpResponse> OnServerRequest(
+      const net::test_server::HttpRequest& request);
+
+  // Called on every robots request.
+  void OnRequestMonitor(const net::test_server::HttpRequest& request);
+
+  // Robots rules proto keyed by origin.
+  std::map<std::string, std::string> robots_rules_proto_;
+
+  // Whether the robots server should return failure.
+  FailureMode failure_mode_ = FailureMode::kNone;
+
+  // All the origins the robots rules are requested for.
+  std::set<std::string> received_requests_;
+
+  net::EmbeddedTestServer server_;
+};
+
+// Embedded test server to serve the image resources.
+class ImageCompressionTestServer {
+ public:
+  // Different failures modes the image server should return
+  enum FailureMode {
+    kNone = 0,
+    kLoadshed503RetryAfterResponse,
+  };
+  ImageCompressionTestServer();
+  ~ImageCompressionTestServer();
+
+  // Start the server.
+  bool Start();
+
+  std::string GetURL() const {
+    return server_.GetURL("imagecompression.com", "/").spec();
+  }
+
+  void VerifyRequestedImagePaths(const std::set<std::string>& paths);
+
+  void set_failure_mode(FailureMode failure_mode) {
+    failure_mode_ = failure_mode;
+  }
+
+ private:
+  std::unique_ptr<net::test_server::HttpResponse> OnServerRequest(
+      const net::test_server::HttpRequest& request);
+
+  // Called on every subresource request.
+  void OnRequestMonitor(const net::test_server::HttpRequest& request);
+
+  // All the URL paths of the requested images.
+  std::set<std::string> received_request_paths_;
+
+  // Whether the subresource server should return failure.
+  FailureMode failure_mode_ = FailureMode::kNone;
+
+  net::EmbeddedTestServer server_;
+};
+
+}  // namespace subresource_redirect
+
+#endif  // COMPONENTS_SUBRESOURCE_REDIRECT_SUBRESOURCE_REDIRECT_BROWSER_TEST_UTIL_H_
diff --git a/chrome/renderer/subresource_redirect/login_robots_decider_test_util.cc b/components/subresource_redirect/subresource_redirect_test_util.cc
similarity index 65%
rename from chrome/renderer/subresource_redirect/login_robots_decider_test_util.cc
rename to components/subresource_redirect/subresource_redirect_test_util.cc
index e09d736..0ba4a48d 100644
--- a/chrome/renderer/subresource_redirect/login_robots_decider_test_util.cc
+++ b/components/subresource_redirect/subresource_redirect_test_util.cc
@@ -1,14 +1,14 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
+// Copyright 2021 The Chromium Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/renderer/subresource_redirect/login_robots_decider_test_util.h"
+#include "components/subresource_redirect/subresource_redirect_test_util.h"
 
-#include "components/data_reduction_proxy/proto/robots_rules.pb.h"
+#include "components/subresource_redirect/proto/robots_rules.pb.h"
 
 namespace subresource_redirect {
 
-std::string GetRobotsRulesProtoString(const std::vector<Rule>& patterns) {
+std::string GetRobotsRulesProtoString(const std::vector<RobotsRule>& patterns) {
   proto::RobotsRules robots_rules;
   for (const auto& pattern : patterns) {
     auto* new_rule = robots_rules.add_image_ordered_rules();
diff --git a/components/subresource_redirect/subresource_redirect_test_util.h b/components/subresource_redirect/subresource_redirect_test_util.h
new file mode 100644
index 0000000..713197fe
--- /dev/null
+++ b/components/subresource_redirect/subresource_redirect_test_util.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_SUBRESOURCE_REDIRECT_SUBRESOURCE_REDIRECT_TEST_UTIL_H_
+#define COMPONENTS_SUBRESOURCE_REDIRECT_SUBRESOURCE_REDIRECT_TEST_UTIL_H_
+
+#include <string>
+
+namespace subresource_redirect {
+
+const bool kRuleTypeAllow = true;
+const bool kRuleTypeDisallow = false;
+
+// Holds one allow or disallow robots rule
+struct RobotsRule {
+  RobotsRule(bool rule_type, const std::string& pattern)
+      : rule_type(rule_type), pattern(pattern) {}
+
+  bool rule_type;
+  std::string pattern;
+};
+
+// Convert robots rules to its proto.
+std::string GetRobotsRulesProtoString(const std::vector<RobotsRule>& patterns);
+
+}  // namespace subresource_redirect
+
+#endif  // COMPONENTS_SUBRESOURCE_REDIRECT_SUBRESOURCE_REDIRECT_TEST_UTIL_H_
diff --git a/components/vector_icons/BUILD.gn b/components/vector_icons/BUILD.gn
index 1200e64f..0270b3b 100644
--- a/components/vector_icons/BUILD.gn
+++ b/components/vector_icons/BUILD.gn
@@ -9,6 +9,7 @@
 
   sources = [
     "accessibility.icon",
+    "add_cellular_network.icon",
     "ads.icon",
     "back_arrow.icon",
     "blocked_badge.icon",
diff --git a/components/vector_icons/add_cellular_network.icon b/components/vector_icons/add_cellular_network.icon
new file mode 100644
index 0000000..3cd14272
--- /dev/null
+++ b/components/vector_icons/add_cellular_network.icon
@@ -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.
+
+CANVAS_DIMENSIONS, 20,
+MOVE_TO, 16, 8,
+CUBIC_TO, 16.34f, 8, 16.68f, 8.03f, 17, 8.08f,
+V_LINE_TO, 5,
+CUBIC_TO, 17, 4.11f, 15.92f, 3.66f, 15.29f, 4.29f,
+LINE_TO, 4.29f, 15.29f,
+CUBIC_TO, 3.66f, 15.92f, 4.11f, 17, 5, 17,
+H_LINE_TO, 10.8f,
+CUBIC_TO, 10.29f, 16.12f, 10, 15.09f, 10, 14,
+CUBIC_TO, 10, 10.69f, 12.69f, 8, 16, 8,
+CLOSE,
+MOVE_TO, 19, 13,
+H_LINE_TO, 17,
+V_LINE_TO, 11,
+H_LINE_TO, 15,
+V_LINE_TO, 13,
+H_LINE_TO, 13,
+V_LINE_TO, 15,
+H_LINE_TO, 15,
+V_LINE_TO, 17,
+H_LINE_TO, 17,
+V_LINE_TO, 15,
+H_LINE_TO, 19,
+V_LINE_TO, 13,
+CLOSE
\ No newline at end of file
diff --git a/components/viz/common/quads/compositor_render_pass_unittest.cc b/components/viz/common/quads/compositor_render_pass_unittest.cc
index e4d22c5..755a54a 100644
--- a/components/viz/common/quads/compositor_render_pass_unittest.cc
+++ b/components/viz/common/quads/compositor_render_pass_unittest.cc
@@ -15,7 +15,6 @@
 #include "components/viz/common/quads/solid_color_draw_quad.h"
 #include "components/viz/common/surfaces/subtree_capture_id.h"
 #include "testing/gtest/include/gtest/gtest.h"
-#include "third_party/skia/include/effects/SkBlurImageFilter.h"
 #include "ui/gfx/geometry/rect_conversions.h"
 #include "ui/gfx/transform.h"
 
diff --git a/components/viz/common/quads/draw_quad_unittest.cc b/components/viz/common/quads/draw_quad_unittest.cc
index 4b4e05d..f584bad 100644
--- a/components/viz/common/quads/draw_quad_unittest.cc
+++ b/components/viz/common/quads/draw_quad_unittest.cc
@@ -32,7 +32,6 @@
 #include "components/viz/common/quads/video_hole_draw_quad.h"
 #include "components/viz/common/quads/yuv_video_draw_quad.h"
 #include "testing/gtest/include/gtest/gtest.h"
-#include "third_party/skia/include/effects/SkBlurImageFilter.h"
 #include "ui/gfx/hdr_metadata.h"
 #include "ui/gfx/transform.h"
 
diff --git a/components/viz/common/quads/render_pass_io.cc b/components/viz/common/quads/render_pass_io.cc
index ed09e2e..fa34566 100644
--- a/components/viz/common/quads/render_pass_io.cc
+++ b/components/viz/common/quads/render_pass_io.cc
@@ -455,7 +455,8 @@
       dict.SetIntKey("zoom_inset", filter.zoom_inset());
       break;
     case cc::FilterOperation::BLUR:
-      dict.SetIntKey("blur_tile_mode", filter.blur_tile_mode());
+      dict.SetIntKey("blur_tile_mode",
+                     static_cast<int>(filter.blur_tile_mode()));
       break;
     default:
       break;
@@ -536,7 +537,7 @@
       if (!blur_tile_mode)
         return false;
       filter.set_blur_tile_mode(
-          static_cast<SkBlurImageFilter::TileMode>(blur_tile_mode.value()));
+          static_cast<SkTileMode>(blur_tile_mode.value()));
       break;
     default:
       break;
diff --git a/components/viz/common/skia_helper.cc b/components/viz/common/skia_helper.cc
index ff849d1..26ba3210 100644
--- a/components/viz/common/skia_helper.cc
+++ b/components/viz/common/skia_helper.cc
@@ -4,8 +4,8 @@
 #include "components/viz/common/skia_helper.h"
 #include "base/trace_event/trace_event.h"
 #include "cc/base/math_util.h"
-#include "third_party/skia/include/effects/SkColorFilterImageFilter.h"
 #include "third_party/skia/include/effects/SkColorMatrix.h"
+#include "third_party/skia/include/effects/SkImageFilters.h"
 #include "third_party/skia/include/effects/SkOverdrawColorFilter.h"
 #include "third_party/skia/include/gpu/GrBackendSurface.h"
 #include "third_party/skia/include/gpu/GrRecordingContext.h"
@@ -69,8 +69,7 @@
 sk_sp<SkImageFilter> SkiaHelper::BuildOpacityFilter(float opacity) {
   SkColorMatrix matrix;
   matrix.setScale(1.f, 1.f, 1.f, opacity);
-  return SkColorFilterImageFilter::Make(SkColorFilters::Matrix(matrix),
-                                        nullptr);
+  return SkImageFilters::ColorFilter(SkColorFilters::Matrix(matrix), nullptr);
 }
 
 }  // namespace viz
diff --git a/components/viz/service/display/renderer_pixeltest.cc b/components/viz/service/display/renderer_pixeltest.cc
index bb96eb6..024b574 100644
--- a/components/viz/service/display/renderer_pixeltest.cc
+++ b/components/viz/service/display/renderer_pixeltest.cc
@@ -560,7 +560,7 @@
       video_frame->natural_size(), video_frame->timestamp());
 
   // Copy all metadata.
-  ret->metadata()->MergeMetadataFrom(video_frame->metadata());
+  ret->metadata().MergeMetadataFrom(video_frame->metadata());
 
   for (int plane = media::VideoFrame::kYPlane;
        plane <= media::VideoFrame::kVPlane; ++plane) {
diff --git a/components/viz/service/display/skia_renderer.cc b/components/viz/service/display/skia_renderer.cc
index b5b629f..d4d0c56 100644
--- a/components/viz/service/display/skia_renderer.cc
+++ b/components/viz/service/display/skia_renderer.cc
@@ -54,7 +54,6 @@
 #include "third_party/skia/include/core/SkPixelRef.h"
 #include "third_party/skia/include/core/SkShader.h"
 #include "third_party/skia/include/core/SkString.h"
-#include "third_party/skia/include/effects/SkColorFilterImageFilter.h"
 #include "third_party/skia/include/effects/SkColorMatrix.h"
 #include "third_party/skia/include/effects/SkGradientShader.h"
 #include "third_party/skia/include/effects/SkImageFilters.h"
@@ -1176,7 +1175,7 @@
       if (params->opacity != 1.f) {
         // Apply opacity as the last step of image filter so it is uniform
         // across any overlapping content produced by the image filters.
-        paint->setImageFilter(SkColorFilterImageFilter::Make(
+        paint->setImageFilter(SkImageFilters::ColorFilter(
             MakeOpacityFilter(params->opacity, nullptr),
             rpdq_params.image_filter));
         paint->setAlphaf(1.f);
diff --git a/components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl.cc b/components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl.cc
index ce5fca55..47072ee 100644
--- a/components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl.cc
+++ b/components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl.cc
@@ -524,21 +524,21 @@
   // At this point, the capture is going to proceed. Populate the VideoFrame's
   // metadata, and notify the oracle.
   const int64_t capture_frame_number = next_capture_frame_number_++;
-  VideoFrameMetadata* const metadata = frame->metadata();
-  metadata->capture_begin_time = clock_->NowTicks();
-  metadata->capture_counter = capture_frame_number;
-  metadata->frame_duration = oracle_->estimated_frame_duration();
-  metadata->frame_rate = 1.0 / oracle_->min_capture_period().InSecondsF();
-  metadata->reference_time = event_time;
-  metadata->device_scale_factor = frame_metadata.device_scale_factor;
-  metadata->page_scale_factor = frame_metadata.page_scale_factor;
-  metadata->root_scroll_offset_x = frame_metadata.root_scroll_offset.x();
-  metadata->root_scroll_offset_y = frame_metadata.root_scroll_offset.y();
+  VideoFrameMetadata& metadata = frame->metadata();
+  metadata.capture_begin_time = clock_->NowTicks();
+  metadata.capture_counter = capture_frame_number;
+  metadata.frame_duration = oracle_->estimated_frame_duration();
+  metadata.frame_rate = 1.0 / oracle_->min_capture_period().InSecondsF();
+  metadata.reference_time = event_time;
+  metadata.device_scale_factor = frame_metadata.device_scale_factor;
+  metadata.page_scale_factor = frame_metadata.page_scale_factor;
+  metadata.root_scroll_offset_x = frame_metadata.root_scroll_offset.x();
+  metadata.root_scroll_offset_y = frame_metadata.root_scroll_offset.y();
   if (frame_metadata.top_controls_visible_height.has_value()) {
     last_top_controls_visible_height_ =
         *frame_metadata.top_controls_visible_height;
   }
-  metadata->top_controls_visible_height = last_top_controls_visible_height_;
+  metadata.top_controls_visible_height = last_top_controls_visible_height_;
 
   oracle_->RecordCapture(utilization);
   TRACE_EVENT_ASYNC_BEGIN2("gpu.capture", "Capture", oracle_frame_number,
@@ -573,7 +573,7 @@
     if (pixel_format_ == media::PIXEL_FORMAT_I420)
       update_rect = ExpandRectToI420SubsampleBoundaries(update_rect);
   }
-  metadata->capture_update_rect = update_rect;
+  metadata.capture_update_rect = update_rect;
 
   // Extreme edge-case: If somehow the source size is so tiny that the content
   // region becomes empty, just deliver a frame filled with black.
@@ -801,7 +801,7 @@
   DCHECK_GE(capture_frame_number, next_delivery_frame_number_);
 
   if (frame)
-    frame->metadata()->capture_end_time = clock_->NowTicks();
+    frame->metadata().capture_end_time = clock_->NowTicks();
 
   // Ensure frames are delivered in-order by using a min-heap, and only
   // deliver the next frame(s) in-sequence when they are found at the top.
@@ -866,7 +866,7 @@
   // the consumer.
   media::mojom::VideoFrameInfoPtr info = media::mojom::VideoFrameInfo::New();
   info->timestamp = frame->timestamp();
-  info->metadata = *(frame->metadata());
+  info->metadata = frame->metadata();
   info->pixel_format = frame->format();
   info->coded_size = frame->coded_size();
   info->visible_rect = frame->visible_rect();
diff --git a/components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl_unittest.cc b/components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl_unittest.cc
index 0f427669..eb80de2 100644
--- a/components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl_unittest.cc
+++ b/components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl_unittest.cc
@@ -60,10 +60,10 @@
     float device_scale_factor,
     float page_scale_factor,
     const gfx::Vector2dF& root_scroll_offset) {
-  auto dsf = frame.metadata()->device_scale_factor;
-  auto psf = frame.metadata()->page_scale_factor;
-  auto rso_x = frame.metadata()->root_scroll_offset_x;
-  auto rso_y = frame.metadata()->root_scroll_offset_y;
+  auto dsf = frame.metadata().device_scale_factor;
+  auto psf = frame.metadata().page_scale_factor;
+  auto rso_x = frame.metadata().root_scroll_offset_x;
+  auto rso_y = frame.metadata().root_scroll_offset_y;
 
   bool valid = dsf.has_value() && psf.has_value() && rso_x.has_value() &&
                rso_y.has_value();
@@ -650,16 +650,16 @@
     EXPECT_EQ(gfx::Rect(size_set().capture_size), frame->visible_rect());
     EXPECT_LT(last_timestamp, frame->timestamp());
     last_timestamp = frame->timestamp();
-    const VideoFrameMetadata* metadata = frame->metadata();
-    EXPECT_EQ(expected_capture_begin_time, *metadata->capture_begin_time);
-    EXPECT_EQ(expected_capture_end_time, *metadata->capture_end_time);
+    const VideoFrameMetadata& metadata = frame->metadata();
+    EXPECT_EQ(expected_capture_begin_time, *metadata.capture_begin_time);
+    EXPECT_EQ(expected_capture_end_time, *metadata.capture_end_time);
     EXPECT_EQ(gfx::ColorSpace::CreateREC709(), frame->ColorSpace());
     // frame_duration is an estimate computed by the VideoCaptureOracle, so it
     // its exact value is not being checked here.
-    EXPECT_TRUE(metadata->frame_duration.has_value());
-    EXPECT_NEAR(media::limits::kMaxFramesPerSecond, *metadata->frame_rate,
+    EXPECT_TRUE(metadata.frame_duration.has_value());
+    EXPECT_NEAR(media::limits::kMaxFramesPerSecond, *metadata.frame_rate,
                 0.001);
-    EXPECT_EQ(expected_reference_time, *metadata->reference_time);
+    EXPECT_EQ(expected_reference_time, *metadata.reference_time);
 
     // Notify the capturer that the consumer is done with the frame.
     consumer.SendDoneNotification(i);
@@ -1149,9 +1149,9 @@
   {
     auto received_frame = consumer.TakeFrame(cur_frame_index);
     EXPECT_EQ(gfx::Rect(size_set().capture_size),
-              received_frame->metadata()->capture_update_rect);
+              received_frame->metadata().capture_update_rect);
     previous_capture_counter_received =
-        *received_frame->metadata()->capture_counter;
+        *received_frame->metadata().capture_counter;
   }
   consumer.SendDoneNotification(cur_frame_index);
 
@@ -1178,9 +1178,9 @@
   EXPECT_EQ(expected_frames_count, consumer.num_frames_received());
   {
     auto received_frame = consumer.TakeFrame(++cur_frame_index);
-    int received_capture_counter = *received_frame->metadata()->capture_counter;
+    int received_capture_counter = *received_frame->metadata().capture_counter;
     EXPECT_EQ(expected_frame_update_rect,
-              *received_frame->metadata()->capture_update_rect);
+              *received_frame->metadata().capture_update_rect);
     EXPECT_EQ(previous_capture_counter_received + 1, received_capture_counter);
     previous_capture_counter_received = received_capture_counter;
   }
@@ -1196,8 +1196,8 @@
   EXPECT_EQ(expected_frames_count, consumer.num_frames_received());
   {
     auto received_frame = consumer.TakeFrame(++cur_frame_index);
-    int received_capture_counter = *received_frame->metadata()->capture_counter;
-    EXPECT_TRUE(received_frame->metadata()->capture_update_rect->IsEmpty());
+    int received_capture_counter = *received_frame->metadata().capture_counter;
+    EXPECT_TRUE(received_frame->metadata().capture_update_rect->IsEmpty());
     EXPECT_EQ(previous_capture_counter_received + 1, received_capture_counter);
     previous_capture_counter_received = received_capture_counter;
   }
@@ -1214,9 +1214,9 @@
   EXPECT_EQ(expected_frames_count, consumer.num_frames_received());
   {
     auto received_frame = consumer.TakeFrame(++cur_frame_index);
-    int received_capture_counter = *received_frame->metadata()->capture_counter;
+    int received_capture_counter = *received_frame->metadata().capture_counter;
     EXPECT_EQ(gfx::Rect(size_set().capture_size),
-              *received_frame->metadata()->capture_update_rect);
+              *received_frame->metadata().capture_update_rect);
     EXPECT_EQ(previous_capture_counter_received + 1, received_capture_counter);
     previous_capture_counter_received = received_capture_counter;
   }
@@ -1233,9 +1233,9 @@
   EXPECT_EQ(expected_frames_count, consumer.num_frames_received());
   {
     auto received_frame = consumer.TakeFrame(++cur_frame_index);
-    int received_capture_counter = *received_frame->metadata()->capture_counter;
+    int received_capture_counter = *received_frame->metadata().capture_counter;
     EXPECT_EQ(gfx::Rect(size_set().capture_size),
-              *received_frame->metadata()->capture_update_rect);
+              *received_frame->metadata().capture_update_rect);
     EXPECT_EQ(previous_capture_counter_received + 1, received_capture_counter);
     previous_capture_counter_received = received_capture_counter;
   }
@@ -1264,9 +1264,9 @@
   {
     auto received_frame = consumer.TakeFrame(cur_receive_frame_index);
     EXPECT_EQ(gfx::Rect(size_set().capture_size),
-              *received_frame->metadata()->capture_update_rect);
+              *received_frame->metadata().capture_update_rect);
     previous_capture_counter_received =
-        *received_frame->metadata()->capture_counter;
+        *received_frame->metadata().capture_counter;
   }
   consumer.SendDoneNotification(cur_receive_frame_index);
 
@@ -1292,7 +1292,7 @@
   {
     auto received_frame = consumer.TakeFrame(++cur_receive_frame_index);
     EXPECT_NE(previous_capture_counter_received + 1,
-              *received_frame->metadata()->capture_counter);
+              *received_frame->metadata().capture_counter);
   }
   StopCapture();
 }
diff --git a/content/browser/browser_interface_binders.cc b/content/browser/browser_interface_binders.cc
index c94711e..a45f9d3 100644
--- a/content/browser/browser_interface_binders.cc
+++ b/content/browser/browser_interface_binders.cc
@@ -179,7 +179,7 @@
 void BindShapeDetectionServiceOnIOThread(
     mojo::PendingReceiver<shape_detection::mojom::ShapeDetectionService>
         receiver) {
-#if BUILDFLAG(GOOGLE_CHROME_BRANDING) && BUILDFLAG(IS_ASH)
+#if BUILDFLAG(GOOGLE_CHROME_BRANDING) && BUILDFLAG(IS_CHROMEOS_ASH)
   content::ServiceProcessHost::Launch<
       shape_detection::mojom::ShapeDetectionService>(
       std::move(receiver), content::ServiceProcessHost::Options()
diff --git a/content/browser/conversions/conversion_host_unittest.cc b/content/browser/conversions/conversion_host_unittest.cc
index f8337f8..78c20508 100644
--- a/content/browser/conversions/conversion_host_unittest.cc
+++ b/content/browser/conversions/conversion_host_unittest.cc
@@ -337,8 +337,9 @@
   EXPECT_EQ(0u, test_manager_.num_impressions());
 }
 
+// TODO(crbug.com/1165985): Fix Linux ChromiumOS MSan Tests
 TEST_F(ConversionHostTest,
-       ImpressionNavigation_OriginTrustworthyChecksPerformed) {
+       DISABLED_ImpressionNavigation_OriginTrustworthyChecksPerformed) {
   const char kLocalHost[] = "http://localhost";
 
   struct {
diff --git a/content/browser/devtools/protocol/page_handler.cc b/content/browser/devtools/protocol/page_handler.cc
index 6d08d6d..5c6f2cf 100644
--- a/content/browser/devtools/protocol/page_handler.cc
+++ b/content/browser/devtools/protocol/page_handler.cc
@@ -163,11 +163,11 @@
                           gfx::Vector2dF* root_scroll_offset,
                           double* top_controls_visible_height) {
   // Get metadata from |frame|. This will CHECK if metadata is missing.
-  *device_scale_factor = *frame.metadata()->device_scale_factor;
-  *page_scale_factor = *frame.metadata()->page_scale_factor;
-  root_scroll_offset->set_x(*frame.metadata()->root_scroll_offset_x);
-  root_scroll_offset->set_y(*frame.metadata()->root_scroll_offset_y);
-  *top_controls_visible_height = *frame.metadata()->top_controls_visible_height;
+  *device_scale_factor = *frame.metadata().device_scale_factor;
+  *page_scale_factor = *frame.metadata().page_scale_factor;
+  root_scroll_offset->set_x(*frame.metadata().root_scroll_offset_x);
+  root_scroll_offset->set_y(*frame.metadata().root_scroll_offset_y);
+  *top_controls_visible_height = *frame.metadata().top_controls_visible_height;
 }
 
 template <typename ProtocolCallback>
diff --git a/content/browser/devtools/protocol/tracing_handler.cc b/content/browser/devtools/protocol/tracing_handler.cc
index e2c8664d..8cfe1af 100644
--- a/content/browser/devtools/protocol/tracing_handler.cc
+++ b/content/browser/devtools/protocol/tracing_handler.cc
@@ -1014,7 +1014,7 @@
     scoped_refptr<media::VideoFrame> frame) {
   const SkBitmap skbitmap = DevToolsVideoConsumer::GetSkBitmapFromFrame(frame);
 
-  base::TimeTicks reference_time = *frame->metadata()->reference_time;
+  base::TimeTicks reference_time = *frame->metadata().reference_time;
 
   TRACE_EVENT_OBJECT_SNAPSHOT_WITH_ID_AND_TIMESTAMP(
       TRACE_DISABLED_BY_DEFAULT("devtools.screenshot"), "Screenshot", 1,
diff --git a/content/browser/media/capture/slow_window_capturer_chromeos.cc b/content/browser/media/capture/slow_window_capturer_chromeos.cc
index 98014b68..b32e519 100644
--- a/content/browser/media/capture/slow_window_capturer_chromeos.cc
+++ b/content/browser/media/capture/slow_window_capturer_chromeos.cc
@@ -20,7 +20,6 @@
 #include "ui/gfx/geometry/rect.h"
 
 using media::VideoFrame;
-using media::VideoFrameMetadata;
 
 namespace content {
 
@@ -261,11 +260,10 @@
       begin_time - first_frame_reference_time_));
   auto* const frame = in_flight_frame->video_frame();
   DCHECK(frame);
-  VideoFrameMetadata* const metadata = frame->metadata();
-  metadata->capture_begin_time = begin_time;
-  metadata->frame_duration = capture_period_;
-  metadata->frame_rate = 1.0 / capture_period_.InSecondsF();
-  metadata->reference_time = begin_time;
+  frame->metadata().capture_begin_time = begin_time;
+  frame->metadata().frame_duration = capture_period_;
+  frame->metadata().frame_rate = 1.0 / capture_period_.InSecondsF();
+  frame->metadata().reference_time = begin_time;
   frame->set_color_space(gfx::ColorSpace::CreateREC709());
 
   // Compute the region of the VideoFrame that will contain the content. If
@@ -344,7 +342,7 @@
     std::unique_ptr<InFlightFrame> in_flight_frame) {
   auto* const frame = in_flight_frame->video_frame();
   DCHECK(frame);
-  frame->metadata()->capture_end_time = base::TimeTicks::Now();
+  frame->metadata().capture_end_time = base::TimeTicks::Now();
 
   // Clone the buffer handle for the consumer.
   base::ReadOnlySharedMemoryRegion handle =
@@ -357,7 +355,7 @@
   // the consumer.
   media::mojom::VideoFrameInfoPtr info = media::mojom::VideoFrameInfo::New();
   info->timestamp = frame->timestamp();
-  info->metadata = *(frame->metadata());
+  info->metadata = frame->metadata();
   info->pixel_format = frame->format();
   info->coded_size = frame->coded_size();
   info->visible_rect = frame->visible_rect();
diff --git a/content/browser/renderer_host/mock_render_widget_host.cc b/content/browser/renderer_host/mock_render_widget_host.cc
index 3487f74..3126d39 100644
--- a/content/browser/renderer_host/mock_render_widget_host.cc
+++ b/content/browser/renderer_host/mock_render_widget_host.cc
@@ -6,6 +6,7 @@
 
 #include "components/viz/test/mock_compositor_frame_sink_client.h"
 #include "content/browser/renderer_host/frame_token_message_queue.h"
+#include "content/test/test_render_widget_host.h"
 
 namespace content {
 
@@ -42,11 +43,8 @@
     RenderWidgetHostDelegate* delegate,
     AgentSchedulingGroupHost& agent_scheduling_group,
     int32_t routing_id) {
-  mojo::AssociatedRemote<blink::mojom::Widget> blink_widget;
-  auto blink_widget_receiver =
-      blink_widget.BindNewEndpointAndPassDedicatedReceiver();
   return Create(delegate, agent_scheduling_group, routing_id,
-                blink_widget.Unbind());
+                TestRenderWidgetHost::CreateStubWidgetRemote());
 }
 
 // static
diff --git a/content/browser/renderer_host/render_frame_host_manager.cc b/content/browser/renderer_host/render_frame_host_manager.cc
index c1bf1f9..d1446d9 100644
--- a/content/browser/renderer_host/render_frame_host_manager.cc
+++ b/content/browser/renderer_host/render_frame_host_manager.cc
@@ -914,10 +914,18 @@
           navigation_rfh == speculative_render_frame_host_.get()));
   DCHECK(!navigation_rfh->must_be_replaced());
 
-  // If the RenderFrame that needs to navigate is not live (its process was
-  // just created), initialize it.
+  // If the RenderFrame that needs to navigate is not live (its process was just
+  // created), initialize it. This can only happen for the initial main frame of
+  // a WebContents which starts non-live but non-crashed.
+  //
+  // A speculative RenderFrameHost is created in the live state. A crashed
+  // RenderFrameHost is replaced by a new speculative RenderFrameHost. A
+  // non-speculative RenderFrameHost that is being reused is already live. This
+  // leaves only a non-speculative RenderFrameHost that has never been used
+  // before.
   if (!navigation_rfh->IsRenderFrameLive()) {
-    if (!ReinitializeRenderFrame(navigation_rfh))
+    DCHECK(!frame_tree_node_->parent());
+    if (!ReinitializeMainRenderFrame(navigation_rfh))
       return nullptr;
 
     notify_webui_of_rf_creation = true;
@@ -1756,7 +1764,7 @@
 
   render_frame_host_->reset_must_be_replaced();
 
-  if (!ReinitializeRenderFrame(render_frame_host_.get())) {
+  if (!ReinitializeMainRenderFrame(render_frame_host_.get())) {
     NOTREACHED();
     return false;
   }
@@ -2842,8 +2850,10 @@
   }
 }
 
-bool RenderFrameHostManager::ReinitializeRenderFrame(
+bool RenderFrameHostManager::ReinitializeMainRenderFrame(
     RenderFrameHostImpl* render_frame_host) {
+  CHECK(!frame_tree_node_->parent());
+
   // This should be used only when the RenderFrame is not live.
   DCHECK(!render_frame_host->IsRenderFrameLive());
   DCHECK(!render_frame_host->must_be_replaced());
@@ -2852,29 +2862,10 @@
   CreateOpenerProxies(render_frame_host->GetSiteInstance(), frame_tree_node_);
 
   // Main frames need both the RenderView and RenderFrame reinitialized, so
-  // use InitRenderView.  For cross-process subframes, InitRenderView won't
-  // recreate the RenderFrame, so use InitRenderFrame instead.  Note that for
-  // subframe RenderFrameHosts, the inactive RenderView in their SiteInstance
-  // will be recreated as part of CreateOpenerProxies above.
-  if (!frame_tree_node_->parent()) {
-    DCHECK(!GetRenderFrameProxyHost(render_frame_host->GetSiteInstance()));
-    if (!InitRenderView(render_frame_host->render_view_host(), nullptr))
-      return false;
-  } else {
-    if (!InitRenderFrame(render_frame_host))
-      return false;
-
-    // When a subframe renderer dies, its RenderWidgetHostView is cleared in
-    // its CrossProcessFrameConnector, so we need to restore it now that it
-    // is re-initialized.
-    RenderFrameProxyHost* proxy_to_parent = GetProxyToParent();
-    if (proxy_to_parent) {
-      const gfx::Size* size = render_frame_host->frame_size()
-                                  ? &*render_frame_host->frame_size()
-                                  : nullptr;
-      GetProxyToParent()->SetChildRWHView(render_frame_host->GetView(), size);
-    }
-  }
+  // use InitRenderView.
+  DCHECK(!GetRenderFrameProxyHost(render_frame_host->GetSiteInstance()));
+  if (!InitRenderView(render_frame_host->render_view_host(), nullptr))
+    return false;
 
   DCHECK(render_frame_host->IsRenderFrameLive());
 
diff --git a/content/browser/renderer_host/render_frame_host_manager.h b/content/browser/renderer_host/render_frame_host_manager.h
index 995935b4..882ddc8 100644
--- a/content/browser/renderer_host/render_frame_host_manager.h
+++ b/content/browser/renderer_host/render_frame_host_manager.h
@@ -851,7 +851,7 @@
   // Helper to reinitialize the RenderFrame, RenderView, and the opener chain
   // for the provided |render_frame_host|.  Used when the |render_frame_host|
   // needs to be reused for a new navigation, but it is not live.
-  bool ReinitializeRenderFrame(RenderFrameHostImpl* render_frame_host);
+  bool ReinitializeMainRenderFrame(RenderFrameHostImpl* render_frame_host);
 
   // Sets the |pending_rfh| to be the active one. Called when the pending
   // RenderFrameHost commits.
diff --git a/content/browser/renderer_host/render_widget_host_unittest.cc b/content/browser/renderer_host/render_widget_host_unittest.cc
index 3e6e4c9..b76df4c 100644
--- a/content/browser/renderer_host/render_widget_host_unittest.cc
+++ b/content/browser/renderer_host/render_widget_host_unittest.cc
@@ -617,11 +617,10 @@
   }
 
   void ReinitalizeHost() {
-    mojo::AssociatedRemote<blink::mojom::WidgetHost> widget_host;
     host_->BindWidgetInterfaces(
-        widget_host.BindNewEndpointAndPassDedicatedReceiver(),
+        mojo::AssociatedRemote<blink::mojom::WidgetHost>()
+            .BindNewEndpointAndPassDedicatedReceiver(),
         widget_.GetNewRemote());
-
     host_->BindFrameWidgetInterfaces(
         mojo::AssociatedRemote<blink::mojom::FrameWidgetHost>()
             .BindNewEndpointAndPassDedicatedReceiver(),
diff --git a/content/browser/renderer_host/render_widget_host_view_aura.cc b/content/browser/renderer_host/render_widget_host_view_aura.cc
index fdd49c9..70e2acf 100644
--- a/content/browser/renderer_host/render_widget_host_view_aura.cc
+++ b/content/browser/renderer_host/render_widget_host_view_aura.cc
@@ -307,6 +307,11 @@
       device_scale_factor_(0.0f),
       event_handler_(new RenderWidgetHostViewEventHandler(host(), this, this)),
       frame_sink_id_(host()->GetFrameSinkId()) {
+  // CreateDelegatedFrameHostClient() and CreateAuraWindow() assume that the
+  // FrameSinkId is valid. RenderWidgetHostImpl::GetFrameSinkId() always returns
+  // a valid FrameSinkId.
+  DCHECK(frame_sink_id_.is_valid());
+
   CreateDelegatedFrameHostClient();
 
   host()->SetView(this);
@@ -2015,14 +2020,10 @@
                                                   : SK_ColorWHITE);
   // This needs to happen only after |window_| has been initialized using
   // Init(), because it needs to have the layer.
-  if (frame_sink_id_.is_valid())
-    window_->SetEmbedFrameSinkId(frame_sink_id_);
+  window_->SetEmbedFrameSinkId(frame_sink_id_);
 }
 
 void RenderWidgetHostViewAura::CreateDelegatedFrameHostClient() {
-  if (!frame_sink_id_.is_valid())
-    return;
-
   delegated_frame_host_client_ =
       std::make_unique<DelegatedFrameHostClientAura>(this);
   delegated_frame_host_ = std::make_unique<DelegatedFrameHost>(
diff --git a/content/browser/renderer_host/render_widget_host_view_aura_unittest.cc b/content/browser/renderer_host/render_widget_host_view_aura_unittest.cc
index d4b50bcb7..6bbd9f2 100644
--- a/content/browser/renderer_host/render_widget_host_view_aura_unittest.cc
+++ b/content/browser/renderer_host/render_widget_host_view_aura_unittest.cc
@@ -337,23 +337,23 @@
   ~MockRenderWidgetHostImpl() override {}
 
   // Extracts |latency_info| for wheel event, and stores it in
-  // |lastWheelOrTouchEventLatencyInfo|.
+  // |last_wheel_or_touch_event_latency_info_|.
   void ForwardWheelEventWithLatencyInfo(
       const blink::WebMouseWheelEvent& wheel_event,
       const ui::LatencyInfo& ui_latency) override {
     RenderWidgetHostImpl::ForwardWheelEventWithLatencyInfo(wheel_event,
                                                            ui_latency);
-    lastWheelOrTouchEventLatencyInfo = ui::LatencyInfo(ui_latency);
+    last_wheel_or_touch_event_latency_info_ = ui::LatencyInfo(ui_latency);
   }
 
   // Extracts |latency_info| for touch event, and stores it in
-  // |lastWheelOrTouchEventLatencyInfo|.
+  // |last_wheel_or_touch_event_latency_info_|.
   void ForwardTouchEventWithLatencyInfo(
       const blink::WebTouchEvent& touch_event,
       const ui::LatencyInfo& ui_latency) override {
     RenderWidgetHostImpl::ForwardTouchEventWithLatencyInfo(touch_event,
                                                            ui_latency);
-    lastWheelOrTouchEventLatencyInfo = ui::LatencyInfo(ui_latency);
+    last_wheel_or_touch_event_latency_info_ = ui::LatencyInfo(ui_latency);
   }
 
   void ForwardGestureEventWithLatencyInfo(
@@ -397,7 +397,6 @@
     return new MockRenderWidgetHostImpl(delegate, agent_scheduling_group,
                                         routing_id);
   }
-  ui::LatencyInfo lastWheelOrTouchEventLatencyInfo;
 
   MockWidgetInputHandler* input_handler() { return &input_handler_; }
 
@@ -417,6 +416,10 @@
     widget_.SetTouchActionFromMain(touch_action);
   }
 
+  const ui::LatencyInfo& LastWheelOrTouchEventLatencyInfo() const {
+    return last_wheel_or_touch_event_latency_info_;
+  }
+
  private:
   MockRenderWidgetHostImpl(RenderWidgetHostDelegate* delegate,
                            AgentSchedulingGroupHost& agent_scheduling_group,
@@ -428,17 +431,16 @@
                              /*hidden=*/false,
                              /*renderer_initiated_creation=*/false,
                              std::make_unique<FrameTokenMessageQueue>()) {
-    lastWheelOrTouchEventLatencyInfo = ui::LatencyInfo();
-    mojo::AssociatedRemote<blink::mojom::WidgetHost> blink_widget_host;
-    BindWidgetInterfaces(
-        blink_widget_host.BindNewEndpointAndPassDedicatedReceiver(),
-        widget_.GetNewRemote());
+    BindWidgetInterfaces(mojo::AssociatedRemote<blink::mojom::WidgetHost>()
+                             .BindNewEndpointAndPassDedicatedReceiver(),
+                         widget_.GetNewRemote());
   }
 
   void NotifyNewContentRenderingTimeoutForTesting() override {
     new_content_rendering_timeout_fired_ = true;
   }
 
+  ui::LatencyInfo last_wheel_or_touch_event_latency_info_;
   bool new_content_rendering_timeout_fired_ = false;
   MockWidgetInputHandler input_handler_;
   MockWidget widget_;
@@ -560,16 +562,10 @@
                                           aura_test_helper_->GetContext(),
                                           gfx::Rect());
 
-    mojo::AssociatedRemote<blink::mojom::FrameWidgetHost>
-        parent_frame_widget_host;
-    auto parent_frame_widget_host_receiver =
-        parent_frame_widget_host.BindNewEndpointAndPassDedicatedReceiver();
-    mojo::AssociatedRemote<blink::mojom::FrameWidget> parent_frame_widget;
-    auto parent_frame_widget_receiver =
-        parent_frame_widget.BindNewEndpointAndPassDedicatedReceiver();
     parent_host_->BindFrameWidgetInterfaces(
-        std::move(parent_frame_widget_host_receiver),
-        parent_frame_widget.Unbind());
+        mojo::AssociatedRemote<blink::mojom::FrameWidgetHost>()
+            .BindNewEndpointAndPassDedicatedReceiver(),
+        TestRenderWidgetHost::CreateStubFrameWidgetRemote());
     parent_host_->RendererWidgetCreated(/*for_frame_widget=*/true);
     // The RenderWidgetHostImpl sets up additional connections over mojo to the
     // renderer widget, which we need to complete before the test runs.
@@ -3371,8 +3367,9 @@
   ui::ScrollEvent scroll(ui::ET_SCROLL, gfx::Point(2, 2), ui::EventTimeForNow(),
                          0, 0, 0, 0, 0, 2);
   view_->OnScrollEvent(&scroll);
-  EXPECT_EQ(widget_host_->lastWheelOrTouchEventLatencyInfo.source_event_type(),
-            ui::SourceEventType::WHEEL);
+  EXPECT_EQ(
+      widget_host_->LastWheelOrTouchEventLatencyInfo().source_event_type(),
+      ui::SourceEventType::WHEEL);
 
   // TOUCH source exists.
   ui::TouchEvent press(ui::ET_TOUCH_PRESSED, gfx::Point(30, 30),
@@ -3386,8 +3383,9 @@
                          ui::PointerDetails(ui::EventPointerType::kTouch, 0));
   view_->OnTouchEvent(&press);
   view_->OnTouchEvent(&move);
-  EXPECT_EQ(widget_host_->lastWheelOrTouchEventLatencyInfo.source_event_type(),
-            ui::SourceEventType::TOUCH);
+  EXPECT_EQ(
+      widget_host_->LastWheelOrTouchEventLatencyInfo().source_event_type(),
+      ui::SourceEventType::TOUCH);
   view_->OnTouchEvent(&release);
 }
 
@@ -6114,19 +6112,17 @@
   for (auto index : active_view_sequence_) {
     ActivateViewForTextInputManager(views_[index], ui::TEXT_INPUT_TYPE_TEXT);
 
-    mojo::AssociatedRemote<blink::mojom::FrameWidgetHost>
-        blink_frame_widget_host;
-    auto blink_frame_widget_host_receiver =
-        blink_frame_widget_host.BindNewEndpointAndPassDedicatedReceiver();
-    mojo::AssociatedRemote<blink::mojom::FrameWidget> blink_frame_widget;
-    auto blink_frame_widget_receiver =
-        blink_frame_widget.BindNewEndpointAndPassDedicatedReceiver();
-
+    mojo::AssociatedRemote<blink::mojom::FrameWidget> frame_widget_remote;
+    mojo::PendingAssociatedReceiver<blink::mojom::FrameWidget>
+        frame_widget_receiver =
+            frame_widget_remote.BindNewEndpointAndPassDedicatedReceiver();
     static_cast<RenderWidgetHostImpl*>(views_[index]->GetRenderWidgetHost())
-        ->BindFrameWidgetInterfaces(std::move(blink_frame_widget_host_receiver),
-                                    blink_frame_widget.Unbind());
+        ->BindFrameWidgetInterfaces(
+            mojo::AssociatedRemote<blink::mojom::FrameWidgetHost>()
+                .BindNewEndpointAndPassDedicatedReceiver(),
+            frame_widget_remote.Unbind());
 
-    FakeFrameWidget fake_frame_widget(std::move(blink_frame_widget_receiver));
+    FakeFrameWidget fake_frame_widget(std::move(frame_widget_receiver));
 
     ime_finish_session_call.Run();
     base::RunLoop().RunUntilIdle();
diff --git a/content/browser/renderer_host/render_widget_host_view_child_frame_unittest.cc b/content/browser/renderer_host/render_widget_host_view_child_frame_unittest.cc
index b572402..37a3354 100644
--- a/content/browser/renderer_host/render_widget_host_view_child_frame_unittest.cc
+++ b/content/browser/renderer_host/render_widget_host_view_child_frame_unittest.cc
@@ -33,6 +33,7 @@
 #include "content/test/mock_render_widget_host_delegate.h"
 #include "content/test/mock_widget.h"
 #include "content/test/test_render_view_host.h"
+#include "content/test/test_render_widget_host.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/remote.h"
@@ -129,18 +130,14 @@
         /*hidden=*/false, /*renderer_initiated_creation=*/false,
         std::make_unique<FrameTokenMessageQueue>());
 
-    mojo::AssociatedRemote<blink::mojom::WidgetHost> blink_widget_host;
     widget_host_->BindWidgetInterfaces(
-        blink_widget_host.BindNewEndpointAndPassDedicatedReceiver(),
+        mojo::AssociatedRemote<blink::mojom::WidgetHost>()
+            .BindNewEndpointAndPassDedicatedReceiver(),
         widget_.GetNewRemote());
-
-    mojo::AssociatedRemote<blink::mojom::FrameWidgetHost> frame_widget_host;
-    mojo::AssociatedRemote<blink::mojom::FrameWidget> frame_widget;
-    auto frame_widget_receiver =
-        frame_widget.BindNewEndpointAndPassDedicatedReceiver();
     widget_host_->BindFrameWidgetInterfaces(
-        frame_widget_host.BindNewEndpointAndPassDedicatedReceiver(),
-        frame_widget.Unbind());
+        mojo::AssociatedRemote<blink::mojom::FrameWidgetHost>()
+            .BindNewEndpointAndPassDedicatedReceiver(),
+        TestRenderWidgetHost::CreateStubFrameWidgetRemote());
 
     blink::ScreenInfo screen_info;
     screen_info.rect = gfx::Rect(1, 2, 3, 4);
diff --git a/content/browser/service_worker/service_worker_container_host.cc b/content/browser/service_worker/service_worker_container_host.cc
index 860326a..0b1981e 100644
--- a/content/browser/service_worker/service_worker_container_host.cc
+++ b/content/browser/service_worker/service_worker_container_host.cc
@@ -1585,6 +1585,13 @@
   return true;
 }
 
+const GURL ServiceWorkerContainerHost::GetOrigin() const {
+  // Ideally, the origins of GetUrlForScopeMatch() and url() should be the same
+  // but GURL::GetOrigin() doesn't work with blob URL.
+  // See https://crbug.com/1144717
+  return GetUrlForScopeMatch().GetOrigin();
+}
+
 const GURL& ServiceWorkerContainerHost::GetUrlForScopeMatch() const {
   DCHECK(IsContainerForClient());
   if (!scope_match_url_for_blob_client_.is_empty())
diff --git a/content/browser/service_worker/service_worker_container_host.h b/content/browser/service_worker/service_worker_container_host.h
index c5f9a0a..a6ec841 100644
--- a/content/browser/service_worker/service_worker_container_host.h
+++ b/content/browser/service_worker/service_worker_container_host.h
@@ -448,6 +448,12 @@
   void EnterBackForwardCacheForTesting() { is_in_back_forward_cache_ = true; }
   void LeaveBackForwardCacheForTesting() { is_in_back_forward_cache_ = false; }
 
+  // Returns the origin of this container host.
+  // Note that you must use this function instead of retrieving the origin from
+  // url(). That can be invalid when this container host is created for a blob
+  // URL context. See comments on GetUrlForScopeMatch() for details.
+  const GURL GetOrigin() const;
+
   // For service worker clients. Returns the URL that is used for scope matching
   // algorithm. This can be different from url() in the case of blob URL
   // workers. In that case, url() may be like "blob://https://a.test" and the
diff --git a/content/browser/service_worker/service_worker_context_core.cc b/content/browser/service_worker/service_worker_context_core.cc
index 7b088cf..078e6f110 100644
--- a/content/browser/service_worker/service_worker_context_core.cc
+++ b/content/browser/service_worker/service_worker_context_core.cc
@@ -137,7 +137,7 @@
       container_host->IsInBackForwardCache()) {
     return false;
   }
-  return container_host->url().GetOrigin() == origin &&
+  return container_host->GetOrigin() == origin &&
          (allow_reserved_client || container_host->is_execution_ready());
 }
 
diff --git a/content/gpu/BUILD.gn b/content/gpu/BUILD.gn
index 0a6edbf..049ff7bc 100644
--- a/content/gpu/BUILD.gn
+++ b/content/gpu/BUILD.gn
@@ -85,7 +85,7 @@
     "//ui/latency/ipc",
   ]
 
-  if (!is_ash || !is_chrome_branded) {
+  if (!is_chromeos_ash || !is_chrome_branded) {
     deps += [
       "//services/shape_detection:lib",
       "//services/shape_detection/public/mojom",
diff --git a/content/gpu/gpu_child_thread_receiver_bindings.cc b/content/gpu/gpu_child_thread_receiver_bindings.cc
index 9e4175b..664cedb 100644
--- a/content/gpu/gpu_child_thread_receiver_bindings.cc
+++ b/content/gpu/gpu_child_thread_receiver_bindings.cc
@@ -11,7 +11,7 @@
 #include "build/chromeos_buildflags.h"
 #include "media/mojo/buildflags.h"
 
-#if !BUILDFLAG(GOOGLE_CHROME_BRANDING) || !BUILDFLAG(IS_ASH)
+#if !BUILDFLAG(GOOGLE_CHROME_BRANDING) || !BUILDFLAG(IS_CHROMEOS_ASH)
 #include "services/shape_detection/public/mojom/shape_detection_service.mojom.h"  // nogncheck
 #include "services/shape_detection/shape_detection_service.h"  // nogncheck
 #endif
@@ -30,7 +30,7 @@
     return;
   }
 
-#if !BUILDFLAG(GOOGLE_CHROME_BRANDING) || !BUILDFLAG(IS_ASH)
+#if !BUILDFLAG(GOOGLE_CHROME_BRANDING) || !BUILDFLAG(IS_CHROMEOS_ASH)
   if (auto shape_detection_receiver =
           receiver.As<shape_detection::mojom::ShapeDetectionService>()) {
     static base::NoDestructor<shape_detection::ShapeDetectionService> service{
diff --git a/content/public/android/java/src/org/chromium/content/browser/selection/AdditionalMenuItemProviderImpl.java b/content/public/android/java/src/org/chromium/content/browser/selection/AdditionalMenuItemProviderImpl.java
index 72aae48d..a0d2dc58 100644
--- a/content/public/android/java/src/org/chromium/content/browser/selection/AdditionalMenuItemProviderImpl.java
+++ b/content/public/android/java/src/org/chromium/content/browser/selection/AdditionalMenuItemProviderImpl.java
@@ -9,7 +9,6 @@
 import android.app.RemoteAction;
 import android.content.Context;
 import android.os.Build;
-import android.os.Handler;
 import android.text.TextUtils;
 import android.view.Menu;
 import android.view.MenuItem;
@@ -54,7 +53,7 @@
 
             MenuItem item = menu.findItem(android.R.id.textAssist);
             if (primaryAction.shouldShowIcon()) {
-                setItemIconFromRemoteAction(context, primaryAction, item);
+                item.setIcon(primaryAction.getIcon().loadDrawable(context));
             } else {
                 item.setIcon(null);
             }
@@ -73,7 +72,7 @@
                     MENU_ITEM_ORDER_SECONDARY_ASSIST_ACTIONS_START + i, action.getTitle());
             item.setContentDescription(action.getContentDescription());
             if (action.shouldShowIcon()) {
-                setItemIconFromRemoteAction(context, action, item);
+                item.setIcon(action.getIcon().loadDrawable(context));
             }
             // Set this flag to SHOW_AS_ACTION_IF_ROOM to match text processing menu items. So
             // Android could put them to the same level and then consider their actual order.
@@ -106,9 +105,4 @@
             }
         };
     }
-
-    private static void setItemIconFromRemoteAction(
-            Context context, RemoteAction action, MenuItem item) {
-        action.getIcon().loadDrawableAsync(context, item::setIcon, new Handler());
-    }
 }
diff --git a/content/public/android/java/src/org/chromium/content/browser/selection/SmartSelectionProvider.java b/content/public/android/java/src/org/chromium/content/browser/selection/SmartSelectionProvider.java
index 66cc0d4..5a421f8 100644
--- a/content/public/android/java/src/org/chromium/content/browser/selection/SmartSelectionProvider.java
+++ b/content/public/android/java/src/org/chromium/content/browser/selection/SmartSelectionProvider.java
@@ -6,7 +6,6 @@
 
 import android.annotation.SuppressLint;
 import android.annotation.TargetApi;
-import android.app.RemoteAction;
 import android.content.Context;
 import android.os.Build;
 import android.os.Handler;
@@ -18,7 +17,6 @@
 
 import androidx.annotation.IntDef;
 
-import org.chromium.base.compat.ApiHelperForP;
 import org.chromium.base.task.AsyncTask;
 import org.chromium.content.browser.WindowEventObserver;
 import org.chromium.content.browser.WindowEventObserverManager;
@@ -28,7 +26,6 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
-import java.util.List;
 
 /**
  * Controls Smart Text selection. Talks to the Android TextClassificationManager API.
@@ -192,27 +189,7 @@
 
         @Override
         protected void onPostExecute(SelectionClient.Result result) {
-            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
-                mResultCallback.onClassified(result);
-                return;
-            }
-
-            Context context = mWindowAndroid.getContext().get();
-            if (context == null || result.textClassification == null) {
-                mResultCallback.onClassified(result);
-                return;
-            }
-
-            List<RemoteAction> actions = ApiHelperForP.getActions(result.textClassification);
-            if (actions == null || actions.size() == 0) {
-                mResultCallback.onClassified(result);
-                return;
-            }
-
-            RemoteAction primaryAction = actions.get(0);
-            // Wait until the drawable for the primary action is loaded.
-            primaryAction.getIcon().loadDrawableAsync(
-                    context, (drawable) -> mResultCallback.onClassified(result), new Handler());
+            mResultCallback.onClassified(result);
         }
     }
 }
diff --git a/content/renderer/media/android/stream_texture_wrapper_impl.cc b/content/renderer/media/android/stream_texture_wrapper_impl.cc
index 6574e4668..5a8740df 100644
--- a/content/renderer/media/android/stream_texture_wrapper_impl.cc
+++ b/content/renderer/media/android/stream_texture_wrapper_impl.cc
@@ -86,7 +86,7 @@
   new_frame->set_ycbcr_info(ycbcr_info);
 
   if (enable_texture_copy_) {
-    new_frame->metadata()->copy_mode =
+    new_frame->metadata().copy_mode =
         media::VideoFrameMetadata::CopyMode::kCopyToNewTexture;
   }
 
diff --git a/content/renderer/pepper/pepper_video_capture_host.cc b/content/renderer/pepper/pepper_video_capture_host.cc
index d87fe2c..54e7822 100644
--- a/content/renderer/pepper/pepper_video_capture_host.cc
+++ b/content/renderer/pepper/pepper_video_capture_host.cc
@@ -127,9 +127,9 @@
   if (alloc_size_ != frame.visible_rect().size() || buffers_.empty()) {
     alloc_size_ = frame.visible_rect().size();
     int rounded_frame_rate;
-    if (frame.metadata()->frame_rate.has_value()) {
+    if (frame.metadata().frame_rate.has_value()) {
       rounded_frame_rate =
-          static_cast<int>(*frame.metadata()->frame_rate + 0.5 /* round */);
+          static_cast<int>(*frame.metadata().frame_rate + 0.5 /* round */);
     } else {
       rounded_frame_rate = blink::MediaStreamVideoSource::kUnknownFrameRate;
     }
diff --git a/content/renderer/pepper/video_decoder_shim.cc b/content/renderer/pepper/video_decoder_shim.cc
index 76d9f470..ac276116 100644
--- a/content/renderer/pepper/video_decoder_shim.cc
+++ b/content/renderer/pepper/video_decoder_shim.cc
@@ -281,7 +281,7 @@
   DCHECK(awaiting_decoder_);
 
   std::unique_ptr<PendingFrame> pending_frame;
-  if (!frame->metadata()->end_of_stream)
+  if (!frame->metadata().end_of_stream)
     pending_frame.reset(new PendingFrame(decode_id_, std::move(frame)));
   else
     pending_frame.reset(new PendingFrame(decode_id_));
diff --git a/content/test/test_render_view_host.cc b/content/test/test_render_view_host.cc
index 3fce0a1c..d97bb4e 100644
--- a/content/test/test_render_view_host.cc
+++ b/content/test/test_render_view_host.cc
@@ -273,21 +273,15 @@
     // Pretend that we started a renderer process and created the renderer Frame
     // with its Widget. We bind all the mojom interfaces, but they all just talk
     // into the void.
-    mojo::AssociatedRemote<blink::mojom::WidgetHost> blink_widget_host;
-    mojo::AssociatedRemote<blink::mojom::Widget> blink_widget;
-    auto blink_widget_receiver =
-        blink_widget.BindNewEndpointAndPassDedicatedReceiver();
-    GetWidget()->BindWidgetInterfaces(
-        blink_widget_host.BindNewEndpointAndPassDedicatedReceiver(),
-        blink_widget.Unbind());
-
-    mojo::AssociatedRemote<blink::mojom::FrameWidgetHost> frame_widget_host;
-    mojo::AssociatedRemote<blink::mojom::FrameWidget> frame_widget;
-    auto frame_widget_receiver =
-        frame_widget.BindNewEndpointAndPassDedicatedReceiver();
-    GetWidget()->BindFrameWidgetInterfaces(
-        frame_widget_host.BindNewEndpointAndPassDedicatedReceiver(),
-        frame_widget.Unbind());
+    RenderWidgetHostImpl* main_frame_widget = main_frame->GetRenderWidgetHost();
+    main_frame_widget->BindWidgetInterfaces(
+        mojo::PendingAssociatedRemote<blink::mojom::WidgetHost>()
+            .InitWithNewEndpointAndPassReceiver(),
+        TestRenderWidgetHost::CreateStubWidgetRemote());
+    main_frame_widget->BindFrameWidgetInterfaces(
+        mojo::PendingAssociatedRemote<blink::mojom::FrameWidgetHost>()
+            .InitWithNewEndpointAndPassReceiver(),
+        TestRenderWidgetHost::CreateStubFrameWidgetRemote());
 
     // This also initializes the RenderWidgetHost attached to the frame.
     main_frame->RenderFrameCreated();
diff --git a/content/utility/BUILD.gn b/content/utility/BUILD.gn
index bc79e4e..4e7eb3b 100644
--- a/content/utility/BUILD.gn
+++ b/content/utility/BUILD.gn
@@ -81,7 +81,7 @@
     ]
   }
 
-  if (is_ash && is_chrome_branded) {
+  if (is_chromeos_ash && is_chrome_branded) {
     deps += [
       "//services/shape_detection:lib",
       "//services/shape_detection/public/mojom",
diff --git a/content/utility/services.cc b/content/utility/services.cc
index f2af063..2df8d9e 100644
--- a/content/utility/services.cc
+++ b/content/utility/services.cc
@@ -63,7 +63,7 @@
 #include "sandbox/policy/sandbox_type.h"
 #endif  // defined(OS_LINUX) || defined(OS_CHROMEOS)
 
-#if BUILDFLAG(GOOGLE_CHROME_BRANDING) && BUILDFLAG(IS_ASH)
+#if BUILDFLAG(GOOGLE_CHROME_BRANDING) && BUILDFLAG(IS_CHROMEOS_ASH)
 #include "services/shape_detection/public/mojom/shape_detection_service.mojom.h"  // nogncheck
 #include "services/shape_detection/shape_detection_service.h"  // nogncheck
 #endif
@@ -175,7 +175,7 @@
   return audio::CreateStandaloneService(std::move(receiver));
 }
 
-#if BUILDFLAG(GOOGLE_CHROME_BRANDING) && BUILDFLAG(IS_ASH)
+#if BUILDFLAG(GOOGLE_CHROME_BRANDING) && BUILDFLAG(IS_CHROMEOS_ASH)
 auto RunShapeDetectionService(
     mojo::PendingReceiver<shape_detection::mojom::ShapeDetectionService>
         receiver) {
@@ -242,7 +242,7 @@
   services.Add(RunTracing);
   services.Add(RunVideoCapture);
 
-#if BUILDFLAG(GOOGLE_CHROME_BRANDING) && BUILDFLAG(IS_ASH)
+#if BUILDFLAG(GOOGLE_CHROME_BRANDING) && BUILDFLAG(IS_CHROMEOS_ASH)
   services.Add(RunShapeDetectionService);
 #endif
 
diff --git a/crypto/BUILD.gn b/crypto/BUILD.gn
index 54ca26c6..4ac418a 100644
--- a/crypto/BUILD.gn
+++ b/crypto/BUILD.gn
@@ -26,8 +26,6 @@
     "hmac.h",
     "openssl_util.cc",
     "openssl_util.h",
-    "p224.cc",
-    "p224.h",
     "p224_spake.cc",
     "p224_spake.h",
     "random.cc",
@@ -123,7 +121,7 @@
     sources += [ "nss_util_chromeos.cc" ]
   }
 
-  if (is_chromeos || is_lacros) {
+  if (is_chromeos || is_chromeos_lacros) {
     sources += [
       "chaps_support.cc",
       "chaps_support.h",
@@ -145,7 +143,6 @@
     "encryptor_unittest.cc",
     "hmac_unittest.cc",
     "p224_spake_unittest.cc",
-    "p224_unittest.cc",
     "random_unittest.cc",
     "rsa_private_key_unittest.cc",
     "secure_hash_unittest.cc",
diff --git a/crypto/nss_util.cc b/crypto/nss_util.cc
index f7f57dc..ad5fb8a 100644
--- a/crypto/nss_util.cc
+++ b/crypto/nss_util.cc
@@ -35,7 +35,7 @@
 
 namespace {
 
-#if BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_LACROS)
+#if BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS)
 
 // Fake certificate authority database used for testing.
 static const base::FilePath::CharType kReadOnlyCertDB[] =
@@ -59,7 +59,7 @@
   return dir;
 }
 
-#endif  // BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_LACROS)
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS)
 
 // On non-Chrome OS platforms, return the default config directory. On Chrome OS
 // test images, return a read-only directory with fake root CA certs (which are
@@ -67,7 +67,7 @@
 // code). On Chrome OS non-test images (where the read-only directory doesn't
 // exist), return an empty path.
 base::FilePath GetInitialConfigDirectory() {
-#if BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_LACROS)
+#if BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS)
   base::FilePath database_dir = base::FilePath(kReadOnlyCertDB);
   if (!base::PathExists(database_dir))
     database_dir.clear();
@@ -160,7 +160,7 @@
       // Use "sql:" which can be shared by multiple processes safely.
       std::string nss_config_dir =
           base::StringPrintf("sql:%s", database_dir.value().c_str());
-#if BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_LACROS)
+#if BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS)
       status = NSS_Init(nss_config_dir.c_str());
 #else
       status = NSS_InitReadWrite(nss_config_dir.c_str());
diff --git a/crypto/p224.cc b/crypto/p224.cc
deleted file mode 100644
index 2ac0712..0000000
--- a/crypto/p224.cc
+++ /dev/null
@@ -1,747 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// This is an implementation of the P224 elliptic curve group. It's written to
-// be short and simple rather than fast, although it's still constant-time.
-//
-// See http://www.imperialviolet.org/2010/12/04/ecc.html ([1]) for background.
-
-#include "crypto/p224.h"
-
-#include <stddef.h>
-#include <stdint.h>
-#include <string.h>
-
-#include "base/sys_byteorder.h"
-
-namespace {
-
-using base::HostToNet32;
-using base::NetToHost32;
-
-// Field element functions.
-//
-// The field that we're dealing with is ℤ/pℤ where p = 2**224 - 2**96 + 1.
-//
-// Field elements are represented by a FieldElement, which is a typedef to an
-// array of 8 uint32_t's. The value of a FieldElement, a, is:
-//   a[0] + 2**28·a[1] + 2**56·a[1] + ... + 2**196·a[7]
-//
-// Using 28-bit limbs means that there's only 4 bits of headroom, which is less
-// than we would really like. But it has the useful feature that we hit 2**224
-// exactly, making the reflections during a reduce much nicer.
-
-using crypto::p224::FieldElement;
-
-// kP is the P224 prime.
-const FieldElement kP = {
-  1, 0, 0, 268431360,
-  268435455, 268435455, 268435455, 268435455,
-};
-
-void Contract(FieldElement* inout);
-
-// IsZero returns 0xffffffff if a == 0 mod p and 0 otherwise.
-uint32_t IsZero(const FieldElement& a) {
-  FieldElement minimal;
-  memcpy(&minimal, &a, sizeof(minimal));
-  Contract(&minimal);
-
-  uint32_t is_zero = 0, is_p = 0;
-  for (unsigned i = 0; i < 8; i++) {
-    is_zero |= minimal[i];
-    is_p |= minimal[i] - kP[i];
-  }
-
-  // If either is_zero or is_p is 0, then we should return 1.
-  is_zero |= is_zero >> 16;
-  is_zero |= is_zero >> 8;
-  is_zero |= is_zero >> 4;
-  is_zero |= is_zero >> 2;
-  is_zero |= is_zero >> 1;
-
-  is_p |= is_p >> 16;
-  is_p |= is_p >> 8;
-  is_p |= is_p >> 4;
-  is_p |= is_p >> 2;
-  is_p |= is_p >> 1;
-
-  // For is_zero and is_p, the LSB is 0 iff all the bits are zero.
-  is_zero &= is_p & 1;
-  is_zero = (~is_zero) << 31;
-  is_zero = static_cast<int32_t>(is_zero) >> 31;
-  return is_zero;
-}
-
-// Add computes *out = a+b
-//
-// a[i] + b[i] < 2**32
-void Add(FieldElement* out, const FieldElement& a, const FieldElement& b) {
-  for (int i = 0; i < 8; i++) {
-    (*out)[i] = a[i] + b[i];
-  }
-}
-
-static const uint32_t kTwo31p3 = (1u << 31) + (1u << 3);
-static const uint32_t kTwo31m3 = (1u << 31) - (1u << 3);
-static const uint32_t kTwo31m15m3 = (1u << 31) - (1u << 15) - (1u << 3);
-// kZero31ModP is 0 mod p where bit 31 is set in all limbs so that we can
-// subtract smaller amounts without underflow. See the section "Subtraction" in
-// [1] for why.
-static const FieldElement kZero31ModP = {
-  kTwo31p3, kTwo31m3, kTwo31m3, kTwo31m15m3,
-  kTwo31m3, kTwo31m3, kTwo31m3, kTwo31m3
-};
-
-// Subtract computes *out = a-b
-//
-// a[i], b[i] < 2**30
-// out[i] < 2**32
-void Subtract(FieldElement* out, const FieldElement& a, const FieldElement& b) {
-  for (int i = 0; i < 8; i++) {
-    // See the section on "Subtraction" in [1] for details.
-    (*out)[i] = a[i] + kZero31ModP[i] - b[i];
-  }
-}
-
-static const uint64_t kTwo63p35 = (1ull << 63) + (1ull << 35);
-static const uint64_t kTwo63m35 = (1ull << 63) - (1ull << 35);
-static const uint64_t kTwo63m35m19 = (1ull << 63) - (1ull << 35) - (1ull << 19);
-// kZero63ModP is 0 mod p where bit 63 is set in all limbs. See the section
-// "Subtraction" in [1] for why.
-static const uint64_t kZero63ModP[8] = {
-    kTwo63p35,    kTwo63m35, kTwo63m35, kTwo63m35,
-    kTwo63m35m19, kTwo63m35, kTwo63m35, kTwo63m35,
-};
-
-static const uint32_t kBottom28Bits = 0xfffffff;
-
-// LargeFieldElement also represents an element of the field. The limbs are
-// still spaced 28-bits apart and in little-endian order. So the limbs are at
-// 0, 28, 56, ..., 392 bits, each 64-bits wide.
-typedef uint64_t LargeFieldElement[15];
-
-// ReduceLarge converts a LargeFieldElement to a FieldElement.
-//
-// in[i] < 2**62
-void ReduceLarge(FieldElement* out, LargeFieldElement* inptr) {
-  LargeFieldElement& in(*inptr);
-
-  for (int i = 0; i < 8; i++) {
-    in[i] += kZero63ModP[i];
-  }
-
-  // Eliminate the coefficients at 2**224 and greater while maintaining the
-  // same value mod p.
-  for (int i = 14; i >= 8; i--) {
-    in[i-8] -= in[i];  // reflection off the "+1" term of p.
-    in[i-5] += (in[i] & 0xffff) << 12;  // part of the "-2**96" reflection.
-    in[i-4] += in[i] >> 16;  // the rest of the "-2**96" reflection.
-  }
-  in[8] = 0;
-  // in[0..8] < 2**64
-
-  // As the values become small enough, we start to store them in |out| and use
-  // 32-bit operations.
-  for (int i = 1; i < 8; i++) {
-    in[i+1] += in[i] >> 28;
-    (*out)[i] = static_cast<uint32_t>(in[i] & kBottom28Bits);
-  }
-  // Eliminate the term at 2*224 that we introduced while keeping the same
-  // value mod p.
-  in[0] -= in[8];  // reflection off the "+1" term of p.
-  (*out)[3] += static_cast<uint32_t>(in[8] & 0xffff) << 12;  // "-2**96" term
-  (*out)[4] += static_cast<uint32_t>(in[8] >> 16);  // rest of "-2**96" term
-  // in[0] < 2**64
-  // out[3] < 2**29
-  // out[4] < 2**29
-  // out[1,2,5..7] < 2**28
-
-  (*out)[0] = static_cast<uint32_t>(in[0] & kBottom28Bits);
-  (*out)[1] += static_cast<uint32_t>((in[0] >> 28) & kBottom28Bits);
-  (*out)[2] += static_cast<uint32_t>(in[0] >> 56);
-  // out[0] < 2**28
-  // out[1..4] < 2**29
-  // out[5..7] < 2**28
-}
-
-// Mul computes *out = a*b
-//
-// a[i] < 2**29, b[i] < 2**30 (or vice versa)
-// out[i] < 2**29
-void Mul(FieldElement* out, const FieldElement& a, const FieldElement& b) {
-  LargeFieldElement tmp;
-  memset(&tmp, 0, sizeof(tmp));
-
-  for (int i = 0; i < 8; i++) {
-    for (int j = 0; j < 8; j++) {
-      tmp[i + j] += static_cast<uint64_t>(a[i]) * static_cast<uint64_t>(b[j]);
-    }
-  }
-
-  ReduceLarge(out, &tmp);
-}
-
-// Square computes *out = a*a
-//
-// a[i] < 2**29
-// out[i] < 2**29
-void Square(FieldElement* out, const FieldElement& a) {
-  LargeFieldElement tmp;
-  memset(&tmp, 0, sizeof(tmp));
-
-  for (int i = 0; i < 8; i++) {
-    for (int j = 0; j <= i; j++) {
-      uint64_t r = static_cast<uint64_t>(a[i]) * static_cast<uint64_t>(a[j]);
-      if (i == j) {
-        tmp[i+j] += r;
-      } else {
-        tmp[i+j] += r << 1;
-      }
-    }
-  }
-
-  ReduceLarge(out, &tmp);
-}
-
-// Reduce reduces the coefficients of in_out to smaller bounds.
-//
-// On entry: a[i] < 2**31 + 2**30
-// On exit: a[i] < 2**29
-void Reduce(FieldElement* in_out) {
-  FieldElement& a = *in_out;
-
-  for (int i = 0; i < 7; i++) {
-    a[i+1] += a[i] >> 28;
-    a[i] &= kBottom28Bits;
-  }
-  uint32_t top = a[7] >> 28;
-  a[7] &= kBottom28Bits;
-
-  // top < 2**4
-  // Constant-time: mask = (top != 0) ? 0xffffffff : 0
-  uint32_t mask = top;
-  mask |= mask >> 2;
-  mask |= mask >> 1;
-  mask <<= 31;
-  mask = static_cast<uint32_t>(static_cast<int32_t>(mask) >> 31);
-
-  // Eliminate top while maintaining the same value mod p.
-  a[0] -= top;
-  a[3] += top << 12;
-
-  // We may have just made a[0] negative but, if we did, then we must
-  // have added something to a[3], thus it's > 2**12. Therefore we can
-  // carry down to a[0].
-  a[3] -= 1 & mask;
-  a[2] += mask & ((1<<28) - 1);
-  a[1] += mask & ((1<<28) - 1);
-  a[0] += mask & (1<<28);
-}
-
-// Invert calcuates *out = in**-1 by computing in**(2**224 - 2**96 - 1), i.e.
-// Fermat's little theorem.
-void Invert(FieldElement* out, const FieldElement& in) {
-  FieldElement f1, f2, f3, f4;
-
-  Square(&f1, in);                        // 2
-  Mul(&f1, f1, in);                       // 2**2 - 1
-  Square(&f1, f1);                        // 2**3 - 2
-  Mul(&f1, f1, in);                       // 2**3 - 1
-  Square(&f2, f1);                        // 2**4 - 2
-  Square(&f2, f2);                        // 2**5 - 4
-  Square(&f2, f2);                        // 2**6 - 8
-  Mul(&f1, f1, f2);                       // 2**6 - 1
-  Square(&f2, f1);                        // 2**7 - 2
-  for (int i = 0; i < 5; i++) {           // 2**12 - 2**6
-    Square(&f2, f2);
-  }
-  Mul(&f2, f2, f1);                       // 2**12 - 1
-  Square(&f3, f2);                        // 2**13 - 2
-  for (int i = 0; i < 11; i++) {          // 2**24 - 2**12
-    Square(&f3, f3);
-  }
-  Mul(&f2, f3, f2);                       // 2**24 - 1
-  Square(&f3, f2);                        // 2**25 - 2
-  for (int i = 0; i < 23; i++) {          // 2**48 - 2**24
-    Square(&f3, f3);
-  }
-  Mul(&f3, f3, f2);                       // 2**48 - 1
-  Square(&f4, f3);                        // 2**49 - 2
-  for (int i = 0; i < 47; i++) {          // 2**96 - 2**48
-    Square(&f4, f4);
-  }
-  Mul(&f3, f3, f4);                       // 2**96 - 1
-  Square(&f4, f3);                        // 2**97 - 2
-  for (int i = 0; i < 23; i++) {          // 2**120 - 2**24
-    Square(&f4, f4);
-  }
-  Mul(&f2, f4, f2);                       // 2**120 - 1
-  for (int i = 0; i < 6; i++) {           // 2**126 - 2**6
-    Square(&f2, f2);
-  }
-  Mul(&f1, f1, f2);                       // 2**126 - 1
-  Square(&f1, f1);                        // 2**127 - 2
-  Mul(&f1, f1, in);                       // 2**127 - 1
-  for (int i = 0; i < 97; i++) {          // 2**224 - 2**97
-    Square(&f1, f1);
-  }
-  Mul(out, f1, f3);                       // 2**224 - 2**96 - 1
-}
-
-// Contract converts a FieldElement to its minimal, distinguished form.
-//
-// On entry, in[i] < 2**29
-// On exit, in[i] < 2**28
-void Contract(FieldElement* inout) {
-  FieldElement& out = *inout;
-
-  // Reduce the coefficients to < 2**28.
-  for (int i = 0; i < 7; i++) {
-    out[i+1] += out[i] >> 28;
-    out[i] &= kBottom28Bits;
-  }
-  uint32_t top = out[7] >> 28;
-  out[7] &= kBottom28Bits;
-
-  // Eliminate top while maintaining the same value mod p.
-  out[0] -= top;
-  out[3] += top << 12;
-
-  // We may just have made out[0] negative. So we carry down. If we made
-  // out[0] negative then we know that out[3] is sufficiently positive
-  // because we just added to it.
-  for (int i = 0; i < 3; i++) {
-    uint32_t mask = static_cast<uint32_t>(static_cast<int32_t>(out[i]) >> 31);
-    out[i] += (1 << 28) & mask;
-    out[i+1] -= 1 & mask;
-  }
-
-  // We might have pushed out[3] over 2**28 so we perform another, partial
-  // carry chain.
-  for (int i = 3; i < 7; i++) {
-    out[i+1] += out[i] >> 28;
-    out[i] &= kBottom28Bits;
-  }
-  top = out[7] >> 28;
-  out[7] &= kBottom28Bits;
-
-  // Eliminate top while maintaining the same value mod p.
-  out[0] -= top;
-  out[3] += top << 12;
-
-  // There are two cases to consider for out[3]:
-  //   1) The first time that we eliminated top, we didn't push out[3] over
-  //      2**28. In this case, the partial carry chain didn't change any values
-  //      and top is zero.
-  //   2) We did push out[3] over 2**28 the first time that we eliminated top.
-  //      The first value of top was in [0..16), therefore, prior to eliminating
-  //      the first top, 0xfff1000 <= out[3] <= 0xfffffff. Therefore, after
-  //      overflowing and being reduced by the second carry chain, out[3] <=
-  //      0xf000. Thus it cannot have overflowed when we eliminated top for the
-  //      second time.
-
-  // Again, we may just have made out[0] negative, so do the same carry down.
-  // As before, if we made out[0] negative then we know that out[3] is
-  // sufficiently positive.
-  for (int i = 0; i < 3; i++) {
-    uint32_t mask = static_cast<uint32_t>(static_cast<int32_t>(out[i]) >> 31);
-    out[i] += (1 << 28) & mask;
-    out[i+1] -= 1 & mask;
-  }
-
-  // The value is < 2**224, but maybe greater than p. In order to reduce to a
-  // unique, minimal value we see if the value is >= p and, if so, subtract p.
-
-  // First we build a mask from the top four limbs, which must all be
-  // equal to bottom28Bits if the whole value is >= p. If top_4_all_ones
-  // ends up with any zero bits in the bottom 28 bits, then this wasn't
-  // true.
-  uint32_t top_4_all_ones = 0xffffffffu;
-  for (int i = 4; i < 8; i++) {
-    top_4_all_ones &= out[i];
-  }
-  top_4_all_ones |= 0xf0000000;
-  // Now we replicate any zero bits to all the bits in top_4_all_ones.
-  top_4_all_ones &= top_4_all_ones >> 16;
-  top_4_all_ones &= top_4_all_ones >> 8;
-  top_4_all_ones &= top_4_all_ones >> 4;
-  top_4_all_ones &= top_4_all_ones >> 2;
-  top_4_all_ones &= top_4_all_ones >> 1;
-  top_4_all_ones =
-      static_cast<uint32_t>(static_cast<int32_t>(top_4_all_ones << 31) >> 31);
-
-  // Now we test whether the bottom three limbs are non-zero.
-  uint32_t bottom_3_non_zero = out[0] | out[1] | out[2];
-  bottom_3_non_zero |= bottom_3_non_zero >> 16;
-  bottom_3_non_zero |= bottom_3_non_zero >> 8;
-  bottom_3_non_zero |= bottom_3_non_zero >> 4;
-  bottom_3_non_zero |= bottom_3_non_zero >> 2;
-  bottom_3_non_zero |= bottom_3_non_zero >> 1;
-  bottom_3_non_zero =
-      static_cast<uint32_t>(static_cast<int32_t>(bottom_3_non_zero) >> 31);
-
-  // Everything depends on the value of out[3].
-  //    If it's > 0xffff000 and top_4_all_ones != 0 then the whole value is >= p
-  //    If it's = 0xffff000 and top_4_all_ones != 0 and bottom_3_non_zero != 0,
-  //      then the whole value is >= p
-  //    If it's < 0xffff000, then the whole value is < p
-  uint32_t n = out[3] - 0xffff000;
-  uint32_t out_3_equal = n;
-  out_3_equal |= out_3_equal >> 16;
-  out_3_equal |= out_3_equal >> 8;
-  out_3_equal |= out_3_equal >> 4;
-  out_3_equal |= out_3_equal >> 2;
-  out_3_equal |= out_3_equal >> 1;
-  out_3_equal =
-      ~static_cast<uint32_t>(static_cast<int32_t>(out_3_equal << 31) >> 31);
-
-  // If out[3] > 0xffff000 then n's MSB will be zero.
-  uint32_t out_3_gt =
-      ~static_cast<uint32_t>(static_cast<int32_t>(n << 31) >> 31);
-
-  uint32_t mask =
-      top_4_all_ones & ((out_3_equal & bottom_3_non_zero) | out_3_gt);
-  out[0] -= 1 & mask;
-  out[3] -= 0xffff000 & mask;
-  out[4] -= 0xfffffff & mask;
-  out[5] -= 0xfffffff & mask;
-  out[6] -= 0xfffffff & mask;
-  out[7] -= 0xfffffff & mask;
-}
-
-
-// Group element functions.
-//
-// These functions deal with group elements. The group is an elliptic curve
-// group with a = -3 defined in FIPS 186-3, section D.2.2.
-
-using crypto::p224::Point;
-
-// kB is parameter of the elliptic curve.
-const FieldElement kB = {
-  55967668, 11768882, 265861671, 185302395,
-  39211076, 180311059, 84673715, 188764328,
-};
-
-void CopyConditional(Point* out, const Point& a, uint32_t mask);
-void DoubleJacobian(Point* out, const Point& a);
-
-// AddJacobian computes *out = a+b where a != b.
-void AddJacobian(Point *out,
-                 const Point& a,
-                 const Point& b) {
-  // See http://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-3.html#addition-add-2007-bl
-  FieldElement z1z1, z2z2, u1, u2, s1, s2, h, i, j, r, v;
-
-  uint32_t z1_is_zero = IsZero(a.z);
-  uint32_t z2_is_zero = IsZero(b.z);
-
-  // Z1Z1 = Z1²
-  Square(&z1z1, a.z);
-
-  // Z2Z2 = Z2²
-  Square(&z2z2, b.z);
-
-  // U1 = X1*Z2Z2
-  Mul(&u1, a.x, z2z2);
-
-  // U2 = X2*Z1Z1
-  Mul(&u2, b.x, z1z1);
-
-  // S1 = Y1*Z2*Z2Z2
-  Mul(&s1, b.z, z2z2);
-  Mul(&s1, a.y, s1);
-
-  // S2 = Y2*Z1*Z1Z1
-  Mul(&s2, a.z, z1z1);
-  Mul(&s2, b.y, s2);
-
-  // H = U2-U1
-  Subtract(&h, u2, u1);
-  Reduce(&h);
-  uint32_t x_equal = IsZero(h);
-
-  // I = (2*H)²
-  for (int k = 0; k < 8; k++) {
-    i[k] = h[k] << 1;
-  }
-  Reduce(&i);
-  Square(&i, i);
-
-  // J = H*I
-  Mul(&j, h, i);
-  // r = 2*(S2-S1)
-  Subtract(&r, s2, s1);
-  Reduce(&r);
-  uint32_t y_equal = IsZero(r);
-
-  if (x_equal && y_equal && !z1_is_zero && !z2_is_zero) {
-    // The two input points are the same therefore we must use the dedicated
-    // doubling function as the slope of the line is undefined.
-    DoubleJacobian(out, a);
-    return;
-  }
-
-  for (int k = 0; k < 8; k++) {
-    r[k] <<= 1;
-  }
-  Reduce(&r);
-
-  // V = U1*I
-  Mul(&v, u1, i);
-
-  // Z3 = ((Z1+Z2)²-Z1Z1-Z2Z2)*H
-  Add(&z1z1, z1z1, z2z2);
-  Add(&z2z2, a.z, b.z);
-  Reduce(&z2z2);
-  Square(&z2z2, z2z2);
-  Subtract(&out->z, z2z2, z1z1);
-  Reduce(&out->z);
-  Mul(&out->z, out->z, h);
-
-  // X3 = r²-J-2*V
-  for (int k = 0; k < 8; k++) {
-    z1z1[k] = v[k] << 1;
-  }
-  Add(&z1z1, j, z1z1);
-  Reduce(&z1z1);
-  Square(&out->x, r);
-  Subtract(&out->x, out->x, z1z1);
-  Reduce(&out->x);
-
-  // Y3 = r*(V-X3)-2*S1*J
-  for (int k = 0; k < 8; k++) {
-    s1[k] <<= 1;
-  }
-  Mul(&s1, s1, j);
-  Subtract(&z1z1, v, out->x);
-  Reduce(&z1z1);
-  Mul(&z1z1, z1z1, r);
-  Subtract(&out->y, z1z1, s1);
-  Reduce(&out->y);
-
-  CopyConditional(out, a, z2_is_zero);
-  CopyConditional(out, b, z1_is_zero);
-}
-
-// DoubleJacobian computes *out = a+a.
-void DoubleJacobian(Point* out, const Point& a) {
-  // See http://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-3.html#doubling-dbl-2001-b
-  FieldElement delta, gamma, beta, alpha, t;
-
-  Square(&delta, a.z);
-  Square(&gamma, a.y);
-  Mul(&beta, a.x, gamma);
-
-  // alpha = 3*(X1-delta)*(X1+delta)
-  Add(&t, a.x, delta);
-  for (int i = 0; i < 8; i++) {
-          t[i] += t[i] << 1;
-  }
-  Reduce(&t);
-  Subtract(&alpha, a.x, delta);
-  Reduce(&alpha);
-  Mul(&alpha, alpha, t);
-
-  // Z3 = (Y1+Z1)²-gamma-delta
-  Add(&out->z, a.y, a.z);
-  Reduce(&out->z);
-  Square(&out->z, out->z);
-  Subtract(&out->z, out->z, gamma);
-  Reduce(&out->z);
-  Subtract(&out->z, out->z, delta);
-  Reduce(&out->z);
-
-  // X3 = alpha²-8*beta
-  for (int i = 0; i < 8; i++) {
-          delta[i] = beta[i] << 3;
-  }
-  Reduce(&delta);
-  Square(&out->x, alpha);
-  Subtract(&out->x, out->x, delta);
-  Reduce(&out->x);
-
-  // Y3 = alpha*(4*beta-X3)-8*gamma²
-  for (int i = 0; i < 8; i++) {
-          beta[i] <<= 2;
-  }
-  Reduce(&beta);
-  Subtract(&beta, beta, out->x);
-  Reduce(&beta);
-  Square(&gamma, gamma);
-  for (int i = 0; i < 8; i++) {
-          gamma[i] <<= 3;
-  }
-  Reduce(&gamma);
-  Mul(&out->y, alpha, beta);
-  Subtract(&out->y, out->y, gamma);
-  Reduce(&out->y);
-}
-
-// CopyConditional sets *out=a if mask is 0xffffffff. mask must be either 0 of
-// 0xffffffff.
-void CopyConditional(Point* out, const Point& a, uint32_t mask) {
-  for (int i = 0; i < 8; i++) {
-    out->x[i] ^= mask & (a.x[i] ^ out->x[i]);
-    out->y[i] ^= mask & (a.y[i] ^ out->y[i]);
-    out->z[i] ^= mask & (a.z[i] ^ out->z[i]);
-  }
-}
-
-// ScalarMult calculates *out = a*scalar where scalar is a big-endian number of
-// length scalar_len and != 0.
-void ScalarMult(Point* out,
-                const Point& a,
-                const uint8_t* scalar,
-                size_t scalar_len) {
-  memset(out, 0, sizeof(*out));
-  Point tmp;
-
-  for (size_t i = 0; i < scalar_len; i++) {
-    for (unsigned int bit_num = 0; bit_num < 8; bit_num++) {
-      DoubleJacobian(out, *out);
-      uint32_t bit = static_cast<uint32_t>(static_cast<int32_t>(
-          (((scalar[i] >> (7 - bit_num)) & 1) << 31) >> 31));
-      AddJacobian(&tmp, a, *out);
-      CopyConditional(out, tmp, bit);
-    }
-  }
-}
-
-// Get224Bits reads 7 words from in and scatters their contents in
-// little-endian form into 8 words at out, 28 bits per output word.
-void Get224Bits(uint32_t* out, const uint32_t* in) {
-  out[0] = NetToHost32(in[6]) & kBottom28Bits;
-  out[1] = ((NetToHost32(in[5]) << 4) |
-            (NetToHost32(in[6]) >> 28)) & kBottom28Bits;
-  out[2] = ((NetToHost32(in[4]) << 8) |
-            (NetToHost32(in[5]) >> 24)) & kBottom28Bits;
-  out[3] = ((NetToHost32(in[3]) << 12) |
-            (NetToHost32(in[4]) >> 20)) & kBottom28Bits;
-  out[4] = ((NetToHost32(in[2]) << 16) |
-            (NetToHost32(in[3]) >> 16)) & kBottom28Bits;
-  out[5] = ((NetToHost32(in[1]) << 20) |
-            (NetToHost32(in[2]) >> 12)) & kBottom28Bits;
-  out[6] = ((NetToHost32(in[0]) << 24) |
-            (NetToHost32(in[1]) >> 8)) & kBottom28Bits;
-  out[7] = (NetToHost32(in[0]) >> 4) & kBottom28Bits;
-}
-
-// Put224Bits performs the inverse operation to Get224Bits: taking 28 bits from
-// each of 8 input words and writing them in big-endian order to 7 words at
-// out.
-void Put224Bits(uint32_t* out, const uint32_t* in) {
-  out[6] = HostToNet32((in[0] >> 0) | (in[1] << 28));
-  out[5] = HostToNet32((in[1] >> 4) | (in[2] << 24));
-  out[4] = HostToNet32((in[2] >> 8) | (in[3] << 20));
-  out[3] = HostToNet32((in[3] >> 12) | (in[4] << 16));
-  out[2] = HostToNet32((in[4] >> 16) | (in[5] << 12));
-  out[1] = HostToNet32((in[5] >> 20) | (in[6] << 8));
-  out[0] = HostToNet32((in[6] >> 24) | (in[7] << 4));
-}
-
-}  // anonymous namespace
-
-namespace crypto {
-
-namespace p224 {
-
-bool Point::SetFromString(base::StringPiece in) {
-  if (in.size() != 2*28)
-    return false;
-  const uint32_t* inwords = reinterpret_cast<const uint32_t*>(in.data());
-  Get224Bits(x, inwords);
-  Get224Bits(y, inwords + 7);
-  memset(&z, 0, sizeof(z));
-  z[0] = 1;
-
-  // Check that the point is on the curve, i.e. that y² = x³ - 3x + b.
-  FieldElement lhs;
-  Square(&lhs, y);
-  Contract(&lhs);
-
-  FieldElement rhs;
-  Square(&rhs, x);
-  Mul(&rhs, x, rhs);
-
-  FieldElement three_x;
-  for (int i = 0; i < 8; i++) {
-    three_x[i] = x[i] * 3;
-  }
-  Reduce(&three_x);
-  Subtract(&rhs, rhs, three_x);
-  Reduce(&rhs);
-
-  ::Add(&rhs, rhs, kB);
-  Contract(&rhs);
-  return memcmp(&lhs, &rhs, sizeof(lhs)) == 0;
-}
-
-std::string Point::ToString() const {
-  FieldElement zinv, zinv_sq, xx, yy;
-
-  // If this is the point at infinity we return a string of all zeros.
-  if (IsZero(this->z)) {
-    static const char zeros[56] = {0};
-    return std::string(zeros, sizeof(zeros));
-  }
-
-  Invert(&zinv, this->z);
-  Square(&zinv_sq, zinv);
-  Mul(&xx, x, zinv_sq);
-  Mul(&zinv_sq, zinv_sq, zinv);
-  Mul(&yy, y, zinv_sq);
-
-  Contract(&xx);
-  Contract(&yy);
-
-  uint32_t outwords[14];
-  Put224Bits(outwords, xx);
-  Put224Bits(outwords + 7, yy);
-  return std::string(reinterpret_cast<const char*>(outwords), sizeof(outwords));
-}
-
-void ScalarMult(const Point& in, const uint8_t* scalar, Point* out) {
-  ::ScalarMult(out, in, scalar, 28);
-}
-
-// kBasePoint is the base point (generator) of the elliptic curve group.
-static const Point kBasePoint = {
-  {22813985, 52956513, 34677300, 203240812,
-   12143107, 133374265, 225162431, 191946955},
-  {83918388, 223877528, 122119236, 123340192,
-   266784067, 263504429, 146143011, 198407736},
-  {1, 0, 0, 0, 0, 0, 0, 0},
-};
-
-void ScalarBaseMult(const uint8_t* scalar, Point* out) {
-  ::ScalarMult(out, kBasePoint, scalar, 28);
-}
-
-void Add(const Point& a, const Point& b, Point* out) {
-  AddJacobian(out, a, b);
-}
-
-void Negate(const Point& in, Point* out) {
-  // Guide to elliptic curve cryptography, page 89 suggests that (X : X+Y : Z)
-  // is the negative in Jacobian coordinates, but it doesn't actually appear to
-  // be true in testing so this performs the negation in affine coordinates.
-  FieldElement zinv, zinv_sq, y;
-  Invert(&zinv, in.z);
-  Square(&zinv_sq, zinv);
-  Mul(&out->x, in.x, zinv_sq);
-  Mul(&zinv_sq, zinv_sq, zinv);
-  Mul(&y, in.y, zinv_sq);
-
-  Subtract(&out->y, kP, y);
-  Reduce(&out->y);
-
-  memset(&out->z, 0, sizeof(out->z));
-  out->z[0] = 1;
-}
-
-}  // namespace p224
-
-}  // namespace crypto
diff --git a/crypto/p224.h b/crypto/p224.h
deleted file mode 100644
index f02e657b..0000000
--- a/crypto/p224.h
+++ /dev/null
@@ -1,64 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CRYPTO_P224_H_
-#define CRYPTO_P224_H_
-
-#include <stddef.h>
-#include <stdint.h>
-
-#include <string>
-
-#include "base/strings/string_piece.h"
-#include "crypto/crypto_export.h"
-
-namespace crypto {
-
-// P224 implements an elliptic curve group, commonly known as P224 and defined
-// in FIPS 186-3, section D.2.2.
-namespace p224 {
-
-// An element of the field (ℤ/pℤ) is represented with 8, 28-bit limbs in
-// little endian order.
-typedef uint32_t FieldElement[8];
-
-struct CRYPTO_EXPORT Point {
-  // SetFromString the value of the point from the 56 byte, external
-  // representation. The external point representation is an (x, y) pair of a
-  // point on the curve. Each field element is represented as a big-endian
-  // number < p.
-  bool SetFromString(base::StringPiece in);
-
-  // ToString returns an external representation of the Point.
-  std::string ToString() const;
-
-  // An Point is represented in Jacobian form (x/z², y/z³).
-  FieldElement x, y, z;
-};
-
-// kScalarBytes is the number of bytes needed to represent an element of the
-// P224 field.
-static const size_t kScalarBytes = 28;
-
-// ScalarMult computes *out = in*scalar where scalar is a 28-byte, big-endian
-// number.
-void CRYPTO_EXPORT ScalarMult(const Point& in,
-                              const uint8_t* scalar,
-                              Point* out);
-
-// ScalarBaseMult computes *out = g*scalar where g is the base point of the
-// curve and scalar is a 28-byte, big-endian number.
-void CRYPTO_EXPORT ScalarBaseMult(const uint8_t* scalar, Point* out);
-
-// Add computes *out = a+b.
-void CRYPTO_EXPORT Add(const Point& a, const Point& b, Point* out);
-
-// Negate calculates out = -a;
-void CRYPTO_EXPORT Negate(const Point& a, Point* out);
-
-}  // namespace p224
-
-}  // namespace crypto
-
-#endif  // CRYPTO_P224_H_
diff --git a/crypto/p224_spake.cc b/crypto/p224_spake.cc
index 3877818..58b9161 100644
--- a/crypto/p224_spake.cc
+++ b/crypto/p224_spake.cc
@@ -12,9 +12,11 @@
 #include <algorithm>
 
 #include "base/logging.h"
-#include "crypto/p224.h"
 #include "crypto/random.h"
 #include "crypto/secure_util.h"
+#include "third_party/boringssl/src/include/openssl/bn.h"
+#include "third_party/boringssl/src/include/openssl/ec.h"
+#include "third_party/boringssl/src/include/openssl/obj.h"
 
 namespace {
 
@@ -82,22 +84,87 @@
 //   return 0;
 // }
 
-const crypto::p224::Point kM = {
-  {174237515, 77186811, 235213682, 33849492,
-   33188520, 48266885, 177021753, 81038478},
-  {104523827, 245682244, 266509668, 236196369,
-   28372046, 145351378, 198520366, 113345994},
-  {1, 0, 0, 0, 0, 0, 0, 0},
+const uint8_t kM_X962[1 + 28 + 28] = {
+    0x04, 0x4d, 0x48, 0xc8, 0xea, 0x8d, 0x23, 0x39, 0x2e, 0x07, 0xe8, 0x51,
+    0xfa, 0x6a, 0xa8, 0x20, 0x48, 0x09, 0x4e, 0x05, 0x13, 0x72, 0x49, 0x9c,
+    0x6f, 0xba, 0x62, 0xa7, 0x4b, 0x6c, 0x18, 0x5c, 0xab, 0xd5, 0x2e, 0x2e,
+    0x8a, 0x9e, 0x2d, 0x21, 0xb0, 0xec, 0x4e, 0xe1, 0x41, 0x21, 0x1f, 0xe2,
+    0x9d, 0x64, 0xea, 0x4d, 0x04, 0x46, 0x3a, 0xe8, 0x33,
 };
 
-const crypto::p224::Point kN = {
-  {136176322, 263523628, 251628795, 229292285,
-   5034302, 185981975, 171998428, 11653062},
-  {197567436, 51226044, 60372156, 175772188,
-   42075930, 8083165, 160827401, 65097570},
-  {1, 0, 0, 0, 0, 0, 0, 0},
+const uint8_t kN_X962[1 + 28 + 28] = {
+    0x04, 0x0b, 0x1c, 0xfc, 0x6a, 0x40, 0x7c, 0xdc, 0xb1, 0x5d, 0xc1, 0x70,
+    0x4c, 0xd1, 0x3e, 0xda, 0xab, 0x8f, 0xde, 0xff, 0x8c, 0xfb, 0xfb, 0x50,
+    0xd2, 0xc8, 0x1d, 0xe2, 0xc2, 0x3e, 0x14, 0xf6, 0x29, 0x96, 0x08, 0x09,
+    0x07, 0xb5, 0x6d, 0xd2, 0x82, 0x07, 0x1a, 0xa7, 0xa1, 0x21, 0xc3, 0x99,
+    0x34, 0xbc, 0x30, 0xda, 0x5b, 0xcb, 0xc6, 0xa3, 0xcc,
 };
 
+// ToBignum returns |big_endian_bytes| interpreted as a big-endian number.
+bssl::UniquePtr<BIGNUM> ToBignum(base::span<const uint8_t> big_endian_bytes) {
+  bssl::UniquePtr<BIGNUM> bn(BN_new());
+  CHECK(BN_bin2bn(big_endian_bytes.data(), big_endian_bytes.size(), bn.get()));
+  return bn;
+}
+
+// GetPoint decodes and returns the given X.962-encoded point. It will crash if
+// |x962| is not a valid P-224 point.
+bssl::UniquePtr<EC_POINT> GetPoint(
+    const EC_GROUP* p224,
+    base::span<const uint8_t, 1 + 28 + 28> x962) {
+  bssl::UniquePtr<EC_POINT> point(EC_POINT_new(p224));
+  CHECK(EC_POINT_oct2point(p224, point.get(), x962.data(), x962.size(),
+                           /*ctx=*/nullptr));
+  return point;
+}
+
+// GetMask returns (M|N)**pw, where the choice of M or N is controlled by
+// |use_m|.
+bssl::UniquePtr<EC_POINT> GetMask(const EC_GROUP* p224,
+                                  bool use_m,
+                                  base::span<const uint8_t> pw) {
+  bssl::UniquePtr<EC_POINT> MN(GetPoint(p224, use_m ? kM_X962 : kN_X962));
+  bssl::UniquePtr<EC_POINT> MNpw(EC_POINT_new(p224));
+  bssl::UniquePtr<BIGNUM> pw_bn(ToBignum(pw));
+  CHECK(EC_POINT_mul(p224, MNpw.get(), nullptr, MN.get(), pw_bn.get(),
+                     /*ctx=*/nullptr));
+  return MNpw;
+}
+
+// ToMessage serialises |in| as a 56-byte string that contains the big-endian
+// representations of x and y, or is all zeros if |in| is infinity.
+std::string ToMessage(const EC_GROUP* p224, const EC_POINT* in) {
+  if (EC_POINT_is_at_infinity(p224, in)) {
+    return std::string(28 + 28, 0);
+  }
+
+  uint8_t x962[1 + 28 + 28];
+  CHECK(EC_POINT_point2oct(p224, in, POINT_CONVERSION_UNCOMPRESSED, x962,
+                           sizeof(x962), /*ctx=*/nullptr) == sizeof(x962));
+  return std::string(reinterpret_cast<const char*>(&x962[1]), sizeof(x962) - 1);
+}
+
+// FromMessage converts a message, as generated by |ToMessage|, into a point. It
+// returns |nullptr| if the input is invalid or not on the curve.
+bssl::UniquePtr<EC_POINT> FromMessage(const EC_GROUP* p224,
+                                      base::StringPiece in) {
+  if (in.size() != 56) {
+    return nullptr;
+  }
+
+  uint8_t x962[1 + 56];
+  x962[0] = 4;
+  memcpy(&x962[1], in.data(), sizeof(x962) - 1);
+
+  bssl::UniquePtr<EC_POINT> ret(EC_POINT_new(p224));
+  if (!EC_POINT_oct2point(p224, ret.get(), x962, sizeof(x962),
+                          /*ctx=*/nullptr)) {
+    return nullptr;
+  }
+
+  return ret;
+}
+
 }  // anonymous namespace
 
 namespace crypto {
@@ -120,19 +187,25 @@
 
 void P224EncryptedKeyExchange::Init() {
   // X = g**x_
-  p224::Point X;
-  p224::ScalarBaseMult(x_, &X);
+  bssl::UniquePtr<EC_GROUP> p224(EC_GROUP_new_by_curve_name(NID_secp224r1));
+  bssl::UniquePtr<EC_POINT> X(EC_POINT_new(p224.get()));
+  bssl::UniquePtr<BIGNUM> x_bn(ToBignum(x_));
+  // x_bn may be >= the order, but |EC_POINT_mul| handles that. It doesn't do so
+  // in constant-time, but the these values are locally generated and so this
+  // occurs with negligible probability. (Same with |pw_|, just below.)
+  CHECK(EC_POINT_mul(p224.get(), X.get(), x_bn.get(), nullptr, nullptr,
+                     /*ctx=*/nullptr));
 
   // The client masks the Diffie-Hellman value, X, by adding M**pw and the
   // server uses N**pw.
-  p224::Point MNpw;
-  p224::ScalarMult(is_server_ ? kN : kM, pw_, &MNpw);
+  bssl::UniquePtr<EC_POINT> MNpw(GetMask(p224.get(), !is_server_, pw_));
 
   // X* = X + (N|M)**pw
-  p224::Point Xstar;
-  p224::Add(X, MNpw, &Xstar);
+  bssl::UniquePtr<EC_POINT> Xstar(EC_POINT_new(p224.get()));
+  CHECK(EC_POINT_add(p224.get(), Xstar.get(), X.get(), MNpw.get(),
+                     /*ctx=*/nullptr));
 
-  next_message_ = Xstar.ToString();
+  next_message_ = ToMessage(p224.get(), Xstar.get());
 }
 
 const std::string& P224EncryptedKeyExchange::GetNextMessage() {
@@ -175,26 +248,31 @@
     return kResultFailed;
   }
 
+  bssl::UniquePtr<EC_GROUP> p224(EC_GROUP_new_by_curve_name(NID_secp224r1));
+
   // Y* is the other party's masked, Diffie-Hellman value.
-  p224::Point Ystar;
-  if (!Ystar.SetFromString(message)) {
+  bssl::UniquePtr<EC_POINT> Ystar(FromMessage(p224.get(), message));
+  if (!Ystar) {
     error_ = "failed to parse peer's masked Diffie-Hellman value";
     return kResultFailed;
   }
 
   // We calculate the mask value: (N|M)**pw
-  p224::Point MNpw, minus_MNpw, Y, k;
-  p224::ScalarMult(is_server_ ? kM : kN, pw_, &MNpw);
-  p224::Negate(MNpw, &minus_MNpw);
-
+  bssl::UniquePtr<EC_POINT> MNpw(GetMask(p224.get(), is_server_, pw_));
   // Y = Y* - (N|M)**pw
-  p224::Add(Ystar, minus_MNpw, &Y);
+  CHECK(EC_POINT_invert(p224.get(), MNpw.get(), /*ctx=*/nullptr));
+  bssl::UniquePtr<EC_POINT> Y(EC_POINT_new(p224.get()));
+  CHECK(EC_POINT_add(p224.get(), Y.get(), Ystar.get(), MNpw.get(),
+                     /*ctx=*/nullptr));
 
   // K = Y**x_
-  p224::ScalarMult(Y, x_, &k);
+  bssl::UniquePtr<EC_POINT> K(EC_POINT_new(p224.get()));
+  bssl::UniquePtr<BIGNUM> x_bn(ToBignum(x_));
+  CHECK(EC_POINT_mul(p224.get(), K.get(), nullptr, Y.get(), x_bn.get(),
+                     /*ctx=*/nullptr));
 
   // If everything worked out, then K is the same for both parties.
-  key_ = k.ToString();
+  key_ = ToMessage(p224.get(), K.get());
 
   std::string client_masked_dh, server_masked_dh;
   if (is_server_) {
diff --git a/crypto/p224_spake.h b/crypto/p224_spake.h
index 823bd45..7a0995d 100644
--- a/crypto/p224_spake.h
+++ b/crypto/p224_spake.h
@@ -11,7 +11,6 @@
 
 #include "base/gtest_prod_util.h"
 #include "base/strings/string_piece.h"
-#include "crypto/p224.h"
 #include "crypto/sha2.h"
 
 namespace crypto {
@@ -110,12 +109,14 @@
                      const std::string& k,
                      uint8_t* out_digest);
 
+  // kScalarBytes is the number of bytes in a P-224 scalar.
+  static constexpr size_t kScalarBytes = 28;
   // x_ is the secret Diffie-Hellman exponent (see paper referenced in .cc
   // file).
-  uint8_t x_[p224::kScalarBytes];
+  uint8_t x_[kScalarBytes];
   // pw_ is SHA256(P(password), P(session))[:28] where P() prepends a uint32_t,
   // big-endian length prefix (see paper referenced in .cc file).
-  uint8_t pw_[p224::kScalarBytes];
+  uint8_t pw_[kScalarBytes];
   // expected_authenticator_ is used to store the hash value expected from the
   // other party.
   uint8_t expected_authenticator_[kSHA256Length];
diff --git a/crypto/p224_unittest.cc b/crypto/p224_unittest.cc
deleted file mode 100644
index db3662a6..0000000
--- a/crypto/p224_unittest.cc
+++ /dev/null
@@ -1,825 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include <stddef.h>
-#include <stdint.h>
-#include <stdio.h>
-#include <string.h>
-
-#include "crypto/p224.h"
-
-#include "base/stl_util.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace crypto {
-
-using p224::Point;
-
-// kBasePointExternal is the P224 base point in external representation.
-static const uint8_t kBasePointExternal[56] = {
-    0xb7, 0x0e, 0x0c, 0xbd, 0x6b, 0xb4, 0xbf, 0x7f, 0x32, 0x13, 0x90, 0xb9,
-    0x4a, 0x03, 0xc1, 0xd3, 0x56, 0xc2, 0x11, 0x22, 0x34, 0x32, 0x80, 0xd6,
-    0x11, 0x5c, 0x1d, 0x21, 0xbd, 0x37, 0x63, 0x88, 0xb5, 0xf7, 0x23, 0xfb,
-    0x4c, 0x22, 0xdf, 0xe6, 0xcd, 0x43, 0x75, 0xa0, 0x5a, 0x07, 0x47, 0x64,
-    0x44, 0xd5, 0x81, 0x99, 0x85, 0x00, 0x7e, 0x34,
-};
-
-// TestVector represents a test of scalar multiplication of the base point.
-// |scalar| is a big-endian scalar and |affine| is the external representation
-// of g*scalar.
-struct TestVector {
-  uint8_t scalar[28];
-  uint8_t affine[28 * 2];
-};
-
-static const int kNumNISTTestVectors = 52;
-
-// kNISTTestVectors are the NIST test vectors for P224.
-static const TestVector kNISTTestVectors[kNumNISTTestVectors] = {
-  {
-    {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-     0x00, 0x00, 0x00, 0x01},
-    {0xb7, 0x0e, 0x0c, 0xbd, 0x6b, 0xb4, 0xbf, 0x7f,
-     0x32, 0x13, 0x90, 0xb9, 0x4a, 0x03, 0xc1, 0xd3,
-     0x56, 0xc2, 0x11, 0x22, 0x34, 0x32, 0x80, 0xd6,
-     0x11, 0x5c, 0x1d, 0x21, 0xbd, 0x37, 0x63, 0x88,
-     0xb5, 0xf7, 0x23, 0xfb, 0x4c, 0x22, 0xdf, 0xe6,
-     0xcd, 0x43, 0x75, 0xa0, 0x5a, 0x07, 0x47, 0x64,
-     0x44, 0xd5, 0x81, 0x99, 0x85, 0x00, 0x7e, 0x34
-    },
-  },
-  {
-    {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-     0x00, 0x00, 0x00, 0x02, },
-
-    {0x70, 0x6a, 0x46, 0xdc, 0x76, 0xdc, 0xb7, 0x67,
-     0x98, 0xe6, 0x0e, 0x6d, 0x89, 0x47, 0x47, 0x88,
-     0xd1, 0x6d, 0xc1, 0x80, 0x32, 0xd2, 0x68, 0xfd,
-     0x1a, 0x70, 0x4f, 0xa6, 0x1c, 0x2b, 0x76, 0xa7,
-     0xbc, 0x25, 0xe7, 0x70, 0x2a, 0x70, 0x4f, 0xa9,
-     0x86, 0x89, 0x28, 0x49, 0xfc, 0xa6, 0x29, 0x48,
-     0x7a, 0xcf, 0x37, 0x09, 0xd2, 0xe4, 0xe8, 0xbb,
-    },
-  },
-  {
-    {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-     0x00, 0x00, 0x00, 0x03, },
-    {0xdf, 0x1b, 0x1d, 0x66, 0xa5, 0x51, 0xd0, 0xd3,
-     0x1e, 0xff, 0x82, 0x25, 0x58, 0xb9, 0xd2, 0xcc,
-     0x75, 0xc2, 0x18, 0x02, 0x79, 0xfe, 0x0d, 0x08,
-     0xfd, 0x89, 0x6d, 0x04, 0xa3, 0xf7, 0xf0, 0x3c,
-     0xad, 0xd0, 0xbe, 0x44, 0x4c, 0x0a, 0xa5, 0x68,
-     0x30, 0x13, 0x0d, 0xdf, 0x77, 0xd3, 0x17, 0x34,
-     0x4e, 0x1a, 0xf3, 0x59, 0x19, 0x81, 0xa9, 0x25,
-    },
-  },
-  {
-    {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-     0x00, 0x00, 0x00, 0x04, },
-    {0xae, 0x99, 0xfe, 0xeb, 0xb5, 0xd2, 0x69, 0x45,
-     0xb5, 0x48, 0x92, 0x09, 0x2a, 0x8a, 0xee, 0x02,
-     0x91, 0x29, 0x30, 0xfa, 0x41, 0xcd, 0x11, 0x4e,
-     0x40, 0x44, 0x73, 0x01, 0x04, 0x82, 0x58, 0x0a,
-     0x0e, 0xc5, 0xbc, 0x47, 0xe8, 0x8b, 0xc8, 0xc3,
-     0x78, 0x63, 0x2c, 0xd1, 0x96, 0xcb, 0x3f, 0xa0,
-     0x58, 0xa7, 0x11, 0x4e, 0xb0, 0x30, 0x54, 0xc9,
-    },
-  },
-  {
-    {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-     0x00, 0x00, 0x00, 0x05, },
-    {0x31, 0xc4, 0x9a, 0xe7, 0x5b, 0xce, 0x78, 0x07,
-     0xcd, 0xff, 0x22, 0x05, 0x5d, 0x94, 0xee, 0x90,
-     0x21, 0xfe, 0xdb, 0xb5, 0xab, 0x51, 0xc5, 0x75,
-     0x26, 0xf0, 0x11, 0xaa, 0x27, 0xe8, 0xbf, 0xf1,
-     0x74, 0x56, 0x35, 0xec, 0x5b, 0xa0, 0xc9, 0xf1,
-     0xc2, 0xed, 0xe1, 0x54, 0x14, 0xc6, 0x50, 0x7d,
-     0x29, 0xff, 0xe3, 0x7e, 0x79, 0x0a, 0x07, 0x9b,
-    },
-  },
-  {
-    {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-     0x00, 0x00, 0x00, 0x06, },
-    {0x1f, 0x24, 0x83, 0xf8, 0x25, 0x72, 0x25, 0x1f,
-     0xca, 0x97, 0x5f, 0xea, 0x40, 0xdb, 0x82, 0x1d,
-     0xf8, 0xad, 0x82, 0xa3, 0xc0, 0x02, 0xee, 0x6c,
-     0x57, 0x11, 0x24, 0x08, 0x89, 0xfa, 0xf0, 0xcc,
-     0xb7, 0x50, 0xd9, 0x9b, 0x55, 0x3c, 0x57, 0x4f,
-     0xad, 0x7e, 0xcf, 0xb0, 0x43, 0x85, 0x86, 0xeb,
-     0x39, 0x52, 0xaf, 0x5b, 0x4b, 0x15, 0x3c, 0x7e,
-    },
-  },
-  {
-    {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-     0x00, 0x00, 0x00, 0x07, },
-    {0xdb, 0x2f, 0x6b, 0xe6, 0x30, 0xe2, 0x46, 0xa5,
-     0xcf, 0x7d, 0x99, 0xb8, 0x51, 0x94, 0xb1, 0x23,
-     0xd4, 0x87, 0xe2, 0xd4, 0x66, 0xb9, 0x4b, 0x24,
-     0xa0, 0x3c, 0x3e, 0x28, 0x0f, 0x3a, 0x30, 0x08,
-     0x54, 0x97, 0xf2, 0xf6, 0x11, 0xee, 0x25, 0x17,
-     0xb1, 0x63, 0xef, 0x8c, 0x53, 0xb7, 0x15, 0xd1,
-     0x8b, 0xb4, 0xe4, 0x80, 0x8d, 0x02, 0xb9, 0x63,
-    },
-  },
-  {
-    {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-     0x00, 0x00, 0x00, 0x08, },
-    {0x85, 0x8e, 0x6f, 0x9c, 0xc6, 0xc1, 0x2c, 0x31,
-     0xf5, 0xdf, 0x12, 0x4a, 0xa7, 0x77, 0x67, 0xb0,
-     0x5c, 0x8b, 0xc0, 0x21, 0xbd, 0x68, 0x3d, 0x2b,
-     0x55, 0x57, 0x15, 0x50, 0x04, 0x6d, 0xcd, 0x3e,
-     0xa5, 0xc4, 0x38, 0x98, 0xc5, 0xc5, 0xfc, 0x4f,
-     0xda, 0xc7, 0xdb, 0x39, 0xc2, 0xf0, 0x2e, 0xbe,
-     0xe4, 0xe3, 0x54, 0x1d, 0x1e, 0x78, 0x04, 0x7a,
-    },
-  },
-  {
-    {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-     0x00, 0x00, 0x00, 0x09, },
-    {0x2f, 0xdc, 0xcc, 0xfe, 0xe7, 0x20, 0xa7, 0x7e,
-     0xf6, 0xcb, 0x3b, 0xfb, 0xb4, 0x47, 0xf9, 0x38,
-     0x31, 0x17, 0xe3, 0xda, 0xa4, 0xa0, 0x7e, 0x36,
-     0xed, 0x15, 0xf7, 0x8d, 0x37, 0x17, 0x32, 0xe4,
-     0xf4, 0x1b, 0xf4, 0xf7, 0x88, 0x30, 0x35, 0xe6,
-     0xa7, 0x9f, 0xce, 0xdc, 0x0e, 0x19, 0x6e, 0xb0,
-     0x7b, 0x48, 0x17, 0x16, 0x97, 0x51, 0x74, 0x63,
-    },
-  },
-  {
-    {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-     0x00, 0x00, 0x00, 0x0a, },
-    {0xae, 0xa9, 0xe1, 0x7a, 0x30, 0x65, 0x17, 0xeb,
-     0x89, 0x15, 0x2a, 0xa7, 0x09, 0x6d, 0x2c, 0x38,
-     0x1e, 0xc8, 0x13, 0xc5, 0x1a, 0xa8, 0x80, 0xe7,
-     0xbe, 0xe2, 0xc0, 0xfd, 0x39, 0xbb, 0x30, 0xea,
-     0xb3, 0x37, 0xe0, 0xa5, 0x21, 0xb6, 0xcb, 0xa1,
-     0xab, 0xe4, 0xb2, 0xb3, 0xa3, 0xe5, 0x24, 0xc1,
-     0x4a, 0x3f, 0xe3, 0xeb, 0x11, 0x6b, 0x65, 0x5f,
-    },
-  },
-  {
-    {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-     0x00, 0x00, 0x00, 0x0b, },
-    {0xef, 0x53, 0xb6, 0x29, 0x4a, 0xca, 0x43, 0x1f,
-     0x0f, 0x3c, 0x22, 0xdc, 0x82, 0xeb, 0x90, 0x50,
-     0x32, 0x4f, 0x1d, 0x88, 0xd3, 0x77, 0xe7, 0x16,
-     0x44, 0x8e, 0x50, 0x7c, 0x20, 0xb5, 0x10, 0x00,
-     0x40, 0x92, 0xe9, 0x66, 0x36, 0xcf, 0xb7, 0xe3,
-     0x2e, 0xfd, 0xed, 0x82, 0x65, 0xc2, 0x66, 0xdf,
-     0xb7, 0x54, 0xfa, 0x6d, 0x64, 0x91, 0xa6, 0xda,
-    },
-  },
-  {
-    {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-     0x00, 0x00, 0x00, 0x0c, },
-    {0x6e, 0x31, 0xee, 0x1d, 0xc1, 0x37, 0xf8, 0x1b,
-     0x05, 0x67, 0x52, 0xe4, 0xde, 0xab, 0x14, 0x43,
-     0xa4, 0x81, 0x03, 0x3e, 0x9b, 0x4c, 0x93, 0xa3,
-     0x04, 0x4f, 0x4f, 0x7a, 0x20, 0x7d, 0xdd, 0xf0,
-     0x38, 0x5b, 0xfd, 0xea, 0xb6, 0xe9, 0xac, 0xda,
-     0x8d, 0xa0, 0x6b, 0x3b, 0xbe, 0xf2, 0x24, 0xa9,
-     0x3a, 0xb1, 0xe9, 0xe0, 0x36, 0x10, 0x9d, 0x13,
-    },
-  },
-  {
-    {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-     0x00, 0x00, 0x00, 0x0d, },
-    {0x34, 0xe8, 0xe1, 0x7a, 0x43, 0x0e, 0x43, 0x28,
-     0x97, 0x93, 0xc3, 0x83, 0xfa, 0xc9, 0x77, 0x42,
-     0x47, 0xb4, 0x0e, 0x9e, 0xbd, 0x33, 0x66, 0x98,
-     0x1f, 0xcf, 0xae, 0xca, 0x25, 0x28, 0x19, 0xf7,
-     0x1c, 0x7f, 0xb7, 0xfb, 0xcb, 0x15, 0x9b, 0xe3,
-     0x37, 0xd3, 0x7d, 0x33, 0x36, 0xd7, 0xfe, 0xb9,
-     0x63, 0x72, 0x4f, 0xdf, 0xb0, 0xec, 0xb7, 0x67,
-    },
-  },
-  {
-    {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-     0x00, 0x00, 0x00, 0x0e, },
-    {0xa5, 0x36, 0x40, 0xc8, 0x3d, 0xc2, 0x08, 0x60,
-     0x3d, 0xed, 0x83, 0xe4, 0xec, 0xf7, 0x58, 0xf2,
-     0x4c, 0x35, 0x7d, 0x7c, 0xf4, 0x80, 0x88, 0xb2,
-     0xce, 0x01, 0xe9, 0xfa, 0xd5, 0x81, 0x4c, 0xd7,
-     0x24, 0x19, 0x9c, 0x4a, 0x5b, 0x97, 0x4a, 0x43,
-     0x68, 0x5f, 0xbf, 0x5b, 0x8b, 0xac, 0x69, 0x45,
-     0x9c, 0x94, 0x69, 0xbc, 0x8f, 0x23, 0xcc, 0xaf,
-    },
-  },
-  {
-    {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-     0x00, 0x00, 0x00, 0x0f, },
-    {0xba, 0xa4, 0xd8, 0x63, 0x55, 0x11, 0xa7, 0xd2,
-     0x88, 0xae, 0xbe, 0xed, 0xd1, 0x2c, 0xe5, 0x29,
-     0xff, 0x10, 0x2c, 0x91, 0xf9, 0x7f, 0x86, 0x7e,
-     0x21, 0x91, 0x6b, 0xf9, 0x97, 0x9a, 0x5f, 0x47,
-     0x59, 0xf8, 0x0f, 0x4f, 0xb4, 0xec, 0x2e, 0x34,
-     0xf5, 0x56, 0x6d, 0x59, 0x56, 0x80, 0xa1, 0x17,
-     0x35, 0xe7, 0xb6, 0x10, 0x46, 0x12, 0x79, 0x89,
-    },
-  },
-  {
-    {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-     0x00, 0x00, 0x00, 0x10, },
-    {0x0b, 0x6e, 0xc4, 0xfe, 0x17, 0x77, 0x38, 0x24,
-     0x04, 0xef, 0x67, 0x99, 0x97, 0xba, 0x8d, 0x1c,
-     0xc5, 0xcd, 0x8e, 0x85, 0x34, 0x92, 0x59, 0xf5,
-     0x90, 0xc4, 0xc6, 0x6d, 0x33, 0x99, 0xd4, 0x64,
-     0x34, 0x59, 0x06, 0xb1, 0x1b, 0x00, 0xe3, 0x63,
-     0xef, 0x42, 0x92, 0x21, 0xf2, 0xec, 0x72, 0x0d,
-     0x2f, 0x66, 0x5d, 0x7d, 0xea, 0xd5, 0xb4, 0x82,
-    },
-  },
-  {
-    {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-     0x00, 0x00, 0x00, 0x11, },
-    {0xb8, 0x35, 0x7c, 0x3a, 0x6c, 0xee, 0xf2, 0x88,
-     0x31, 0x0e, 0x17, 0xb8, 0xbf, 0xef, 0xf9, 0x20,
-     0x08, 0x46, 0xca, 0x8c, 0x19, 0x42, 0x49, 0x7c,
-     0x48, 0x44, 0x03, 0xbc, 0xff, 0x14, 0x9e, 0xfa,
-     0x66, 0x06, 0xa6, 0xbd, 0x20, 0xef, 0x7d, 0x1b,
-     0x06, 0xbd, 0x92, 0xf6, 0x90, 0x46, 0x39, 0xdc,
-     0xe5, 0x17, 0x4d, 0xb6, 0xcc, 0x55, 0x4a, 0x26,
-    },
-  },
-  {
-    {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-     0x00, 0x00, 0x00, 0x12, },
-    {0xc9, 0xff, 0x61, 0xb0, 0x40, 0x87, 0x4c, 0x05,
-     0x68, 0x47, 0x92, 0x16, 0x82, 0x4a, 0x15, 0xea,
-     0xb1, 0xa8, 0x38, 0xa7, 0x97, 0xd1, 0x89, 0x74,
-     0x62, 0x26, 0xe4, 0xcc, 0xea, 0x98, 0xd6, 0x0e,
-     0x5f, 0xfc, 0x9b, 0x8f, 0xcf, 0x99, 0x9f, 0xab,
-     0x1d, 0xf7, 0xe7, 0xef, 0x70, 0x84, 0xf2, 0x0d,
-     0xdb, 0x61, 0xbb, 0x04, 0x5a, 0x6c, 0xe0, 0x02,
-    },
-  },
-  {
-    {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-     0x00, 0x00, 0x00, 0x13, },
-    {0xa1, 0xe8, 0x1c, 0x04, 0xf3, 0x0c, 0xe2, 0x01,
-     0xc7, 0xc9, 0xac, 0xe7, 0x85, 0xed, 0x44, 0xcc,
-     0x33, 0xb4, 0x55, 0xa0, 0x22, 0xf2, 0xac, 0xdb,
-     0xc6, 0xca, 0xe8, 0x3c, 0xdc, 0xf1, 0xf6, 0xc3,
-     0xdb, 0x09, 0xc7, 0x0a, 0xcc, 0x25, 0x39, 0x1d,
-     0x49, 0x2f, 0xe2, 0x5b, 0x4a, 0x18, 0x0b, 0xab,
-     0xd6, 0xce, 0xa3, 0x56, 0xc0, 0x47, 0x19, 0xcd,
-    },
-  },
-  {
-    {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-     0x00, 0x00, 0x00, 0x14, },
-    {0xfc, 0xc7, 0xf2, 0xb4, 0x5d, 0xf1, 0xcd, 0x5a,
-     0x3c, 0x0c, 0x07, 0x31, 0xca, 0x47, 0xa8, 0xaf,
-     0x75, 0xcf, 0xb0, 0x34, 0x7e, 0x83, 0x54, 0xee,
-     0xfe, 0x78, 0x24, 0x55, 0x0d, 0x5d, 0x71, 0x10,
-     0x27, 0x4c, 0xba, 0x7c, 0xde, 0xe9, 0x0e, 0x1a,
-     0x8b, 0x0d, 0x39, 0x4c, 0x37, 0x6a, 0x55, 0x73,
-     0xdb, 0x6b, 0xe0, 0xbf, 0x27, 0x47, 0xf5, 0x30,
-    },
-  },
-  {
-    {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-     0x00, 0x00, 0x00, 0x00, 0x01, 0x8e, 0xbb, 0xb9,
-     0x5e, 0xed, 0x0e, 0x13, },
-    {0x61, 0xf0, 0x77, 0xc6, 0xf6, 0x2e, 0xd8, 0x02,
-     0xda, 0xd7, 0xc2, 0xf3, 0x8f, 0x5c, 0x67, 0xf2,
-     0xcc, 0x45, 0x36, 0x01, 0xe6, 0x1b, 0xd0, 0x76,
-     0xbb, 0x46, 0x17, 0x9e, 0x22, 0x72, 0xf9, 0xe9,
-     0xf5, 0x93, 0x3e, 0x70, 0x38, 0x8e, 0xe6, 0x52,
-     0x51, 0x34, 0x43, 0xb5, 0xe2, 0x89, 0xdd, 0x13,
-     0x5d, 0xcc, 0x0d, 0x02, 0x99, 0xb2, 0x25, 0xe4,
-    },
-  },
-  {
-    {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-     0x00, 0x00, 0x00, 0x00, 0x00, 0x15, 0x9d, 0x89,
-     0x3d, 0x4c, 0xdd, 0x74, 0x72, 0x46, 0xcd, 0xca,
-     0x43, 0x59, 0x0e, 0x13, },
-    {0x02, 0x98, 0x95, 0xf0, 0xaf, 0x49, 0x6b, 0xfc,
-     0x62, 0xb6, 0xef, 0x8d, 0x8a, 0x65, 0xc8, 0x8c,
-     0x61, 0x39, 0x49, 0xb0, 0x36, 0x68, 0xaa, 0xb4,
-     0xf0, 0x42, 0x9e, 0x35, 0x3e, 0xa6, 0xe5, 0x3f,
-     0x9a, 0x84, 0x1f, 0x20, 0x19, 0xec, 0x24, 0xbd,
-     0xe1, 0xa7, 0x56, 0x77, 0xaa, 0x9b, 0x59, 0x02,
-     0xe6, 0x10, 0x81, 0xc0, 0x10, 0x64, 0xde, 0x93,
-    },
-  },
-  {
-    {0x41, 0xff, 0xc1, 0xff, 0xff, 0xfe, 0x01, 0xff,
-     0xfc, 0x00, 0x03, 0xff, 0xfe, 0x00, 0x07, 0xc0,
-     0x01, 0xff, 0xf0, 0x00, 0x03, 0xff, 0xf0, 0x7f,
-     0xfe, 0x00, 0x07, 0xc0, },
-    {0xab, 0x68, 0x99, 0x30, 0xbc, 0xae, 0x4a, 0x4a,
-     0xa5, 0xf5, 0xcb, 0x08, 0x5e, 0x82, 0x3e, 0x8a,
-     0xe3, 0x0f, 0xd3, 0x65, 0xeb, 0x1d, 0xa4, 0xab,
-     0xa9, 0xcf, 0x03, 0x79, 0x33, 0x45, 0xa1, 0x21,
-     0xbb, 0xd2, 0x33, 0x54, 0x8a, 0xf0, 0xd2, 0x10,
-     0x65, 0x4e, 0xb4, 0x0b, 0xab, 0x78, 0x8a, 0x03,
-     0x66, 0x64, 0x19, 0xbe, 0x6f, 0xbd, 0x34, 0xe7,
-    },
-  },
-  {
-    {0x7f, 0xff, 0xff, 0xc0, 0x3f, 0xff, 0xc0, 0x03,
-     0xff, 0xff, 0xfc, 0x00, 0x7f, 0xff, 0x00, 0x00,
-     0x00, 0x00, 0x07, 0x00, 0x00, 0x10, 0x00, 0x00,
-     0x00, 0x0e, 0x00, 0xff, },
-    {0xbd, 0xb6, 0xa8, 0x81, 0x7c, 0x1f, 0x89, 0xda,
-     0x1c, 0x2f, 0x3d, 0xd8, 0xe9, 0x7f, 0xeb, 0x44,
-     0x94, 0xf2, 0xed, 0x30, 0x2a, 0x4c, 0xe2, 0xbc,
-     0x7f, 0x5f, 0x40, 0x25, 0x4c, 0x70, 0x20, 0xd5,
-     0x7c, 0x00, 0x41, 0x18, 0x89, 0x46, 0x2d, 0x77,
-     0xa5, 0x43, 0x8b, 0xb4, 0xe9, 0x7d, 0x17, 0x77,
-     0x00, 0xbf, 0x72, 0x43, 0xa0, 0x7f, 0x16, 0x80,
-    },
-  },
-  {
-    {0x7f, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, 0x00,
-     0xff, 0xff, 0xf0, 0x1f, 0xff, 0xf8, 0xff, 0xff,
-     0xc0, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00,
-     0x00, 0x0f, 0xff, 0xff, },
-    {0xd5, 0x8b, 0x61, 0xaa, 0x41, 0xc3, 0x2d, 0xd5,
-     0xeb, 0xa4, 0x62, 0x64, 0x7d, 0xba, 0x75, 0xc5,
-     0xd6, 0x7c, 0x83, 0x60, 0x6c, 0x0a, 0xf2, 0xbd,
-     0x92, 0x84, 0x46, 0xa9, 0xd2, 0x4b, 0xa6, 0xa8,
-     0x37, 0xbe, 0x04, 0x60, 0xdd, 0x10, 0x7a, 0xe7,
-     0x77, 0x25, 0x69, 0x6d, 0x21, 0x14, 0x46, 0xc5,
-     0x60, 0x9b, 0x45, 0x95, 0x97, 0x6b, 0x16, 0xbd,
-    },
-  },
-  {
-    {0x7f, 0xff, 0xff, 0xc0, 0x00, 0xff, 0xfe, 0x3f,
-     0xff, 0xfc, 0x10, 0x00, 0x00, 0x20, 0x00, 0x3f,
-     0xff, 0xff, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x00,
-     0x3f, 0xff, 0xff, 0xff, },
-    {0xdc, 0x9f, 0xa7, 0x79, 0x78, 0xa0, 0x05, 0x51,
-     0x09, 0x80, 0xe9, 0x29, 0xa1, 0x48, 0x5f, 0x63,
-     0x71, 0x6d, 0xf6, 0x95, 0xd7, 0xa0, 0xc1, 0x8b,
-     0xb5, 0x18, 0xdf, 0x03, 0xed, 0xe2, 0xb0, 0x16,
-     0xf2, 0xdd, 0xff, 0xc2, 0xa8, 0xc0, 0x15, 0xb1,
-     0x34, 0x92, 0x82, 0x75, 0xce, 0x09, 0xe5, 0x66,
-     0x1b, 0x7a, 0xb1, 0x4c, 0xe0, 0xd1, 0xd4, 0x03,
-    },
-  },
-  {
-    {0x70, 0x01, 0xf0, 0x00, 0x1c, 0x00, 0x01, 0xc0,
-     0x00, 0x00, 0x1f, 0xff, 0xff, 0xfc, 0x00, 0x00,
-     0x1f, 0xff, 0xff, 0xf8, 0x00, 0x0f, 0xc0, 0x00,
-     0x00, 0x01, 0xfc, 0x00, },
-    {0x49, 0x9d, 0x8b, 0x28, 0x29, 0xcf, 0xb8, 0x79,
-     0xc9, 0x01, 0xf7, 0xd8, 0x5d, 0x35, 0x70, 0x45,
-     0xed, 0xab, 0x55, 0x02, 0x88, 0x24, 0xd0, 0xf0,
-     0x5b, 0xa2, 0x79, 0xba, 0xbf, 0x92, 0x95, 0x37,
-     0xb0, 0x6e, 0x40, 0x15, 0x91, 0x96, 0x39, 0xd9,
-     0x4f, 0x57, 0x83, 0x8f, 0xa3, 0x3f, 0xc3, 0xd9,
-     0x52, 0x59, 0x8d, 0xcd, 0xbb, 0x44, 0xd6, 0x38,
-    },
-  },
-  {
-    {0x00, 0x00, 0x00, 0x00, 0x1f, 0xfc, 0x00, 0x00,
-     0x00, 0xff, 0xf0, 0x30, 0x00, 0x1f, 0x00, 0x00,
-     0xff, 0xff, 0xf0, 0x00, 0x00, 0x38, 0x00, 0x00,
-     0x00, 0x00, 0x00, 0x02, },
-    {0x82, 0x46, 0xc9, 0x99, 0x13, 0x71, 0x86, 0x63,
-     0x2c, 0x5f, 0x9e, 0xdd, 0xf3, 0xb1, 0xb0, 0xe1,
-     0x76, 0x4c, 0x5e, 0x8b, 0xd0, 0xe0, 0xd8, 0xa5,
-     0x54, 0xb9, 0xcb, 0x77, 0xe8, 0x0e, 0xd8, 0x66,
-     0x0b, 0xc1, 0xcb, 0x17, 0xac, 0x7d, 0x84, 0x5b,
-     0xe4, 0x0a, 0x7a, 0x02, 0x2d, 0x33, 0x06, 0xf1,
-     0x16, 0xae, 0x9f, 0x81, 0xfe, 0xa6, 0x59, 0x47,
-    },
-  },
-  {
-    {0x7f, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
-     0x07, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-     0x00, 0x00, 0xff, 0xfe, 0x08, 0x00, 0x00, 0x1f,
-     0xf0, 0x00, 0x1f, 0xff, },
-    {0x66, 0x70, 0xc2, 0x0a, 0xfc, 0xce, 0xae, 0xa6,
-     0x72, 0xc9, 0x7f, 0x75, 0xe2, 0xe9, 0xdd, 0x5c,
-     0x84, 0x60, 0xe5, 0x4b, 0xb3, 0x85, 0x38, 0xeb,
-     0xb4, 0xbd, 0x30, 0xeb, 0xf2, 0x80, 0xd8, 0x00,
-     0x8d, 0x07, 0xa4, 0xca, 0xf5, 0x42, 0x71, 0xf9,
-     0x93, 0x52, 0x7d, 0x46, 0xff, 0x3f, 0xf4, 0x6f,
-     0xd1, 0x19, 0x0a, 0x3f, 0x1f, 0xaa, 0x4f, 0x74,
-    },
-  },
-  {
-    {0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff,
-     0xff, 0xc0, 0x00, 0x07, 0xff, 0xff, 0xe0, 0xff,
-     0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0xff,
-     0xff, 0xff, 0xff, 0xff, },
-    {0x00, 0x0e, 0xca, 0x93, 0x42, 0x47, 0x42, 0x5c,
-     0xfd, 0x94, 0x9b, 0x79, 0x5c, 0xb5, 0xce, 0x1e,
-     0xff, 0x40, 0x15, 0x50, 0x38, 0x6e, 0x28, 0xd1,
-     0xa4, 0xc5, 0xa8, 0xeb, 0xd4, 0xc0, 0x10, 0x40,
-     0xdb, 0xa1, 0x96, 0x28, 0x93, 0x1b, 0xc8, 0x85,
-     0x53, 0x70, 0x31, 0x7c, 0x72, 0x2c, 0xbd, 0x9c,
-     0xa6, 0x15, 0x69, 0x85, 0xf1, 0xc2, 0xe9, 0xce,
-    },
-  },
-  {
-    {0x7f, 0xff, 0xfc, 0x03, 0xff, 0x80, 0x7f, 0xff,
-     0xe0, 0x00, 0x1f, 0xff, 0xff, 0x80, 0x0f, 0xff,
-     0x80, 0x00, 0x01, 0xff, 0xff, 0x00, 0x01, 0xff,
-     0xff, 0xfe, 0x00, 0x1f, },
-    {0xef, 0x35, 0x3b, 0xf5, 0xc7, 0x3c, 0xd5, 0x51,
-     0xb9, 0x6d, 0x59, 0x6f, 0xbc, 0x9a, 0x67, 0xf1,
-     0x6d, 0x61, 0xdd, 0x9f, 0xe5, 0x6a, 0xf1, 0x9d,
-     0xe1, 0xfb, 0xa9, 0xcd, 0x21, 0x77, 0x1b, 0x9c,
-     0xdc, 0xe3, 0xe8, 0x43, 0x0c, 0x09, 0xb3, 0x83,
-     0x8b, 0xe7, 0x0b, 0x48, 0xc2, 0x1e, 0x15, 0xbc,
-     0x09, 0xee, 0x1f, 0x2d, 0x79, 0x45, 0xb9, 0x1f,
-    },
-  },
-  {
-    {0x00, 0x00, 0x00, 0x07, 0xff, 0xc0, 0x7f, 0xff,
-     0xff, 0xff, 0x01, 0xff, 0xfe, 0x03, 0xff, 0xfe,
-     0x40, 0x00, 0x38, 0x00, 0x07, 0xe0, 0x00, 0x3f,
-     0xfe, 0x00, 0x00, 0x00, },
-    {0x40, 0x36, 0x05, 0x2a, 0x30, 0x91, 0xeb, 0x48,
-     0x10, 0x46, 0xad, 0x32, 0x89, 0xc9, 0x5d, 0x3a,
-     0xc9, 0x05, 0xca, 0x00, 0x23, 0xde, 0x2c, 0x03,
-     0xec, 0xd4, 0x51, 0xcf, 0xd7, 0x68, 0x16, 0x5a,
-     0x38, 0xa2, 0xb9, 0x6f, 0x81, 0x25, 0x86, 0xa9,
-     0xd5, 0x9d, 0x41, 0x36, 0x03, 0x5d, 0x9c, 0x85,
-     0x3a, 0x5b, 0xf2, 0xe1, 0xc8, 0x6a, 0x49, 0x93,
-    },
-  },
-  {
-    {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
-     0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x16, 0xa2,
-     0xe0, 0xb8, 0xf0, 0x3e, 0x13, 0xdd, 0x29, 0x45,
-     0x5c, 0x5c, 0x2a, 0x29, },
-    {0xfc, 0xc7, 0xf2, 0xb4, 0x5d, 0xf1, 0xcd, 0x5a,
-     0x3c, 0x0c, 0x07, 0x31, 0xca, 0x47, 0xa8, 0xaf,
-     0x75, 0xcf, 0xb0, 0x34, 0x7e, 0x83, 0x54, 0xee,
-     0xfe, 0x78, 0x24, 0x55, 0xf2, 0xa2, 0x8e, 0xef,
-     0xd8, 0xb3, 0x45, 0x83, 0x21, 0x16, 0xf1, 0xe5,
-     0x74, 0xf2, 0xc6, 0xb2, 0xc8, 0x95, 0xaa, 0x8c,
-     0x24, 0x94, 0x1f, 0x40, 0xd8, 0xb8, 0x0a, 0xd1,
-    },
-  },
-  {
-    {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
-     0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x16, 0xa2,
-     0xe0, 0xb8, 0xf0, 0x3e, 0x13, 0xdd, 0x29, 0x45,
-     0x5c, 0x5c, 0x2a, 0x2a, },
-    {0xa1, 0xe8, 0x1c, 0x04, 0xf3, 0x0c, 0xe2, 0x01,
-     0xc7, 0xc9, 0xac, 0xe7, 0x85, 0xed, 0x44, 0xcc,
-     0x33, 0xb4, 0x55, 0xa0, 0x22, 0xf2, 0xac, 0xdb,
-     0xc6, 0xca, 0xe8, 0x3c, 0x23, 0x0e, 0x09, 0x3c,
-     0x24, 0xf6, 0x38, 0xf5, 0x33, 0xda, 0xc6, 0xe2,
-     0xb6, 0xd0, 0x1d, 0xa3, 0xb5, 0xe7, 0xf4, 0x54,
-     0x29, 0x31, 0x5c, 0xa9, 0x3f, 0xb8, 0xe6, 0x34,
-    },
-  },
-  {
-    {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
-     0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x16, 0xa2,
-     0xe0, 0xb8, 0xf0, 0x3e, 0x13, 0xdd, 0x29, 0x45,
-     0x5c, 0x5c, 0x2a, 0x2b, },
-    {0xc9, 0xff, 0x61, 0xb0, 0x40, 0x87, 0x4c, 0x05,
-     0x68, 0x47, 0x92, 0x16, 0x82, 0x4a, 0x15, 0xea,
-     0xb1, 0xa8, 0x38, 0xa7, 0x97, 0xd1, 0x89, 0x74,
-     0x62, 0x26, 0xe4, 0xcc, 0x15, 0x67, 0x29, 0xf1,
-     0xa0, 0x03, 0x64, 0x70, 0x30, 0x66, 0x60, 0x54,
-     0xe2, 0x08, 0x18, 0x0f, 0x8f, 0x7b, 0x0d, 0xf2,
-     0x24, 0x9e, 0x44, 0xfb, 0xa5, 0x93, 0x1f, 0xff,
-    },
-  },
-  {
-    {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
-     0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x16, 0xa2,
-     0xe0, 0xb8, 0xf0, 0x3e, 0x13, 0xdd, 0x29, 0x45,
-     0x5c, 0x5c, 0x2a, 0x2c, },
-    {0xb8, 0x35, 0x7c, 0x3a, 0x6c, 0xee, 0xf2, 0x88,
-     0x31, 0x0e, 0x17, 0xb8, 0xbf, 0xef, 0xf9, 0x20,
-     0x08, 0x46, 0xca, 0x8c, 0x19, 0x42, 0x49, 0x7c,
-     0x48, 0x44, 0x03, 0xbc, 0x00, 0xeb, 0x61, 0x05,
-     0x99, 0xf9, 0x59, 0x42, 0xdf, 0x10, 0x82, 0xe4,
-     0xf9, 0x42, 0x6d, 0x08, 0x6f, 0xb9, 0xc6, 0x23,
-     0x1a, 0xe8, 0xb2, 0x49, 0x33, 0xaa, 0xb5, 0xdb,
-    },
-  },
-  {
-    {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
-     0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x16, 0xa2,
-     0xe0, 0xb8, 0xf0, 0x3e, 0x13, 0xdd, 0x29, 0x45,
-     0x5c, 0x5c, 0x2a, 0x2d, },
-    {0x0b, 0x6e, 0xc4, 0xfe, 0x17, 0x77, 0x38, 0x24,
-     0x04, 0xef, 0x67, 0x99, 0x97, 0xba, 0x8d, 0x1c,
-     0xc5, 0xcd, 0x8e, 0x85, 0x34, 0x92, 0x59, 0xf5,
-     0x90, 0xc4, 0xc6, 0x6d, 0xcc, 0x66, 0x2b, 0x9b,
-     0xcb, 0xa6, 0xf9, 0x4e, 0xe4, 0xff, 0x1c, 0x9c,
-     0x10, 0xbd, 0x6d, 0xdd, 0x0d, 0x13, 0x8d, 0xf2,
-     0xd0, 0x99, 0xa2, 0x82, 0x15, 0x2a, 0x4b, 0x7f,
-    },
-  },
-  {
-    {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
-     0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x16, 0xa2,
-     0xe0, 0xb8, 0xf0, 0x3e, 0x13, 0xdd, 0x29, 0x45,
-     0x5c, 0x5c, 0x2a, 0x2e, },
-    {0xba, 0xa4, 0xd8, 0x63, 0x55, 0x11, 0xa7, 0xd2,
-     0x88, 0xae, 0xbe, 0xed, 0xd1, 0x2c, 0xe5, 0x29,
-     0xff, 0x10, 0x2c, 0x91, 0xf9, 0x7f, 0x86, 0x7e,
-     0x21, 0x91, 0x6b, 0xf9, 0x68, 0x65, 0xa0, 0xb8,
-     0xa6, 0x07, 0xf0, 0xb0, 0x4b, 0x13, 0xd1, 0xcb,
-     0x0a, 0xa9, 0x92, 0xa5, 0xa9, 0x7f, 0x5e, 0xe8,
-     0xca, 0x18, 0x49, 0xef, 0xb9, 0xed, 0x86, 0x78,
-    },
-  },
-  {
-    {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
-     0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x16, 0xa2,
-     0xe0, 0xb8, 0xf0, 0x3e, 0x13, 0xdd, 0x29, 0x45,
-     0x5c, 0x5c, 0x2a, 0x2f, },
-    {0xa5, 0x36, 0x40, 0xc8, 0x3d, 0xc2, 0x08, 0x60,
-     0x3d, 0xed, 0x83, 0xe4, 0xec, 0xf7, 0x58, 0xf2,
-     0x4c, 0x35, 0x7d, 0x7c, 0xf4, 0x80, 0x88, 0xb2,
-     0xce, 0x01, 0xe9, 0xfa, 0x2a, 0x7e, 0xb3, 0x28,
-     0xdb, 0xe6, 0x63, 0xb5, 0xa4, 0x68, 0xb5, 0xbc,
-     0x97, 0xa0, 0x40, 0xa3, 0x74, 0x53, 0x96, 0xba,
-     0x63, 0x6b, 0x96, 0x43, 0x70, 0xdc, 0x33, 0x52,
-    },
-  },
-  {
-    {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
-     0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x16, 0xa2,
-     0xe0, 0xb8, 0xf0, 0x3e, 0x13, 0xdd, 0x29, 0x45,
-     0x5c, 0x5c, 0x2a, 0x30, },
-    {0x34, 0xe8, 0xe1, 0x7a, 0x43, 0x0e, 0x43, 0x28,
-     0x97, 0x93, 0xc3, 0x83, 0xfa, 0xc9, 0x77, 0x42,
-     0x47, 0xb4, 0x0e, 0x9e, 0xbd, 0x33, 0x66, 0x98,
-     0x1f, 0xcf, 0xae, 0xca, 0xda, 0xd7, 0xe6, 0x08,
-     0xe3, 0x80, 0x48, 0x04, 0x34, 0xea, 0x64, 0x1c,
-     0xc8, 0x2c, 0x82, 0xcb, 0xc9, 0x28, 0x01, 0x46,
-     0x9c, 0x8d, 0xb0, 0x20, 0x4f, 0x13, 0x48, 0x9a,
-    },
-  },
-  {
-    {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
-     0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x16, 0xa2,
-     0xe0, 0xb8, 0xf0, 0x3e, 0x13, 0xdd, 0x29, 0x45,
-     0x5c, 0x5c, 0x2a, 0x31, },
-    {0x6e, 0x31, 0xee, 0x1d, 0xc1, 0x37, 0xf8, 0x1b,
-     0x05, 0x67, 0x52, 0xe4, 0xde, 0xab, 0x14, 0x43,
-     0xa4, 0x81, 0x03, 0x3e, 0x9b, 0x4c, 0x93, 0xa3,
-     0x04, 0x4f, 0x4f, 0x7a, 0xdf, 0x82, 0x22, 0x0f,
-     0xc7, 0xa4, 0x02, 0x15, 0x49, 0x16, 0x53, 0x25,
-     0x72, 0x5f, 0x94, 0xc3, 0x41, 0x0d, 0xdb, 0x56,
-     0xc5, 0x4e, 0x16, 0x1f, 0xc9, 0xef, 0x62, 0xee,
-    },
-  },
-  {
-    {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
-     0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x16, 0xa2,
-     0xe0, 0xb8, 0xf0, 0x3e, 0x13, 0xdd, 0x29, 0x45,
-     0x5c, 0x5c, 0x2a, 0x32, },
-    {0xef, 0x53, 0xb6, 0x29, 0x4a, 0xca, 0x43, 0x1f,
-     0x0f, 0x3c, 0x22, 0xdc, 0x82, 0xeb, 0x90, 0x50,
-     0x32, 0x4f, 0x1d, 0x88, 0xd3, 0x77, 0xe7, 0x16,
-     0x44, 0x8e, 0x50, 0x7c, 0xdf, 0x4a, 0xef, 0xff,
-     0xbf, 0x6d, 0x16, 0x99, 0xc9, 0x30, 0x48, 0x1c,
-     0xd1, 0x02, 0x12, 0x7c, 0x9a, 0x3d, 0x99, 0x20,
-     0x48, 0xab, 0x05, 0x92, 0x9b, 0x6e, 0x59, 0x27,
-    },
-  },
-  {
-    {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
-     0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x16, 0xa2,
-     0xe0, 0xb8, 0xf0, 0x3e, 0x13, 0xdd, 0x29, 0x45,
-     0x5c, 0x5c, 0x2a, 0x33, },
-    {0xae, 0xa9, 0xe1, 0x7a, 0x30, 0x65, 0x17, 0xeb,
-     0x89, 0x15, 0x2a, 0xa7, 0x09, 0x6d, 0x2c, 0x38,
-     0x1e, 0xc8, 0x13, 0xc5, 0x1a, 0xa8, 0x80, 0xe7,
-     0xbe, 0xe2, 0xc0, 0xfd, 0xc6, 0x44, 0xcf, 0x15,
-     0x4c, 0xc8, 0x1f, 0x5a, 0xde, 0x49, 0x34, 0x5e,
-     0x54, 0x1b, 0x4d, 0x4b, 0x5c, 0x1a, 0xdb, 0x3e,
-     0xb5, 0xc0, 0x1c, 0x14, 0xee, 0x94, 0x9a, 0xa2,
-    },
-  },
-  {
-    {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
-     0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x16, 0xa2,
-     0xe0, 0xb8, 0xf0, 0x3e, 0x13, 0xdd, 0x29, 0x45,
-     0x5c, 0x5c, 0x2a, 0x34, },
-    {0x2f, 0xdc, 0xcc, 0xfe, 0xe7, 0x20, 0xa7, 0x7e,
-     0xf6, 0xcb, 0x3b, 0xfb, 0xb4, 0x47, 0xf9, 0x38,
-     0x31, 0x17, 0xe3, 0xda, 0xa4, 0xa0, 0x7e, 0x36,
-     0xed, 0x15, 0xf7, 0x8d, 0xc8, 0xe8, 0xcd, 0x1b,
-     0x0b, 0xe4, 0x0b, 0x08, 0x77, 0xcf, 0xca, 0x19,
-     0x58, 0x60, 0x31, 0x22, 0xf1, 0xe6, 0x91, 0x4f,
-     0x84, 0xb7, 0xe8, 0xe9, 0x68, 0xae, 0x8b, 0x9e,
-    },
-  },
-  {
-    {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
-     0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x16, 0xa2,
-     0xe0, 0xb8, 0xf0, 0x3e, 0x13, 0xdd, 0x29, 0x45,
-     0x5c, 0x5c, 0x2a, 0x35, },
-    {0x85, 0x8e, 0x6f, 0x9c, 0xc6, 0xc1, 0x2c, 0x31,
-     0xf5, 0xdf, 0x12, 0x4a, 0xa7, 0x77, 0x67, 0xb0,
-     0x5c, 0x8b, 0xc0, 0x21, 0xbd, 0x68, 0x3d, 0x2b,
-     0x55, 0x57, 0x15, 0x50, 0xfb, 0x92, 0x32, 0xc1,
-     0x5a, 0x3b, 0xc7, 0x67, 0x3a, 0x3a, 0x03, 0xb0,
-     0x25, 0x38, 0x24, 0xc5, 0x3d, 0x0f, 0xd1, 0x41,
-     0x1b, 0x1c, 0xab, 0xe2, 0xe1, 0x87, 0xfb, 0x87,
-    },
-  },
-  {
-    {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
-     0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x16, 0xa2,
-     0xe0, 0xb8, 0xf0, 0x3e, 0x13, 0xdd, 0x29, 0x45,
-     0x5c, 0x5c, 0x2a, 0x36, },
-    {0xdb, 0x2f, 0x6b, 0xe6, 0x30, 0xe2, 0x46, 0xa5,
-     0xcf, 0x7d, 0x99, 0xb8, 0x51, 0x94, 0xb1, 0x23,
-     0xd4, 0x87, 0xe2, 0xd4, 0x66, 0xb9, 0x4b, 0x24,
-     0xa0, 0x3c, 0x3e, 0x28, 0xf0, 0xc5, 0xcf, 0xf7,
-     0xab, 0x68, 0x0d, 0x09, 0xee, 0x11, 0xda, 0xe8,
-     0x4e, 0x9c, 0x10, 0x72, 0xac, 0x48, 0xea, 0x2e,
-     0x74, 0x4b, 0x1b, 0x7f, 0x72, 0xfd, 0x46, 0x9e,
-    },
-  },
-  {
-    {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
-     0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x16, 0xa2,
-     0xe0, 0xb8, 0xf0, 0x3e, 0x13, 0xdd, 0x29, 0x45,
-     0x5c, 0x5c, 0x2a, 0x37, },
-    {0x1f, 0x24, 0x83, 0xf8, 0x25, 0x72, 0x25, 0x1f,
-     0xca, 0x97, 0x5f, 0xea, 0x40, 0xdb, 0x82, 0x1d,
-     0xf8, 0xad, 0x82, 0xa3, 0xc0, 0x02, 0xee, 0x6c,
-     0x57, 0x11, 0x24, 0x08, 0x76, 0x05, 0x0f, 0x33,
-     0x48, 0xaf, 0x26, 0x64, 0xaa, 0xc3, 0xa8, 0xb0,
-     0x52, 0x81, 0x30, 0x4e, 0xbc, 0x7a, 0x79, 0x14,
-     0xc6, 0xad, 0x50, 0xa4, 0xb4, 0xea, 0xc3, 0x83,
-    },
-  },
-  {
-    {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
-     0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x16, 0xa2,
-     0xe0, 0xb8, 0xf0, 0x3e, 0x13, 0xdd, 0x29, 0x45,
-     0x5c, 0x5c, 0x2a, 0x38, },
-    {0x31, 0xc4, 0x9a, 0xe7, 0x5b, 0xce, 0x78, 0x07,
-     0xcd, 0xff, 0x22, 0x05, 0x5d, 0x94, 0xee, 0x90,
-     0x21, 0xfe, 0xdb, 0xb5, 0xab, 0x51, 0xc5, 0x75,
-     0x26, 0xf0, 0x11, 0xaa, 0xd8, 0x17, 0x40, 0x0e,
-     0x8b, 0xa9, 0xca, 0x13, 0xa4, 0x5f, 0x36, 0x0e,
-     0x3d, 0x12, 0x1e, 0xaa, 0xeb, 0x39, 0xaf, 0x82,
-     0xd6, 0x00, 0x1c, 0x81, 0x86, 0xf5, 0xf8, 0x66,
-    },
-  },
-  {
-    {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
-     0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x16, 0xa2,
-     0xe0, 0xb8, 0xf0, 0x3e, 0x13, 0xdd, 0x29, 0x45,
-     0x5c, 0x5c, 0x2a, 0x39, },
-    {0xae, 0x99, 0xfe, 0xeb, 0xb5, 0xd2, 0x69, 0x45,
-     0xb5, 0x48, 0x92, 0x09, 0x2a, 0x8a, 0xee, 0x02,
-     0x91, 0x29, 0x30, 0xfa, 0x41, 0xcd, 0x11, 0x4e,
-     0x40, 0x44, 0x73, 0x01, 0xfb, 0x7d, 0xa7, 0xf5,
-     0xf1, 0x3a, 0x43, 0xb8, 0x17, 0x74, 0x37, 0x3c,
-     0x87, 0x9c, 0xd3, 0x2d, 0x69, 0x34, 0xc0, 0x5f,
-     0xa7, 0x58, 0xee, 0xb1, 0x4f, 0xcf, 0xab, 0x38,
-    },
-  },
-  {
-    {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
-     0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x16, 0xa2,
-     0xe0, 0xb8, 0xf0, 0x3e, 0x13, 0xdd, 0x29, 0x45,
-     0x5c, 0x5c, 0x2a, 0x3a, },
-    {0xdf, 0x1b, 0x1d, 0x66, 0xa5, 0x51, 0xd0, 0xd3,
-     0x1e, 0xff, 0x82, 0x25, 0x58, 0xb9, 0xd2, 0xcc,
-     0x75, 0xc2, 0x18, 0x02, 0x79, 0xfe, 0x0d, 0x08,
-     0xfd, 0x89, 0x6d, 0x04, 0x5c, 0x08, 0x0f, 0xc3,
-     0x52, 0x2f, 0x41, 0xbb, 0xb3, 0xf5, 0x5a, 0x97,
-     0xcf, 0xec, 0xf2, 0x1f, 0x88, 0x2c, 0xe8, 0xcb,
-     0xb1, 0xe5, 0x0c, 0xa6, 0xe6, 0x7e, 0x56, 0xdc,
-    },
-  },
-  {
-    {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
-     0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x16, 0xa2,
-     0xe0, 0xb8, 0xf0, 0x3e, 0x13, 0xdd, 0x29, 0x45,
-     0x5c, 0x5c, 0x2a, 0x3b, },
-    {0x70, 0x6a, 0x46, 0xdc, 0x76, 0xdc, 0xb7, 0x67,
-     0x98, 0xe6, 0x0e, 0x6d, 0x89, 0x47, 0x47, 0x88,
-     0xd1, 0x6d, 0xc1, 0x80, 0x32, 0xd2, 0x68, 0xfd,
-     0x1a, 0x70, 0x4f, 0xa6, 0xe3, 0xd4, 0x89, 0x58,
-     0x43, 0xda, 0x18, 0x8f, 0xd5, 0x8f, 0xb0, 0x56,
-     0x79, 0x76, 0xd7, 0xb5, 0x03, 0x59, 0xd6, 0xb7,
-     0x85, 0x30, 0xc8, 0xf6, 0x2d, 0x1b, 0x17, 0x46,
-    },
-  },
-  {
-    {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
-     0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x16, 0xa2,
-     0xe0, 0xb8, 0xf0, 0x3e, 0x13, 0xdd, 0x29, 0x45,
-     0x5c, 0x5c, 0x2a, 0x3c, },
-    {0xb7, 0x0e, 0x0c, 0xbd, 0x6b, 0xb4, 0xbf, 0x7f,
-     0x32, 0x13, 0x90, 0xb9, 0x4a, 0x03, 0xc1, 0xd3,
-     0x56, 0xc2, 0x11, 0x22, 0x34, 0x32, 0x80, 0xd6,
-     0x11, 0x5c, 0x1d, 0x21, 0x42, 0xc8, 0x9c, 0x77,
-     0x4a, 0x08, 0xdc, 0x04, 0xb3, 0xdd, 0x20, 0x19,
-     0x32, 0xbc, 0x8a, 0x5e, 0xa5, 0xf8, 0xb8, 0x9b,
-     0xbb, 0x2a, 0x7e, 0x66, 0x7a, 0xff, 0x81, 0xcd,
-    },
-  },
-};
-
-TEST(P224, ExternalToInternalAndBack) {
-  Point point;
-
-  EXPECT_TRUE(point.SetFromString(base::StringPiece(
-      reinterpret_cast<const char *>(kBasePointExternal),
-      sizeof(kBasePointExternal))));
-
-  const std::string external = point.ToString();
-
-  ASSERT_EQ(external.size(), 56u);
-  EXPECT_EQ(0, memcmp(external.data(), kBasePointExternal,
-                      sizeof(kBasePointExternal)));
-}
-
-TEST(P224, ScalarBaseMult) {
-  Point point;
-
-  for (size_t i = 0; i < base::size(kNISTTestVectors); i++) {
-    p224::ScalarBaseMult(kNISTTestVectors[i].scalar, &point);
-    const std::string external = point.ToString();
-    ASSERT_EQ(external.size(), 56u);
-    EXPECT_EQ(0, memcmp(external.data(), kNISTTestVectors[i].affine,
-                        external.size()));
-  }
-}
-
-TEST(P224, Addition) {
-  Point a, b, minus_b, sum, a_again;
-
-  ASSERT_TRUE(a.SetFromString(base::StringPiece(
-      reinterpret_cast<const char *>(kNISTTestVectors[10].affine), 56)));
-  ASSERT_TRUE(b.SetFromString(base::StringPiece(
-      reinterpret_cast<const char *>(kNISTTestVectors[11].affine), 56)));
-
-  p224::Negate(b, &minus_b);
-  p224::Add(a, b, &sum);
-  EXPECT_NE(0, memcmp(&sum, &a, sizeof(sum)));
-  p224::Add(minus_b, sum, &a_again);
-  EXPECT_EQ(a_again.ToString(), a.ToString());
-}
-
-TEST(P224, Infinity) {
-  char zeros[56];
-  memset(zeros, 0, sizeof(zeros));
-
-  // Test that x^0 = ∞.
-  Point a;
-  p224::ScalarBaseMult(reinterpret_cast<const uint8_t*>(zeros), &a);
-  EXPECT_EQ(0, memcmp(zeros, a.ToString().data(), sizeof(zeros)));
-
-  // We shouldn't allow ∞ to be imported.
-  EXPECT_FALSE(a.SetFromString(std::string(zeros, sizeof(zeros))));
-}
-
-}  // namespace crypto
diff --git a/extensions/browser/api/bluetooth/bluetooth_appshell_test.cc b/extensions/browser/api/bluetooth/bluetooth_appshell_test.cc
index 15c1ba63..c2f2bfb7 100644
--- a/extensions/browser/api/bluetooth/bluetooth_appshell_test.cc
+++ b/extensions/browser/api/bluetooth/bluetooth_appshell_test.cc
@@ -2,10 +2,17 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "build/build_config.h"
 #include "extensions/shell/test/shell_apitest.h"
 
 using BluetoothShellApiTest = extensions::ShellApiTest;
 
-IN_PROC_BROWSER_TEST_F(BluetoothShellApiTest, ApiSanityCheck) {
+// TODO(crbug.com/1165955): this test flakes on Mac ASAN
+#if defined(OS_MAC)
+#define MAYBE_ApiSanityCheck DISABLED_ApiSanityCheck
+#else
+#define MAYBE_ApiSanityCheck ApiSanityCheck
+#endif
+IN_PROC_BROWSER_TEST_F(BluetoothShellApiTest, MAYBE_ApiSanityCheck) {
   ASSERT_TRUE(RunAppTest("api_test/bluetooth"));
 }
diff --git a/extensions/common/api/_api_features.json b/extensions/common/api/_api_features.json
index 11b93cb9..0bc144a 100644
--- a/extensions/common/api/_api_features.json
+++ b/extensions/common/api/_api_features.json
@@ -308,7 +308,8 @@
       "chrome://tab-strip/*",
       "chrome://welcome/*",
       "chrome://profile-picker/*",
-      "chrome://file-manager/*"
+      "chrome://file-manager/*",
+      "chrome://help-app/*"
     ]
   }, {
     "channel": "stable",
diff --git a/ios/chrome/browser/ui/content_suggestions/content_suggestions_header_view_controller.h b/ios/chrome/browser/ui/content_suggestions/content_suggestions_header_view_controller.h
index 424e382..e4f23ba1 100644
--- a/ios/chrome/browser/ui/content_suggestions/content_suggestions_header_view_controller.h
+++ b/ios/chrome/browser/ui/content_suggestions/content_suggestions_header_view_controller.h
@@ -59,6 +59,10 @@
 // omnibox.
 - (void)focusFakebox;
 
+// Returns the height of the content suggestions header, not including the
+// omnibox.
+- (CGFloat)heightAboveFakeOmnibox;
+
 @end
 
 #endif  // IOS_CHROME_BROWSER_UI_CONTENT_SUGGESTIONS_CONTENT_SUGGESTIONS_HEADER_VIEW_CONTROLLER_H_
diff --git a/ios/chrome/browser/ui/content_suggestions/content_suggestions_header_view_controller.mm b/ios/chrome/browser/ui/content_suggestions/content_suggestions_header_view_controller.mm
index c0c3f33..d5899fa 100644
--- a/ios/chrome/browser/ui/content_suggestions/content_suggestions_header_view_controller.mm
+++ b/ios/chrome/browser/ui/content_suggestions/content_suggestions_header_view_controller.mm
@@ -150,6 +150,14 @@
   [self.accessibilityButton removeObserver:self forKeyPath:@"highlighted"];
 }
 
+- (CGFloat)heightAboveFakeOmnibox {
+  return self.view.frame.size.height -
+         ntp_header::kFakeOmniboxScrolledToTopMargin -
+         self.fakeOmnibox.frame.size.height -
+         ToolbarExpandedHeight(
+             [UIApplication sharedApplication].preferredContentSizeCategory);
+}
+
 #pragma mark - ContentSuggestionsHeaderControlling
 
 - (void)updateFakeOmniboxForOffset:(CGFloat)offset
diff --git a/ios/chrome/browser/ui/content_suggestions/content_suggestions_layout.h b/ios/chrome/browser/ui/content_suggestions/content_suggestions_layout.h
index 181e88c..d62d6d4 100644
--- a/ios/chrome/browser/ui/content_suggestions/content_suggestions_layout.h
+++ b/ios/chrome/browser/ui/content_suggestions/content_suggestions_layout.h
@@ -19,6 +19,14 @@
 // The total scroll height of the NTP.
 @property(nonatomic, assign) CGFloat ntpHeight;
 
+// The parent collection view that contains the content suggestions collection
+// view.
+@property(nonatomic, weak) UICollectionView* parentCollectionView;
+
+// Whether or not the user has scrolled into the feed, transferring ownership of
+// the omnibox to allow it to stick to the top of the NTP.
+@property(nonatomic, assign) BOOL isScrolledIntoFeed;
+
 // Creates layout with |offset| as additional height. Allows the view's height
 // to be increased enough to maintain the scroll position. Only needed if
 // Discover feed is enabled.
diff --git a/ios/chrome/browser/ui/content_suggestions/content_suggestions_layout.mm b/ios/chrome/browser/ui/content_suggestions/content_suggestions_layout.mm
index 5a0bf21..7923bd6 100644
--- a/ios/chrome/browser/ui/content_suggestions/content_suggestions_layout.mm
+++ b/ios/chrome/browser/ui/content_suggestions/content_suggestions_layout.mm
@@ -115,8 +115,14 @@
 
   if ([kind isEqualToString:UICollectionElementKindSectionHeader] &&
       indexPath.section == 0) {
-    UICollectionView* collectionView = self.collectionView;
-    CGPoint contentOffset = collectionView.contentOffset;
+    CGFloat contentOffset;
+    if (IsRefactoredNTP()) {
+      contentOffset = self.parentCollectionView.contentOffset.y +
+                      self.collectionView.contentSize.height;
+    } else {
+      contentOffset = self.collectionView.contentOffset.y;
+    }
+
     CGFloat headerHeight = CGRectGetHeight(attributes.frame);
     CGPoint origin = attributes.frame.origin;
 
@@ -130,8 +136,10 @@
         ToolbarExpandedHeight(
             [UIApplication sharedApplication].preferredContentSizeCategory) -
         topSafeArea;
-    if (contentOffset.y > minY)
-      origin.y = contentOffset.y - minY;
+    if (contentOffset > minY &&
+        (!IsRefactoredNTP() || !self.isScrolledIntoFeed)) {
+      origin.y = contentOffset - minY;
+    }
     attributes.frame = {origin, attributes.frame.size};
   }
   return attributes;
diff --git a/ios/chrome/browser/ui/content_suggestions/content_suggestions_view_controller.mm b/ios/chrome/browser/ui/content_suggestions/content_suggestions_view_controller.mm
index 49724a5..2725816 100644
--- a/ios/chrome/browser/ui/content_suggestions/content_suggestions_view_controller.mm
+++ b/ios/chrome/browser/ui/content_suggestions/content_suggestions_view_controller.mm
@@ -96,6 +96,9 @@
 // The CollectionViewController scroll position when an scrolling event starts.
 @property(nonatomic, assign) int scrollStartPosition;
 
+// The layout of the content suggestions collection view.
+@property(nonatomic, strong) ContentSuggestionsLayout* layout;
+
 @end
 
 @implementation ContentSuggestionsViewController
@@ -115,9 +118,8 @@
 - (instancetype)initWithStyle:(CollectionViewControllerStyle)style
                        offset:(CGFloat)offset {
   _offset = offset;
-  UICollectionViewLayout* layout =
-      [[ContentSuggestionsLayout alloc] initWithOffset:offset];
-  self = [super initWithLayout:layout style:style];
+  _layout = [[ContentSuggestionsLayout alloc] initWithOffset:offset];
+  self = [super initWithLayout:_layout style:style];
   if (self) {
     _collectionUpdater = [[ContentSuggestionsCollectionUpdater alloc] init];
     _initialContentOffset = NAN;
@@ -309,6 +311,9 @@
   [self.collectionView.collectionViewLayout invalidateLayout];
   // Ensure initial fake omnibox layout.
   [self.headerSynchronizer updateFakeOmniboxOnCollectionScroll];
+  // TODO(crbug.com/1114792): Plumb the collection view.
+  self.layout.parentCollectionView =
+      static_cast<UICollectionView*>(self.view.superview);
 }
 
 - (void)viewDidAppear:(BOOL)animated {
@@ -319,9 +324,7 @@
   // Remove forced height if it was already applied, since the scroll position
   // was already maintained.
   if (self.offset > 0) {
-    ContentSuggestionsLayout* layout = static_cast<ContentSuggestionsLayout*>(
-        self.collectionView.collectionViewLayout);
-    layout.offset = 0;
+    self.layout.offset = 0;
   }
 }
 
diff --git a/ios/chrome/browser/ui/ntp/BUILD.gn b/ios/chrome/browser/ui/ntp/BUILD.gn
index 900068f..b88dee2a 100644
--- a/ios/chrome/browser/ui/ntp/BUILD.gn
+++ b/ios/chrome/browser/ui/ntp/BUILD.gn
@@ -4,6 +4,7 @@
 
 source_set("ntp") {
   sources = [
+    "new_tab_page_content_delegate.h",
     "new_tab_page_controller_delegate.h",
     "new_tab_page_header_constants.h",
     "new_tab_page_header_constants.mm",
diff --git a/ios/chrome/browser/ui/ntp/new_tab_page_content_delegate.h b/ios/chrome/browser/ui/ntp/new_tab_page_content_delegate.h
new file mode 100644
index 0000000..5989b555
--- /dev/null
+++ b/ios/chrome/browser/ui/ntp/new_tab_page_content_delegate.h
@@ -0,0 +1,20 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IOS_CHROME_BROWSER_UI_NTP_NEW_TAB_PAGE_CONTENT_DELEGATE_H_
+#define IOS_CHROME_BROWSER_UI_NTP_NEW_TAB_PAGE_CONTENT_DELEGATE_H_
+
+// Delegate for actions relating to the NTP content.
+@protocol NewTabPageContentDelegate
+
+// Reloads content suggestions collection view.
+- (void)reloadContentSuggestions;
+
+// Returns the height of the content suggestions header, not including the
+// omnibox.
+- (CGFloat)heightAboveFakeOmnibox;
+
+@end
+
+#endif  // IOS_CHROME_BROWSER_UI_NTP_NEW_TAB_PAGE_CONTENT_DELEGATE_H_
diff --git a/ios/chrome/browser/ui/ntp/new_tab_page_coordinator.mm b/ios/chrome/browser/ui/ntp/new_tab_page_coordinator.mm
index 74b19a8..a8b42fd 100644
--- a/ios/chrome/browser/ui/ntp/new_tab_page_coordinator.mm
+++ b/ios/chrome/browser/ui/ntp/new_tab_page_coordinator.mm
@@ -22,6 +22,7 @@
 #import "ios/chrome/browser/ui/main/scene_state_observer.h"
 #import "ios/chrome/browser/ui/ntp/discover_feed_wrapper_view_controller.h"
 #import "ios/chrome/browser/ui/ntp/incognito_view_controller.h"
+#import "ios/chrome/browser/ui/ntp/new_tab_page_content_delegate.h"
 #import "ios/chrome/browser/ui/ntp/new_tab_page_feature.h"
 #import "ios/chrome/browser/ui/ntp/new_tab_page_view_controller.h"
 #import "ios/chrome/browser/ui/overscroll_actions/overscroll_actions_controller.h"
@@ -37,7 +38,8 @@
 #error "This file requires ARC support."
 #endif
 
-@interface NewTabPageCoordinator () <OverscrollActionsControllerDelegate,
+@interface NewTabPageCoordinator () <NewTabPageContentDelegate,
+                                     OverscrollActionsControllerDelegate,
                                      SceneStateObserver>
 
 // Coordinator for the ContentSuggestions.
@@ -132,6 +134,10 @@
       self.ntpViewController.discoverFeedWrapperViewController =
           self.discoverFeedWrapperViewController;
       self.ntpViewController.overscrollDelegate = self;
+      self.ntpViewController.ntpContentDelegate = self;
+
+      self.ntpViewController.headerController =
+          self.contentSuggestionsCoordinator.headerController;
     }
 
     base::RecordAction(base::UserMetricsAction("MobileNTPShowMostVisited"));
@@ -228,7 +234,7 @@
   if (IsRefactoredNTP()) {
     ios::GetChromeBrowserProvider()->GetDiscoverFeedProvider()->RefreshFeed();
   }
-  [self.contentSuggestionsCoordinator reload];
+  [self reloadContentSuggestions];
 }
 
 - (void)locationBarDidBecomeFirstResponder {
@@ -329,4 +335,15 @@
   return nullptr;
 }
 
+#pragma mark - NewTabPageContentDelegate
+
+- (void)reloadContentSuggestions {
+  [self.contentSuggestionsCoordinator reload];
+}
+
+- (CGFloat)heightAboveFakeOmnibox {
+  return [self.contentSuggestionsCoordinator
+              .headerController heightAboveFakeOmnibox];
+}
+
 @end
diff --git a/ios/chrome/browser/ui/ntp/new_tab_page_view_controller.h b/ios/chrome/browser/ui/ntp/new_tab_page_view_controller.h
index 26bd4dc..48009a8 100644
--- a/ios/chrome/browser/ui/ntp/new_tab_page_view_controller.h
+++ b/ios/chrome/browser/ui/ntp/new_tab_page_view_controller.h
@@ -9,8 +9,10 @@
 
 #import "ios/chrome/browser/ui/content_suggestions/content_suggestions_collection_controlling.h"
 
+@class ContentSuggestionsHeaderViewController;
 @class ContentSuggestionsViewController;
 @class DiscoverFeedWrapperViewController;
+@protocol NewTabPageContentDelegate;
 @protocol OverscrollActionsControllerDelegate;
 
 // View controller containing all the content presented on a standard,
@@ -27,6 +29,12 @@
 @property(nonatomic, weak) id<OverscrollActionsControllerDelegate>
     overscrollDelegate;
 
+// The content suggestions header, containing the fake omnibox and the doodle.
+@property(nonatomic, weak) UIViewController* headerController;
+
+// Delegate for actions relating to the NTP content.
+@property(nonatomic, weak) id<NewTabPageContentDelegate> ntpContentDelegate;
+
 // Initializes view controller with NTP content view controllers.
 // |discoverFeedViewController| represents the Discover feed for suggesting
 // articles. |contentSuggestionsViewController| represents other content
diff --git a/ios/chrome/browser/ui/ntp/new_tab_page_view_controller.mm b/ios/chrome/browser/ui/ntp/new_tab_page_view_controller.mm
index a9937f7..ce51aa8 100644
--- a/ios/chrome/browser/ui/ntp/new_tab_page_view_controller.mm
+++ b/ios/chrome/browser/ui/ntp/new_tab_page_view_controller.mm
@@ -8,9 +8,12 @@
 
 #import "base/check.h"
 #import "ios/chrome/browser/ui/content_suggestions/content_suggestions_header_synchronizing.h"
+#import "ios/chrome/browser/ui/content_suggestions/content_suggestions_layout.h"
 #import "ios/chrome/browser/ui/content_suggestions/ntp_home_constant.h"
 #import "ios/chrome/browser/ui/ntp/discover_feed_wrapper_view_controller.h"
+#import "ios/chrome/browser/ui/ntp/new_tab_page_content_delegate.h"
 #import "ios/chrome/browser/ui/overscroll_actions/overscroll_actions_controller.h"
+#import "ios/chrome/browser/ui/util/named_guide.h"
 #import "ios/chrome/browser/ui/util/uikit_ui_util.h"
 #import "ios/chrome/common/ui/util/constraints_ui_util.h"
 
@@ -18,6 +21,15 @@
 #error "This file requires ARC support."
 #endif
 
+namespace {
+// The offset from the bottom of the content suggestions header before changing
+// ownership of the fake omnibox. This value can be a large range of numbers, as
+// long as it is larger than the omnibox height (plus an addional offset to make
+// it look smooth). Otherwise, the omnibox hides beneath the feed before
+// changing ownership.
+const CGFloat kOffsetToPinOmnibox = 100;
+}
+
 @interface NewTabPageViewController ()
 
 // View controller representing the NTP content suggestions. These suggestions
@@ -30,6 +42,14 @@
 @property(nonatomic, strong)
     OverscrollActionsController* overscrollActionsController;
 
+// Whether or not the user has scrolled into the feed, transferring ownership of
+// the omnibox to allow it to stick to the top of the NTP.
+@property(nonatomic, assign, getter=isScrolledIntoFeed) BOOL scrolledIntoFeed;
+
+// The collection view layout for the uppermost content suggestions collection
+// view.
+@property(nonatomic, weak) ContentSuggestionsLayout* contentSuggestionsLayout;
+
 @end
 
 @implementation NewTabPageViewController
@@ -43,6 +63,9 @@
   self = [super initWithNibName:nil bundle:nil];
   if (self) {
     _contentSuggestionsViewController = contentSuggestionsViewController;
+    // TODO(crbug.com/1114792): Instantiate this depending on the initial scroll
+    // position.
+    _scrolledIntoFeed = NO;
   }
 
   return self;
@@ -99,6 +122,11 @@
   [self updateOverscrollActionsState];
 
   self.view.backgroundColor = ntp_home::kNTPBackgroundColor();
+
+  _contentSuggestionsLayout = static_cast<ContentSuggestionsLayout*>(
+      self.contentSuggestionsViewController.collectionView
+          .collectionViewLayout);
+  _contentSuggestionsLayout.isScrolledIntoFeed = self.isScrolledIntoFeed;
 }
 
 - (void)viewDidLayoutSubviews {
@@ -185,6 +213,23 @@
   [self.headerSynchronizer updateFakeOmniboxOnCollectionScroll];
   self.scrolledToTop =
       scrollView.contentOffset.y >= [self.headerSynchronizer pinnedOffsetY];
+  // Fixes the content suggestions collection view layout so that the header
+  // scrolls at the same rate as the rest.
+  if (scrollView.contentOffset.y > -self.contentSuggestionsViewController
+                                        .collectionView.contentSize.height) {
+    [self.contentSuggestionsViewController.collectionView
+            .collectionViewLayout invalidateLayout];
+  }
+  // Changes ownership of fake omnibox view based on scroll position.
+  if (!self.isScrolledIntoFeed &&
+      scrollView.contentOffset.y > -kOffsetToPinOmnibox) {
+    [self setIsScrolledIntoFeed:YES];
+    [self stickFakeOmniboxToTop];
+  } else if (self.isScrolledIntoFeed &&
+             scrollView.contentOffset.y <= -kOffsetToPinOmnibox) {
+    [self setIsScrolledIntoFeed:NO];
+    [self resetFakeOmnibox];
+  }
 }
 
 - (void)scrollViewWillBeginDragging:(UIScrollView*)scrollView {
@@ -250,4 +295,45 @@
   }
 }
 
+// Lets this view own the fake omnibox and sticks it to the top of the NTP.
+- (void)stickFakeOmniboxToTop {
+  [self.view addSubview:self.headerController.view];
+  [NSLayoutConstraint activateConstraints:@[
+    [self.headerController.view.topAnchor
+        constraintEqualToAnchor:self.discoverFeedWrapperViewController.view
+                                    .topAnchor
+                       constant:-([self.ntpContentDelegate
+                                          heightAboveFakeOmnibox] +
+                                  2)],
+    [self.headerController.view.leadingAnchor
+        constraintEqualToAnchor:self.discoverFeedWrapperViewController.view
+                                    .leadingAnchor],
+    [self.headerController.view.trailingAnchor
+        constraintEqualToAnchor:self.discoverFeedWrapperViewController.view
+                                    .trailingAnchor],
+    [self.headerController.view.heightAnchor
+        constraintEqualToConstant:self.headerController.view.frame.size.height],
+  ]];
+}
+
+// Gives content suggestions collection view ownership of the fake omnibox for
+// the width animation.
+- (void)resetFakeOmnibox {
+  [self.headerController.view removeFromSuperview];
+  // Reload the content suggestions so that the fake omnibox goes back where it
+  // belongs. This can probably be optimized by just reloading the header, if
+  // that doesn't mess up any collection/header interactions.
+  [self.ntpContentDelegate reloadContentSuggestions];
+}
+
+#pragma mark - Setters
+
+// Sets whether or not the NTP is scrolled into the feed and notifies the
+// content suggestions layout to avoid it changing the omnibox frame when this
+// view controls its position.
+- (void)setIsScrolledIntoFeed:(BOOL)scrolledIntoFeed {
+  _scrolledIntoFeed = scrolledIntoFeed;
+  self.contentSuggestionsLayout.isScrolledIntoFeed = scrolledIntoFeed;
+}
+
 @end
diff --git a/media/base/video_frame.cc b/media/base/video_frame.cc
index 89711a7..9d4c72a 100644
--- a/media/base/video_frame.cc
+++ b/media/base/video_frame.cc
@@ -341,7 +341,7 @@
   scoped_refptr<VideoFrame> frame =
       new VideoFrame(*layout, StorageType::STORAGE_OPAQUE,
                      gfx::Rect(natural_size), natural_size, timestamp);
-  frame->metadata()->overlay_plane_id = overlay_plane_id;
+  frame->metadata().overlay_plane_id = overlay_plane_id;
   return frame;
 }
 
@@ -850,7 +850,7 @@
                      natural_size, frame->timestamp()));
 
   // Copy all metadata to the wrapped frame->
-  wrapping_frame->metadata()->MergeMetadataFrom(frame->metadata());
+  wrapping_frame->metadata().MergeMetadataFrom(frame->metadata());
 
   if (frame->IsMappable()) {
     for (size_t i = 0; i < NumPlanes(format); ++i) {
@@ -882,7 +882,7 @@
   }
   scoped_refptr<VideoFrame> frame = new VideoFrame(
       *layout, STORAGE_UNKNOWN, gfx::Rect(), gfx::Size(), kNoTimestamp);
-  frame->metadata()->end_of_stream = true;
+  frame->metadata().end_of_stream = true;
   return frame;
 }
 
@@ -1258,7 +1258,7 @@
 }
 
 std::string VideoFrame::AsHumanReadableString() const {
-  if (metadata()->end_of_stream)
+  if (metadata().end_of_stream)
     return "end of stream";
 
   std::ostringstream s;
diff --git a/media/base/video_frame.h b/media/base/video_frame.h
index 4185e3c..471085e 100644
--- a/media/base/video_frame.h
+++ b/media/base/video_frame.h
@@ -559,10 +559,8 @@
   //
   // TODO(miu): Move some of the "extra" members of VideoFrame (below) into
   // here as a later clean-up step.
-  //
-  // TODO(https://crbug.com/1096727): change the return type to const&.
-  const VideoFrameMetadata* metadata() const { return &metadata_; }
-  VideoFrameMetadata* metadata() { return &metadata_; }
+  const VideoFrameMetadata& metadata() const { return metadata_; }
+  VideoFrameMetadata& metadata() { return metadata_; }
   void set_metadata(const VideoFrameMetadata& metadata) {
     metadata_ = metadata;
   }
diff --git a/media/base/video_frame_metadata.cc b/media/base/video_frame_metadata.cc
index 6cd1c5dc..c0837c1 100644
--- a/media/base/video_frame_metadata.cc
+++ b/media/base/video_frame_metadata.cc
@@ -21,11 +21,11 @@
     default;
 
 #define MERGE_FIELD(a, source) \
-  if (source->a)               \
-    this->a = source->a
+  if (source.a)                \
+  this->a = source.a
 
 void VideoFrameMetadata::MergeMetadataFrom(
-    const VideoFrameMetadata* metadata_source) {
+    const VideoFrameMetadata& metadata_source) {
   MERGE_FIELD(allow_overlay, metadata_source);
   MERGE_FIELD(capture_begin_time, metadata_source);
   MERGE_FIELD(capture_end_time, metadata_source);
diff --git a/media/base/video_frame_metadata.h b/media/base/video_frame_metadata.h
index ded4b13..c357956 100644
--- a/media/base/video_frame_metadata.h
+++ b/media/base/video_frame_metadata.h
@@ -46,7 +46,7 @@
   };
 
   // Merges internal values from |metadata_source|.
-  void MergeMetadataFrom(const VideoFrameMetadata* metadata_source);
+  void MergeMetadataFrom(const VideoFrameMetadata& metadata_source);
 
   // Sources of VideoFrames use this marker to indicate that the associated
   // VideoFrame can be overlaid, case in which its contents do not need to be
diff --git a/media/base/video_frame_unittest.cc b/media/base/video_frame_unittest.cc
index 30f2bbc..1d5beaf 100644
--- a/media/base/video_frame_unittest.cc
+++ b/media/base/video_frame_unittest.cc
@@ -290,7 +290,7 @@
 
   // Test an empty frame.
   frame = VideoFrame::CreateEOSFrame();
-  EXPECT_TRUE(frame->metadata()->end_of_stream);
+  EXPECT_TRUE(frame->metadata().end_of_stream);
 }
 
 TEST(VideoFrame, CreateZeroInitializedFrame) {
@@ -326,7 +326,7 @@
 
   // Test basic properties.
   EXPECT_EQ(0, frame->timestamp().InMicroseconds());
-  EXPECT_FALSE(frame->metadata()->end_of_stream);
+  EXPECT_FALSE(frame->metadata().end_of_stream);
 
   // Test |frame| properties.
   EXPECT_EQ(PIXEL_FORMAT_I420, frame->format());
@@ -368,7 +368,7 @@
 
     gfx::Rect visible_rect(1, 1, 1, 1);
     gfx::Size natural_size = visible_rect.size();
-    wrapped_frame->metadata()->frame_duration = kFrameDuration;
+    wrapped_frame->metadata().frame_duration = kFrameDuration;
     frame = media::VideoFrame::WrapVideoFrame(
         wrapped_frame, wrapped_frame->format(), visible_rect, natural_size);
     wrapped_frame->AddDestructionObserver(
@@ -382,12 +382,12 @@
     EXPECT_EQ(natural_size, frame->natural_size());
 
     // Verify metadata was copied to the wrapped frame.
-    EXPECT_EQ(*frame->metadata()->frame_duration, kFrameDuration);
+    EXPECT_EQ(*frame->metadata().frame_duration, kFrameDuration);
 
     // Verify the metadata copy was a deep copy.
     wrapped_frame->clear_metadata();
-    EXPECT_NE(wrapped_frame->metadata()->frame_duration.has_value(),
-              frame->metadata()->frame_duration.has_value());
+    EXPECT_NE(wrapped_frame->metadata().frame_duration.has_value(),
+              frame->metadata().frame_duration.has_value());
   }
 
   // Verify that |wrapped_frame| outlives |frame|.
@@ -738,11 +738,11 @@
   VideoFrameMetadata empty_metadata;
 
   // Merging empty metadata into full metadata should be a no-op.
-  full_metadata.MergeMetadataFrom(&empty_metadata);
+  full_metadata.MergeMetadataFrom(empty_metadata);
   VerifyVideoFrameMetadataEquality(full_metadata, reference_metadata);
 
   // Merging full metadata into empty metadata should fill it up.
-  empty_metadata.MergeMetadataFrom(&full_metadata);
+  empty_metadata.MergeMetadataFrom(full_metadata);
   VerifyVideoFrameMetadataEquality(empty_metadata, reference_metadata);
 }
 
@@ -761,7 +761,7 @@
   partial_metadata.allow_overlay = false;
 
   // Merging partial metadata into full metadata partially override it.
-  full_metadata.MergeMetadataFrom(&partial_metadata);
+  full_metadata.MergeMetadataFrom(partial_metadata);
 
   EXPECT_EQ(partial_metadata.capture_update_rect, kTempRect);
   EXPECT_EQ(partial_metadata.reference_time, kTempTicks);
diff --git a/media/base/video_util.cc b/media/base/video_util.cc
index 86ed815..b3627f4 100644
--- a/media/base/video_util.cc
+++ b/media/base/video_util.cc
@@ -426,7 +426,7 @@
   }
 
   mapped_frame->set_color_space(video_frame->ColorSpace());
-  mapped_frame->metadata()->MergeMetadataFrom(video_frame->metadata());
+  mapped_frame->metadata().MergeMetadataFrom(video_frame->metadata());
 
   // Pass |video_frame| so that it outlives |mapped_frame| and the mapped buffer
   // is unmapped on destruction.
diff --git a/media/blink/webcontentdecryptionmodule_impl.cc b/media/blink/webcontentdecryptionmodule_impl.cc
index b267d9d7..b1f01ee 100644
--- a/media/blink/webcontentdecryptionmodule_impl.cc
+++ b/media/blink/webcontentdecryptionmodule_impl.cc
@@ -28,6 +28,7 @@
 
 namespace {
 
+const char kCreateSessionSessionTypeUMAName[] = "CreateSession.SessionType";
 const char kSetServerCertificateUMAName[] = "SetServerCertificate";
 const char kGetStatusForPolicyUMAName[] = "GetStatusForPolicy";
 
@@ -121,6 +122,9 @@
 std::unique_ptr<blink::WebContentDecryptionModuleSession>
 WebContentDecryptionModuleImpl::CreateSession(
     blink::WebEncryptedMediaSessionType session_type) {
+  base::UmaHistogramEnumeration(
+      adapter_->GetKeySystemUMAPrefix() + kCreateSessionSessionTypeUMAName,
+      session_type);
   return adapter_->CreateSession(session_type);
 }
 
diff --git a/media/blink/webmediaplayer_impl.cc b/media/blink/webmediaplayer_impl.cc
index d715ba3..9eeb556 100644
--- a/media/blink/webmediaplayer_impl.cc
+++ b/media/blink/webmediaplayer_impl.cc
@@ -1496,9 +1496,9 @@
   out_metadata->frame_id = frame->unique_id();
   out_metadata->visible_rect = frame->visible_rect();
   out_metadata->timestamp = frame->timestamp();
-  if (frame->metadata()->frame_duration.has_value()) {
+  if (frame->metadata().frame_duration.has_value()) {
     out_metadata->expected_timestamp =
-        frame->timestamp() + *frame->metadata()->frame_duration;
+        frame->timestamp() + *frame->metadata().frame_duration;
   };
   bool skip_possible = already_uploaded_id != -1;
   bool same_frame_id = frame->unique_id() == already_uploaded_id;
diff --git a/media/capture/content/android/thread_safe_capture_oracle.cc b/media/capture/content/android/thread_safe_capture_oracle.cc
index 5714497..af63b85 100644
--- a/media/capture/content/android/thread_safe_capture_oracle.cc
+++ b/media/capture/content/android/thread_safe_capture_oracle.cc
@@ -221,18 +221,18 @@
   if (!should_deliver_frame || !client_)
     return;
 
-  frame->metadata()->frame_rate = params_.requested_format.frame_rate;
-  frame->metadata()->capture_begin_time = capture->begin_time;
-  frame->metadata()->capture_end_time = base::TimeTicks::Now();
-  frame->metadata()->frame_duration = capture->frame_duration;
-  frame->metadata()->reference_time = reference_time;
+  frame->metadata().frame_rate = params_.requested_format.frame_rate;
+  frame->metadata().capture_begin_time = capture->begin_time;
+  frame->metadata().capture_end_time = base::TimeTicks::Now();
+  frame->metadata().frame_duration = capture->frame_duration;
+  frame->metadata().reference_time = reference_time;
 
   media::VideoCaptureFormat format(frame->coded_size(),
                                    params_.requested_format.frame_rate,
                                    frame->format());
   client_->OnIncomingCapturedBufferExt(
       std::move(capture->buffer), format, frame->ColorSpace(), reference_time,
-      frame->timestamp(), frame->visible_rect(), *frame->metadata());
+      frame->timestamp(), frame->visible_rect(), frame->metadata());
 }
 
 void ThreadSafeCaptureOracle::OnConsumerReportingUtilization(
diff --git a/media/capture/video/chromeos/video_capture_jpeg_decoder_impl.cc b/media/capture/video/chromeos/video_capture_jpeg_decoder_impl.cc
index feca687..8a5cc12 100644
--- a/media/capture/video/chromeos/video_capture_jpeg_decoder_impl.cc
+++ b/media/capture/video/chromeos/video_capture_jpeg_decoder_impl.cc
@@ -131,8 +131,8 @@
   out_frame->BackWithOwnedSharedMemory(std::move(out_region),
                                        std::move(out_mapping));
 
-  out_frame->metadata()->frame_rate = frame_format.frame_rate;
-  out_frame->metadata()->reference_time = reference_time;
+  out_frame->metadata().frame_rate = frame_format.frame_rate;
+  out_frame->metadata().reference_time = reference_time;
 
   media::mojom::VideoFrameInfoPtr out_frame_info =
       media::mojom::VideoFrameInfo::New();
@@ -140,7 +140,7 @@
   out_frame_info->pixel_format = media::PIXEL_FORMAT_I420;
   out_frame_info->coded_size = dimensions;
   out_frame_info->visible_rect = gfx::Rect(dimensions);
-  out_frame_info->metadata = *(out_frame->metadata());
+  out_frame_info->metadata = out_frame->metadata();
   out_frame_info->color_space = out_frame->ColorSpace();
 
   {
diff --git a/media/cast/sender/external_video_encoder.cc b/media/cast/sender/external_video_encoder.cc
index 17a7d95..f9688c6 100644
--- a/media/cast/sender/external_video_encoder.cc
+++ b/media/cast/sender/external_video_encoder.cc
@@ -326,7 +326,7 @@
       // If FRAME_DURATION metadata was provided in the source VideoFrame,
       // compute the utilization metrics.
       base::TimeDelta frame_duration =
-          request.video_frame->metadata()->frame_duration.value_or(
+          request.video_frame->metadata().frame_duration.value_or(
               base::TimeDelta());
       if (frame_duration > base::TimeDelta()) {
         // Compute encoder utilization in terms of the number of frames in
diff --git a/media/cast/sender/performance_metrics_overlay.cc b/media/cast/sender/performance_metrics_overlay.cc
index 49328234..0bc1e97 100644
--- a/media/cast/sender/performance_metrics_overlay.cc
+++ b/media/cast/sender/performance_metrics_overlay.cc
@@ -265,7 +265,7 @@
       memcpy(dst, src, bytes_per_row);
     }
   }
-  frame->metadata()->MergeMetadataFrom(source->metadata());
+  frame->metadata().MergeMetadataFrom(source->metadata());
   // Important: After all consumers are done with the frame, copy-back the
   // changed/new metadata to the source frame, as it contains feedback signals
   // that need to propagate back up the video stack. The destruction callback
@@ -273,18 +273,18 @@
   // the source frame has the right metadata before its destruction observers
   // are invoked.
   frame->AddDestructionObserver(base::BindOnce(
-      [](const VideoFrameMetadata* sent_frame_metadata,
+      [](const VideoFrameMetadata& sent_frame_metadata,
          scoped_refptr<VideoFrame> source_frame) {
-        source_frame->set_metadata(*sent_frame_metadata);
+        source_frame->set_metadata(sent_frame_metadata);
       },
       frame->metadata(), std::move(source)));
 
   // Line 3: Frame duration, resolution, and timestamp.
   int frame_duration_ms = 0;
   int frame_duration_ms_frac = 0;
-  if (frame->metadata()->frame_duration.has_value()) {
+  if (frame->metadata().frame_duration.has_value()) {
     const int decimilliseconds = base::saturated_cast<int>(
-        frame->metadata()->frame_duration->InMicroseconds() / 100.0 + 0.5);
+        frame->metadata().frame_duration->InMicroseconds() / 100.0 + 0.5);
     frame_duration_ms = decimilliseconds / 10;
     frame_duration_ms_frac = decimilliseconds % 10;
   }
@@ -309,11 +309,11 @@
   // Line 2: Capture duration, target playout delay, low-latency mode, and
   // target bitrate.
   int capture_duration_ms = 0;
-  if (frame->metadata()->capture_begin_time &&
-      frame->metadata()->capture_end_time) {
+  if (frame->metadata().capture_begin_time &&
+      frame->metadata().capture_end_time) {
     capture_duration_ms =
-        base::saturated_cast<int>((*frame->metadata()->capture_end_time -
-                                   *frame->metadata()->capture_begin_time)
+        base::saturated_cast<int>((*frame->metadata().capture_end_time -
+                                   *frame->metadata().capture_begin_time)
                                       .InMillisecondsF() +
                                   0.5);
   }
diff --git a/media/cast/sender/video_sender.cc b/media/cast/sender/video_sender.cc
index a2960f8..aa8bacf 100644
--- a/media/cast/sender/video_sender.cc
+++ b/media/cast/sender/video_sender.cc
@@ -63,11 +63,10 @@
   capture_end_event->width = video_frame.visible_rect().width();
   capture_end_event->height = video_frame.visible_rect().height();
 
-  if (video_frame.metadata()->capture_begin_time.has_value() &&
-      video_frame.metadata()->capture_end_time.has_value()) {
-    capture_begin_event->timestamp =
-        *video_frame.metadata()->capture_begin_time;
-    capture_end_event->timestamp = *video_frame.metadata()->capture_end_time;
+  if (video_frame.metadata().capture_begin_time.has_value() &&
+      video_frame.metadata().capture_end_time.has_value()) {
+    capture_begin_event->timestamp = *video_frame.metadata().capture_begin_time;
+    capture_end_event->timestamp = *video_frame.metadata().capture_end_time;
   } else {
     // The frame capture timestamps were not provided by the video capture
     // source.  Simply log the events as happening right now.
@@ -150,7 +149,7 @@
                        "rtp_timestamp", rtp_timestamp.lower_32_bits());
 
   {
-    bool new_low_latency_mode = video_frame->metadata()->interactive_content;
+    bool new_low_latency_mode = video_frame->metadata().interactive_content;
     if (new_low_latency_mode && !low_latency_mode_) {
       VLOG(1) << "Interactive mode playout time " << min_playout_delay_;
       playout_delay_change_cb_.Run(min_playout_delay_);
diff --git a/media/cast/sender/vp8_encoder.cc b/media/cast/sender/vp8_encoder.cc
index 70fab74..93348f7 100644
--- a/media/cast/sender/vp8_encoder.cc
+++ b/media/cast/sender/vp8_encoder.cc
@@ -232,7 +232,7 @@
       base::TimeDelta::FromSecondsD(static_cast<double>(kRestartFramePeriods) /
                                         cast_config_.max_frame_rate);
   base::TimeDelta predicted_frame_duration =
-      video_frame->metadata()->frame_duration.value_or(base::TimeDelta());
+      video_frame->metadata().frame_duration.value_or(base::TimeDelta());
   if (predicted_frame_duration <= base::TimeDelta()) {
     // The source of the video frame did not provide the frame duration.  Use
     // the actual amount of time between the current and previous frame as a
diff --git a/media/cdm/cdm_adapter.cc b/media/cdm/cdm_adapter.cc
index af83941..63a4aeb 100644
--- a/media/cdm/cdm_adapter.cc
+++ b/media/cdm/cdm_adapter.cc
@@ -617,7 +617,7 @@
     return;
   }
 
-  decoded_frame->metadata()->protected_video = is_video_encrypted_;
+  decoded_frame->metadata().protected_video = is_video_encrypted_;
 
   video_decode_cb.Run(Decryptor::kSuccess, decoded_frame);
 }
diff --git a/media/cdm/library_cdm/clear_key_cdm/cdm_video_decoder.cc b/media/cdm/library_cdm/clear_key_cdm/cdm_video_decoder.cc
index c1fc886..8ebe8cb2 100644
--- a/media/cdm/library_cdm/clear_key_cdm/cdm_video_decoder.cc
+++ b/media/cdm/library_cdm/clear_key_cdm/cdm_video_decoder.cc
@@ -257,7 +257,7 @@
 
   void OnVideoFrameReady(scoped_refptr<VideoFrame> video_frame) {
     // Do not queue EOS frames, which is not needed.
-    if (video_frame->metadata()->end_of_stream)
+    if (video_frame->metadata().end_of_stream)
       return;
 
     decoded_video_frames_.push(std::move(video_frame));
diff --git a/media/filters/dav1d_video_decoder.cc b/media/filters/dav1d_video_decoder.cc
index bb31801e..9776413 100644
--- a/media/filters/dav1d_video_decoder.cc
+++ b/media/filters/dav1d_video_decoder.cc
@@ -368,7 +368,7 @@
       color_space = config_.color_space_info();
 
     frame->set_color_space(color_space.ToGfxColorSpace());
-    frame->metadata()->power_efficient = false;
+    frame->metadata().power_efficient = false;
     frame->set_hdr_metadata(config_.hdr_metadata());
 
     // When we use bind mode, our image data is dependent on the Dav1dPicture,
diff --git a/media/filters/dav1d_video_decoder_unittest.cc b/media/filters/dav1d_video_decoder_unittest.cc
index 19572a0..0711b2f 100644
--- a/media/filters/dav1d_video_decoder_unittest.cc
+++ b/media/filters/dav1d_video_decoder_unittest.cc
@@ -169,7 +169,7 @@
   }
 
   void FrameReady(scoped_refptr<VideoFrame> frame) {
-    DCHECK(!frame->metadata()->end_of_stream);
+    DCHECK(!frame->metadata().end_of_stream);
     output_frames_.push_back(std::move(frame));
   }
 
diff --git a/media/filters/decoder_stream_traits.cc b/media/filters/decoder_stream_traits.cc
index c9e20bf..0670f28 100644
--- a/media/filters/decoder_stream_traits.cc
+++ b/media/filters/decoder_stream_traits.cc
@@ -234,8 +234,8 @@
     return PostDecodeAction::DELIVER;
 
   // Add a timestamp here to enable buffering delay measurements down the line.
-  buffer->metadata()->decode_begin_time = it->second.decode_begin_time;
-  buffer->metadata()->decode_end_time = base::TimeTicks::Now();
+  buffer->metadata().decode_begin_time = it->second.decode_begin_time;
+  buffer->metadata().decode_end_time = base::TimeTicks::Now();
 
   auto action = it->second.should_drop ? PostDecodeAction::DROP
                                        : PostDecodeAction::DELIVER;
@@ -243,7 +243,7 @@
   // Provide duration information to help the rendering algorithm on the very
   // first and very last frames.
   if (it->second.duration != kNoTimestamp)
-    buffer->metadata()->frame_duration = it->second.duration;
+    buffer->metadata().frame_duration = it->second.duration;
 
   // We erase from the beginning onward to our target frame since frames should
   // be returned in presentation order. It's possible to accumulate entries in
@@ -255,12 +255,12 @@
 
 void DecoderStreamTraits<DemuxerStream::VIDEO>::OnOutputReady(
     OutputType* buffer) {
-  if (!buffer->metadata()->decode_begin_time.has_value())
+  if (!buffer->metadata().decode_begin_time.has_value())
     return;
 
   // Tag buffer with elapsed time since creation.
-  buffer->metadata()->processing_time =
-      base::TimeTicks::Now() - *buffer->metadata()->decode_begin_time;
+  buffer->metadata().processing_time =
+      base::TimeTicks::Now() - *buffer->metadata().decode_begin_time;
 }
 
 }  // namespace media
diff --git a/media/filters/decrypting_video_decoder.cc b/media/filters/decrypting_video_decoder.cc
index 43601b5..00455e3 100644
--- a/media/filters/decrypting_video_decoder.cc
+++ b/media/filters/decrypting_video_decoder.cc
@@ -298,7 +298,7 @@
   CHECK(frame);
 
   // Frame returned with kSuccess should not be an end-of-stream frame.
-  DCHECK(!frame->metadata()->end_of_stream);
+  DCHECK(!frame->metadata().end_of_stream);
 
   // If color space is not set, use the color space in the |config_|.
   if (!frame->ColorSpace().IsValid()) {
diff --git a/media/filters/fake_video_decoder_unittest.cc b/media/filters/fake_video_decoder_unittest.cc
index 4913015..58baa49 100644
--- a/media/filters/fake_video_decoder_unittest.cc
+++ b/media/filters/fake_video_decoder_unittest.cc
@@ -91,7 +91,7 @@
   }
 
   void FrameReady(scoped_refptr<VideoFrame> frame) {
-    DCHECK(!frame->metadata()->end_of_stream);
+    DCHECK(!frame->metadata().end_of_stream);
     last_decoded_frame_ = std::move(frame);
     num_decoded_frames_++;
   }
diff --git a/media/filters/ffmpeg_video_decoder.cc b/media/filters/ffmpeg_video_decoder.cc
index d703189..0b7d95e 100644
--- a/media/filters/ffmpeg_video_decoder.cc
+++ b/media/filters/ffmpeg_video_decoder.cc
@@ -359,7 +359,7 @@
       reinterpret_cast<VideoFrame*>(av_buffer_get_opaque(frame->buf[0]));
   video_frame->set_timestamp(
       base::TimeDelta::FromMicroseconds(frame->reordered_opaque));
-  video_frame->metadata()->power_efficient = false;
+  video_frame->metadata().power_efficient = false;
   output_cb_.Run(video_frame);
   return true;
 }
diff --git a/media/filters/ffmpeg_video_decoder_unittest.cc b/media/filters/ffmpeg_video_decoder_unittest.cc
index 599fb12..3a07a38 100644
--- a/media/filters/ffmpeg_video_decoder_unittest.cc
+++ b/media/filters/ffmpeg_video_decoder_unittest.cc
@@ -197,7 +197,7 @@
   }
 
   void FrameReady(scoped_refptr<VideoFrame> frame) {
-    DCHECK(!frame->metadata()->end_of_stream);
+    DCHECK(!frame->metadata().end_of_stream);
     output_frames_.push_back(std::move(frame));
   }
 
diff --git a/media/filters/fuchsia/fuchsia_video_decoder.cc b/media/filters/fuchsia/fuchsia_video_decoder.cc
index dbbdbe9..eb6c97d 100644
--- a/media/filters/fuchsia/fuchsia_video_decoder.cc
+++ b/media/filters/fuchsia/fuchsia_video_decoder.cc
@@ -123,7 +123,7 @@
         coded_size, visible_rect, natural_size, timestamp);
 
     // Request a fence we'll wait on before reusing the buffer.
-    frame->metadata()->read_lock_fences_enabled = true;
+    frame->metadata().read_lock_fences_enabled = true;
 
     return frame;
   }
@@ -949,11 +949,11 @@
   // codec may still decode on hardware even when |enable_sw_decoding_| is set
   // (i.e. power_efficient flag would not be set correctly in that case). It
   // doesn't matter because software decoders can be enabled only for tests.
-  frame->metadata()->power_efficient = !enable_sw_decoding_;
+  frame->metadata().power_efficient = !enable_sw_decoding_;
 
   // Allow this video frame to be promoted as an overlay, because it was
   // registered with an ImagePipe.
-  frame->metadata()->allow_overlay = use_overlays_for_video_;
+  frame->metadata().allow_overlay = use_overlays_for_video_;
 
   output_cb_.Run(std::move(frame));
 }
diff --git a/media/filters/gav1_video_decoder.cc b/media/filters/gav1_video_decoder.cc
index 8e0e69c..900e20e 100644
--- a/media/filters/gav1_video_decoder.cc
+++ b/media/filters/gav1_video_decoder.cc
@@ -224,7 +224,7 @@
     color_space = container_color_space;
 
   frame->set_color_space(color_space.ToGfxColorSpace());
-  frame->metadata()->power_efficient = false;
+  frame->metadata().power_efficient = false;
 
   return frame;
 }
diff --git a/media/filters/gav1_video_decoder_unittest.cc b/media/filters/gav1_video_decoder_unittest.cc
index 4169f61..33ccf13e 100644
--- a/media/filters/gav1_video_decoder_unittest.cc
+++ b/media/filters/gav1_video_decoder_unittest.cc
@@ -194,7 +194,7 @@
   }
 
   void FrameReady(scoped_refptr<VideoFrame> frame) {
-    DCHECK(!frame->metadata()->end_of_stream);
+    DCHECK(!frame->metadata().end_of_stream);
     output_frames_.push_back(std::move(frame));
   }
 
diff --git a/media/filters/video_decoder_stream_unittest.cc b/media/filters/video_decoder_stream_unittest.cc
index 617028d..667afda 100644
--- a/media/filters/video_decoder_stream_unittest.cc
+++ b/media/filters/video_decoder_stream_unittest.cc
@@ -358,9 +358,8 @@
     DCHECK(pending_read_);
     frame_read_ = frame;
     last_read_status_ = status;
-    if (frame && !frame->metadata()->end_of_stream) {
-      EXPECT_EQ(*frame->metadata()->frame_duration,
-                demuxer_stream_->duration());
+    if (frame && !frame->metadata().end_of_stream) {
+      EXPECT_EQ(*frame->metadata().frame_duration, demuxer_stream_->duration());
 
       num_decoded_frames_++;
     }
@@ -390,7 +389,7 @@
   void ReadAllFrames(int expected_decoded_frames) {
     do {
       ReadOneFrame();
-    } while (frame_read_.get() && !frame_read_->metadata()->end_of_stream);
+    } while (frame_read_.get() && !frame_read_->metadata().end_of_stream);
 
     DCHECK_EQ(expected_decoded_frames, num_decoded_frames_);
   }
@@ -691,17 +690,17 @@
 
   EXPECT_TRUE(frame_read_);
 
-  auto* metadata = frame_read_->metadata();
+  const VideoFrameMetadata& metadata = frame_read_->metadata();
 
   // Verify the decoding metadata is accurate.
-  EXPECT_EQ(*metadata->decode_end_time - *metadata->decode_begin_time,
+  EXPECT_EQ(*metadata.decode_end_time - *metadata.decode_begin_time,
             kDecodeDelay);
 
   // Verify the processing metadata is accurate.
   const base::TimeDelta expected_processing_time =
       GetParam().has_prepare ? (kDecodeDelay + kPrepareDelay) : kDecodeDelay;
 
-  EXPECT_EQ(*metadata->processing_time, expected_processing_time);
+  EXPECT_EQ(*metadata.processing_time, expected_processing_time);
 }
 
 TEST_P(VideoDecoderStreamTest, Read_BlockedDemuxer) {
@@ -818,7 +817,7 @@
 
   // The read output should indicate end of stream.
   ASSERT_TRUE(frame_read_.get());
-  EXPECT_TRUE(frame_read_->metadata()->end_of_stream);
+  EXPECT_TRUE(frame_read_->metadata().end_of_stream);
 }
 
 TEST_P(VideoDecoderStreamTest, Read_DemuxerStreamReadError) {
@@ -1066,14 +1065,14 @@
   // A frame should have been emitted.
   EXPECT_FALSE(pending_read_);
   EXPECT_EQ(last_read_status_, VideoDecoderStream::OK);
-  EXPECT_FALSE(frame_read_->metadata()->end_of_stream);
+  EXPECT_FALSE(frame_read_->metadata().end_of_stream);
   EXPECT_GT(decoder_->total_bytes_decoded(), 0);
 
   ReadOneFrame();
 
   EXPECT_FALSE(pending_read_);
   EXPECT_EQ(0, video_decoder_stream_->get_fallback_buffers_size_for_testing());
-  EXPECT_TRUE(frame_read_->metadata()->end_of_stream);
+  EXPECT_TRUE(frame_read_->metadata().end_of_stream);
 }
 
 TEST_P(VideoDecoderStreamTest,
diff --git a/media/filters/video_renderer_algorithm.cc b/media/filters/video_renderer_algorithm.cc
index 0d33804..df19cd7 100644
--- a/media/filters/video_renderer_algorithm.cc
+++ b/media/filters/video_renderer_algorithm.cc
@@ -322,12 +322,12 @@
 
 void VideoRendererAlgorithm::EnqueueFrame(scoped_refptr<VideoFrame> frame) {
   DCHECK(frame);
-  DCHECK(!frame->metadata()->end_of_stream);
+  DCHECK(!frame->metadata().end_of_stream);
 
   // Note: Not all frames have duration. E.g., this class is used with WebRTC
   // which does not provide duration information for its frames.
   base::TimeDelta metadata_frame_duration =
-      frame->metadata()->frame_duration.value_or(base::TimeDelta());
+      frame->metadata().frame_duration.value_or(base::TimeDelta());
   auto timestamp = frame->timestamp();
   ReadyFrame ready_frame(std::move(frame));
   auto it = frame_queue_.empty()
@@ -394,7 +394,7 @@
     wallclock_duration = ready_frame.end_time - ready_frame.start_time;
   }
 
-  ready_frame.frame->metadata()->wallclock_frame_duration = wallclock_duration;
+  ready_frame.frame->metadata().wallclock_frame_duration = wallclock_duration;
 
   // The vast majority of cases should always append to the back, but in rare
   // circumstance we get out of order timestamps, http://crbug.com/386551.
@@ -476,7 +476,7 @@
   {
     const auto& last_frame = frame_queue_.back().frame;
     base::TimeDelta metadata_frame_duration =
-        last_frame->metadata()->frame_duration.value_or(base::TimeDelta());
+        last_frame->metadata().frame_duration.value_or(base::TimeDelta());
     if (metadata_frame_duration > base::TimeDelta()) {
       have_metadata_duration = true;
       media_timestamps.push_back(last_frame->timestamp() +
diff --git a/media/filters/video_renderer_algorithm_unittest.cc b/media/filters/video_renderer_algorithm_unittest.cc
index 26d919f..32d387493 100644
--- a/media/filters/video_renderer_algorithm_unittest.cc
+++ b/media/filters/video_renderer_algorithm_unittest.cc
@@ -1212,7 +1212,7 @@
   // as effective since we know the duration of it. It is not removed since we
   // only have one frame in the queue though.
   auto frame = CreateFrame(tg.interval(0));
-  frame->metadata()->frame_duration = tg.interval(1);
+  frame->metadata().frame_duration = tg.interval(1);
   algorithm_.EnqueueFrame(frame);
   ASSERT_EQ(0u, algorithm_.RemoveExpiredFrames(tg.current() + tg.interval(3)));
   EXPECT_EQ(0u, EffectiveFramesQueued());
@@ -1584,7 +1584,7 @@
   TickGenerator tg(tick_clock_->NowTicks(), 50);
 
   auto frame = CreateFrame(kInfiniteDuration);
-  frame->metadata()->frame_duration = tg.interval(1);
+  frame->metadata().frame_duration = tg.interval(1);
   algorithm_.EnqueueFrame(frame);
 
   // This should not crash or fail.
@@ -1597,7 +1597,7 @@
   TickGenerator tg(tick_clock_->NowTicks(), 50);
 
   auto frame = CreateFrame(tg.interval(0));
-  frame->metadata()->frame_duration = tg.interval(1);
+  frame->metadata().frame_duration = tg.interval(1);
   algorithm_.EnqueueFrame(frame);
 
   // This should not crash or fail.
@@ -1609,7 +1609,7 @@
   constexpr base::TimeDelta kLongDuration = base::TimeDelta::FromSeconds(3);
   for (int i = 1; i < 4; ++i) {
     frame = CreateFrame(tg.interval(i));
-    frame->metadata()->frame_duration = i == 3 ? kLongDuration : tg.interval(1);
+    frame->metadata().frame_duration = i == 3 ? kLongDuration : tg.interval(1);
     algorithm_.EnqueueFrame(frame);
   }
 
@@ -1631,7 +1631,7 @@
 
   for (int i = 0; i < frame_count; i++) {
     auto frame = CreateFrame(tg.interval(i));
-    frame->metadata()->frame_duration = tg.interval(1);
+    frame->metadata().frame_duration = tg.interval(1);
     algorithm_.EnqueueFrame(frame);
   }
 
@@ -1641,7 +1641,7 @@
 
     SCOPED_TRACE(base::StringPrintf("Frame #%d", i));
 
-    EXPECT_EQ(*frame->metadata()->wallclock_frame_duration, intended_duration);
+    EXPECT_EQ(*frame->metadata().wallclock_frame_duration, intended_duration);
     EXPECT_EQ(algorithm_.average_frame_duration(), intended_duration);
   }
 }
diff --git a/media/filters/vpx_video_decoder.cc b/media/filters/vpx_video_decoder.cc
index 748226ba..8930b7b 100644
--- a/media/filters/vpx_video_decoder.cc
+++ b/media/filters/vpx_video_decoder.cc
@@ -182,7 +182,7 @@
   // We might get a successful VpxDecode but not a frame if only a partial
   // decode happened.
   if (video_frame) {
-    video_frame->metadata()->power_efficient = false;
+    video_frame->metadata().power_efficient = false;
     output_cb_.Run(video_frame);
   }
 
diff --git a/media/filters/vpx_video_decoder_unittest.cc b/media/filters/vpx_video_decoder_unittest.cc
index be6824bf..0eab443 100644
--- a/media/filters/vpx_video_decoder_unittest.cc
+++ b/media/filters/vpx_video_decoder_unittest.cc
@@ -160,7 +160,7 @@
   }
 
   void FrameReady(scoped_refptr<VideoFrame> frame) {
-    DCHECK(!frame->metadata()->end_of_stream);
+    DCHECK(!frame->metadata().end_of_stream);
     output_frames_.push_back(std::move(frame));
   }
 
diff --git a/media/gpu/android/media_codec_video_decoder.cc b/media/gpu/android/media_codec_video_decoder.cc
index c61cf8d..3bae356 100644
--- a/media/gpu/android/media_codec_video_decoder.cc
+++ b/media/gpu/android/media_codec_video_decoder.cc
@@ -1021,7 +1021,7 @@
   if (reset_generation == reset_generation_) {
     // TODO(liberato): We might actually have a SW decoder.  Consider setting
     // this to false if so, especially for higher bitrates.
-    frame->metadata()->power_efficient = true;
+    frame->metadata().power_efficient = true;
     output_cb_.Run(std::move(frame));
   }
 }
diff --git a/media/gpu/android/media_codec_video_decoder_unittest.cc b/media/gpu/android/media_codec_video_decoder_unittest.cc
index e76456ae..6db564d 100644
--- a/media/gpu/android/media_codec_video_decoder_unittest.cc
+++ b/media/gpu/android/media_codec_video_decoder_unittest.cc
@@ -918,7 +918,7 @@
   base::RunLoop().RunUntilIdle();
 
   EXPECT_TRUE(!!most_recent_frame_);
-  EXPECT_TRUE(most_recent_frame_->metadata()->power_efficient);
+  EXPECT_TRUE(most_recent_frame_->metadata().power_efficient);
 }
 
 TEST_P(MediaCodecVideoDecoderH264Test, CsdIsIncludedInCodecConfig) {
diff --git a/media/gpu/android/video_frame_factory_impl.cc b/media/gpu/android/video_frame_factory_impl.cc
index 6531d80..8be7a01f 100644
--- a/media/gpu/android/video_frame_factory_impl.cc
+++ b/media/gpu/android/video_frame_factory_impl.cc
@@ -301,7 +301,7 @@
     std::move(output_cb).Run(nullptr);
     return;
   }
-  frame->metadata()->copy_mode = copy_mode;
+  frame->metadata().copy_mode = copy_mode;
   const bool is_surface_control =
       overlay_mode == OverlayMode::kSurfaceControlSecure ||
       overlay_mode == OverlayMode::kSurfaceControlInsecure;
@@ -319,9 +319,9 @@
     allow_overlay = !is_texture_owner_backed || wants_promotion_hints;
   }
 
-  frame->metadata()->allow_overlay = allow_overlay;
-  frame->metadata()->wants_promotion_hint = wants_promotion_hints;
-  frame->metadata()->texture_owner = is_texture_owner_backed;
+  frame->metadata().allow_overlay = allow_overlay;
+  frame->metadata().wants_promotion_hint = wants_promotion_hints;
+  frame->metadata().texture_owner = is_texture_owner_backed;
 
   // TODO(liberato): if this is run via being dropped, then it would be nice
   // to find that out rather than treating the image as unused.  If the renderer
diff --git a/media/gpu/av1_decoder.cc b/media/gpu/av1_decoder.cc
index 486b0582..49478d7f 100644
--- a/media/gpu/av1_decoder.cc
+++ b/media/gpu/av1_decoder.cc
@@ -98,23 +98,6 @@
   state_.reset();
 }
 
-bool AV1Decoder::HasNewSequenceHeader() const {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  DCHECK(parser_);
-  const auto& obu_headers = parser_->obu_headers();
-  const bool has_sequence_header =
-      std::find_if(obu_headers.begin(), obu_headers.end(),
-                   [](const auto& obu_header) {
-                     return obu_header.type == libgav1::kObuSequenceHeader;
-                   }) != obu_headers.end();
-  if (!has_sequence_header)
-    return false;
-  if (!current_sequence_header_)
-    return true;
-  return parser_->sequence_header().ParametersChanged(
-      *current_sequence_header_);
-}
-
 bool AV1Decoder::Flush() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DVLOG(2) << "Decoder flush";
@@ -208,9 +191,7 @@
 
       current_frame_header_ = parser_->frame_header();
       // Detects if a new coded video sequence is starting.
-      // TODO(b/171853869): Replace HasNewSequenceHeader() with whatever
-      // libgav1::ObuParser provides for more than one sequence headers case.
-      if (HasNewSequenceHeader()) {
+      if (parser_->sequence_header_changed()) {
         // TODO(b/171853869): Remove this check once libgav1::ObuParser does
         // this check.
         if (current_frame_header_->frame_type != libgav1::kFrameKey ||
diff --git a/media/gpu/av1_decoder.h b/media/gpu/av1_decoder.h
index dc67b2a..6be916a4 100644
--- a/media/gpu/av1_decoder.h
+++ b/media/gpu/av1_decoder.h
@@ -112,8 +112,6 @@
   size_t GetNumReferenceFrames() const override;
 
  private:
-  // Returns whether the current stream contains a new OBU sequence header.
-  bool HasNewSequenceHeader() const;
   bool DecodeAndOutputPicture(
       scoped_refptr<AV1Picture> pic,
       const libgav1::Vector<libgav1::TileBuffer>& tile_buffers);
diff --git a/media/gpu/chromeos/mailbox_video_frame_converter.cc b/media/gpu/chromeos/mailbox_video_frame_converter.cc
index 515146c..c7ce5363 100644
--- a/media/gpu/chromeos/mailbox_video_frame_converter.cc
+++ b/media/gpu/chromeos/mailbox_video_frame_converter.cc
@@ -237,8 +237,8 @@
       GetRectSizeFromOrigin(frame->visible_rect()), frame->visible_rect(),
       frame->natural_size(), frame->timestamp());
   mailbox_frame->set_color_space(frame->ColorSpace());
-  mailbox_frame->set_metadata(*(frame->metadata()));
-  mailbox_frame->metadata()->read_lock_fences_enabled = true;
+  mailbox_frame->set_metadata(frame->metadata());
+  mailbox_frame->metadata().read_lock_fences_enabled = true;
 
   output_cb_.Run(mailbox_frame);
 }
diff --git a/media/gpu/chromeos/platform_video_frame_pool.cc b/media/gpu/chromeos/platform_video_frame_pool.cc
index 5c1cdd7..401aa08 100644
--- a/media/gpu/chromeos/platform_video_frame_pool.cc
+++ b/media/gpu/chromeos/platform_video_frame_pool.cc
@@ -267,7 +267,7 @@
 
   if (IsSameFormat_Locked(origin_frame->format(), origin_frame->coded_size(),
                           origin_frame->visible_rect(),
-                          origin_frame->metadata()->hw_protected)) {
+                          origin_frame->metadata().hw_protected)) {
     InsertFreeFrame_Locked(std::move(origin_frame));
   }
 
diff --git a/media/gpu/chromeos/vd_video_decode_accelerator.cc b/media/gpu/chromeos/vd_video_decode_accelerator.cc
index 4b5ca97..e582898f 100644
--- a/media/gpu/chromeos/vd_video_decode_accelerator.cc
+++ b/media/gpu/chromeos/vd_video_decode_accelerator.cc
@@ -408,10 +408,9 @@
   }
   int32_t picture_buffer_id = it->second;
   int32_t bitstream_id = FakeTimestampToBitstreamId(frame.timestamp());
-  bool allow_overlay = frame.metadata()->allow_overlay;
   return base::make_optional(Picture(picture_buffer_id, bitstream_id,
                                      frame.visible_rect(), frame.ColorSpace(),
-                                     allow_overlay));
+                                     frame.metadata().allow_overlay));
 }
 
 // static
diff --git a/media/gpu/chromeos/video_decoder_pipeline.cc b/media/gpu/chromeos/video_decoder_pipeline.cc
index 3f5e486..f8777f9 100644
--- a/media/gpu/chromeos/video_decoder_pipeline.cc
+++ b/media/gpu/chromeos/video_decoder_pipeline.cc
@@ -432,9 +432,9 @@
   }
 
   // Flag that the video frame is capable of being put in an overlay.
-  frame->metadata()->allow_overlay = true;
+  frame->metadata().allow_overlay = true;
   // Flag that the video frame was decoded in a power efficient way.
-  frame->metadata()->power_efficient = true;
+  frame->metadata().power_efficient = true;
 
   // MojoVideoDecoderService expects the |output_cb_| to be called on the client
   // task runner, even though media::VideoDecoder states frames should be output
diff --git a/media/gpu/ipc/service/picture_buffer_manager.cc b/media/gpu/ipc/service/picture_buffer_manager.cc
index 04981b8e..5eed6cb 100644
--- a/media/gpu/ipc/service/picture_buffer_manager.cc
+++ b/media/gpu/ipc/service/picture_buffer_manager.cc
@@ -243,12 +243,12 @@
 
     frame->set_color_space(picture.color_space());
 
-    frame->metadata()->allow_overlay = picture.allow_overlay();
-    frame->metadata()->read_lock_fences_enabled =
+    frame->metadata().allow_overlay = picture.allow_overlay();
+    frame->metadata().read_lock_fences_enabled =
         picture.read_lock_fences_enabled();
 
     // TODO(sandersd): Provide an API for VDAs to control this.
-    frame->metadata()->power_efficient = true;
+    frame->metadata().power_efficient = true;
 
     return frame;
   }
diff --git a/media/gpu/mac/vt_video_encode_accelerator_mac.cc b/media/gpu/mac/vt_video_encode_accelerator_mac.cc
index 40301bf..5b7a0205 100644
--- a/media/gpu/mac/vt_video_encode_accelerator_mac.cc
+++ b/media/gpu/mac/vt_video_encode_accelerator_mac.cc
@@ -287,7 +287,7 @@
           force_keyframe ? kCFBooleanTrue : kCFBooleanFalse);
 
   base::TimeTicks ref_time =
-      frame->metadata()->reference_time.value_or(base::TimeTicks::Now());
+      frame->metadata().reference_time.value_or(base::TimeTicks::Now());
   auto timestamp_cm =
       CMTimeMake(frame->timestamp().InMicroseconds(), USEC_PER_SEC);
   // Wrap information we'll need after the frame is encoded in a heap object.
diff --git a/media/gpu/test/video_player/frame_renderer_dummy.cc b/media/gpu/test/video_player/frame_renderer_dummy.cc
index e024252..75634de 100644
--- a/media/gpu/test/video_player/frame_renderer_dummy.cc
+++ b/media/gpu/test/video_player/frame_renderer_dummy.cc
@@ -94,7 +94,7 @@
   // immediately released for reuse. Only frames dropped due to slow decoding
   // will be counted as dropped frames.
   base::AutoLock auto_lock(renderer_lock_);
-  if (!video_frame->metadata()->end_of_stream) {
+  if (!video_frame->metadata().end_of_stream) {
     if (frames_to_drop_rendering_slow_ > 0) {
       frames_to_drop_rendering_slow_--;
       return;
@@ -174,7 +174,7 @@
     frames_to_drop_decoding_slow_++;
   }
 
-  if (!active_frame_->metadata()->end_of_stream) {
+  if (!active_frame_->metadata().end_of_stream) {
     ScheduleNextRenderFrameTask();
   } else {
     next_frame_time_ = base::TimeTicks();
diff --git a/media/gpu/test/video_player/frame_renderer_thumbnail.cc b/media/gpu/test/video_player/frame_renderer_thumbnail.cc
index fdfea6b..4e3eabf 100644
--- a/media/gpu/test/video_player/frame_renderer_thumbnail.cc
+++ b/media/gpu/test/video_player/frame_renderer_thumbnail.cc
@@ -199,7 +199,7 @@
     scoped_refptr<VideoFrame> video_frame) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(renderer_sequence_checker_);
 
-  if (video_frame->metadata()->end_of_stream)
+  if (video_frame->metadata().end_of_stream)
     return;
 
   if (!renderer_task_runner_)
diff --git a/media/gpu/test/video_player/test_vda_video_decoder.cc b/media/gpu/test/video_player/test_vda_video_decoder.cc
index 5db0935..31442b6 100644
--- a/media/gpu/test/video_player/test_vda_video_decoder.cc
+++ b/media/gpu/test/video_player/test_vda_video_decoder.cc
@@ -358,7 +358,7 @@
   DCHECK(wrapped_video_frame);
 
   // Flag that the video frame was decoded in a power efficient way.
-  wrapped_video_frame->metadata()->power_efficient = true;
+  wrapped_video_frame->metadata().power_efficient = true;
 
   // It's important to bind the original video frame to the destruction callback
   // of the wrapped frame, to avoid deleting it before rendering of the wrapped
diff --git a/media/gpu/test/video_player/video_decoder_client.cc b/media/gpu/test/video_player/video_decoder_client.cc
index da475de..621eae4 100644
--- a/media/gpu/test/video_player/video_decoder_client.cc
+++ b/media/gpu/test/video_player/video_decoder_client.cc
@@ -388,7 +388,7 @@
 
 void VideoDecoderClient::FrameReadyTask(scoped_refptr<VideoFrame> video_frame) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(decoder_client_sequence_checker_);
-  DCHECK(video_frame->metadata()->power_efficient);
+  DCHECK(video_frame->metadata().power_efficient);
 
   frame_renderer_->RenderFrame(video_frame);
 
diff --git a/media/gpu/vaapi/av1_vaapi_video_decoder_delegate.cc b/media/gpu/vaapi/av1_vaapi_video_decoder_delegate.cc
index 0a185234..d26084d 100644
--- a/media/gpu/vaapi/av1_vaapi_video_decoder_delegate.cc
+++ b/media/gpu/vaapi/av1_vaapi_video_decoder_delegate.cc
@@ -392,16 +392,10 @@
   mode_control.delta_lf_present_flag = frame_header.delta_lf.present;
   mode_control.log2_delta_lf_res = frame_header.delta_lf.scale;
   mode_control.delta_lf_multi = frame_header.delta_lf.multi;
-  switch (frame_header.tx_mode) {
-    case libgav1::TxMode::kTxModeOnly4x4:
-    case libgav1::TxMode::kTxModeLargest:
-    case libgav1::TxMode::kTxModeSelect:
-      mode_control.tx_mode = base::strict_cast<uint32_t>(frame_header.tx_mode);
-      break;
-    default:
-      NOTREACHED() << "Unknown tx mode: "
-                   << base::strict_cast<int>(frame_header.tx_mode);
-  }
+  DCHECK_LE(0u, frame_header.tx_mode);
+  DCHECK_LE(frame_header.tx_mode, 2u);
+  mode_control.tx_mode = frame_header.tx_mode;
+
   mode_control.reference_select = frame_header.reference_mode_select;
   mode_control.reduced_tx_set_used = frame_header.reduced_tx_set;
   mode_control.skip_mode_present = frame_header.skip_mode_present;
@@ -409,7 +403,8 @@
 
 void FillLoopRestorationInfo(VADecPictureParameterBufferAV1& va_pic_param,
                              const libgav1::LoopRestoration& loop_restoration) {
-  auto to_frame_restoration_type = [](libgav1::LoopRestorationType lr_type) {
+  auto to_frame_restoration_type =
+      [](libgav1::LoopRestorationType lr_type) -> uint16_t {
     // Spec. 6.10.15
     switch (lr_type) {
       case libgav1::LoopRestorationType::kLoopRestorationTypeNone:
@@ -420,7 +415,6 @@
         return 1;
       case libgav1::LoopRestorationType::kLoopRestorationTypeSgrProj:
         return 2;
-      case libgav1::LoopRestorationType::kNumLoopRestorationTypes:
       default:
         NOTREACHED() << "Invalid restoration type"
                      << base::strict_cast<int>(lr_type);
@@ -428,7 +422,8 @@
     }
   };
   static_assert(
-      ARRAY_SIZE(loop_restoration.type) == libgav1::kMaxPlanes &&
+      libgav1::kMaxPlanes == 3 &&
+          ARRAY_SIZE(loop_restoration.type) == libgav1::kMaxPlanes &&
           ARRAY_SIZE(loop_restoration.unit_size_log2) == libgav1::kMaxPlanes,
       "Invalid size of loop restoration values");
   auto& va_loop_restoration = va_pic_param.loop_restoration_fields.bits;
@@ -448,9 +443,14 @@
                    }) != (loop_restoration.type + num_planes);
   if (!use_loop_restoration)
     return;
+  static_assert(libgav1::kPlaneY == 0u && libgav1::kPlaneU == 1u,
+                "Invalid plane index");
   DCHECK_GE(loop_restoration.unit_size_log2[0], 6);
   DCHECK_GE(loop_restoration.unit_size_log2[0],
             loop_restoration.unit_size_log2[1]);
+  DCHECK_LE(
+      loop_restoration.unit_size_log2[0] - loop_restoration.unit_size_log2[1],
+      1);
   va_loop_restoration.lr_unit_shift = loop_restoration.unit_size_log2[0] - 6;
   va_loop_restoration.lr_uv_shift =
       loop_restoration.unit_size_log2[0] - loop_restoration.unit_size_log2[1];
diff --git a/media/gpu/vaapi/vaapi_wrapper.cc b/media/gpu/vaapi/vaapi_wrapper.cc
index a5debd39..bc1f34c 100644
--- a/media/gpu/vaapi/vaapi_wrapper.cc
+++ b/media/gpu/vaapi/vaapi_wrapper.cc
@@ -795,7 +795,7 @@
 #if BUILDFLAG(IS_CHROMEOS_ASH)
   if (mode == VaapiWrapper::kDecodeProtected && profile != VAProfileProtected) {
     required_attribs->push_back(
-        {VAConfigAttribEncryption, VA_ENCRYPTION_TYPE_CENC_CTR});
+        {VAConfigAttribEncryption, VA_ENCRYPTION_TYPE_CTR_128});
   }
 #endif
 
@@ -2591,8 +2591,8 @@
     for (auto& attrib : required_attribs) {
       if (attrib.type == VAConfigAttribEncryption) {
         attrib.value = (encryption_scheme == EncryptionScheme::kCbcs)
-                           ? VA_ENCRYPTION_TYPE_CENC_CBC
-                           : VA_ENCRYPTION_TYPE_CENC_CTR;
+                           ? VA_ENCRYPTION_TYPE_CBC
+                           : VA_ENCRYPTION_TYPE_CTR_128;
       }
     }
   }
diff --git a/media/gpu/windows/d3d11_video_decoder.cc b/media/gpu/windows/d3d11_video_decoder.cc
index a98333e..79372c10 100644
--- a/media/gpu/windows/d3d11_video_decoder.cc
+++ b/media/gpu/windows/d3d11_video_decoder.cc
@@ -863,7 +863,7 @@
   frame->SetReleaseMailboxCB(
       base::BindOnce(release_mailbox_cb_, std::move(wait_complete_cb)));
 
-  frame->metadata()->power_efficient = true;
+  frame->metadata().power_efficient = true;
   // For NV12, overlay is allowed by default. If the decoder is going to support
   // non-NV12 textures, then this may have to be conditionally set. Also note
   // that ALLOW_OVERLAY is required for encrypted video path.
@@ -878,7 +878,7 @@
   // presenter decide if it wants to.
   const bool allow_overlay =
       base::FeatureList::IsEnabled(kD3D11VideoDecoderAllowOverlay);
-  frame->metadata()->allow_overlay = allow_overlay;
+  frame->metadata().allow_overlay = allow_overlay;
 
   frame->set_color_space(output_color_space);
   frame->set_hdr_metadata(config_.hdr_metadata());
diff --git a/media/mojo/mojom/video_frame_mojom_traits.cc b/media/mojo/mojom/video_frame_mojom_traits.cc
index 4f7bae7a..3cef7af2 100644
--- a/media/mojo/mojom/video_frame_mojom_traits.cc
+++ b/media/mojo/mojom/video_frame_mojom_traits.cc
@@ -31,7 +31,7 @@
 
 media::mojom::VideoFrameDataPtr MakeVideoFrameData(
     const media::VideoFrame* input) {
-  if (input->metadata()->end_of_stream) {
+  if (input->metadata().end_of_stream) {
     return media::mojom::VideoFrameData::NewEosData(
         media::mojom::EosVideoFrameData::New());
   }
diff --git a/media/mojo/mojom/video_frame_mojom_traits.h b/media/mojo/mojom/video_frame_mojom_traits.h
index 3323ef2..9071089 100644
--- a/media/mojo/mojom/video_frame_mojom_traits.h
+++ b/media/mojo/mojom/video_frame_mojom_traits.h
@@ -79,7 +79,7 @@
   // const &.
   static const media::VideoFrameMetadata& metadata(
       const scoped_refptr<media::VideoFrame>& input) {
-    return *(input->metadata());
+    return input->metadata();
   }
 
   static bool Read(media::mojom::VideoFrameDataView input,
diff --git a/media/mojo/mojom/video_frame_mojom_traits_unittest.cc b/media/mojo/mojom/video_frame_mojom_traits_unittest.cc
index 3c6a2c3..1ac03ed 100644
--- a/media/mojo/mojom/video_frame_mojom_traits_unittest.cc
+++ b/media/mojo/mojom/video_frame_mojom_traits_unittest.cc
@@ -77,7 +77,7 @@
 
   ASSERT_TRUE(RoundTrip(&frame));
   ASSERT_TRUE(frame);
-  EXPECT_TRUE(frame->metadata()->end_of_stream);
+  EXPECT_TRUE(frame->metadata().end_of_stream);
 }
 
 TEST_F(VideoFrameStructTraitsTest, MojoSharedBufferVideoFrame) {
@@ -86,12 +86,12 @@
     scoped_refptr<VideoFrame> frame =
         MojoSharedBufferVideoFrame::CreateDefaultForTesting(
             format, gfx::Size(100, 100), base::TimeDelta::FromSeconds(100));
-    frame->metadata()->frame_rate = 42.0;
+    frame->metadata().frame_rate = 42.0;
 
     ASSERT_TRUE(RoundTrip(&frame));
     ASSERT_TRUE(frame);
-    EXPECT_FALSE(frame->metadata()->end_of_stream);
-    EXPECT_EQ(*frame->metadata()->frame_rate, 42.0);
+    EXPECT_FALSE(frame->metadata().end_of_stream);
+    EXPECT_EQ(*frame->metadata().frame_rate, 42.0);
     EXPECT_EQ(frame->coded_size(), gfx::Size(100, 100));
     EXPECT_EQ(frame->timestamp(), base::TimeDelta::FromSeconds(100));
 
@@ -126,7 +126,7 @@
 
   ASSERT_TRUE(RoundTrip(&frame));
   ASSERT_TRUE(frame);
-  EXPECT_FALSE(frame->metadata()->end_of_stream);
+  EXPECT_FALSE(frame->metadata().end_of_stream);
   EXPECT_EQ(frame->format(), PIXEL_FORMAT_NV12);
   EXPECT_EQ(frame->coded_size(), gfx::Size(1280, 720));
   EXPECT_EQ(frame->visible_rect(), gfx::Rect(0, 0, 1280, 720));
@@ -148,7 +148,7 @@
 
   ASSERT_TRUE(RoundTrip(&frame));
   ASSERT_TRUE(frame);
-  EXPECT_FALSE(frame->metadata()->end_of_stream);
+  EXPECT_FALSE(frame->metadata().end_of_stream);
   EXPECT_EQ(frame->format(), PIXEL_FORMAT_ARGB);
   EXPECT_EQ(frame->coded_size(), gfx::Size(100, 100));
   EXPECT_EQ(frame->visible_rect(), gfx::Rect(10, 10, 80, 80));
@@ -182,7 +182,7 @@
   ASSERT_TRUE(frame);
   ASSERT_EQ(frame->storage_type(), VideoFrame::STORAGE_GPU_MEMORY_BUFFER);
   EXPECT_TRUE(frame->HasGpuMemoryBuffer());
-  EXPECT_FALSE(frame->metadata()->end_of_stream);
+  EXPECT_FALSE(frame->metadata().end_of_stream);
   EXPECT_EQ(frame->format(), PIXEL_FORMAT_NV12);
   EXPECT_EQ(frame->coded_size(), coded_size);
   EXPECT_EQ(frame->visible_rect(), visible_rect);
diff --git a/media/mojo/services/mojo_video_decoder_service.cc b/media/mojo/services/mojo_video_decoder_service.cc
index 15a1f724..30d2c50 100644
--- a/media/mojo/services/mojo_video_decoder_service.cc
+++ b/media/mojo/services/mojo_video_decoder_service.cc
@@ -344,7 +344,7 @@
   // All MojoVideoDecoder-based decoders are hardware decoders. If you're the
   // first to implement an out-of-process decoder that is not power efficent,
   // you can remove this DCHECK.
-  DCHECK(frame->metadata()->power_efficient);
+  DCHECK(frame->metadata().power_efficient);
 
   base::Optional<base::UnguessableToken> release_token;
   if (frame->HasReleaseMailboxCB() && video_frame_handle_releaser_) {
diff --git a/media/mojo/test/mojo_video_decoder_integration_test.cc b/media/mojo/test/mojo_video_decoder_integration_test.cc
index 6abedd2..6ab33c92 100644
--- a/media/mojo/test/mojo_video_decoder_integration_test.cc
+++ b/media/mojo/test/mojo_video_decoder_integration_test.cc
@@ -144,7 +144,7 @@
             PIXEL_FORMAT_ARGB, mailbox_holders, GetReleaseMailboxCB(),
             config_.coded_size(), config_.visible_rect(),
             config_.natural_size(), buffer->timestamp());
-        frame->metadata()->power_efficient = true;
+        frame->metadata().power_efficient = true;
         output_cb_.Run(frame);
       }
     }
diff --git a/media/muxers/webm_muxer.cc b/media/muxers/webm_muxer.cc
index 5ead1c0..4cfd31f 100644
--- a/media/muxers/webm_muxer.cc
+++ b/media/muxers/webm_muxer.cc
@@ -145,7 +145,7 @@
 WebmMuxer::VideoParameters::VideoParameters(
     scoped_refptr<media::VideoFrame> frame)
     : visible_rect_size(frame->visible_rect().size()),
-      frame_rate(frame->metadata()->frame_rate.value_or(0.0)),
+      frame_rate(frame->metadata().frame_rate.value_or(0.0)),
       codec(kUnknownVideoCodec),
       color_space(frame->ColorSpace()) {}
 
diff --git a/media/renderers/paint_canvas_video_renderer.cc b/media/renderers/paint_canvas_video_renderer.cc
index 6565908e..a2020006 100644
--- a/media/renderers/paint_canvas_video_renderer.cc
+++ b/media/renderers/paint_canvas_video_renderer.cc
@@ -314,7 +314,7 @@
   WaitAndReplaceSyncTokenClient client(ri);
   video_frame->UpdateReleaseSyncToken(&client);
 
-  if (video_frame->metadata()->read_lock_fences_enabled) {
+  if (video_frame->metadata().read_lock_fences_enabled) {
     // |video_frame| must be kept alive during read operations.
     DCHECK(context_support);
     unsigned query_id = 0;
@@ -1039,7 +1039,7 @@
   ret->set_color_space(video_frame->ColorSpace());
   // Copy all metadata.
   // (May be enough to copy color space)
-  ret->metadata()->MergeMetadataFrom(video_frame->metadata());
+  ret->metadata().MergeMetadataFrom(video_frame->metadata());
 
   for (int plane = VideoFrame::kYPlane; plane <= VideoFrame::kVPlane; ++plane) {
     int width = ret->row_bytes(plane);
@@ -1311,7 +1311,7 @@
   DCHECK(video_frame);
   DCHECK(video_frame->HasTextures());
   if (video_frame->NumTextures() > 1 ||
-      video_frame->metadata()->read_lock_fences_enabled) {
+      video_frame->metadata().read_lock_fences_enabled) {
     if (!raster_context_provider)
       return false;
     GrDirectContext* gr_context = raster_context_provider->GrContext();
diff --git a/media/renderers/paint_canvas_video_renderer_unittest.cc b/media/renderers/paint_canvas_video_renderer_unittest.cc
index 108617d5..f84d6e9bc 100644
--- a/media/renderers/paint_canvas_video_renderer_unittest.cc
+++ b/media/renderers/paint_canvas_video_renderer_unittest.cc
@@ -1396,7 +1396,7 @@
        CopyVideoFrameTexturesToGLTextureRGBA_ReadLockFence) {
   base::RunLoop run_loop;
   scoped_refptr<VideoFrame> frame = CreateTestRGBAFrame(run_loop.QuitClosure());
-  frame->metadata()->read_lock_fences_enabled = true;
+  frame->metadata().read_lock_fences_enabled = true;
 
   CopyVideoFrameTexturesAndCheckPixels(frame, &CheckRGBAFramePixels);
 
diff --git a/media/renderers/video_renderer_impl.cc b/media/renderers/video_renderer_impl.cc
index c25efa8..faabcaa 100644
--- a/media/renderers/video_renderer_impl.cc
+++ b/media/renderers/video_renderer_impl.cc
@@ -574,7 +574,7 @@
   last_frame_ready_time_ = tick_clock_->NowTicks();
   last_decoder_stream_avg_duration_ = video_decoder_stream_->AverageDuration();
 
-  const bool is_eos = frame->metadata()->end_of_stream;
+  const bool is_eos = frame->metadata().end_of_stream;
   const bool is_before_start_time = !is_eos && IsBeforeStartTime(*frame);
   const bool cant_read = !video_decoder_stream_->CanReadWithoutStalling();
 
@@ -606,8 +606,8 @@
     // RemoveFramesForUnderflowOrBackgroundRendering() below to actually expire
     // this frame if it's too far behind the current media time. Without this,
     // we may resume too soon after a track change in the low delay case.
-    if (!frame->metadata()->frame_duration.has_value())
-      frame->metadata()->frame_duration = last_decoder_stream_avg_duration_;
+    if (!frame->metadata().frame_duration.has_value())
+      frame->metadata().frame_duration = last_decoder_stream_avg_duration_;
 
     AddReadyFrame_Locked(std::move(frame));
   }
@@ -736,11 +736,11 @@
 void VideoRendererImpl::AddReadyFrame_Locked(scoped_refptr<VideoFrame> frame) {
   DCHECK(task_runner_->BelongsToCurrentThread());
   lock_.AssertAcquired();
-  DCHECK(!frame->metadata()->end_of_stream);
+  DCHECK(!frame->metadata().end_of_stream);
 
   ++stats_.video_frames_decoded;
 
-  if (frame->metadata()->power_efficient)
+  if (frame->metadata().power_efficient)
     ++stats_.video_frames_decoded_power_efficient;
 
   algorithm_->EnqueueFrame(std::move(frame));
@@ -930,7 +930,7 @@
 
 bool VideoRendererImpl::IsBeforeStartTime(const VideoFrame& frame) {
   // Prefer the actual frame duration over the average if available.
-  return frame.timestamp() + frame.metadata()->frame_duration.value_or(
+  return frame.timestamp() + frame.metadata().frame_duration.value_or(
                                  last_decoder_stream_avg_duration_) <
          start_timestamp_;
 }
diff --git a/media/renderers/video_resource_updater.cc b/media/renderers/video_resource_updater.cc
index 2197db9..68f950d0 100644
--- a/media/renderers/video_resource_updater.cc
+++ b/media/renderers/video_resource_updater.cc
@@ -495,9 +495,9 @@
 
 void VideoResourceUpdater::ObtainFrameResources(
     scoped_refptr<VideoFrame> video_frame) {
-  if (video_frame->metadata()->overlay_plane_id.has_value()) {
+  if (video_frame->metadata().overlay_plane_id.has_value()) {
     // This is a hole punching VideoFrame, there is nothing to display.
-    overlay_plane_id_ = *video_frame->metadata()->overlay_plane_id;
+    overlay_plane_id_ = *video_frame->metadata().overlay_plane_id;
     frame_resource_type_ = VideoFrameResourceType::VIDEO_HOLE;
     return;
   }
@@ -618,8 +618,8 @@
           frame_resource_multiplier_, frame_bits_per_channel_);
       if (frame->hdr_metadata().has_value())
         yuv_video_quad->hdr_metadata = frame->hdr_metadata().value();
-      if (frame->metadata()->protected_video) {
-        if (frame->metadata()->hw_protected) {
+      if (frame->metadata().protected_video) {
+        if (frame->metadata().hw_protected) {
           yuv_video_quad->protected_video_type =
               gfx::ProtectedVideoType::kHardwareProtected;
         } else {
@@ -647,8 +647,8 @@
       bool nearest_neighbor = false;
       gfx::ProtectedVideoType protected_video_type =
           gfx::ProtectedVideoType::kClear;
-      if (frame->metadata()->protected_video) {
-        if (frame->metadata()->hw_protected)
+      if (frame->metadata().protected_video) {
+        if (frame->metadata().hw_protected)
           protected_video_type = gfx::ProtectedVideoType::kHardwareProtected;
         else
           protected_video_type = gfx::ProtectedVideoType::kSoftwareProtected;
@@ -848,7 +848,7 @@
   VideoFrameExternalResources external_resources;
   gfx::ColorSpace resource_color_space = video_frame->ColorSpace();
 
-  const auto& copy_mode = video_frame->metadata()->copy_mode;
+  const auto& copy_mode = video_frame->metadata().copy_mode;
   GLuint target = video_frame->mailbox_holder(0).texture_target;
   // If texture copy is required, then we will copy into a GL_TEXTURE_2D target.
   if (copy_mode == VideoFrameMetadata::CopyMode::kCopyToNewTexture)
@@ -902,18 +902,18 @@
       const gfx::Size plane_size(width, height);
       auto transfer_resource = viz::TransferableResource::MakeGL(
           mailbox, GL_LINEAR, mailbox_holder.texture_target, sync_token,
-          plane_size, video_frame->metadata()->allow_overlay);
+          plane_size, video_frame->metadata().allow_overlay);
       transfer_resource.color_space = resource_color_space;
       transfer_resource.read_lock_fences_enabled =
-          video_frame->metadata()->read_lock_fences_enabled;
+          video_frame->metadata().read_lock_fences_enabled;
       transfer_resource.format = viz::GetResourceFormat(buffer_formats[i]);
       transfer_resource.ycbcr_info = video_frame->ycbcr_info();
 
 #if defined(OS_ANDROID)
       transfer_resource.is_backed_by_surface_texture =
-          video_frame->metadata()->texture_owner;
+          video_frame->metadata().texture_owner;
       transfer_resource.wants_promotion_hint =
-          video_frame->metadata()->wants_promotion_hint;
+          video_frame->metadata().wants_promotion_hint;
 #endif
       external_resources.resources.push_back(std::move(transfer_resource));
       if (copy_mode == VideoFrameMetadata::CopyMode::kCopyMailboxesOnly) {
diff --git a/media/renderers/video_resource_updater_unittest.cc b/media/renderers/video_resource_updater_unittest.cc
index 055a5de..b865e08 100644
--- a/media/renderers/video_resource_updater_unittest.cc
+++ b/media/renderers/video_resource_updater_unittest.cc
@@ -204,7 +204,7 @@
       base::Optional<media::VideoFrameMetadata::CopyMode> copy_mode) {
     scoped_refptr<media::VideoFrame> video_frame = CreateTestHardwareVideoFrame(
         media::PIXEL_FORMAT_ARGB, GL_TEXTURE_EXTERNAL_OES);
-    video_frame->metadata()->copy_mode = std::move(copy_mode);
+    video_frame->metadata().copy_mode = std::move(copy_mode);
     return video_frame;
   }
 
@@ -530,7 +530,7 @@
 
   video_frame = CreateTestYuvHardwareVideoFrame(media::PIXEL_FORMAT_I420, 3,
                                                 GL_TEXTURE_RECTANGLE_ARB);
-  video_frame->metadata()->read_lock_fences_enabled = true;
+  video_frame->metadata().read_lock_fences_enabled = true;
 
   resources = updater->CreateExternalResourcesFromVideoFrame(video_frame);
   EXPECT_TRUE(resources.resources[0].read_lock_fences_enabled);
diff --git a/media/video/gpu_memory_buffer_video_frame_pool.cc b/media/video/gpu_memory_buffer_video_frame_pool.cc
index ed4238b..e871409 100644
--- a/media/video/gpu_memory_buffer_video_frame_pool.cc
+++ b/media/video/gpu_memory_buffer_video_frame_pool.cc
@@ -1032,7 +1032,7 @@
 #else
   switch (output_format_) {
     case GpuVideoAcceleratorFactories::OutputFormat::I420:
-      allow_overlay = video_frame->metadata()->allow_overlay;
+      allow_overlay = video_frame->metadata().allow_overlay;
       break;
     case GpuVideoAcceleratorFactories::OutputFormat::P010:
     case GpuVideoAcceleratorFactories::OutputFormat::NV12_SINGLE_GMB:
@@ -1062,9 +1062,9 @@
       break;
   }
 #endif  // OS_WIN
-  frame->metadata()->MergeMetadataFrom(video_frame->metadata());
-  frame->metadata()->allow_overlay = allow_overlay;
-  frame->metadata()->read_lock_fences_enabled = true;
+  frame->metadata().MergeMetadataFrom(video_frame->metadata());
+  frame->metadata().allow_overlay = allow_overlay;
+  frame->metadata().read_lock_fences_enabled = true;
 
   CompleteCopyRequestAndMaybeStartNextCopy(std::move(frame));
 }
diff --git a/media/video/gpu_memory_buffer_video_frame_pool_unittest.cc b/media/video/gpu_memory_buffer_video_frame_pool_unittest.cc
index 6ffe20d..be9e6fc 100644
--- a/media/video/gpu_memory_buffer_video_frame_pool_unittest.cc
+++ b/media/video/gpu_memory_buffer_video_frame_pool_unittest.cc
@@ -308,7 +308,7 @@
   EXPECT_EQ(PIXEL_FORMAT_NV12, frame->format());
   EXPECT_EQ(1u, frame->NumTextures());
   EXPECT_EQ(1u, sii_->shared_image_count());
-  EXPECT_TRUE(frame->metadata()->read_lock_fences_enabled);
+  EXPECT_TRUE(frame->metadata().read_lock_fences_enabled);
 }
 
 TEST_F(GpuMemoryBufferVideoFramePoolTest, CreateOneHardwareNV12Frame2) {
@@ -325,7 +325,7 @@
   EXPECT_EQ(PIXEL_FORMAT_NV12, frame->format());
   EXPECT_EQ(2u, frame->NumTextures());
   EXPECT_EQ(2u, sii_->shared_image_count());
-  EXPECT_TRUE(frame->metadata()->read_lock_fences_enabled);
+  EXPECT_TRUE(frame->metadata().read_lock_fences_enabled);
 }
 
 TEST_F(GpuMemoryBufferVideoFramePoolTest, CreateOneHardwareFrameForNV12Input) {
@@ -358,7 +358,7 @@
   EXPECT_EQ(PIXEL_FORMAT_XR30, frame->format());
   EXPECT_EQ(1u, frame->NumTextures());
   EXPECT_EQ(1u, sii_->shared_image_count());
-  EXPECT_TRUE(frame->metadata()->read_lock_fences_enabled);
+  EXPECT_TRUE(frame->metadata().read_lock_fences_enabled);
 
   EXPECT_EQ(1u, mock_gpu_factories_->created_memory_buffers().size());
   mock_gpu_factories_->created_memory_buffers()[0]->Map();
@@ -381,7 +381,7 @@
   EXPECT_EQ(PIXEL_FORMAT_P016LE, frame->format());
   EXPECT_EQ(1u, frame->NumTextures());
   EXPECT_EQ(1u, sii_->shared_image_count());
-  EXPECT_TRUE(frame->metadata()->read_lock_fences_enabled);
+  EXPECT_TRUE(frame->metadata().read_lock_fences_enabled);
 
   EXPECT_EQ(1u, mock_gpu_factories_->created_memory_buffers().size());
   mock_gpu_factories_->created_memory_buffers()[0]->Map();
@@ -413,7 +413,7 @@
   EXPECT_EQ(PIXEL_FORMAT_XR30, frame->format());
   EXPECT_EQ(1u, frame->NumTextures());
   EXPECT_EQ(1u, sii_->shared_image_count());
-  EXPECT_TRUE(frame->metadata()->read_lock_fences_enabled);
+  EXPECT_TRUE(frame->metadata().read_lock_fences_enabled);
 
   EXPECT_EQ(1u, mock_gpu_factories_->created_memory_buffers().size());
   mock_gpu_factories_->created_memory_buffers()[0]->Map();
@@ -437,7 +437,7 @@
   EXPECT_EQ(PIXEL_FORMAT_XR30, frame->format());
   EXPECT_EQ(1u, frame->NumTextures());
   EXPECT_EQ(1u, sii_->shared_image_count());
-  EXPECT_TRUE(frame->metadata()->read_lock_fences_enabled);
+  EXPECT_TRUE(frame->metadata().read_lock_fences_enabled);
 
   EXPECT_EQ(1u, mock_gpu_factories_->created_memory_buffers().size());
   mock_gpu_factories_->created_memory_buffers()[0]->Map();
@@ -460,7 +460,7 @@
   EXPECT_EQ(PIXEL_FORMAT_XB30, frame->format());
   EXPECT_EQ(1u, frame->NumTextures());
   EXPECT_EQ(1u, sii_->shared_image_count());
-  EXPECT_TRUE(frame->metadata()->read_lock_fences_enabled);
+  EXPECT_TRUE(frame->metadata().read_lock_fences_enabled);
 }
 
 TEST_F(GpuMemoryBufferVideoFramePoolTest, CreateOneHardwareRGBAFrame) {
@@ -477,15 +477,15 @@
   EXPECT_EQ(PIXEL_FORMAT_ABGR, frame->format());
   EXPECT_EQ(1u, frame->NumTextures());
   EXPECT_EQ(1u, sii_->shared_image_count());
-  EXPECT_TRUE(frame->metadata()->read_lock_fences_enabled);
+  EXPECT_TRUE(frame->metadata().read_lock_fences_enabled);
 }
 
 TEST_F(GpuMemoryBufferVideoFramePoolTest, PreservesMetadata) {
   scoped_refptr<VideoFrame> software_frame = CreateTestYUVVideoFrame(10);
-  software_frame->metadata()->end_of_stream = true;
+  software_frame->metadata().end_of_stream = true;
   base::TimeTicks kTestReferenceTime =
       base::TimeDelta::FromMilliseconds(12345) + base::TimeTicks();
-  software_frame->metadata()->reference_time = kTestReferenceTime;
+  software_frame->metadata().reference_time = kTestReferenceTime;
   scoped_refptr<VideoFrame> frame;
   gpu_memory_buffer_pool_->MaybeCreateHardwareFrame(
       software_frame, base::BindOnce(MaybeCreateHardwareFrameCallback, &frame));
@@ -493,8 +493,8 @@
   RunUntilIdle();
 
   EXPECT_NE(software_frame.get(), frame.get());
-  EXPECT_TRUE(frame->metadata()->end_of_stream);
-  EXPECT_EQ(kTestReferenceTime, *frame->metadata()->reference_time);
+  EXPECT_TRUE(frame->metadata().end_of_stream);
+  EXPECT_EQ(kTestReferenceTime, *frame->metadata().reference_time);
 }
 
 // CreateGpuMemoryBuffer can return null (e.g: when the GPU process is down).
@@ -695,7 +695,7 @@
   EXPECT_EQ(PIXEL_FORMAT_ABGR, frame_1->format());
   EXPECT_EQ(1u, frame_1->NumTextures());
   EXPECT_EQ(1u, sii_->shared_image_count());
-  EXPECT_TRUE(frame_1->metadata()->read_lock_fences_enabled);
+  EXPECT_TRUE(frame_1->metadata().read_lock_fences_enabled);
 
   scoped_refptr<VideoFrame> software_frame_2 = CreateTestYUVVideoFrame(10);
   mock_gpu_factories_->SetVideoFrameOutputFormat(
diff --git a/media/video/video_encode_accelerator_adapter.cc b/media/video/video_encode_accelerator_adapter.cc
index 99d2ce3c..b0820d5 100644
--- a/media/video/video_encode_accelerator_adapter.cc
+++ b/media/video/video_encode_accelerator_adapter.cc
@@ -703,7 +703,7 @@
       gfx::Rect(size), size, std::move(gmb), empty_mailboxes,
       base::NullCallback(), src_frame->timestamp());
   gpu_frame->set_color_space(src_frame->ColorSpace());
-  gpu_frame->metadata()->MergeMetadataFrom(src_frame->metadata());
+  gpu_frame->metadata().MergeMetadataFrom(src_frame->metadata());
 
   // Don't be scared. ConvertToMemoryMappedFrame() doesn't copy pixel data
   // it just maps GPU buffer owned by |gpu_frame| and presents it as mapped
diff --git a/media/video/vpx_video_encoder.cc b/media/video/vpx_video_encoder.cc
index 1a9dad2..1d3be74 100644
--- a/media/video/vpx_video_encoder.cc
+++ b/media/video/vpx_video_encoder.cc
@@ -455,8 +455,8 @@
 
 base::TimeDelta VpxVideoEncoder::GetFrameDuration(const VideoFrame& frame) {
   // Frame has duration in metadata, use it.
-  if (frame.metadata()->frame_duration.has_value())
-    return frame.metadata()->frame_duration.value();
+  if (frame.metadata().frame_duration.has_value())
+    return frame.metadata().frame_duration.value();
 
   // Options have framerate specified, use it.
   if (options_.framerate.has_value())
diff --git a/net/cert/nss_cert_database.cc b/net/cert/nss_cert_database.cc
index e880b1ab..aba4aba 100644
--- a/net/cert/nss_cert_database.cc
+++ b/net/cert/nss_cert_database.cc
@@ -32,7 +32,7 @@
 #include "net/third_party/mozilla_security_manager/nsNSSCertificateDB.h"
 #include "net/third_party/mozilla_security_manager/nsPKCS12Blob.h"
 
-#if defined(OS_CHROMEOS) || BUILDFLAG(IS_LACROS)
+#if defined(OS_CHROMEOS) || BUILDFLAG(IS_CHROMEOS_LACROS)
 #include "crypto/chaps_support.h"
 #endif
 
@@ -440,7 +440,7 @@
   if (!slot || !PK11_IsHW(slot))
     return false;
 
-#if BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_LACROS)
+#if BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS)
   // Chaps announces PK11_IsHW(slot) for all slots. However, it is possible for
   // a key in chaps to be not truly hardware-backed, either because it has been
   // requested to be software-backed, or because the TPM does not support the
diff --git a/services/viz/public/cpp/compositing/filter_operation_mojom_traits.h b/services/viz/public/cpp/compositing/filter_operation_mojom_traits.h
index d512c9257..68b884ad 100644
--- a/services/viz/public/cpp/compositing/filter_operation_mojom_traits.h
+++ b/services/viz/public/cpp/compositing/filter_operation_mojom_traits.h
@@ -12,7 +12,7 @@
 #include "cc/paint/paint_filter.h"
 #include "services/viz/public/cpp/compositing/paint_filter_mojom_traits.h"
 #include "services/viz/public/mojom/compositing/filter_operation.mojom-shared.h"
-#include "skia/public/mojom/blur_image_filter_tile_mode_mojom_traits.h"
+#include "skia/public/mojom/tile_mode_mojom_traits.h"
 #include "ui/gfx/geometry/mojom/geometry_mojom_traits.h"
 
 namespace mojo {
@@ -157,12 +157,10 @@
     return operation.zoom_inset();
   }
 
-  static skia::mojom::BlurTileMode blur_tile_mode(
-      const cc::FilterOperation& operation) {
+  static SkTileMode blur_tile_mode(const cc::FilterOperation& operation) {
     if (operation.type() != cc::FilterOperation::BLUR)
-      return skia::mojom::BlurTileMode::CLAMP_TO_BLACK;
-    return EnumTraits<skia::mojom::BlurTileMode, SkBlurImageFilter::TileMode>::
-        ToMojom(operation.blur_tile_mode());
+      return SkTileMode::kDecal;
+    return operation.blur_tile_mode();
   }
 
   static bool Read(viz::mojom::FilterOperationDataView data,
@@ -182,7 +180,7 @@
         return true;
       case cc::FilterOperation::BLUR:
         out->set_amount(data.amount());
-        SkBlurImageFilter::TileMode tile_mode;
+        SkTileMode tile_mode;
         if (!data.ReadBlurTileMode(&tile_mode))
           return false;
         out->set_blur_tile_mode(tile_mode);
diff --git a/services/viz/public/cpp/compositing/mojom_traits_perftest.cc b/services/viz/public/cpp/compositing/mojom_traits_perftest.cc
index b7ba834..5da884d 100644
--- a/services/viz/public/cpp/compositing/mojom_traits_perftest.cc
+++ b/services/viz/public/cpp/compositing/mojom_traits_perftest.cc
@@ -198,9 +198,8 @@
     arbitrary_filters1.Append(
         cc::FilterOperation::CreateGrayscaleFilter(arbitrary_float1));
     arbitrary_filters1.Append(cc::FilterOperation::CreateReferenceFilter(
-        sk_make_sp<cc::BlurPaintFilter>(
-            arbitrary_sigma, arbitrary_sigma,
-            cc::BlurPaintFilter::TileMode::kClampToBlack_TileMode, nullptr)));
+        sk_make_sp<cc::BlurPaintFilter>(arbitrary_sigma, arbitrary_sigma,
+                                        SkTileMode::kDecal, nullptr)));
 
     cc::FilterOperations arbitrary_filters2;
     arbitrary_filters2.Append(
diff --git a/services/viz/public/cpp/compositing/mojom_traits_unittest.cc b/services/viz/public/cpp/compositing/mojom_traits_unittest.cc
index 4393f48..f527927 100644
--- a/services/viz/public/cpp/compositing/mojom_traits_unittest.cc
+++ b/services/viz/public/cpp/compositing/mojom_traits_unittest.cc
@@ -55,7 +55,7 @@
 #include "services/viz/public/mojom/compositing/surface_range.mojom.h"
 #include "services/viz/public/mojom/compositing/transferable_resource.mojom.h"
 #include "skia/public/mojom/bitmap_skbitmap_mojom_traits.h"
-#include "skia/public/mojom/blur_image_filter_tile_mode_mojom_traits.h"
+#include "skia/public/mojom/tile_mode_mojom_traits.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/skia/include/core/SkString.h"
 #include "ui/gfx/geometry/mojom/geometry_mojom_traits.h"
@@ -188,7 +188,7 @@
       sk_make_sp<cc::DropShadowPaintFilter>(
           SkIntToScalar(3), SkIntToScalar(8), SkIntToScalar(4),
           SkIntToScalar(9), SK_ColorBLACK,
-          SkDropShadowImageFilter::kDrawShadowAndForeground_ShadowMode,
+          cc::DropShadowPaintFilter::ShadowMode::kDrawShadowAndForeground,
           nullptr));
 
   cc::FilterOperation output;
diff --git a/services/viz/public/mojom/compositing/filter_operation.mojom b/services/viz/public/mojom/compositing/filter_operation.mojom
index 4b32add..d70733f 100644
--- a/services/viz/public/mojom/compositing/filter_operation.mojom
+++ b/services/viz/public/mojom/compositing/filter_operation.mojom
@@ -5,7 +5,7 @@
 module viz.mojom;
 
 import "services/viz/public/mojom/compositing/paint_filter.mojom";
-import "skia/public/mojom/blur_image_filter_tile_mode.mojom";
+import "skia/public/mojom/tile_mode.mojom";
 import "ui/gfx/geometry/mojom/geometry.mojom";
 
 enum FilterType {
@@ -37,7 +37,7 @@
   PaintFilter image_filter;
   array<float, 20>? matrix;
   int32 zoom_inset;
-  skia.mojom.BlurTileMode blur_tile_mode;
+  skia.mojom.TileMode blur_tile_mode;
   array<gfx.mojom.Rect>? shape;
 };
 
diff --git a/skia/config/SkUserConfig.h b/skia/config/SkUserConfig.h
index d1d56d2b1..282344f4 100644
--- a/skia/config/SkUserConfig.h
+++ b/skia/config/SkUserConfig.h
@@ -221,6 +221,12 @@
 // Max. verb count for paths rendered by the edge-AA tessellating path renderer.
 #define GR_AA_TESSELLATOR_MAX_VERB_COUNT 100
 
+
+#ifndef SK_SUPPORT_NEAREST_PICTURESHADER_POSTFILTER
+#define SK_SUPPORT_NEAREST_PICTURESHADER_POSTFILTER
+#endif
+
+
 #ifndef SK_SUPPORT_LEGACY_AAA_CHOICE
 #define SK_SUPPORT_LEGACY_AAA_CHOICE
 #endif
diff --git a/skia/public/mojom/BUILD.gn b/skia/public/mojom/BUILD.gn
index 3c7ca37..006ed9f 100644
--- a/skia/public/mojom/BUILD.gn
+++ b/skia/public/mojom/BUILD.gn
@@ -32,9 +32,9 @@
   generate_java = true
   sources = [
     "bitmap.mojom",
-    "blur_image_filter_tile_mode.mojom",
     "image_info.mojom",
     "skcolor.mojom",
+    "tile_mode.mojom",
   ]
 
   public_deps = [ "//mojo/public/mojom/base" ]
@@ -43,11 +43,11 @@
     {
       types = [
         {
-          mojom = "skia.mojom.BlurTileMode"
-          cpp = "::SkBlurImageFilter::TileMode"
+          mojom = "skia.mojom.TileMode"
+          cpp = "::SkTileMode"
         },
       ]
-      traits_headers = [ "blur_image_filter_tile_mode_mojom_traits.h" ]
+      traits_headers = [ "tile_mode_mojom_traits.h" ]
       traits_public_deps = [ "//skia" ]
     },
     {
diff --git a/skia/public/mojom/blur_image_filter_tile_mode_mojom_traits.h b/skia/public/mojom/blur_image_filter_tile_mode_mojom_traits.h
deleted file mode 100644
index 62cf60b..0000000
--- a/skia/public/mojom/blur_image_filter_tile_mode_mojom_traits.h
+++ /dev/null
@@ -1,48 +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.
-
-#ifndef SKIA_PUBLIC_MOJOM_BLUR_IMAGE_FILTER_TILE_MODE_MOJOM_TRAITS_H_
-#define SKIA_PUBLIC_MOJOM_BLUR_IMAGE_FILTER_TILE_MODE_MOJOM_TRAITS_H_
-
-#include "skia/public/mojom/blur_image_filter_tile_mode.mojom-shared.h"
-#include "third_party/skia/include/effects/SkBlurImageFilter.h"
-
-namespace mojo {
-
-template <>
-struct EnumTraits<skia::mojom::BlurTileMode, SkBlurImageFilter::TileMode> {
-  static skia::mojom::BlurTileMode ToMojom(
-      SkBlurImageFilter::TileMode tile_mode) {
-    switch (tile_mode) {
-      case SkBlurImageFilter::kClamp_TileMode:
-        return skia::mojom::BlurTileMode::CLAMP;
-      case SkBlurImageFilter::kRepeat_TileMode:
-        return skia::mojom::BlurTileMode::REPEAT;
-      case SkBlurImageFilter::kClampToBlack_TileMode:
-        return skia::mojom::BlurTileMode::CLAMP_TO_BLACK;
-    }
-    NOTREACHED();
-    return skia::mojom::BlurTileMode::CLAMP_TO_BLACK;
-  }
-
-  static bool FromMojom(skia::mojom::BlurTileMode input,
-                        SkBlurImageFilter::TileMode* out) {
-    switch (input) {
-      case skia::mojom::BlurTileMode::CLAMP:
-        *out = SkBlurImageFilter::kClamp_TileMode;
-        return true;
-      case skia::mojom::BlurTileMode::REPEAT:
-        *out = SkBlurImageFilter::kRepeat_TileMode;
-        return true;
-      case skia::mojom::BlurTileMode::CLAMP_TO_BLACK:
-        *out = SkBlurImageFilter::kClampToBlack_TileMode;
-        return true;
-    }
-    return false;
-  }
-};
-
-}  // namespace mojo
-
-#endif  // SKIA_PUBLIC_MOJOM_BLUR_IMAGE_FILTER_TILE_MODE_MOJOM_TRAITS_H_
diff --git a/skia/public/mojom/test/mojom_traits_unittest.cc b/skia/public/mojom/test/mojom_traits_unittest.cc
index 2c48560..279f297f 100644
--- a/skia/public/mojom/test/mojom_traits_unittest.cc
+++ b/skia/public/mojom/test/mojom_traits_unittest.cc
@@ -5,17 +5,15 @@
 #include "mojo/public/cpp/test_support/test_utils.h"
 #include "skia/public/mojom/bitmap.mojom.h"
 #include "skia/public/mojom/bitmap_skbitmap_mojom_traits.h"
-#include "skia/public/mojom/blur_image_filter_tile_mode.mojom.h"
-#include "skia/public/mojom/blur_image_filter_tile_mode_mojom_traits.h"
 #include "skia/public/mojom/image_info.mojom.h"
+#include "skia/public/mojom/tile_mode.mojom.h"
+#include "skia/public/mojom/tile_mode_mojom_traits.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/skia/include/core/SkColorFilter.h"
 #include "third_party/skia/include/core/SkColorSpace.h"
 #include "third_party/skia/include/core/SkImageInfo.h"
 #include "third_party/skia/include/core/SkString.h"
-#include "third_party/skia/include/effects/SkBlurImageFilter.h"
-#include "third_party/skia/include/effects/SkColorFilterImageFilter.h"
-#include "third_party/skia/include/effects/SkDropShadowImageFilter.h"
+#include "third_party/skia/include/core/SkTileMode.h"
 #include "third_party/skia/include/third_party/skcms/skcms.h"
 #include "ui/gfx/skia_util.h"
 
@@ -105,18 +103,22 @@
   EXPECT_EQ(input, output);
 }
 
-TEST(StructTraitsTest, BlurImageFilterTileMode) {
-  SkBlurImageFilter::TileMode input(SkBlurImageFilter::kClamp_TileMode);
-  SkBlurImageFilter::TileMode output;
-  ASSERT_TRUE(mojo::test::SerializeAndDeserialize<skia::mojom::BlurTileMode>(
+TEST(StructTraitsTest, TileMode) {
+  SkTileMode input(SkTileMode::kClamp);
+  SkTileMode output;
+  ASSERT_TRUE(mojo::test::SerializeAndDeserialize<skia::mojom::TileMode>(
       input, output));
   EXPECT_EQ(input, output);
-  input = SkBlurImageFilter::kRepeat_TileMode;
-  ASSERT_TRUE(mojo::test::SerializeAndDeserialize<skia::mojom::BlurTileMode>(
+  input = SkTileMode::kRepeat;
+  ASSERT_TRUE(mojo::test::SerializeAndDeserialize<skia::mojom::TileMode>(
       input, output));
   EXPECT_EQ(input, output);
-  input = SkBlurImageFilter::kClampToBlack_TileMode;
-  ASSERT_TRUE(mojo::test::SerializeAndDeserialize<skia::mojom::BlurTileMode>(
+  input = SkTileMode::kMirror;
+  ASSERT_TRUE(mojo::test::SerializeAndDeserialize<skia::mojom::TileMode>(
+      input, output));
+  EXPECT_EQ(input, output);
+  input = SkTileMode::kDecal;
+  ASSERT_TRUE(mojo::test::SerializeAndDeserialize<skia::mojom::TileMode>(
       input, output));
   EXPECT_EQ(input, output);
 }
diff --git a/skia/public/mojom/blur_image_filter_tile_mode.mojom b/skia/public/mojom/tile_mode.mojom
similarity index 63%
rename from skia/public/mojom/blur_image_filter_tile_mode.mojom
rename to skia/public/mojom/tile_mode.mojom
index 3a351c87..bfe9583 100644
--- a/skia/public/mojom/blur_image_filter_tile_mode.mojom
+++ b/skia/public/mojom/tile_mode.mojom
@@ -4,10 +4,10 @@
 
 module skia.mojom;
 
-// Mirror of SkBlurImageFilter::TileMode.
-enum BlurTileMode {
+// Mirror of SkTileMode.
+enum TileMode {
   CLAMP,
   REPEAT,
-  CLAMP_TO_BLACK,
-  BLUR_TILE_MODE_LAST = CLAMP_TO_BLACK
+  MIRROR,
+  DECAL
 };
diff --git a/skia/public/mojom/tile_mode_mojom_traits.h b/skia/public/mojom/tile_mode_mojom_traits.h
new file mode 100644
index 0000000..b67d3a1
--- /dev/null
+++ b/skia/public/mojom/tile_mode_mojom_traits.h
@@ -0,0 +1,51 @@
+// 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.
+
+#ifndef SKIA_PUBLIC_MOJOM_TILE_MODE_MOJOM_TRAITS_H_
+#define SKIA_PUBLIC_MOJOM_TILE_MODE_MOJOM_TRAITS_H_
+
+#include "skia/public/mojom/tile_mode.mojom-shared.h"
+#include "third_party/skia/include/core/SkTileMode.h"
+
+namespace mojo {
+
+template <>
+struct EnumTraits<skia::mojom::TileMode, SkTileMode> {
+  static skia::mojom::TileMode ToMojom(SkTileMode tile_mode) {
+    switch (tile_mode) {
+      case SkTileMode::kClamp:
+        return skia::mojom::TileMode::CLAMP;
+      case SkTileMode::kRepeat:
+        return skia::mojom::TileMode::REPEAT;
+      case SkTileMode::kMirror:
+        return skia::mojom::TileMode::MIRROR;
+      case SkTileMode::kDecal:
+        return skia::mojom::TileMode::DECAL;
+    }
+    NOTREACHED();
+    return skia::mojom::TileMode::DECAL;
+  }
+
+  static bool FromMojom(skia::mojom::TileMode input, SkTileMode* out) {
+    switch (input) {
+      case skia::mojom::TileMode::CLAMP:
+        *out = SkTileMode::kClamp;
+        return true;
+      case skia::mojom::TileMode::REPEAT:
+        *out = SkTileMode::kRepeat;
+        return true;
+      case skia::mojom::TileMode::MIRROR:
+        *out = SkTileMode::kMirror;
+        return true;
+      case skia::mojom::TileMode::DECAL:
+        *out = SkTileMode::kDecal;
+        return true;
+    }
+    return false;
+  }
+};
+
+}  // namespace mojo
+
+#endif  // SKIA_PUBLIC_MOJOM_TILE_MODE_MOJOM_TRAITS_H_
diff --git a/testing/buildbot/chromium.android.fyi.json b/testing/buildbot/chromium.android.fyi.json
index c5ede94..0294c86 100644
--- a/testing/buildbot/chromium.android.fyi.json
+++ b/testing/buildbot/chromium.android.fyi.json
@@ -240,11 +240,11 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "weblayer_instrumentation_test_versions_apk_Client Tests For 87.0.4280.148"
+            "weblayer_instrumentation_test_versions_apk_Client Tests For 87.0.4280.149"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
-        "name": "weblayer_instrumentation_test_versions_apk_Client Tests For 87.0.4280.148",
+        "name": "weblayer_instrumentation_test_versions_apk_Client Tests For 87.0.4280.149",
         "resultdb": {
           "enable": true
         },
@@ -254,7 +254,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M87",
-              "revision": "version:87.0.4280.148"
+              "revision": "version:87.0.4280.149"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -317,11 +317,11 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "weblayer_instrumentation_test_versions_apk_Client Tests For 88.0.4324.85"
+            "weblayer_instrumentation_test_versions_apk_Client Tests For 88.0.4324.86"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
-        "name": "weblayer_instrumentation_test_versions_apk_Client Tests For 88.0.4324.85",
+        "name": "weblayer_instrumentation_test_versions_apk_Client Tests For 88.0.4324.86",
         "resultdb": {
           "enable": true
         },
@@ -331,7 +331,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M88",
-              "revision": "version:88.0.4324.85"
+              "revision": "version:88.0.4324.86"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -394,11 +394,11 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "weblayer_instrumentation_test_versions_apk_Implementation Tests For 87.0.4280.148"
+            "weblayer_instrumentation_test_versions_apk_Implementation Tests For 87.0.4280.149"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
-        "name": "weblayer_instrumentation_test_versions_apk_Implementation Tests For 87.0.4280.148",
+        "name": "weblayer_instrumentation_test_versions_apk_Implementation Tests For 87.0.4280.149",
         "resultdb": {
           "enable": true
         },
@@ -408,7 +408,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M87",
-              "revision": "version:87.0.4280.148"
+              "revision": "version:87.0.4280.149"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -471,11 +471,11 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "weblayer_instrumentation_test_versions_apk_Implementation Tests For 88.0.4324.85"
+            "weblayer_instrumentation_test_versions_apk_Implementation Tests For 88.0.4324.86"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
-        "name": "weblayer_instrumentation_test_versions_apk_Implementation Tests For 88.0.4324.85",
+        "name": "weblayer_instrumentation_test_versions_apk_Implementation Tests For 88.0.4324.86",
         "resultdb": {
           "enable": true
         },
@@ -485,7 +485,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M88",
-              "revision": "version:88.0.4324.85"
+              "revision": "version:88.0.4324.86"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -769,11 +769,11 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "weblayer_instrumentation_test_versions_apk_Client Tests For 87.0.4280.148"
+            "weblayer_instrumentation_test_versions_apk_Client Tests For 87.0.4280.149"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
-        "name": "weblayer_instrumentation_test_versions_apk_Client Tests For 87.0.4280.148",
+        "name": "weblayer_instrumentation_test_versions_apk_Client Tests For 87.0.4280.149",
         "resultdb": {
           "enable": true
         },
@@ -783,7 +783,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M87",
-              "revision": "version:87.0.4280.148"
+              "revision": "version:87.0.4280.149"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -846,11 +846,11 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "weblayer_instrumentation_test_versions_apk_Client Tests For 88.0.4324.85"
+            "weblayer_instrumentation_test_versions_apk_Client Tests For 88.0.4324.86"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
-        "name": "weblayer_instrumentation_test_versions_apk_Client Tests For 88.0.4324.85",
+        "name": "weblayer_instrumentation_test_versions_apk_Client Tests For 88.0.4324.86",
         "resultdb": {
           "enable": true
         },
@@ -860,7 +860,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M88",
-              "revision": "version:88.0.4324.85"
+              "revision": "version:88.0.4324.86"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -923,11 +923,11 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "weblayer_instrumentation_test_versions_apk_Implementation Tests For 87.0.4280.148"
+            "weblayer_instrumentation_test_versions_apk_Implementation Tests For 87.0.4280.149"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
-        "name": "weblayer_instrumentation_test_versions_apk_Implementation Tests For 87.0.4280.148",
+        "name": "weblayer_instrumentation_test_versions_apk_Implementation Tests For 87.0.4280.149",
         "resultdb": {
           "enable": true
         },
@@ -937,7 +937,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M87",
-              "revision": "version:87.0.4280.148"
+              "revision": "version:87.0.4280.149"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -1000,11 +1000,11 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "weblayer_instrumentation_test_versions_apk_Implementation Tests For 88.0.4324.85"
+            "weblayer_instrumentation_test_versions_apk_Implementation Tests For 88.0.4324.86"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
-        "name": "weblayer_instrumentation_test_versions_apk_Implementation Tests For 88.0.4324.85",
+        "name": "weblayer_instrumentation_test_versions_apk_Implementation Tests For 88.0.4324.86",
         "resultdb": {
           "enable": true
         },
@@ -1014,7 +1014,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M88",
-              "revision": "version:88.0.4324.85"
+              "revision": "version:88.0.4324.86"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -1298,11 +1298,11 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "weblayer_instrumentation_test_versions_apk_Client Tests For 87.0.4280.148"
+            "weblayer_instrumentation_test_versions_apk_Client Tests For 87.0.4280.149"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
-        "name": "weblayer_instrumentation_test_versions_apk_Client Tests For 87.0.4280.148",
+        "name": "weblayer_instrumentation_test_versions_apk_Client Tests For 87.0.4280.149",
         "resultdb": {
           "enable": true
         },
@@ -1312,7 +1312,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M87",
-              "revision": "version:87.0.4280.148"
+              "revision": "version:87.0.4280.149"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -1375,11 +1375,11 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "weblayer_instrumentation_test_versions_apk_Client Tests For 88.0.4324.85"
+            "weblayer_instrumentation_test_versions_apk_Client Tests For 88.0.4324.86"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
-        "name": "weblayer_instrumentation_test_versions_apk_Client Tests For 88.0.4324.85",
+        "name": "weblayer_instrumentation_test_versions_apk_Client Tests For 88.0.4324.86",
         "resultdb": {
           "enable": true
         },
@@ -1389,7 +1389,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M88",
-              "revision": "version:88.0.4324.85"
+              "revision": "version:88.0.4324.86"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -1452,11 +1452,11 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "weblayer_instrumentation_test_versions_apk_Implementation Tests For 87.0.4280.148"
+            "weblayer_instrumentation_test_versions_apk_Implementation Tests For 87.0.4280.149"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
-        "name": "weblayer_instrumentation_test_versions_apk_Implementation Tests For 87.0.4280.148",
+        "name": "weblayer_instrumentation_test_versions_apk_Implementation Tests For 87.0.4280.149",
         "resultdb": {
           "enable": true
         },
@@ -1466,7 +1466,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M87",
-              "revision": "version:87.0.4280.148"
+              "revision": "version:87.0.4280.149"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -1529,11 +1529,11 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "weblayer_instrumentation_test_versions_apk_Implementation Tests For 88.0.4324.85"
+            "weblayer_instrumentation_test_versions_apk_Implementation Tests For 88.0.4324.86"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
-        "name": "weblayer_instrumentation_test_versions_apk_Implementation Tests For 88.0.4324.85",
+        "name": "weblayer_instrumentation_test_versions_apk_Implementation Tests For 88.0.4324.86",
         "resultdb": {
           "enable": true
         },
@@ -1543,7 +1543,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M88",
-              "revision": "version:88.0.4324.85"
+              "revision": "version:88.0.4324.86"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -1827,11 +1827,11 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "weblayer_instrumentation_test_versions_apk_Client Tests For 87.0.4280.148"
+            "weblayer_instrumentation_test_versions_apk_Client Tests For 87.0.4280.149"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
-        "name": "weblayer_instrumentation_test_versions_apk_Client Tests For 87.0.4280.148",
+        "name": "weblayer_instrumentation_test_versions_apk_Client Tests For 87.0.4280.149",
         "resultdb": {
           "enable": true
         },
@@ -1841,7 +1841,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M87",
-              "revision": "version:87.0.4280.148"
+              "revision": "version:87.0.4280.149"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -1904,11 +1904,11 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "weblayer_instrumentation_test_versions_apk_Client Tests For 88.0.4324.85"
+            "weblayer_instrumentation_test_versions_apk_Client Tests For 88.0.4324.86"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
-        "name": "weblayer_instrumentation_test_versions_apk_Client Tests For 88.0.4324.85",
+        "name": "weblayer_instrumentation_test_versions_apk_Client Tests For 88.0.4324.86",
         "resultdb": {
           "enable": true
         },
@@ -1918,7 +1918,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M88",
-              "revision": "version:88.0.4324.85"
+              "revision": "version:88.0.4324.86"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -1981,11 +1981,11 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "weblayer_instrumentation_test_versions_apk_Implementation Tests For 87.0.4280.148"
+            "weblayer_instrumentation_test_versions_apk_Implementation Tests For 87.0.4280.149"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
-        "name": "weblayer_instrumentation_test_versions_apk_Implementation Tests For 87.0.4280.148",
+        "name": "weblayer_instrumentation_test_versions_apk_Implementation Tests For 87.0.4280.149",
         "resultdb": {
           "enable": true
         },
@@ -1995,7 +1995,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M87",
-              "revision": "version:87.0.4280.148"
+              "revision": "version:87.0.4280.149"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -2058,11 +2058,11 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "weblayer_instrumentation_test_versions_apk_Implementation Tests For 88.0.4324.85"
+            "weblayer_instrumentation_test_versions_apk_Implementation Tests For 88.0.4324.86"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
-        "name": "weblayer_instrumentation_test_versions_apk_Implementation Tests For 88.0.4324.85",
+        "name": "weblayer_instrumentation_test_versions_apk_Implementation Tests For 88.0.4324.86",
         "resultdb": {
           "enable": true
         },
@@ -2072,7 +2072,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M88",
-              "revision": "version:88.0.4324.85"
+              "revision": "version:88.0.4324.86"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
diff --git a/testing/buildbot/chromium.android.json b/testing/buildbot/chromium.android.json
index 2c3bc48..fe2e308 100644
--- a/testing/buildbot/chromium.android.json
+++ b/testing/buildbot/chromium.android.json
@@ -41673,7 +41673,6 @@
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices"
         ],
-        "experiment_percentage": 100,
         "merge": {
           "args": [
             "--bucket",
@@ -41726,7 +41725,7 @@
           "--git-revision=${got_revision}",
           "--test-launcher-filter-file=../../testing/buildbot/filters/android.pie_arm64_rel.chrome_public_test_apk.filter"
         ],
-        "experiment_percentage": 50,
+        "experiment_percentage": 100,
         "merge": {
           "args": [
             "--bucket",
@@ -42084,7 +42083,6 @@
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices"
         ],
-        "experiment_percentage": 100,
         "merge": {
           "args": [
             "--bucket",
@@ -42242,7 +42240,6 @@
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices"
         ],
-        "experiment_percentage": 100,
         "merge": {
           "args": [
             "--bucket",
diff --git a/testing/buildbot/filters/android.pie_arm64_rel.chrome_public_test_apk.filter b/testing/buildbot/filters/android.pie_arm64_rel.chrome_public_test_apk.filter
index f65bd5b5..6d6df893e 100644
--- a/testing/buildbot/filters/android.pie_arm64_rel.chrome_public_test_apk.filter
+++ b/testing/buildbot/filters/android.pie_arm64_rel.chrome_public_test_apk.filter
@@ -83,3 +83,13 @@
 
 # crbug.com/1128665
 -org.chromium.chrome.browser.download.DownloadTest.testDuplicateHttpPostDownload_Cancel__UseDownloadOfflineContentProviderEnabled
+
+# crbug.com/1165449
+-org.chromium.chrome.browser.firstrun.TosAndUmaFirstRunFragmentWithEnterpriseSupportTest.testAcceptTosWithoutCrashUpload
+
+# crbug.com/1165456
+-org.chromium.chrome.browser.paint_preview.StartupPaintPreviewTest.testRemoveOnActionbarClick
+-org.chromium.chrome.browser.paint_preview.StartupPaintPreviewTest.testSnackbarShow
+
+# crbug.com/1165459
+-org.chromium.chrome.browser.share.long_screenshots.LongScreenshotsEntryTest.testCaptured
diff --git a/testing/buildbot/test_suite_exceptions.pyl b/testing/buildbot/test_suite_exceptions.pyl
index dd06abf..1c58499 100644
--- a/testing/buildbot/test_suite_exceptions.pyl
+++ b/testing/buildbot/test_suite_exceptions.pyl
@@ -729,12 +729,6 @@
     'remove_from': [
       'android-code-coverage-native', # https://crbug.com/1018780
     ],
-    # TODO(crbug.com/1111436): Remove experimental when it works fine.
-    'modifications': {
-      'android-pie-arm64-rel': {
-          'experiment_percentage': 100,
-      },
-    },
   },
   'chrome_public_test_apk': {
     'remove_from': [
@@ -795,7 +789,7 @@
           '--test-launcher-filter-file=../../testing/buildbot/filters/android.pie_arm64_rel.chrome_public_test_apk.filter', # https://crbug.com/1010211
         ],
         # TODO(crbug.com/1111436): Remove experimental when it works fine.
-        'experiment_percentage': 50,
+        'experiment_percentage': 100,
       },
       'android-pie-x86-rel': {
         'args': [
@@ -2935,14 +2929,6 @@
       },
     },
   },
-  # TODO(crbug.com/1111436): Remove this once it works fine.
-  'weblayer_unittests': {
-    'modifications': {
-      'android-pie-arm64-rel': {
-        'experiment_percentage': 100,
-      },
-    },
-  },
   'webview_cts_tests': {
     'modifications': {
       'android-pie-arm64-rel': {
@@ -2993,14 +2979,6 @@
       },
     },
   },
-  # TODO(crbug.com/1111436): Remove this once it works fine.
-  'webview_ui_test_app_test_apk': {
-    'modifications': {
-      'android-pie-arm64-rel': {
-        'experiment_percentage': 100,
-      },
-    },
-  },
   'xr_browser_tests': {
     'remove_from': [
       # This exception probably needs to stay due to lack of capacity
diff --git a/testing/buildbot/variants.pyl b/testing/buildbot/variants.pyl
index d4b12e4..75a243d 100644
--- a/testing/buildbot/variants.pyl
+++ b/testing/buildbot/variants.pyl
@@ -319,13 +319,13 @@
       '../../weblayer/browser/android/javatests/skew/expectations.txt',
       '--impl-version=88',
     ],
-    'identifier': 'Implementation Tests For 88.0.4324.85',
+    'identifier': 'Implementation Tests For 88.0.4324.86',
     'swarming': {
       'cipd_packages': [
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M88',
-          'revision': 'version:88.0.4324.85',
+          'revision': 'version:88.0.4324.86',
         }
       ],
     },
@@ -342,13 +342,13 @@
       '../../weblayer/browser/android/javatests/skew/expectations.txt',
       '--impl-version=87',
     ],
-    'identifier': 'Implementation Tests For 87.0.4280.148',
+    'identifier': 'Implementation Tests For 87.0.4280.149',
     'swarming': {
       'cipd_packages': [
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M87',
-          'revision': 'version:87.0.4280.148',
+          'revision': 'version:87.0.4280.149',
         }
       ],
     },
@@ -388,13 +388,13 @@
       '../../weblayer/browser/android/javatests/skew/expectations.txt',
       '--client-version=88',
     ],
-    'identifier': 'Client Tests For 88.0.4324.85',
+    'identifier': 'Client Tests For 88.0.4324.86',
     'swarming': {
       'cipd_packages': [
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M88',
-          'revision': 'version:88.0.4324.85',
+          'revision': 'version:88.0.4324.86',
         }
       ],
     },
@@ -411,13 +411,13 @@
       '../../weblayer/browser/android/javatests/skew/expectations.txt',
       '--client-version=87',
     ],
-    'identifier': 'Client Tests For 87.0.4280.148',
+    'identifier': 'Client Tests For 87.0.4280.149',
     'swarming': {
       'cipd_packages': [
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M87',
-          'revision': 'version:87.0.4280.148',
+          'revision': 'version:87.0.4280.149',
         }
       ],
     },
diff --git a/testing/scripts/get_compile_targets.py b/testing/scripts/get_compile_targets.py
index 9aadc7c..8f3c6ae 100755
--- a/testing/scripts/get_compile_targets.py
+++ b/testing/scripts/get_compile_targets.py
@@ -33,7 +33,8 @@
                     'PRESUBMIT.py',
                     'sizes_common.py',
                     'wpt_common.py',
-                    'wpt_common_unittest.py'):
+                    'wpt_common_unittest.py',
+                    'run_performance_tests_unittest.py'):
       continue
 
     with common.temporary_file() as tempfile_path:
diff --git a/testing/scripts/run_performance_tests.py b/testing/scripts/run_performance_tests.py
index 2b86257..c06c954 100755
--- a/testing/scripts/run_performance_tests.py
+++ b/testing/scripts/run_performance_tests.py
@@ -419,10 +419,37 @@
       if 'end' in self._story_selection_config:
         selection_args.append('--story-shard-end-index=%d' % (
             self._story_selection_config['end']))
+      if 'sections' in self._story_selection_config:
+        range_string = self._generate_story_index_ranges(
+            self._story_selection_config['sections'])
+        if range_string:
+          selection_args.append('--story-shard-indexes=%s' % range_string)
       if self._story_selection_config.get('abridged', True):
         selection_args.append('--run-abridged-story-set')
     return selection_args
 
+
+  def _generate_story_index_ranges(self, sections):
+    range_string = ''
+    for section in sections:
+      begin = section.get('begin', '')
+      end = section.get('end', '')
+      # If there only one story in the range, we only keep its index.
+      # In general, we expect either begin or end, or both.
+      if begin != '' and end != '' and end - begin == 1:
+        new_range = str(begin)
+      elif begin != '' or end != '':
+        new_range = '%s-%s' % (str(begin), str(end))
+      else:
+        raise ValueError('Index ranges in "sections" in shard map should have'
+                         'at least one of "begin" and "end": %s' % str(section))
+      if range_string:
+        range_string += ',%s' % new_range
+      else:
+        range_string = new_range
+    return range_string
+
+
   def _generate_reference_build_args(self):
     if self._is_reference:
       reference_browser_flag = '--browser=reference'
diff --git a/testing/scripts/run_performance_tests_unittest.py b/testing/scripts/run_performance_tests_unittest.py
new file mode 100644
index 0000000..5389941c4
--- /dev/null
+++ b/testing/scripts/run_performance_tests_unittest.py
@@ -0,0 +1,80 @@
+# Copyright (c) 2021 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import unittest
+import json
+
+import run_performance_tests
+from run_performance_tests import TelemetryCommandGenerator
+
+# The path where the output of a wpt run was written. This is the file that
+# gets processed by BaseWptScriptAdapter.
+OUTPUT_JSON_FILENAME = "out.json"
+
+
+class TelemetryCommandGeneratorTest(unittest.TestCase):
+  def setUp(self):
+    fake_args = [
+        './run_benchmark',
+        '--isolated-script-test-output=output.json'
+    ]
+    self._fake_options = run_performance_tests.parse_arguments(fake_args)
+
+  def testStorySelectionBeginEnd(self):
+    story_selection_config = json.loads(
+        '{"begin": 11, "end": 21, "abridged": false}')
+    generator = TelemetryCommandGenerator(
+        'benchmark_name', self._fake_options, story_selection_config
+    )
+    command = generator.generate('output_dir')
+    self.assertIn('--story-shard-begin-index=11', command)
+    self.assertIn('--story-shard-end-index=21', command)
+    self.assertNotIn('--run-abridged-story-set', command)
+
+
+  def testStorySelectionAbridgedDefault(self):
+    story_selection_config = json.loads(
+        '{"begin": 11, "end": 21}')
+    generator = TelemetryCommandGenerator(
+        'benchmark_name', self._fake_options, story_selection_config
+    )
+    command = generator.generate('output_dir')
+    self.assertIn('--run-abridged-story-set', command)
+
+  def testStorySelectionIndexSectionsSingleIndex(self):
+    story_selection_config = json.loads(
+        '{"sections": [{"begin": 11, "end": 21}, {"begin": 25, "end": 26}]}')
+    generator = TelemetryCommandGenerator(
+        'benchmark_name', self._fake_options, story_selection_config
+    )
+    command = generator.generate('output_dir')
+    self.assertIn('--story-shard-indexes=11-21,25', command)
+
+  def testStorySelectionIndexSectionsOpenEnds(self):
+    story_selection_config = json.loads(
+        '{"sections": [{"end": 10}, {"begin": 15, "end": 16}, {"begin": 20}]}')
+    generator = TelemetryCommandGenerator(
+        'benchmark_name', self._fake_options, story_selection_config
+    )
+    command = generator.generate('output_dir')
+    self.assertIn('--story-shard-indexes=-10,15,20-', command)
+
+
+  def testStorySelectionIndexSectionsIllegalRange(self):
+    with self.assertRaises(ValueError):
+      story_selection_config = json.loads(
+          '{"sections": [{"begin": 15, "end": 16}, {"foo": "bar"}]}')
+      generator = TelemetryCommandGenerator(
+          'benchmark_name', self._fake_options, story_selection_config
+      )
+      generator.generate('output_dir')
+
+  def testStorySelectionIndexSectionsEmpty(self):
+    story_selection_config = json.loads(
+        '{"sections": []}')
+    generator = TelemetryCommandGenerator(
+        'benchmark_name', self._fake_options, story_selection_config
+    )
+    command = generator.generate('output_dir')
+    self.assertNotIn('--story-shard-indexes=', command)
\ No newline at end of file
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index e9d7034..8ce22d35 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -1658,21 +1658,6 @@
             ]
         }
     ],
-    "ChromeOSLauncherAssistantIntegration": [
-        {
-            "platforms": [
-                "chromeos"
-            ],
-            "experiments": [
-                {
-                    "name": "Enabled",
-                    "enable_features": [
-                        "EnableEmbeddedAssistantUI"
-                    ]
-                }
-            ]
-        }
-    ],
     "ChromeOSNeuralPalm": [
         {
             "platforms": [
@@ -4674,6 +4659,28 @@
             ]
         }
     ],
+    "MixedFormInterstitial": [
+        {
+            "platforms": [
+                "android",
+                "chromeos",
+                "linux",
+                "mac",
+                "windows"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "params": {
+                        "mode": "include-redirects-with-form-data"
+                    },
+                    "enable_features": [
+                        "InsecureFormSubmissionInterstitial"
+                    ]
+                }
+            ]
+        }
+    ],
     "MobileIdentityConsistency": [
         {
             "platforms": [
diff --git a/third_party/blink/public/platform/web_encrypted_media_types.h b/third_party/blink/public/platform/web_encrypted_media_types.h
index 2cca6c2..08f2d520 100644
--- a/third_party/blink/public/platform/web_encrypted_media_types.h
+++ b/third_party/blink/public/platform/web_encrypted_media_types.h
@@ -8,11 +8,13 @@
 namespace blink {
 
 // From https://w3c.github.io/encrypted-media/#idl-def-MediaKeySessionType
+// Reported to UMA. Do not change values of existing enums.
 enum class WebEncryptedMediaSessionType {
-  kUnknown,
-  kTemporary,
-  kPersistentLicense,
-  kPersistentUsageRecord,
+  kUnknown = 0,
+  kTemporary = 1,
+  kPersistentLicense = 2,
+  kPersistentUsageRecord = 3,
+  kMaxValue = kPersistentUsageRecord,
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/bindings/idl_in_modules.gni b/third_party/blink/renderer/bindings/idl_in_modules.gni
index 29ef3b5..f2ce1e2 100644
--- a/third_party/blink/renderer/bindings/idl_in_modules.gni
+++ b/third_party/blink/renderer/bindings/idl_in_modules.gni
@@ -870,6 +870,7 @@
           "//third_party/blink/renderer/modules/webgpu/gpu_blend_descriptor.idl",
           "//third_party/blink/renderer/modules/webgpu/gpu_buffer.idl",
           "//third_party/blink/renderer/modules/webgpu/gpu_buffer_binding.idl",
+          "//third_party/blink/renderer/modules/webgpu/gpu_buffer_binding_layout.idl",
           "//third_party/blink/renderer/modules/webgpu/gpu_buffer_copy_view.idl",
           "//third_party/blink/renderer/modules/webgpu/gpu_buffer_descriptor.idl",
           "//third_party/blink/renderer/modules/webgpu/gpu_buffer_usage.idl",
@@ -921,14 +922,17 @@
           "//third_party/blink/renderer/modules/webgpu/gpu_render_pipeline_descriptor.idl",
           "//third_party/blink/renderer/modules/webgpu/gpu_request_adapter_options.idl",
           "//third_party/blink/renderer/modules/webgpu/gpu_sampler.idl",
+          "//third_party/blink/renderer/modules/webgpu/gpu_sampler_binding_layout.idl",
           "//third_party/blink/renderer/modules/webgpu/gpu_sampler_descriptor.idl",
           "//third_party/blink/renderer/modules/webgpu/gpu_shader_module.idl",
           "//third_party/blink/renderer/modules/webgpu/gpu_shader_module_descriptor.idl",
           "//third_party/blink/renderer/modules/webgpu/gpu_shader_stage.idl",
           "//third_party/blink/renderer/modules/webgpu/gpu_stencil_state_face_descriptor.idl",
+          "//third_party/blink/renderer/modules/webgpu/gpu_storage_texture_binding_layout.idl",
           "//third_party/blink/renderer/modules/webgpu/gpu_swap_chain.idl",
           "//third_party/blink/renderer/modules/webgpu/gpu_swap_chain_descriptor.idl",
           "//third_party/blink/renderer/modules/webgpu/gpu_texture.idl",
+          "//third_party/blink/renderer/modules/webgpu/gpu_texture_binding_layout.idl",
           "//third_party/blink/renderer/modules/webgpu/gpu_texture_copy_view.idl",
           "//third_party/blink/renderer/modules/webgpu/gpu_texture_data_layout.idl",
           "//third_party/blink/renderer/modules/webgpu/gpu_texture_descriptor.idl",
diff --git a/third_party/blink/renderer/core/dom/text.cc b/third_party/blink/renderer/core/dom/text.cc
index e817688..2a4619a 100644
--- a/third_party/blink/renderer/core/dom/text.cc
+++ b/third_party/blink/renderer/core/dom/text.cc
@@ -124,8 +124,13 @@
   if (exception_state.HadException())
     return nullptr;
 
-  if (GetLayoutObject())
+  if (GetLayoutObject()) {
     GetLayoutObject()->SetTextWithOffset(DataImpl(), 0, old_str.length());
+    if (data().IsEmpty()) {
+      // To avoid |LayoutText| has empty text, we rebuild layout tree.
+      SetForceReattachLayoutTree();
+    }
+  }
 
   if (parentNode())
     GetDocument().DidSplitTextNode(*this);
diff --git a/third_party/blink/renderer/core/dom/text_test.cc b/third_party/blink/renderer/core/dom/text_test.cc
index da82754..12a800a 100644
--- a/third_party/blink/renderer/core/dom/text_test.cc
+++ b/third_party/blink/renderer/core/dom/text_test.cc
@@ -4,6 +4,7 @@
 
 #include "third_party/blink/renderer/core/dom/text.h"
 
+#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_testing.h"
 #include "third_party/blink/renderer/core/dom/range.h"
 #include "third_party/blink/renderer/core/editing/testing/editing_test_base.h"
 #include "third_party/blink/renderer/core/html/html_pre_element.h"
@@ -40,6 +41,22 @@
   EXPECT_FALSE(text->GetLayoutObject()->IsTextFragment());
 }
 
+TEST_F(TextTest, splitTextToEmpty) {
+  V8TestingScope scope;
+
+  SetBodyContent("<p id=sample>ab</p>");
+  const Element& sample = *GetElementById("sample");
+  Text& text = *To<Text>(sample.firstChild());
+  // |new_text| is after |text|.
+  Text& new_text = *text.splitText(0, ASSERT_NO_EXCEPTION);
+
+  UpdateAllLifecyclePhasesForTest();
+  EXPECT_EQ("", text.data());
+  EXPECT_FALSE(text.GetLayoutObject());
+  EXPECT_EQ("ab", new_text.data());
+  EXPECT_TRUE(new_text.GetLayoutObject());
+}
+
 TEST_F(TextTest, TextLayoutObjectIsNeeded_CannotHaveChildren) {
   SetBodyContent("<img id=image>");
   UpdateAllLifecyclePhasesForTest();
diff --git a/third_party/blink/renderer/core/html/canvas/canvas_rendering_context.cc b/third_party/blink/renderer/core/html/canvas/canvas_rendering_context.cc
index 7d1b69e..5906a3a 100644
--- a/third_party/blink/renderer/core/html/canvas/canvas_rendering_context.cc
+++ b/third_party/blink/renderer/core/html/canvas/canvas_rendering_context.cc
@@ -40,50 +40,8 @@
     CanvasRenderingContextHost* host,
     const CanvasContextCreationAttributesCore& attrs)
     : host_(host),
-      color_params_(CanvasColorSpace::kSRGB,
-                    CanvasPixelFormat::kUint8,
-                    kNonOpaque),
-      creation_attributes_(attrs) {
-  if (creation_attributes_.pixel_format == kF16CanvasPixelFormatName)
-    color_params_.SetCanvasPixelFormat(CanvasPixelFormat::kF16);
-
-  if (creation_attributes_.color_space == kRec2020CanvasColorSpaceName)
-    color_params_.SetCanvasColorSpace(CanvasColorSpace::kRec2020);
-  else if (creation_attributes_.color_space == kP3CanvasColorSpaceName)
-    color_params_.SetCanvasColorSpace(CanvasColorSpace::kP3);
-
-  if (!creation_attributes_.alpha)
-    color_params_.SetOpacityMode(kOpaque);
-
-  // Make creation_attributes_ reflect the effective color_space and
-  // pixel_format rather than the requested one.
-  creation_attributes_.color_space = ColorSpaceAsString();
-  creation_attributes_.pixel_format = PixelFormatAsString();
-}
-
-WTF::String CanvasRenderingContext::ColorSpaceAsString() const {
-  switch (color_params_.ColorSpace()) {
-    case CanvasColorSpace::kSRGB:
-      return kSRGBCanvasColorSpaceName;
-    case CanvasColorSpace::kRec2020:
-      return kRec2020CanvasColorSpaceName;
-    case CanvasColorSpace::kP3:
-      return kP3CanvasColorSpaceName;
-  };
-  CHECK(false);
-  return "";
-}
-
-WTF::String CanvasRenderingContext::PixelFormatAsString() const {
-  switch (color_params_.PixelFormat()) {
-    case CanvasPixelFormat::kF16:
-      return kF16CanvasPixelFormatName;
-    case CanvasPixelFormat::kUint8:
-      return kUint8CanvasPixelFormatName;
-  };
-  CHECK(false);
-  return "";
-}
+      color_params_(attrs.color_space, attrs.pixel_format, attrs.alpha),
+      creation_attributes_(attrs) {}
 
 void CanvasRenderingContext::Dispose() {
   StopListeningForDidProcessTask();
diff --git a/third_party/blink/renderer/core/html/canvas/canvas_rendering_context.h b/third_party/blink/renderer/core/html/canvas/canvas_rendering_context.h
index d81cd42..89fc3ed 100644
--- a/third_party/blink/renderer/core/html/canvas/canvas_rendering_context.h
+++ b/third_party/blink/renderer/core/html/canvas/canvas_rendering_context.h
@@ -90,9 +90,6 @@
 
   CanvasRenderingContextHost* Host() const { return host_; }
 
-  WTF::String ColorSpaceAsString() const;
-  WTF::String PixelFormatAsString() const;
-
   const CanvasColorParams& CanvasRenderingContextColorParams() const {
     return color_params_;
   }
diff --git a/third_party/blink/renderer/core/html/forms/range_input_type.cc b/third_party/blink/renderer/core/html/forms/range_input_type.cc
index 5c3933f..758e6f7 100644
--- a/third_party/blink/renderer/core/html/forms/range_input_type.cc
+++ b/third_party/blink/renderer/core/html/forms/range_input_type.cc
@@ -194,12 +194,8 @@
       std::max((step_range.Maximum() - step_range.Minimum()) / 10, step);
 
   TextDirection dir = TextDirection::kLtr;
-  bool is_vertical = false;
   if (GetElement().GetLayoutObject()) {
     dir = ComputedTextDirection();
-    ControlPart part =
-        GetElement().GetLayoutObject()->Style()->EffectiveAppearance();
-    is_vertical = part == kSliderVerticalPart;
   }
 
   Decimal new_value;
@@ -208,19 +204,17 @@
   } else if (key == "ArrowDown") {
     new_value = current - step;
   } else if (key == "ArrowLeft") {
-    new_value = (is_vertical || dir == TextDirection::kRtl) ? current + step
-                                                            : current - step;
+    new_value = dir == TextDirection::kRtl ? current + step : current - step;
   } else if (key == "ArrowRight") {
-    new_value = (is_vertical || dir == TextDirection::kRtl) ? current - step
-                                                            : current + step;
+    new_value = dir == TextDirection::kRtl ? current - step : current + step;
   } else if (key == "PageUp") {
     new_value = current + big_step;
   } else if (key == "PageDown") {
     new_value = current - big_step;
   } else if (key == "Home") {
-    new_value = is_vertical ? step_range.Maximum() : step_range.Minimum();
+    new_value = step_range.Minimum();
   } else if (key == "End") {
-    new_value = is_vertical ? step_range.Minimum() : step_range.Maximum();
+    new_value = step_range.Maximum();
   } else {
     return;  // Did not match any key binding.
   }
diff --git a/third_party/blink/renderer/core/imagebitmap/image_bitmap.cc b/third_party/blink/renderer/core/imagebitmap/image_bitmap.cc
index 27ffb7ba..358b0a5a 100644
--- a/third_party/blink/renderer/core/imagebitmap/image_bitmap.cc
+++ b/third_party/blink/renderer/core/imagebitmap/image_bitmap.cc
@@ -37,7 +37,6 @@
 #include "third_party/skia/include/core/SkImageInfo.h"
 #include "third_party/skia/include/core/SkSurface.h"
 #include "third_party/skia/include/core/SkSwizzle.h"
-#include "third_party/skia/include/effects/SkColorFilterImageFilter.h"
 
 namespace blink {
 
diff --git a/third_party/blink/renderer/core/layout/layout_text_fragment_test.cc b/third_party/blink/renderer/core/layout/layout_text_fragment_test.cc
index cb8cc1f..e23db67a 100644
--- a/third_party/blink/renderer/core/layout/layout_text_fragment_test.cc
+++ b/third_party/blink/renderer/core/layout/layout_text_fragment_test.cc
@@ -424,8 +424,8 @@
                   ->IsRemainingTextLayoutObject());
   ASSERT_TRUE(letter_a.GetLayoutObject()->GetFirstLetterPart());
   EXPECT_EQ("a", letter_a.GetLayoutObject()->GetFirstLetterPart()->GetText());
-  EXPECT_TRUE(letter_x.GetLayoutObject()->IsTextFragment());
-  EXPECT_FALSE(letter_x.GetLayoutObject()->GetFirstLetterPart());
+  EXPECT_FALSE(letter_x.GetLayoutObject())
+      << "We don't have layout text for empty Text node.";
 
   // Make <div>"x" "a"</div>
   letter_x.setTextContent("x");
diff --git a/third_party/blink/renderer/core/layout/line/breaking_context_inline_headers.h b/third_party/blink/renderer/core/layout/line/breaking_context_inline_headers.h
index 64aec99..01443a3 100644
--- a/third_party/blink/renderer/core/layout/line/breaking_context_inline_headers.h
+++ b/third_party/blink/renderer/core/layout/line/breaking_context_inline_headers.h
@@ -207,6 +207,7 @@
   bool ignoring_spaces_;
   bool current_character_is_space_;
   bool is_space_or_other_space_separator_;
+  bool previous_is_space_or_other_space_separator_;
   bool previous_character_is_space_;
   bool has_former_opportunity_;
   unsigned current_start_offset_;  // initial offset for the current text
@@ -1077,11 +1078,10 @@
   UChar last_character = layout_text_info_.line_break_iterator_.LastCharacter();
   UChar second_to_last_character =
       layout_text_info_.line_break_iterator_.SecondToLastCharacter();
-  bool previous_is_space_or_other_space_separator = false;
   for (; current_.Offset() < layout_text.TextLength();
        current_.FastIncrementInTextNode()) {
     previous_character_is_space_ = current_character_is_space_;
-    previous_is_space_or_other_space_separator =
+    previous_is_space_or_other_space_separator_ =
         is_space_or_other_space_separator_;
     UChar c = current_.Current();
     SetCurrentCharacterIsSpace(c);
@@ -1145,7 +1145,7 @@
       }
 
       PrepareForNextCharacter(layout_text, prohibit_break_inside,
-                              previous_is_space_or_other_space_separator);
+                              previous_is_space_or_other_space_separator_);
       at_start_ = false;
       NextCharacter(c, last_character, second_to_last_character);
       continue;
@@ -1185,7 +1185,7 @@
     // We keep track of the total width contributed by trailing space as we
     // often want to exclude it when determining
     // if a run fits on a line.
-    if (collapse_white_space_ && previous_is_space_or_other_space_separator &&
+    if (collapse_white_space_ && previous_is_space_or_other_space_separator_ &&
         is_space_or_other_space_separator_ && last_width_measurement)
       width_.SetTrailingWhitespaceWidth(last_width_measurement);
 
@@ -1250,9 +1250,9 @@
     if (CanBreakAtWhitespace(
             break_words, word_measurement, stopped_ignoring_spaces, char_width,
             hyphenated, disable_soft_hyphen, hyphen_width, between_words,
-            mid_word_break, can_break_mid_word, previous_character_is_space_,
-            last_width_measurement, layout_text, font, apply_word_spacing,
-            word_spacing))
+            mid_word_break, can_break_mid_word,
+            previous_is_space_or_other_space_separator_, last_width_measurement,
+            layout_text, font, apply_word_spacing, word_spacing))
       return false;
 
     // If there is a hard-break available at this whitespace position then take
@@ -1304,7 +1304,7 @@
     }
 
     PrepareForNextCharacter(layout_text, prohibit_break_inside,
-                            previous_is_space_or_other_space_separator);
+                            previous_is_space_or_other_space_separator_);
     at_start_ = false;
     is_line_empty = line_info_.IsEmpty();
     NextCharacter(c, last_character, second_to_last_character);
@@ -1461,7 +1461,7 @@
   DCHECK(curr_ws_ == EWhiteSpace::kBreakSpaces);
   // Avoid breaking before the first white-space after a word if there is a
   // breaking opportunity before.
-  if (has_former_opportunity_ && !previous_character_is_space_)
+  if (has_former_opportunity_ && !previous_is_space_or_other_space_separator_)
     return;
 
   line_break_.MoveTo(current_.GetLineLayoutItem(), current_.Offset(),
@@ -1469,7 +1469,7 @@
 
   // Avoid breaking before the first white-space after a word, unless
   // overflow-wrap or word-break allow to.
-  if (!previous_character_is_space_ && !can_break_mid_word)
+  if (!previous_is_space_or_other_space_separator_ && !can_break_mid_word)
     line_break_.Increment();
 }
 
diff --git a/third_party/blink/renderer/core/loader/document_loader.cc b/third_party/blink/renderer/core/loader/document_loader.cc
index 5705f06..6160373 100644
--- a/third_party/blink/renderer/core/loader/document_loader.cc
+++ b/third_party/blink/renderer/core/loader/document_loader.cc
@@ -2001,8 +2001,10 @@
     CountUse(
         WebFeature::kCertificateTransparencyNonCompliantResourceInSubframe);
   }
-  if (RuntimeEnabledFeatures::ForceLoadAtTopEnabled(frame_->DomWindow()))
+  if (frame_->DomWindow()->IsFeatureEnabled(
+          mojom::blink::DocumentPolicyFeature::kForceLoadAtTop)) {
     CountUse(WebFeature::kForceLoadAtTop);
+  }
 
   if (response_.IsSignedExchangeInnerResponse()) {
     CountUse(WebFeature::kSignedExchangeInnerResponse);
diff --git a/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor_metrics_test.cc b/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor_metrics_test.cc
index e27c98d..cd401fb 100644
--- a/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor_metrics_test.cc
+++ b/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor_metrics_test.cc
@@ -1620,4 +1620,60 @@
   EXPECT_TRUE(ukm_recorder()->GetEntryMetric(entry, kSourceUkmMetric));
 }
 
+// Tests that loading a page that has a ForceLoadAtTop DocumentPolicy invokes
+// the UseCounter.
+TEST_F(TextFragmentAnchorMetricsTest, ForceLoadAtTopUseCounter) {
+  SimRequest::Params params;
+  params.response_http_headers.insert("Document-Policy", "force-load-at-top");
+  SimRequest request("https://example.com/test.html", "text/html", params);
+  LoadURL("https://example.com/test.html");
+  request.Complete(R"HTML(
+    <!DOCTYPE html>
+    <p>This is a test page</p>
+  )HTML");
+  RunAsyncMatchingTasks();
+
+  // Render two frames to handle the async step added by the beforematch event.
+  Compositor().BeginFrame();
+  BeginEmptyFrame();
+
+  EXPECT_TRUE(GetDocument().IsUseCounted(WebFeature::kForceLoadAtTop));
+}
+
+// Tests that loading a page that explicitly disables ForceLoadAtTop
+// DocumentPolicy or has no DocumentPolicy doesn't invoke the UseCounter for
+// ForceLoadAtTop.
+TEST_F(TextFragmentAnchorMetricsTest, NoForceLoadAtTopUseCounter) {
+  SimRequest::Params params;
+  params.response_http_headers.insert("Document-Policy",
+                                      "no-force-load-at-top");
+  SimRequest request("https://example.com/test.html", "text/html", params);
+  LoadURL("https://example.com/test.html");
+  request.Complete(R"HTML(
+    <!DOCTYPE html>
+    <p>This is a test page</p>
+  )HTML");
+  RunAsyncMatchingTasks();
+
+  // Render two frames to handle the async step added by the beforematch event.
+  Compositor().BeginFrame();
+  BeginEmptyFrame();
+
+  EXPECT_FALSE(GetDocument().IsUseCounted(WebFeature::kForceLoadAtTop));
+
+  // Try without any DocumentPolicy headers.
+  SimRequest request2("https://example.com/test2.html", "text/html");
+  LoadURL("https://example.com/test2.html");
+  request2.Complete(R"HTML(
+    <!DOCTYPE html>
+    <p>This is a different test page</p>
+  )HTML");
+  RunAsyncMatchingTasks();
+
+  Compositor().BeginFrame();
+  BeginEmptyFrame();
+
+  EXPECT_FALSE(GetDocument().IsUseCounted(WebFeature::kForceLoadAtTop));
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/paint/filter_effect_builder.cc b/third_party/blink/renderer/core/paint/filter_effect_builder.cc
index e5727f8b..55a0e131 100644
--- a/third_party/blink/renderer/core/paint/filter_effect_builder.cc
+++ b/third_party/blink/renderer/core/paint/filter_effect_builder.cc
@@ -122,12 +122,11 @@
 
 }  // namespace
 
-FilterEffectBuilder::FilterEffectBuilder(
-    const FloatRect& reference_box,
-    float zoom,
-    const PaintFlags* fill_flags,
-    const PaintFlags* stroke_flags,
-    SkBlurImageFilter::TileMode blur_tile_mode)
+FilterEffectBuilder::FilterEffectBuilder(const FloatRect& reference_box,
+                                         float zoom,
+                                         const PaintFlags* fill_flags,
+                                         const PaintFlags* stroke_flags,
+                                         SkTileMode blur_tile_mode)
     : reference_box_(reference_box),
       zoom_(zoom),
       shorthand_scale_(1),
diff --git a/third_party/blink/renderer/core/paint/filter_effect_builder.h b/third_party/blink/renderer/core/paint/filter_effect_builder.h
index e67d112..f8da253 100644
--- a/third_party/blink/renderer/core/paint/filter_effect_builder.h
+++ b/third_party/blink/renderer/core/paint/filter_effect_builder.h
@@ -31,7 +31,7 @@
 #include "third_party/blink/renderer/platform/graphics/paint/paint_flags.h"
 #include "third_party/blink/renderer/platform/heap/handle.h"
 #include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
-#include "third_party/skia/include/effects/SkBlurImageFilter.h"
+#include "third_party/skia/include/core/SkTileMode.h"
 
 namespace blink {
 
@@ -51,8 +51,7 @@
                       float zoom,
                       const PaintFlags* fill_flags = nullptr,
                       const PaintFlags* stroke_flags = nullptr,
-                      SkBlurImageFilter::TileMode blur_tile_mode =
-                          SkBlurImageFilter::kClampToBlack_TileMode);
+                      SkTileMode blur_tile_mode = SkTileMode::kDecal);
 
   Filter* BuildReferenceFilter(const ReferenceFilterOperation&,
                                FilterEffect* previous_effect,
@@ -73,7 +72,7 @@
   float shorthand_scale_;  // Scale factor for shorthand filter functions.
   const PaintFlags* fill_flags_;
   const PaintFlags* stroke_flags_;
-  const SkBlurImageFilter::TileMode blur_tile_mode_;
+  const SkTileMode blur_tile_mode_;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/paint/paint_layer.cc b/third_party/blink/renderer/core/paint/paint_layer.cc
index 2d5ce78..f0e65235 100644
--- a/third_party/blink/renderer/core/paint/paint_layer.cc
+++ b/third_party/blink/renderer/core/paint/paint_layer.cc
@@ -3536,7 +3536,7 @@
   // Use kClamp tile mode to avoid pixel moving filters bringing in black
   // transparent pixels from the viewport edge.
   return_value = FilterEffectBuilder(reference_box, zoom, nullptr, nullptr,
-                                     SkBlurImageFilter::kClamp_TileMode)
+                                     SkTileMode::kClamp)
                      .BuildFilterOperations(filter_operations);
   // Note that return_value may be empty here, if the |filter_operations| list
   // contains only invalid filters (e.g. invalid reference filters). See
diff --git a/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.cc b/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.cc
index 5e910dde..0d526ab 100644
--- a/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.cc
+++ b/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.cc
@@ -1908,12 +1908,10 @@
           ? CanvasPixelFormat::kF16
           : CanvasPixelFormat::kUint8,
       kNonOpaque);
-  CanvasColorParams context_color_params = CanvasColorParams(
-      GetCanvas2DColorParams().ColorSpace(), PixelFormat(), kNonOpaque);
-
-  if (data_color_params.ColorSpace() != context_color_params.ColorSpace() ||
-      data_color_params.PixelFormat() != context_color_params.PixelFormat() ||
-      PixelFormat() == CanvasPixelFormat::kF16) {
+  if (data_color_params.ColorSpace() != GetCanvas2DColorParams().ColorSpace() ||
+      data_color_params.PixelFormat() !=
+          GetCanvas2DColorParams().PixelFormat() ||
+      GetCanvas2DColorParams().PixelFormat() == CanvasPixelFormat::kF16) {
     SkImageInfo converted_info = data_pixmap.info();
     converted_info =
         converted_info.makeColorType(GetCanvas2DColorParams().GetSkColorType());
diff --git a/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.h b/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.h
index aefc301..57291ae 100644
--- a/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.h
+++ b/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.h
@@ -262,13 +262,6 @@
 
   virtual void WillDrawImage(CanvasImageSource*) const {}
 
-  virtual String ColorSpaceAsString() const {
-    return kSRGBCanvasColorSpaceName;
-  }
-  virtual CanvasPixelFormat PixelFormat() const {
-    return CanvasPixelFormat::kUint8;
-  }
-
   void RestoreMatrixClipStack(cc::PaintCanvas*) const;
 
   String textAlign() const;
diff --git a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.cc b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.cc
index 463a072..2ce496c5 100644
--- a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.cc
+++ b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.cc
@@ -307,10 +307,6 @@
   canvas()->WillDrawImageTo2DContext(source);
 }
 
-String CanvasRenderingContext2D::ColorSpaceAsString() const {
-  return CanvasRenderingContext::ColorSpaceAsString();
-}
-
 CanvasColorParams CanvasRenderingContext2D::GetCanvas2DColorParams() const {
   return CanvasRenderingContext::CanvasRenderingContextColorParams();
 }
@@ -330,10 +326,6 @@
     canvas()->GetCanvas2DLayerBridge()->WillOverwriteCanvas();
 }
 
-CanvasPixelFormat CanvasRenderingContext2D::PixelFormat() const {
-  return GetCanvas2DColorParams().PixelFormat();
-}
-
 void CanvasRenderingContext2D::Reset() {
   // This is a multiple inheritance bootstrap
   BaseRenderingContext2D::reset();
@@ -1134,8 +1126,8 @@
       CanvasRenderingContext2DSettings::Create();
   settings->setAlpha(CreationAttributes().alpha);
   if (RuntimeEnabledFeatures::CanvasColorManagementEnabled()) {
-    settings->setColorSpace(ColorSpaceAsString());
-    settings->setPixelFormat(PixelFormatAsString());
+    settings->setColorSpace(GetCanvas2DColorParams().GetColorSpaceAsString());
+    settings->setPixelFormat(GetCanvas2DColorParams().GetPixelFormatAsString());
   }
   settings->setDesynchronized(Host()->LowLatencyEnabled());
   if (RuntimeEnabledFeatures::NewCanvas2DAPIEnabled())
diff --git a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.h b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.h
index 6bfd11e..d54102f 100644
--- a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.h
+++ b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.h
@@ -271,9 +271,6 @@
     return CanvasRenderingContext::kContext2D;
   }
 
-  String ColorSpaceAsString() const override;
-  CanvasPixelFormat PixelFormat() const override;
-
   bool IsRenderingContext2D() const override { return true; }
   bool IsComposited() const override;
   bool IsAccelerated() const override;
diff --git a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d_state.cc b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d_state.cc
index 799c775..d72a9126 100644
--- a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d_state.cc
+++ b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d_state.cc
@@ -28,7 +28,6 @@
 #include "third_party/blink/renderer/platform/graphics/skia/skia_utils.h"
 #include "third_party/blink/renderer/platform/heap/heap.h"
 #include "third_party/skia/include/effects/SkDashPathEffect.h"
-#include "third_party/skia/include/effects/SkDropShadowImageFilter.h"
 
 static const char defaultFont[] = "10px sans-serif";
 static const char defaultFilter[] = "none";
@@ -497,24 +496,24 @@
 
 sk_sp<PaintFilter> CanvasRenderingContext2DState::ShadowOnlyImageFilter()
     const {
+  using ShadowMode = DropShadowPaintFilter::ShadowMode;
   if (!shadow_only_image_filter_) {
     const auto sigma = BlurRadiusToStdDev(shadow_blur_);
     shadow_only_image_filter_ = sk_make_sp<DropShadowPaintFilter>(
         shadow_offset_.Width(), shadow_offset_.Height(), sigma, sigma,
-        shadow_color_, SkDropShadowImageFilter::kDrawShadowOnly_ShadowMode,
-        nullptr);
+        shadow_color_, ShadowMode::kDrawShadowOnly, nullptr);
   }
   return shadow_only_image_filter_;
 }
 
 sk_sp<PaintFilter>
 CanvasRenderingContext2DState::ShadowAndForegroundImageFilter() const {
+  using ShadowMode = DropShadowPaintFilter::ShadowMode;
   if (!shadow_and_foreground_image_filter_) {
     const auto sigma = BlurRadiusToStdDev(shadow_blur_);
     shadow_and_foreground_image_filter_ = sk_make_sp<DropShadowPaintFilter>(
         shadow_offset_.Width(), shadow_offset_.Height(), sigma, sigma,
-        shadow_color_,
-        SkDropShadowImageFilter::kDrawShadowAndForeground_ShadowMode, nullptr);
+        shadow_color_, ShadowMode::kDrawShadowAndForeground, nullptr);
   }
   return shadow_and_foreground_image_filter_;
 }
diff --git a/third_party/blink/renderer/modules/canvas/offscreencanvas2d/offscreen_canvas_rendering_context_2d.cc b/third_party/blink/renderer/modules/canvas/offscreencanvas2d/offscreen_canvas_rendering_context_2d.cc
index c02500e..420f878f 100644
--- a/third_party/blink/renderer/modules/canvas/offscreencanvas2d/offscreen_canvas_rendering_context_2d.cc
+++ b/third_party/blink/renderer/modules/canvas/offscreencanvas2d/offscreen_canvas_rendering_context_2d.cc
@@ -316,14 +316,6 @@
   return Host()->ResourceProvider();
 }
 
-String OffscreenCanvasRenderingContext2D::ColorSpaceAsString() const {
-  return CanvasRenderingContext::ColorSpaceAsString();
-}
-
-CanvasPixelFormat OffscreenCanvasRenderingContext2D::PixelFormat() const {
-  return GetCanvas2DColorParams().PixelFormat();
-}
-
 CanvasColorParams OffscreenCanvasRenderingContext2D::GetCanvas2DColorParams()
     const {
   return CanvasRenderingContext::CanvasRenderingContextColorParams();
diff --git a/third_party/blink/renderer/modules/canvas/offscreencanvas2d/offscreen_canvas_rendering_context_2d.h b/third_party/blink/renderer/modules/canvas/offscreencanvas2d/offscreen_canvas_rendering_context_2d.h
index ee58136..42c9ddc 100644
--- a/third_party/blink/renderer/modules/canvas/offscreencanvas2d/offscreen_canvas_rendering_context_2d.h
+++ b/third_party/blink/renderer/modules/canvas/offscreencanvas2d/offscreen_canvas_rendering_context_2d.h
@@ -171,8 +171,6 @@
 
   scoped_refptr<CanvasResource> ProduceCanvasResource();
 
-  String ColorSpaceAsString() const override;
-  CanvasPixelFormat PixelFormat() const override;
   SkIRect dirty_rect_for_commit_;
 
   bool is_valid_size_ = false;
diff --git a/third_party/blink/renderer/modules/mediarecorder/audio_track_opus_encoder.cc b/third_party/blink/renderer/modules/mediarecorder/audio_track_opus_encoder.cc
index 626259c..18b3315 100644
--- a/third_party/blink/renderer/modules/mediarecorder/audio_track_opus_encoder.cc
+++ b/third_party/blink/renderer/modules/mediarecorder/audio_track_opus_encoder.cc
@@ -65,9 +65,11 @@
 
 AudioTrackOpusEncoder::AudioTrackOpusEncoder(
     OnEncodedAudioCB on_encoded_audio_cb,
-    int32_t bits_per_second)
+    int32_t bits_per_second,
+    bool vbr_enabled)
     : AudioTrackEncoder(std::move(on_encoded_audio_cb)),
       bits_per_second_(bits_per_second),
+      vbr_enabled_(vbr_enabled),
       opus_encoder_(nullptr) {}
 
 AudioTrackOpusEncoder::~AudioTrackOpusEncoder() {
@@ -144,6 +146,12 @@
     DLOG(ERROR) << "Failed to set Opus bitrate: " << bitrate;
     return;
   }
+
+  const opus_int32 vbr_enabled = static_cast<opus_int32>(vbr_enabled_);
+  if (opus_encoder_ctl(opus_encoder_, OPUS_SET_VBR(vbr_enabled)) != OPUS_OK) {
+    DLOG(ERROR) << "Failed to set Opus VBR mode: " << vbr_enabled;
+    return;
+  }
 }
 
 void AudioTrackOpusEncoder::EncodeAudio(
diff --git a/third_party/blink/renderer/modules/mediarecorder/audio_track_opus_encoder.h b/third_party/blink/renderer/modules/mediarecorder/audio_track_opus_encoder.h
index 3e74481..1541092 100644
--- a/third_party/blink/renderer/modules/mediarecorder/audio_track_opus_encoder.h
+++ b/third_party/blink/renderer/modules/mediarecorder/audio_track_opus_encoder.h
@@ -24,7 +24,8 @@
                               public media::AudioConverter::InputCallback {
  public:
   AudioTrackOpusEncoder(OnEncodedAudioCB on_encoded_audio_cb,
-                        int32_t bits_per_second);
+                        int32_t bits_per_second,
+                        bool vbr_enabled = true);
 
   void OnSetFormat(const media::AudioParameters& params) override;
   void EncodeAudio(std::unique_ptr<media::AudioBus> input_bus,
@@ -44,6 +45,11 @@
   // Target bitrate for Opus. If 0, Opus provide automatic bitrate is used.
   const int32_t bits_per_second_;
 
+  // Opus operates in VBR or constrained VBR modes even when a fixed bitrate
+  // is specified, unless 'hard' CBR is explicitly enabled by disabling VBR
+  // mode with this flag.
+  const bool vbr_enabled_;
+
   // Output parameters after audio conversion. This differs from the input
   // parameters only in sample_rate() and frames_per_buffer(): output should be
   // 48ksamples/s and 2880, respectively.
diff --git a/third_party/blink/renderer/modules/mediarecorder/audio_track_recorder.cc b/third_party/blink/renderer/modules/mediarecorder/audio_track_recorder.cc
index f96f4c97..80af6b9 100644
--- a/third_party/blink/renderer/modules/mediarecorder/audio_track_recorder.cc
+++ b/third_party/blink/renderer/modules/mediarecorder/audio_track_recorder.cc
@@ -48,12 +48,14 @@
     MediaStreamComponent* track,
     OnEncodedAudioCB on_encoded_audio_cb,
     base::OnceClosure on_track_source_ended_cb,
-    int32_t bits_per_second)
+    int32_t bits_per_second,
+    BitrateMode bitrate_mode)
     : TrackRecorder(std::move(on_track_source_ended_cb)),
       track_(track),
       encoder_(CreateAudioEncoder(codec,
                                   std::move(on_encoded_audio_cb),
-                                  bits_per_second)),
+                                  bits_per_second,
+                                  bitrate_mode)),
       encoder_thread_(Thread::CreateThread(
           ThreadCreationParams(ThreadType::kAudioEncoderThread))),
       encoder_task_runner_(encoder_thread_->GetTaskRunner()) {
@@ -75,7 +77,8 @@
 scoped_refptr<AudioTrackEncoder> AudioTrackRecorder::CreateAudioEncoder(
     CodecId codec,
     OnEncodedAudioCB on_encoded_audio_cb,
-    int32_t bits_per_second) {
+    int32_t bits_per_second,
+    BitrateMode bitrate_mode) {
   if (codec == CodecId::PCM) {
     return base::MakeRefCounted<AudioTrackPcmEncoder>(
         media::BindToCurrentLoop(std::move(on_encoded_audio_cb)));
@@ -83,8 +86,8 @@
 
   // All other paths will use the AudioTrackOpusEncoder.
   return base::MakeRefCounted<AudioTrackOpusEncoder>(
-      media::BindToCurrentLoop(std::move(on_encoded_audio_cb)),
-      bits_per_second);
+      media::BindToCurrentLoop(std::move(on_encoded_audio_cb)), bits_per_second,
+      bitrate_mode == BitrateMode::VARIABLE);
 }
 
 void AudioTrackRecorder::OnSetFormat(const media::AudioParameters& params) {
diff --git a/third_party/blink/renderer/modules/mediarecorder/audio_track_recorder.h b/third_party/blink/renderer/modules/mediarecorder/audio_track_recorder.h
index a617ab1..2216e1d1 100644
--- a/third_party/blink/renderer/modules/mediarecorder/audio_track_recorder.h
+++ b/third_party/blink/renderer/modules/mediarecorder/audio_track_recorder.h
@@ -41,6 +41,8 @@
     LAST
   };
 
+  enum class BitrateMode { CONSTANT, VARIABLE };
+
   using OnEncodedAudioCB =
       base::RepeatingCallback<void(const media::AudioParameters& params,
                                    std::string encoded_data,
@@ -52,7 +54,8 @@
                      MediaStreamComponent* track,
                      OnEncodedAudioCB on_encoded_audio_cb,
                      base::OnceClosure on_track_source_ended_cb,
-                     int32_t bits_per_second);
+                     int32_t bits_per_second,
+                     BitrateMode bitrate_mode);
   ~AudioTrackRecorder() override;
 
   // Implement MediaStreamAudioSink.
@@ -69,7 +72,8 @@
   static scoped_refptr<AudioTrackEncoder> CreateAudioEncoder(
       CodecId codec,
       OnEncodedAudioCB on_encoded_audio_cb,
-      int32_t bits_per_second);
+      int32_t bits_per_second,
+      BitrateMode bitrate_mode);
 
   void ConnectToTrack();
   void DisconnectFromTrack();
diff --git a/third_party/blink/renderer/modules/mediarecorder/audio_track_recorder_unittest.cc b/third_party/blink/renderer/modules/mediarecorder/audio_track_recorder_unittest.cc
index 9116be7..8517758 100644
--- a/third_party/blink/renderer/modules/mediarecorder/audio_track_recorder_unittest.cc
+++ b/third_party/blink/renderer/modules/mediarecorder/audio_track_recorder_unittest.cc
@@ -53,6 +53,7 @@
   const media::ChannelLayout channel_layout;
   const int sample_rate;
   const AudioTrackRecorder::CodecId codec;
+  const AudioTrackRecorder::BitrateMode bitrateMode;
 };
 
 const ATRTestParams kATRTestParams[] = {
@@ -60,27 +61,45 @@
     {media::AudioParameters::AUDIO_PCM_LOW_LATENCY, /* input format */
      media::CHANNEL_LAYOUT_STEREO,                  /* channel layout */
      kDefaultSampleRate,                            /* sample rate */
-     AudioTrackRecorder::CodecId::OPUS},            /* codec for encoding */
+     AudioTrackRecorder::CodecId::OPUS,             /* codec for encoding */
+     AudioTrackRecorder::BitrateMode::VARIABLE},    /* constant/variable rate */
+
     // Change to mono:
     {media::AudioParameters::AUDIO_PCM_LOW_LATENCY, media::CHANNEL_LAYOUT_MONO,
-     kDefaultSampleRate, AudioTrackRecorder::CodecId::OPUS},
+     kDefaultSampleRate, AudioTrackRecorder::CodecId::OPUS,
+     AudioTrackRecorder::BitrateMode::VARIABLE},
+
     // Different sampling rate as well:
     {media::AudioParameters::AUDIO_PCM_LOW_LATENCY, media::CHANNEL_LAYOUT_MONO,
-     24000, AudioTrackRecorder::CodecId::OPUS},
+     24000, AudioTrackRecorder::CodecId::OPUS,
+     AudioTrackRecorder::BitrateMode::VARIABLE},
     {media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
-     media::CHANNEL_LAYOUT_STEREO, 8000, AudioTrackRecorder::CodecId::OPUS},
+     media::CHANNEL_LAYOUT_STEREO, 8000, AudioTrackRecorder::CodecId::OPUS,
+     AudioTrackRecorder::BitrateMode::VARIABLE},
+
     // Using a non-default Opus sampling rate (48, 24, 16, 12, or 8 kHz).
     {media::AudioParameters::AUDIO_PCM_LOW_LATENCY, media::CHANNEL_LAYOUT_MONO,
-     22050, AudioTrackRecorder::CodecId::OPUS},
+     22050, AudioTrackRecorder::CodecId::OPUS,
+     AudioTrackRecorder::BitrateMode::VARIABLE},
     {media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
-     media::CHANNEL_LAYOUT_STEREO, 44100, AudioTrackRecorder::CodecId::OPUS},
+     media::CHANNEL_LAYOUT_STEREO, 44100, AudioTrackRecorder::CodecId::OPUS,
+     AudioTrackRecorder::BitrateMode::VARIABLE},
     {media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
-     media::CHANNEL_LAYOUT_STEREO, 96000, AudioTrackRecorder::CodecId::OPUS},
+     media::CHANNEL_LAYOUT_STEREO, 96000, AudioTrackRecorder::CodecId::OPUS,
+     AudioTrackRecorder::BitrateMode::VARIABLE},
     {media::AudioParameters::AUDIO_PCM_LOW_LATENCY, media::CHANNEL_LAYOUT_MONO,
-     kDefaultSampleRate, AudioTrackRecorder::CodecId::PCM},
+     kDefaultSampleRate, AudioTrackRecorder::CodecId::PCM,
+     AudioTrackRecorder::BitrateMode::VARIABLE},
     {media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
      media::CHANNEL_LAYOUT_STEREO, kDefaultSampleRate,
-     AudioTrackRecorder::CodecId::PCM},
+     AudioTrackRecorder::CodecId::PCM,
+     AudioTrackRecorder::BitrateMode::VARIABLE},
+
+    // Use Opus in constatnt bitrate mode:
+    {media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
+     media::CHANNEL_LAYOUT_STEREO, kDefaultSampleRate,
+     AudioTrackRecorder::CodecId::OPUS,
+     AudioTrackRecorder::BitrateMode::CONSTANT},
 };
 
 class AudioTrackRecorderTest : public testing::TestWithParam<ATRTestParams> {
@@ -112,7 +131,7 @@
         WTF::BindRepeating(&AudioTrackRecorderTest::OnEncodedAudio,
                            WTF::Unretained(this)),
         ConvertToBaseOnceCallback(CrossThreadBindOnce([] {})),
-        0 /* bits_per_second */);
+        0 /* bits_per_second */, GetParam().bitrateMode);
   }
 
   ~AudioTrackRecorderTest() {
@@ -260,9 +279,17 @@
   // Give ATR initial audio parameters.
   audio_track_recorder_->OnSetFormat(first_params_);
 
+  std::vector<std::size_t> encodedPacketSizes;
   // TODO(ajose): consider adding WillOnce(SaveArg...) and inspecting, as done
   // in VTR unittests. http://crbug.com/548856
-  EXPECT_CALL(*this, DoOnEncodedAudio(_, _, _)).Times(1);
+  EXPECT_CALL(*this, DoOnEncodedAudio(_, _, _))
+      .Times(1)
+      .WillOnce([&encodedPacketSizes](const media::AudioParameters&,
+                                      std::string encoded_data,
+                                      base::TimeTicks) {
+        encodedPacketSizes.push_back(encoded_data.size());
+      });
+
   audio_track_recorder_->OnData(*GetFirstSourceAudioBus(),
                                 base::TimeTicks::Now());
   for (int i = 0; i < kRatioInputToOutputFrames - 1; ++i) {
@@ -273,9 +300,14 @@
   EXPECT_CALL(*this, DoOnEncodedAudio(_, _, _))
       .Times(1)
       // Only reset the decoder once we've heard back:
-      .WillOnce(
+      .WillOnce(testing::DoAll(
           RunOnceClosure(WTF::Bind(&AudioTrackRecorderTest::ResetDecoder,
-                                   WTF::Unretained(this), second_params_)));
+                                   WTF::Unretained(this), second_params_)),
+          [&encodedPacketSizes](const media::AudioParameters&,
+                                std::string encoded_data, base::TimeTicks) {
+            encodedPacketSizes.push_back(encoded_data.size());
+          }));
+
   audio_track_recorder_->OnData(*GetFirstSourceAudioBus(),
                                 base::TimeTicks::Now());
   for (int i = 0; i < kRatioInputToOutputFrames - 1; ++i) {
@@ -296,7 +328,13 @@
   // Send audio with different params.
   EXPECT_CALL(*this, DoOnEncodedAudio(_, _, _))
       .Times(1)
-      .WillOnce(RunOnceClosure(std::move(quit_closure)));
+      .WillOnce(testing::DoAll(
+          RunOnceClosure(std::move(quit_closure)),
+          [&encodedPacketSizes](const media::AudioParameters&,
+                                std::string encoded_data, base::TimeTicks) {
+            encodedPacketSizes.push_back(encoded_data.size());
+          }));
+
   audio_track_recorder_->OnData(*GetSecondSourceAudioBus(),
                                 base::TimeTicks::Now());
   for (int i = 0; i < kRatioInputToOutputFrames - 1; ++i) {
@@ -304,6 +342,14 @@
                                   base::TimeTicks::Now());
   }
 
+  // Check that in CBR mode, all the packets are the same size, to confirm it
+  // actually made a CBR recording.
+  if (GetParam().bitrateMode == AudioTrackRecorder::BitrateMode::CONSTANT) {
+    if (!encodedPacketSizes.empty()) {
+      EXPECT_THAT(encodedPacketSizes, testing::Each(encodedPacketSizes[0]));
+    }
+  }
+
   run_loop.Run();
   testing::Mock::VerifyAndClearExpectations(this);
 }
diff --git a/third_party/blink/renderer/modules/mediarecorder/media_recorder.cc b/third_party/blink/renderer/modules/mediarecorder/media_recorder.cc
index cf3f588..c9cb73ba 100644
--- a/third_party/blink/renderer/modules/mediarecorder/media_recorder.cc
+++ b/third_party/blink/renderer/modules/mediarecorder/media_recorder.cc
@@ -23,6 +23,7 @@
 #include "third_party/blink/renderer/platform/network/mime/content_type.h"
 #include "third_party/blink/renderer/platform/privacy_budget/identifiability_digest_helpers.h"
 #include "third_party/blink/renderer/platform/wtf/functional.h"
+#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
 
 namespace blink {
 
@@ -49,6 +50,29 @@
   return String();
 }
 
+String BitrateModeToString(AudioTrackRecorder::BitrateMode bitrateMode) {
+  switch (bitrateMode) {
+    case AudioTrackRecorder::BitrateMode::CONSTANT:
+      return "constant";
+    case AudioTrackRecorder::BitrateMode::VARIABLE:
+      return "variable";
+  }
+
+  NOTREACHED();
+  return String();
+}
+
+AudioTrackRecorder::BitrateMode GetBitrateModeFromOptions(
+    const MediaRecorderOptions* const options) {
+  if (options->hasAudioBitrateMode()) {
+    if (!CodeUnitCompareIgnoringASCIICase(options->audioBitrateMode(),
+                                          "constant"))
+      return AudioTrackRecorder::BitrateMode::CONSTANT;
+  }
+
+  return AudioTrackRecorder::BitrateMode::VARIABLE;
+}
+
 // Allocates the requested bit rates from |bitrateOptions| into the respective
 // |{audio,video}BitsPerSecond| (where a value of zero indicates Platform to use
 // whatever it sees fit). If |options.bitsPerSecond()| is specified, it
@@ -190,7 +214,7 @@
   if (!recorder_handler_->Initialize(
           this, stream->Descriptor(), content_type.GetType(),
           content_type.Parameter("codecs"), audio_bits_per_second_,
-          video_bits_per_second_)) {
+          video_bits_per_second_, GetBitrateModeFromOptions(options))) {
     exception_state.ThrowDOMException(
         DOMExceptionCode::kNotSupportedError,
         "Failed to initialize native MediaRecorder the type provided (" +
@@ -206,6 +230,10 @@
   return StateToString(state_);
 }
 
+String MediaRecorder::audioBitrateMode() const {
+  return BitrateModeToString(recorder_handler_->AudioBitrateMode());
+}
+
 void MediaRecorder::start(ExceptionState& exception_state) {
   start(std::numeric_limits<int>::max() /* timeSlice */, exception_state);
 }
diff --git a/third_party/blink/renderer/modules/mediarecorder/media_recorder.h b/third_party/blink/renderer/modules/mediarecorder/media_recorder.h
index 889126c0..fec6d5e 100644
--- a/third_party/blink/renderer/modules/mediarecorder/media_recorder.h
+++ b/third_party/blink/renderer/modules/mediarecorder/media_recorder.h
@@ -49,6 +49,7 @@
   String state() const;
   uint32_t videoBitsPerSecond() const { return video_bits_per_second_; }
   uint32_t audioBitsPerSecond() const { return audio_bits_per_second_; }
+  String audioBitrateMode() const;
 
   DEFINE_ATTRIBUTE_EVENT_LISTENER(start, kStart)
   DEFINE_ATTRIBUTE_EVENT_LISTENER(stop, kStop)
diff --git a/third_party/blink/renderer/modules/mediarecorder/media_recorder.idl b/third_party/blink/renderer/modules/mediarecorder/media_recorder.idl
index 34031913..5dbf681 100644
--- a/third_party/blink/renderer/modules/mediarecorder/media_recorder.idl
+++ b/third_party/blink/renderer/modules/mediarecorder/media_recorder.idl
@@ -23,6 +23,7 @@
     attribute EventHandler onerror;
     [HighEntropy=Direct, MeasureAs=MediaRecorder_VideoBitsPerSecond] readonly attribute unsigned long videoBitsPerSecond;
     [HighEntropy=Direct, MeasureAs=MediaRecorder_AudioBitsPerSecond] readonly attribute unsigned long audioBitsPerSecond;
+    readonly attribute BitrateMode audioBitrateMode;
 
     [RaisesException, Measure] void start(optional long timeslice);
     [RaisesException] void stop();
diff --git a/third_party/blink/renderer/modules/mediarecorder/media_recorder_handler.cc b/third_party/blink/renderer/modules/mediarecorder/media_recorder_handler.cc
index 9da735e1..138a251 100644
--- a/third_party/blink/renderer/modules/mediarecorder/media_recorder_handler.cc
+++ b/third_party/blink/renderer/modules/mediarecorder/media_recorder_handler.cc
@@ -201,12 +201,14 @@
   return true;
 }
 
-bool MediaRecorderHandler::Initialize(MediaRecorder* recorder,
-                                      MediaStreamDescriptor* media_stream,
-                                      const String& type,
-                                      const String& codecs,
-                                      int32_t audio_bits_per_second,
-                                      int32_t video_bits_per_second) {
+bool MediaRecorderHandler::Initialize(
+    MediaRecorder* recorder,
+    MediaStreamDescriptor* media_stream,
+    const String& type,
+    const String& codecs,
+    int32_t audio_bits_per_second,
+    int32_t video_bits_per_second,
+    AudioTrackRecorder::BitrateMode audio_bitrate_mode) {
   DCHECK(IsMainThread());
   // Save histogram data so we can see how much MediaStream Recorder is used.
   // The histogram counts the number of calls to the JS API.
@@ -244,9 +246,14 @@
 
   audio_bits_per_second_ = audio_bits_per_second;
   video_bits_per_second_ = video_bits_per_second;
+  audio_bitrate_mode_ = audio_bitrate_mode;
   return true;
 }
 
+AudioTrackRecorder::BitrateMode MediaRecorderHandler::AudioBitrateMode() {
+  return audio_bitrate_mode_;
+}
+
 bool MediaRecorderHandler::Start(int timeslice) {
   DCHECK(IsMainThread());
   DCHECK(!recording_);
@@ -345,7 +352,8 @@
                   WrapWeakPersistent(this)));
     audio_recorders_.emplace_back(std::make_unique<AudioTrackRecorder>(
         audio_codec_id_, audio_tracks_[0], std::move(on_encoded_audio_cb),
-        std::move(on_track_source_changed_cb), audio_bits_per_second_));
+        std::move(on_track_source_changed_cb), audio_bits_per_second_,
+        audio_bitrate_mode_));
   }
 
   recording_ = true;
diff --git a/third_party/blink/renderer/modules/mediarecorder/media_recorder_handler.h b/third_party/blink/renderer/modules/mediarecorder/media_recorder_handler.h
index 6af9a4d..7eed27d3 100644
--- a/third_party/blink/renderer/modules/mediarecorder/media_recorder_handler.h
+++ b/third_party/blink/renderer/modules/mediarecorder/media_recorder_handler.h
@@ -63,7 +63,11 @@
                   const String& type,
                   const String& codecs,
                   int32_t audio_bits_per_second,
-                  int32_t video_bits_per_second);
+                  int32_t video_bits_per_second,
+                  AudioTrackRecorder::BitrateMode audio_bitrate_mode);
+
+  AudioTrackRecorder::BitrateMode AudioBitrateMode();
+
   bool Start(int timeslice);
   void Stop();
   void Pause();
@@ -137,6 +141,9 @@
   // Audio Codec, OPUS is used by default.
   AudioTrackRecorder::CodecId audio_codec_id_;
 
+  // Audio bitrate mode (constant, variable, etc.), VBR is used by default.
+  AudioTrackRecorder::BitrateMode audio_bitrate_mode_;
+
   // |recorder_| has no notion of time, thus may configure us via
   // start(timeslice) to notify it after a certain |timeslice_| has passed. We
   // use a moving |slice_origin_timestamp_| to track those time chunks.
diff --git a/third_party/blink/renderer/modules/mediarecorder/media_recorder_handler_unittest.cc b/third_party/blink/renderer/modules/mediarecorder/media_recorder_handler_unittest.cc
index 046eaa1..34b9ad1ea 100644
--- a/third_party/blink/renderer/modules/mediarecorder/media_recorder_handler_unittest.cc
+++ b/third_party/blink/renderer/modules/mediarecorder/media_recorder_handler_unittest.cc
@@ -267,6 +267,28 @@
       mime_type_audio, example_unsupported_codecs_2));
 }
 
+// Checks that it uses the specified bitrate mode.
+TEST_P(MediaRecorderHandlerTest, SupportsBitrateMode) {
+  AddTracks();
+  V8TestingScope scope;
+  auto* recorder = MakeGarbageCollected<MockMediaRecorder>(scope);
+
+  const String mime_type(GetParam().mime_type);
+  const String codecs(GetParam().codecs);
+
+  EXPECT_TRUE(media_recorder_handler_->Initialize(
+      recorder, registry_.test_stream(), mime_type, codecs, 0, 0,
+      AudioTrackRecorder::BitrateMode::VARIABLE));
+  EXPECT_EQ(media_recorder_handler_->AudioBitrateMode(),
+            AudioTrackRecorder::BitrateMode::VARIABLE);
+
+  EXPECT_TRUE(media_recorder_handler_->Initialize(
+      recorder, registry_.test_stream(), mime_type, codecs, 0, 0,
+      AudioTrackRecorder::BitrateMode::CONSTANT));
+  EXPECT_EQ(media_recorder_handler_->AudioBitrateMode(),
+            AudioTrackRecorder::BitrateMode::CONSTANT);
+}
+
 // Checks that the initialization-destruction sequence works fine.
 TEST_P(MediaRecorderHandlerTest, InitializeStartStop) {
   AddTracks();
@@ -275,7 +297,8 @@
   const String mime_type(GetParam().mime_type);
   const String codecs(GetParam().codecs);
   EXPECT_TRUE(media_recorder_handler_->Initialize(
-      recorder, registry_.test_stream(), mime_type, codecs, 0, 0));
+      recorder, registry_.test_stream(), mime_type, codecs, 0, 0,
+      AudioTrackRecorder::BitrateMode::VARIABLE));
   EXPECT_FALSE(recording());
   EXPECT_FALSE(hasVideoRecorders());
   EXPECT_FALSE(hasAudioRecorders());
@@ -310,7 +333,8 @@
   const String mime_type(GetParam().mime_type);
   const String codecs(GetParam().codecs);
   EXPECT_TRUE(media_recorder_handler_->Initialize(
-      recorder, registry_.test_stream(), mime_type, codecs, 0, 0));
+      recorder, registry_.test_stream(), mime_type, codecs, 0, 0,
+      AudioTrackRecorder::BitrateMode::VARIABLE));
   EXPECT_TRUE(media_recorder_handler_->Start(0));
 
   InSequence s;
@@ -400,7 +424,8 @@
   const String mime_type(GetParam().mime_type);
   const String codecs(GetParam().codecs);
   EXPECT_TRUE(media_recorder_handler_->Initialize(
-      recorder, registry_.test_stream(), mime_type, codecs, 0, 0));
+      recorder, registry_.test_stream(), mime_type, codecs, 0, 0,
+      AudioTrackRecorder::BitrateMode::VARIABLE));
   EXPECT_TRUE(media_recorder_handler_->Start(0));
 
   InSequence s;
@@ -466,7 +491,8 @@
   const String mime_type(GetParam().mime_type);
   const String codecs(GetParam().codecs);
   EXPECT_TRUE(media_recorder_handler_->Initialize(
-      recorder, registry_.test_stream(), mime_type, codecs, 0, 0));
+      recorder, registry_.test_stream(), mime_type, codecs, 0, 0,
+      AudioTrackRecorder::BitrateMode::VARIABLE));
   EXPECT_TRUE(media_recorder_handler_->Start(0));
 
   InSequence s;
@@ -513,7 +539,8 @@
   const String mime_type(GetParam().mime_type);
   const String codecs(GetParam().codecs);
   EXPECT_TRUE(media_recorder_handler_->Initialize(
-      recorder, registry_.test_stream(), mime_type, codecs, 0, 0));
+      recorder, registry_.test_stream(), mime_type, codecs, 0, 0,
+      AudioTrackRecorder::BitrateMode::VARIABLE));
 
   StringBuilder actual_mime_type;
   actual_mime_type.Append(GetParam().mime_type);
@@ -547,7 +574,8 @@
   const String codecs(GetParam().codecs);
 
   EXPECT_TRUE(media_recorder_handler_->Initialize(
-      recorder, registry_.test_stream(), mime_type, codecs, 0, 0));
+      recorder, registry_.test_stream(), mime_type, codecs, 0, 0,
+      AudioTrackRecorder::BitrateMode::VARIABLE));
   EXPECT_TRUE(media_recorder_handler_->Start(0));
 
   Mock::VerifyAndClearExpectations(recorder);
@@ -578,7 +606,8 @@
   const String codecs(GetParam().codecs);
 
   EXPECT_TRUE(media_recorder_handler_->Initialize(
-      recorder, registry_.test_stream(), mime_type, codecs, 0, 0));
+      recorder, registry_.test_stream(), mime_type, codecs, 0, 0,
+      AudioTrackRecorder::BitrateMode::VARIABLE));
   EXPECT_TRUE(media_recorder_handler_->Start(0));
   media_recorder_handler_->Stop();
 
@@ -633,7 +662,8 @@
   const String mime_type(GetParam().mime_type);
   const String codecs(GetParam().codecs);
   EXPECT_TRUE(media_recorder_handler_->Initialize(
-      recorder, registry_.test_stream(), mime_type, codecs, 0, 0));
+      recorder, registry_.test_stream(), mime_type, codecs, 0, 0,
+      AudioTrackRecorder::BitrateMode::VARIABLE));
 
   String actual_mime_type =
       String(GetParam().mime_type) + ";codecs=" + GetParam().codecs;
@@ -703,8 +733,9 @@
 
   V8TestingScope scope;
   auto* recorder = MakeGarbageCollected<MockMediaRecorder>(scope);
-  media_recorder_handler_->Initialize(recorder, registry_.test_stream(), "", "",
-                                      0, 0);
+  media_recorder_handler_->Initialize(
+      recorder, registry_.test_stream(), "", "", 0, 0,
+      AudioTrackRecorder::BitrateMode::VARIABLE);
   media_recorder_handler_->Start(0);
 
   const size_t kFrameSize = 42;
@@ -736,7 +767,8 @@
   V8TestingScope scope;
   auto* recorder = MakeGarbageCollected<MockMediaRecorder>(scope);
   EXPECT_TRUE(media_recorder_handler_->Initialize(
-      recorder, registry_.test_stream(), "", "", 0, 0));
+      recorder, registry_.test_stream(), "", "", 0, 0,
+      AudioTrackRecorder::BitrateMode::VARIABLE));
   EXPECT_TRUE(media_recorder_handler_->Start(0));
 
   // NOTE, Asan: the prototype of WriteData which has a const char* as data
diff --git a/third_party/blink/renderer/modules/mediarecorder/media_recorder_options.idl b/third_party/blink/renderer/modules/mediarecorder/media_recorder_options.idl
index 02a0848..0e1b40e 100644
--- a/third_party/blink/renderer/modules/mediarecorder/media_recorder_options.idl
+++ b/third_party/blink/renderer/modules/mediarecorder/media_recorder_options.idl
@@ -4,6 +4,8 @@
 
 // https://w3c.github.io/mediacapture-record/#mediarecorderoptions-section
 
+enum BitrateMode { "constant", "variable" };
+
 dictionary MediaRecorderOptions {
     DOMString mimeType = "";  // Encoding mimeType.
 
@@ -11,4 +13,5 @@
     unsigned long audioBitsPerSecond;
     unsigned long videoBitsPerSecond;
     unsigned long bitsPerSecond;
+    BitrateMode audioBitrateMode = "variable";
 };
diff --git a/third_party/blink/renderer/modules/mediarecorder/video_track_recorder.cc b/third_party/blink/renderer/modules/mediarecorder/video_track_recorder.cc
index 2010768..b4e6fbc9 100644
--- a/third_party/blink/renderer/modules/mediarecorder/video_track_recorder.cc
+++ b/third_party/blink/renderer/modules/mediarecorder/video_track_recorder.cc
@@ -378,7 +378,7 @@
     gfx::Size new_visible_size = old_visible_size;
 
     media::VideoRotation video_rotation =
-        video_frame->metadata()->rotation.value_or(media::VIDEO_ROTATION_0);
+        video_frame->metadata().rotation.value_or(media::VIDEO_ROTATION_0);
     if (video_rotation == media::VIDEO_ROTATION_90 ||
         video_rotation == media::VIDEO_ROTATION_270) {
       new_visible_size.SetSize(old_visible_size.height(),
diff --git a/third_party/blink/renderer/modules/mediarecorder/video_track_recorder_unittest.cc b/third_party/blink/renderer/modules/mediarecorder/video_track_recorder_unittest.cc
index 0b2ec4c..fa51ad4 100644
--- a/third_party/blink/renderer/modules/mediarecorder/video_track_recorder_unittest.cc
+++ b/third_party/blink/renderer/modules/mediarecorder/video_track_recorder_unittest.cc
@@ -261,7 +261,7 @@
     ASSERT_TRUE(!!video_frame);
 
   const double kFrameRate = 60.0f;
-  video_frame->metadata()->frame_rate = kFrameRate;
+  video_frame->metadata().frame_rate = kFrameRate;
 
   InSequence s;
   const base::TimeTicks timeticks_now = base::TimeTicks::Now();
diff --git a/third_party/blink/renderer/modules/mediarecorder/vpx_encoder.cc b/third_party/blink/renderer/modules/mediarecorder/vpx_encoder.cc
index 79b1610..aeb1952b 100644
--- a/third_party/blink/renderer/modules/mediarecorder/vpx_encoder.cc
+++ b/third_party/blink/renderer/modules/mediarecorder/vpx_encoder.cc
@@ -345,7 +345,7 @@
   base::TimeDelta predicted_frame_duration =
       frame.timestamp() - last_frame_timestamp_;
   base::TimeDelta frame_duration =
-      frame.metadata()->frame_duration.value_or(predicted_frame_duration);
+      frame.metadata().frame_duration.value_or(predicted_frame_duration);
   last_frame_timestamp_ = frame.timestamp();
   // Make sure |frame_duration| is in a safe range of values.
   const base::TimeDelta kMaxFrameDuration =
diff --git a/third_party/blink/renderer/modules/mediastream/low_latency_video_renderer_algorithm.cc b/third_party/blink/renderer/modules/mediastream/low_latency_video_renderer_algorithm.cc
index c87f993..0ee0b6f 100644
--- a/third_party/blink/renderer/modules/mediastream/low_latency_video_renderer_algorithm.cc
+++ b/third_party/blink/renderer/modules/mediastream/low_latency_video_renderer_algorithm.cc
@@ -107,7 +107,7 @@
 void LowLatencyVideoRendererAlgorithm::EnqueueFrame(
     scoped_refptr<media::VideoFrame> frame) {
   DCHECK(frame);
-  DCHECK(!frame->metadata()->end_of_stream);
+  DCHECK(!frame->metadata().end_of_stream);
   frame_queue_.push_back(std::move(frame));
   ++stats_.total_frames;
 }
@@ -138,7 +138,7 @@
       int max_remaining_queue_length =
           frame_queue_.back()
               ->metadata()
-              ->maximum_composition_delay_in_frames.value_or(
+              .maximum_composition_delay_in_frames.value_or(
                   kDefaultMaxCompositionDelayInFrames);
 
       // The number of frames in the queue is in the range
diff --git a/third_party/blink/renderer/modules/mediastream/low_latency_video_renderer_algorithm_unittest.cc b/third_party/blink/renderer/modules/mediastream/low_latency_video_renderer_algorithm_unittest.cc
index ad9f6de..237d7de 100644
--- a/third_party/blink/renderer/modules/mediastream/low_latency_video_renderer_algorithm_unittest.cc
+++ b/third_party/blink/renderer/modules/mediastream/low_latency_video_renderer_algorithm_unittest.cc
@@ -25,7 +25,7 @@
     scoped_refptr<media::VideoFrame> frame = frame_pool_.CreateFrame(
         media::PIXEL_FORMAT_I420, natural_size, gfx::Rect(natural_size),
         natural_size, base::TimeDelta());
-    frame->metadata()->maximum_composition_delay_in_frames =
+    frame->metadata().maximum_composition_delay_in_frames =
         maximum_composition_delay_in_frames;
     return frame;
   }
diff --git a/third_party/blink/renderer/modules/mediastream/media_stream_video_capturer_source_test.cc b/third_party/blink/renderer/modules/mediastream/media_stream_video_capturer_source_test.cc
index a09e888df..ed7dbe4 100644
--- a/third_party/blink/renderer/modules/mediastream/media_stream_video_capturer_source_test.cc
+++ b/third_party/blink/renderer/modules/mediastream/media_stream_video_capturer_source_test.cc
@@ -91,7 +91,7 @@
   void OnVideoFrame(scoped_refptr<media::VideoFrame> frame,
                     base::TimeTicks capture_time) {
     *capture_time_ = capture_time;
-    *metadata_ = *frame->metadata();
+    *metadata_ = frame->metadata();
     std::move(got_frame_cb_).Run();
   }
 
@@ -248,7 +248,7 @@
   fake_sink.ConnectToTrack(track);
   const scoped_refptr<media::VideoFrame> frame =
       media::VideoFrame::CreateBlackFrame(gfx::Size(2, 2));
-  frame->metadata()->frame_rate = 30.0;
+  frame->metadata().frame_rate = 30.0;
   PostCrossThreadTask(
       *Platform::Current()->GetIOTaskRunner(), FROM_HERE,
       CrossThreadBindOnce(deliver_frame_cb, frame, reference_capture_time));
diff --git a/third_party/blink/renderer/modules/mediastream/media_stream_video_renderer_sink.cc b/third_party/blink/renderer/modules/mediastream/media_stream_video_renderer_sink.cc
index 8a9f215..94687b80 100644
--- a/third_party/blink/renderer/modules/mediastream/media_stream_video_renderer_sink.cc
+++ b/third_party/blink/renderer/modules/mediastream/media_stream_video_renderer_sink.cc
@@ -87,8 +87,8 @@
     if (!video_frame)
       return;
 
-    video_frame->metadata()->end_of_stream = true;
-    video_frame->metadata()->reference_time = base::TimeTicks::Now();
+    video_frame->metadata().end_of_stream = true;
+    video_frame->metadata().reference_time = base::TimeTicks::Now();
     OnVideoFrame(video_frame, base::TimeTicks());
   }
 
diff --git a/third_party/blink/renderer/modules/mediastream/media_stream_video_track.cc b/third_party/blink/renderer/modules/mediastream/media_stream_video_track.cc
index 757d2d8..31a3c80 100644
--- a/third_party/blink/renderer/modules/mediastream/media_stream_video_track.cc
+++ b/third_party/blink/renderer/modules/mediastream/media_stream_video_track.cc
@@ -391,8 +391,8 @@
     return nullptr;
 
   wrapped_black_frame->set_timestamp(reference_frame.timestamp());
-  wrapped_black_frame->metadata()->reference_time =
-      reference_frame.metadata()->reference_time;
+  wrapped_black_frame->metadata().reference_time =
+      reference_frame.metadata().reference_time;
 
   return wrapped_black_frame;
 }
diff --git a/third_party/blink/renderer/modules/mediastream/pushable_media_stream_video_source_test.cc b/third_party/blink/renderer/modules/mediastream/pushable_media_stream_video_source_test.cc
index 4cd633b..940fe39 100644
--- a/third_party/blink/renderer/modules/mediastream/pushable_media_stream_video_source_test.cc
+++ b/third_party/blink/renderer/modules/mediastream/pushable_media_stream_video_source_test.cc
@@ -45,7 +45,7 @@
   void OnVideoFrame(scoped_refptr<media::VideoFrame> frame,
                     base::TimeTicks capture_time) {
     *capture_time_ = capture_time;
-    *metadata_ = *frame->metadata();
+    *metadata_ = frame->metadata();
     *natural_size_ = frame->natural_size();
     std::move(got_frame_cb_).Run();
   }
@@ -118,7 +118,7 @@
   fake_sink.ConnectToTrack(track);
   const scoped_refptr<media::VideoFrame> frame =
       media::VideoFrame::CreateBlackFrame(gfx::Size(100, 50));
-  frame->metadata()->frame_rate = 30.0;
+  frame->metadata().frame_rate = 30.0;
 
   pushable_video_source_->PushFrame(frame, reference_capture_time);
   run_loop.Run();
diff --git a/third_party/blink/renderer/modules/mediastream/video_renderer_algorithm_wrapper.cc b/third_party/blink/renderer/modules/mediastream/video_renderer_algorithm_wrapper.cc
index 2fc60c13..d51e4e8 100644
--- a/third_party/blink/renderer/modules/mediastream/video_renderer_algorithm_wrapper.cc
+++ b/third_party/blink/renderer/modules/mediastream/video_renderer_algorithm_wrapper.cc
@@ -31,7 +31,7 @@
     scoped_refptr<media::VideoFrame> frame) {
   DCHECK(frame);
   if (renderer_algorithm_ == RendererAlgorithm::Default &&
-      frame->metadata()->maximum_composition_delay_in_frames) {
+      frame->metadata().maximum_composition_delay_in_frames) {
     default_rendering_frame_buffer_.release();
     low_latency_rendering_frame_buffer_ =
         std::make_unique<LowLatencyVideoRendererAlgorithm>(media_log_);
diff --git a/third_party/blink/renderer/modules/mediastream/video_track_adapter.cc b/third_party/blink/renderer/modules/mediastream/video_track_adapter.cc
index 4a691e8..a726c52 100644
--- a/third_party/blink/renderer/modules/mediastream/video_track_adapter.cc
+++ b/third_party/blink/renderer/modules/mediastream/video_track_adapter.cc
@@ -309,7 +309,7 @@
                    &source_format_settings_.prev_frame_timestamp);
   MaybeUpdateTracksFormat(*frame);
 
-  double frame_rate = frame->metadata()->frame_rate.value_or(
+  double frame_rate = frame->metadata().frame_rate.value_or(
       MediaStreamVideoSource::kUnknownFrameRate);
 
   auto frame_drop_reason = media::VideoCaptureFrameDropReason::kNone;
diff --git a/third_party/blink/renderer/modules/mediastream/webmediaplayer_ms.cc b/third_party/blink/renderer/modules/mediastream/webmediaplayer_ms.cc
index 94c75cc..5d9165e5 100644
--- a/third_party/blink/renderer/modules/mediastream/webmediaplayer_ms.cc
+++ b/third_party/blink/renderer/modules/mediastream/webmediaplayer_ms.cc
@@ -253,9 +253,9 @@
       bool tracing_enabled = false;
       TRACE_EVENT_CATEGORY_GROUP_ENABLED("media", &tracing_enabled);
       if (tracing_enabled) {
-        if (frame->metadata()->reference_time.has_value()) {
+        if (frame->metadata().reference_time.has_value()) {
           TRACE_EVENT1("media", "EnqueueFrame", "Ideal Render Instant",
-                       frame->metadata()->reference_time->ToInternalValue());
+                       frame->metadata().reference_time->ToInternalValue());
         } else {
           TRACE_EVENT0("media", "EnqueueFrame");
         }
diff --git a/third_party/blink/renderer/modules/mediastream/webmediaplayer_ms_compositor.cc b/third_party/blink/renderer/modules/mediastream/webmediaplayer_ms_compositor.cc
index f510376..b4384a79a 100644
--- a/third_party/blink/renderer/modules/mediastream/webmediaplayer_ms_compositor.cc
+++ b/third_party/blink/renderer/modules/mediastream/webmediaplayer_ms_compositor.cc
@@ -129,7 +129,7 @@
   }
 
   // Transfer metadata keys.
-  new_frame->metadata()->MergeMetadataFrom(frame->metadata());
+  new_frame->metadata().MergeMetadataFrom(frame->metadata());
   return new_frame;
 }
 
@@ -346,7 +346,7 @@
   }
 
   // This is a signal frame saying that the stream is stopped.
-  if (frame->metadata()->end_of_stream) {
+  if (frame->metadata().end_of_stream) {
     rendering_frame_buffer_.reset();
     RenderWithoutAlgorithm(std::move(frame), is_copy);
     return;
@@ -360,8 +360,8 @@
   // note that this is an experimental feature that is only active if certain
   // experimental parameters are specified in WebRTC. See crbug.com/1138888 for
   // more information.
-  if (!frame->metadata()->reference_time.has_value() &&
-      !frame->metadata()->maximum_composition_delay_in_frames) {
+  if (!frame->metadata().reference_time.has_value() &&
+      !frame->metadata().maximum_composition_delay_in_frames) {
     DLOG(WARNING)
         << "Incoming VideoFrames have no reference_time, switching off super "
            "sophisticated rendering algorithm";
@@ -369,8 +369,8 @@
     RenderWithoutAlgorithm(std::move(frame), is_copy);
     return;
   }
-  base::TimeTicks render_time = frame->metadata()->reference_time
-                                    ? *frame->metadata()->reference_time
+  base::TimeTicks render_time = frame->metadata().reference_time
+                                    ? *frame->metadata().reference_time
                                     : base::TimeTicks();
 
   // The code below handles the case where UpdateCurrentFrame() callbacks stop.
@@ -419,9 +419,8 @@
 #endif  // DCHECK_IS_ON()
     if (tracing_or_dcheck_enabled) {
       base::TimeTicks render_time =
-          current_frame_->metadata()->reference_time.value_or(
-              base::TimeTicks());
-      DCHECK(current_frame_->metadata()->reference_time.has_value() ||
+          current_frame_->metadata().reference_time.value_or(base::TimeTicks());
+      DCHECK(current_frame_->metadata().reference_time.has_value() ||
              !rendering_frame_buffer_ ||
              (rendering_frame_buffer_ &&
               !rendering_frame_buffer_->NeedsReferenceTime()))
@@ -611,7 +610,7 @@
   bool has_frame_size_changed = false;
 
   base::Optional<media::VideoRotation> new_rotation =
-      frame->metadata()->rotation.value_or(media::VIDEO_ROTATION_0);
+      frame->metadata().rotation.value_or(media::VIDEO_ROTATION_0);
 
   base::Optional<bool> new_opacity;
   new_opacity = media::IsOpaque(frame->format());
@@ -621,7 +620,7 @@
     is_first_frame = false;
 
     media::VideoRotation current_video_rotation =
-        current_frame_->metadata()->rotation.value_or(media::VIDEO_ROTATION_0);
+        current_frame_->metadata().rotation.value_or(media::VIDEO_ROTATION_0);
 
     has_frame_size_changed =
         RotationAdjustedSize(*new_rotation, frame->natural_size()) !=
diff --git a/third_party/blink/renderer/modules/mediastream/webmediaplayer_ms_test.cc b/third_party/blink/renderer/modules/mediastream/webmediaplayer_ms_test.cc
index b81b4ed5..90c6d89a 100644
--- a/third_party/blink/renderer/modules/mediastream/webmediaplayer_ms_test.cc
+++ b/third_party/blink/renderer/modules/mediastream/webmediaplayer_ms_test.cc
@@ -344,9 +344,9 @@
       // MediaStreamRemoteVideoSource does not explicitly set the rotation
       // for unrotated frames, so that is not done here either.
       if (rotation != media::VIDEO_ROTATION_0)
-        frame->metadata()->rotation = rotation;
+        frame->metadata().rotation = rotation;
 
-      frame->metadata()->reference_time =
+      frame->metadata().reference_time =
           base::TimeTicks::Now() + base::TimeDelta::FromMilliseconds(token);
 
       AddFrame(FrameType::NORMAL_FRAME, frame);
diff --git a/third_party/blink/renderer/modules/peerconnection/media_stream_remote_video_source.cc b/third_party/blink/renderer/modules/peerconnection/media_stream_remote_video_source.cc
index 266f467..873a261 100644
--- a/third_party/blink/renderer/modules/peerconnection/media_stream_remote_video_source.cc
+++ b/third_party/blink/renderer/modules/peerconnection/media_stream_remote_video_source.cc
@@ -278,7 +278,7 @@
 
   // Rotation may be explicitly set sometimes.
   if (incoming_frame.rotation() != webrtc::kVideoRotation_0) {
-    video_frame->metadata()->rotation =
+    video_frame->metadata().rotation =
         WebRtcToMediaVideoRotation(incoming_frame.rotation());
   }
 
@@ -303,25 +303,24 @@
   // Run render smoothness algorithm only when we don't have to render
   // immediately.
   if (!render_immediately)
-    video_frame->metadata()->reference_time = render_time;
+    video_frame->metadata().reference_time = render_time;
 
   if (incoming_frame.max_composition_delay_in_frames()) {
-    video_frame->metadata()->maximum_composition_delay_in_frames =
+    video_frame->metadata().maximum_composition_delay_in_frames =
         *incoming_frame.max_composition_delay_in_frames();
   }
 
-  video_frame->metadata()->decode_end_time = current_time;
+  video_frame->metadata().decode_end_time = current_time;
 
   // RTP_TIMESTAMP, PROCESSING_TIME, and CAPTURE_BEGIN_TIME are all exposed
   // through the JavaScript callback mechanism
   // video.requestVideoFrameCallback().
-  video_frame->metadata()->rtp_timestamp =
+  video_frame->metadata().rtp_timestamp =
       static_cast<double>(incoming_frame.timestamp());
 
   if (incoming_frame.processing_time()) {
-    video_frame->metadata()->processing_time =
-        base::TimeDelta::FromMicroseconds(
-            incoming_frame.processing_time()->Elapsed().us());
+    video_frame->metadata().processing_time = base::TimeDelta::FromMicroseconds(
+        incoming_frame.processing_time()->Elapsed().us());
   }
 
   // Set capture time to the NTP time, which is the estimated capture time
@@ -332,7 +331,7 @@
         base::TimeDelta::FromMilliseconds(incoming_frame.ntp_time_ms() +
                                           ntp_offset_) +
         time_diff_;
-    video_frame->metadata()->capture_begin_time = capture_time;
+    video_frame->metadata().capture_begin_time = capture_time;
   }
 
   // Set receive time to arrival of last packet.
@@ -348,7 +347,7 @@
     const base::TimeTicks receive_time =
         base::TimeTicks() +
         base::TimeDelta::FromMilliseconds(last_packet_arrival_ms) + time_diff_;
-    video_frame->metadata()->receive_time = receive_time;
+    video_frame->metadata().receive_time = receive_time;
   }
 
   // Use our computed render time as estimated capture time. If timestamp_us()
diff --git a/third_party/blink/renderer/modules/peerconnection/media_stream_remote_video_source_test.cc b/third_party/blink/renderer/modules/peerconnection/media_stream_remote_video_source_test.cc
index 0d8e50d..ef73094 100644
--- a/third_party/blink/renderer/modules/peerconnection/media_stream_remote_video_source_test.cc
+++ b/third_party/blink/renderer/modules/peerconnection/media_stream_remote_video_source_test.cc
@@ -436,19 +436,19 @@
   scoped_refptr<media::VideoFrame> output_frame = sink.last_frame();
   EXPECT_TRUE(output_frame);
 
-  EXPECT_FLOAT_EQ(output_frame->metadata()->processing_time->InSecondsF(),
+  EXPECT_FLOAT_EQ(output_frame->metadata().processing_time->InSecondsF(),
                   kProcessingTime);
 
   EXPECT_NEAR(
-      (*output_frame->metadata()->capture_begin_time - kExpectedCaptureTime)
+      (*output_frame->metadata().capture_begin_time - kExpectedCaptureTime)
           .InMillisecondsF(),
       0.0f, kChromiumWebRtcMaxTimeDiffMs);
 
-  EXPECT_NEAR((*output_frame->metadata()->receive_time - kExpectedReceiveTime)
+  EXPECT_NEAR((*output_frame->metadata().receive_time - kExpectedReceiveTime)
                   .InMillisecondsF(),
               0.0f, kChromiumWebRtcMaxTimeDiffMs);
 
-  EXPECT_EQ(static_cast<uint32_t>(*output_frame->metadata()->rtp_timestamp),
+  EXPECT_EQ(static_cast<uint32_t>(*output_frame->metadata().rtp_timestamp),
             kRtpTimestamp);
 
   track->RemoveSink(&sink);
@@ -478,7 +478,7 @@
   scoped_refptr<media::VideoFrame> output_frame = sink.last_frame();
   EXPECT_TRUE(output_frame);
 
-  EXPECT_NEAR((*output_frame->metadata()->reference_time -
+  EXPECT_NEAR((*output_frame->metadata().reference_time -
                (base::TimeTicks() +
                 base::TimeDelta::FromMicroseconds(kTimestampUs) + time_diff()))
                   .InMillisecondsF(),
@@ -508,7 +508,7 @@
   scoped_refptr<media::VideoFrame> output_frame = sink.last_frame();
   EXPECT_TRUE(output_frame);
 
-  EXPECT_FALSE(output_frame->metadata()->reference_time.has_value());
+  EXPECT_FALSE(output_frame->metadata().reference_time.has_value());
 
   track->RemoveSink(&sink);
 }
diff --git a/third_party/blink/renderer/modules/video_rvfc/video_frame_callback_requester_impl_test.cc b/third_party/blink/renderer/modules/video_rvfc/video_frame_callback_requester_impl_test.cc
index 1ff77a8..d63662b 100644
--- a/third_party/blink/renderer/modules/video_rvfc/video_frame_callback_requester_impl_test.cc
+++ b/third_party/blink/renderer/modules/video_rvfc/video_frame_callback_requester_impl_test.cc
@@ -58,8 +58,8 @@
 // constructor, due to it having a media::VideoFrameMetadata instance.
 class MetadataHelper {
  public:
-  static VideoFramePresentationMetadata* GetDefaultMedatada() {
-    return &metadata_;
+  static const VideoFramePresentationMetadata& GetDefaultMedatada() {
+    return metadata_;
   }
 
   static std::unique_ptr<VideoFramePresentationMetadata> CopyDefaultMedatada() {
@@ -71,7 +71,7 @@
     copy->width = metadata_.width;
     copy->height = metadata_.height;
     copy->media_time = metadata_.media_time;
-    copy->metadata.MergeMetadataFrom(&(metadata_.metadata));
+    copy->metadata.MergeMetadataFrom(metadata_.metadata);
 
     return copy;
   }
@@ -121,28 +121,28 @@
     was_invoked_ = true;
     now_ = now;
 
-    auto* expected = MetadataHelper::GetDefaultMedatada();
-    EXPECT_EQ(expected->presented_frames, metadata->presentedFrames());
-    EXPECT_EQ((unsigned int)expected->width, metadata->width());
-    EXPECT_EQ((unsigned int)expected->height, metadata->height());
-    EXPECT_EQ(expected->media_time.InSecondsF(), metadata->mediaTime());
+    auto expected = MetadataHelper::GetDefaultMedatada();
+    EXPECT_EQ(expected.presented_frames, metadata->presentedFrames());
+    EXPECT_EQ((unsigned int)expected.width, metadata->width());
+    EXPECT_EQ((unsigned int)expected.height, metadata->height());
+    EXPECT_EQ(expected.media_time.InSecondsF(), metadata->mediaTime());
 
-    EXPECT_EQ(*expected->metadata.rtp_timestamp, metadata->rtpTimestamp());
+    EXPECT_EQ(*expected.metadata.rtp_timestamp, metadata->rtpTimestamp());
 
     // Verify that values were correctly clamped.
-    VerifyTicksClamping(expected->presentation_time,
+    VerifyTicksClamping(expected.presentation_time,
                         metadata->presentationTime(), "presentation_time");
-    VerifyTicksClamping(expected->expected_display_time,
+    VerifyTicksClamping(expected.expected_display_time,
                         metadata->expectedDisplayTime(),
                         "expected_display_time");
 
-    VerifyTicksClamping(*expected->metadata.capture_begin_time,
+    VerifyTicksClamping(*expected.metadata.capture_begin_time,
                         metadata->captureTime(), "capture_time");
 
-    VerifyTicksClamping(*expected->metadata.receive_time,
+    VerifyTicksClamping(*expected.metadata.receive_time,
                         metadata->receiveTime(), "receive_time");
 
-    base::TimeDelta processing_time = *expected->metadata.processing_time;
+    base::TimeDelta processing_time = *expected.metadata.processing_time;
     EXPECT_EQ(ClampElapsedProcessingTime(processing_time),
               metadata->processingDuration());
     EXPECT_NE(processing_time.InSecondsF(), metadata->processingDuration());
diff --git a/third_party/blink/renderer/modules/webcodecs/BUILD.gn b/third_party/blink/renderer/modules/webcodecs/BUILD.gn
index 834d7c1..2c407ec 100644
--- a/third_party/blink/renderer/modules/webcodecs/BUILD.gn
+++ b/third_party/blink/renderer/modules/webcodecs/BUILD.gn
@@ -32,6 +32,8 @@
     "encoded_video_chunk.cc",
     "encoded_video_chunk.h",
     "encoded_video_metadata.h",
+    "encoder_base.cc",
+    "encoder_base.h",
     "image_decoder_external.cc",
     "image_decoder_external.h",
     "plane.cc",
diff --git a/third_party/blink/renderer/modules/webcodecs/encoder_base.cc b/third_party/blink/renderer/modules/webcodecs/encoder_base.cc
new file mode 100644
index 0000000..e786cba
--- /dev/null
+++ b/third_party/blink/renderer/modules/webcodecs/encoder_base.cc
@@ -0,0 +1,296 @@
+// 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/webcodecs/encoder_base.h"
+
+#include <string>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/callback_helpers.h"
+#include "base/logging.h"
+#include "base/macros.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"
+#include "third_party/blink/renderer/bindings/core/v8/script_promise.h"
+#include "third_party/blink/renderer/bindings/core/v8/v8_dom_exception.h"
+#include "third_party/blink/renderer/bindings/modules/v8/v8_video_encoder_config.h"
+#include "third_party/blink/renderer/bindings/modules/v8/v8_video_encoder_encode_options.h"
+#include "third_party/blink/renderer/bindings/modules/v8/v8_video_encoder_init.h"
+#include "third_party/blink/renderer/core/dom/dom_exception.h"
+#include "third_party/blink/renderer/modules/webcodecs/codec_state_helper.h"
+#include "third_party/blink/renderer/modules/webcodecs/encoded_video_chunk.h"
+#include "third_party/blink/renderer/modules/webcodecs/encoded_video_metadata.h"
+#include "third_party/blink/renderer/modules/webcodecs/video_encoder.h"
+#include "third_party/blink/renderer/platform/bindings/enumeration_base.h"
+#include "third_party/blink/renderer/platform/bindings/exception_state.h"
+#include "third_party/blink/renderer/platform/bindings/script_state.h"
+#include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
+#include "third_party/blink/renderer/platform/scheduler/public/post_cross_thread_task.h"
+#include "third_party/blink/renderer/platform/scheduler/public/thread.h"
+#include "third_party/blink/renderer/platform/wtf/cross_thread_functional.h"
+
+namespace blink {
+
+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) {
+  // TODO(crbug.com/1151005): Use a real MediaLog in worker contexts too.
+  if (IsMainThread()) {
+    logger_ = std::make_unique<CodecLogger>(
+        GetExecutionContext(), Thread::MainThread()->GetTaskRunner());
+  } else {
+    // This will create a logger backed by a NullMediaLog, which does nothing.
+    logger_ = std::make_unique<CodecLogger>();
+  }
+
+  media::MediaLog* log = logger_->log();
+
+  log->SetProperty<media::MediaLogProperty::kFrameTitle>(
+      std::string(Traits::GetNameForDevTools()));
+  log->SetProperty<media::MediaLogProperty::kFrameUrl>(
+      GetExecutionContext()->Url().GetString().Ascii());
+
+  output_callback_ = init->output();
+  if (init->hasError())
+    error_callback_ = init->error();
+}
+
+template <typename Traits>
+EncoderBase<Traits>::~EncoderBase() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+}
+
+template <typename Traits>
+int32_t EncoderBase<Traits>::encodeQueueSize() {
+  return requested_encodes_;
+}
+
+template <typename Traits>
+void EncoderBase<Traits>::configure(const ConfigType* config,
+                                    ExceptionState& exception_state) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  if (ThrowIfCodecStateClosed(state_, "configure", exception_state))
+    return;
+
+  InternalConfigType* parsed_config = ParseConfig(config, exception_state);
+  if (!parsed_config) {
+    DCHECK(exception_state.HadException());
+    return;
+  }
+
+  if (!VerifyCodecSupport(parsed_config, exception_state)) {
+    DCHECK(exception_state.HadException());
+    return;
+  }
+
+  Request* request = MakeGarbageCollected<Request>();
+  request->reset_count = reset_count_;
+  if (media_encoder_ && active_config_ &&
+      state_.AsEnum() == V8CodecState::Enum::kConfigured &&
+      CanReconfigure(*active_config_, *parsed_config)) {
+    request->type = Request::Type::kReconfigure;
+  } else {
+    state_ = V8CodecState(V8CodecState::Enum::kConfigured);
+    request->type = Request::Type::kConfigure;
+  }
+  active_config_ = parsed_config;
+  EnqueueRequest(request);
+}
+
+template <typename Traits>
+void EncoderBase<Traits>::encode(FrameType* frame,
+                                 const EncodeOptionsType* opts,
+                                 ExceptionState& exception_state) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  if (ThrowIfCodecStateClosed(state_, "encode", exception_state))
+    return;
+
+  if (ThrowIfCodecStateUnconfigured(state_, "encode", exception_state))
+    return;
+
+  DCHECK(active_config_);
+  auto* context = GetExecutionContext();
+  if (!context) {
+    exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
+                                      "Context is destroyed.");
+    return;
+  }
+
+  // This will fail if |frame| is already destroyed.
+  auto* internal_frame = CloneFrame(frame, context);
+
+  if (!internal_frame) {
+    exception_state.ThrowDOMException(DOMExceptionCode::kOperationError,
+                                      "Cannot encode destroyed frame.");
+    return;
+  }
+
+  // At this point, we have "consumed" the frame, and will destroy the clone
+  // in ProcessEncode().
+  frame->destroy();
+
+  Request* request = MakeGarbageCollected<Request>();
+  request->reset_count = reset_count_;
+  request->type = Request::Type::kEncode;
+  request->frame = internal_frame;
+  request->encodeOpts = opts;
+  ++requested_encodes_;
+  EnqueueRequest(request);
+}
+
+template <typename Traits>
+void EncoderBase<Traits>::close(ExceptionState& exception_state) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  if (ThrowIfCodecStateClosed(state_, "close", exception_state))
+    return;
+
+  state_ = V8CodecState(V8CodecState::Enum::kClosed);
+
+  ResetInternal();
+  media_encoder_.reset();
+  output_callback_.Clear();
+  error_callback_.Clear();
+}
+
+template <typename Traits>
+ScriptPromise EncoderBase<Traits>::flush(ExceptionState& exception_state) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (ThrowIfCodecStateClosed(state_, "flush", exception_state))
+    return ScriptPromise();
+
+  if (ThrowIfCodecStateUnconfigured(state_, "flush", exception_state))
+    return ScriptPromise();
+
+  Request* request = MakeGarbageCollected<Request>();
+  request->resolver =
+      MakeGarbageCollected<ScriptPromiseResolver>(script_state_);
+  request->reset_count = reset_count_;
+  request->type = Request::Type::kFlush;
+  EnqueueRequest(request);
+  return request->resolver->Promise();
+}
+
+template <typename Traits>
+void EncoderBase<Traits>::reset(ExceptionState& exception_state) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (ThrowIfCodecStateClosed(state_, "reset", exception_state))
+    return;
+
+  state_ = V8CodecState(V8CodecState::Enum::kUnconfigured);
+  ResetInternal();
+  media_encoder_.reset();
+}
+
+template <typename Traits>
+void EncoderBase<Traits>::ResetInternal() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  reset_count_++;
+  while (!requests_.empty()) {
+    Request* pending_req = requests_.TakeFirst();
+    DCHECK(pending_req);
+    if (pending_req->resolver)
+      pending_req->resolver.Release()->Resolve();
+    if (pending_req->frame)
+      pending_req->frame.Release()->destroy();
+  }
+  stall_request_processing_ = false;
+}
+
+template <typename Traits>
+void EncoderBase<Traits>::HandleError(DOMException* ex) {
+  if (state_.AsEnum() == V8CodecState::Enum::kClosed)
+    return;
+
+  // Save a temp before we clear the callback.
+  V8WebCodecsErrorCallback* error_callback = error_callback_.Get();
+
+  state_ = V8CodecState(V8CodecState::Enum::kClosed);
+
+  ResetInternal();
+
+  // Errors are permanent. Shut everything down.
+  error_callback_.Clear();
+  media_encoder_.reset();
+  output_callback_.Clear();
+
+  // Prevent further logging.
+  logger_->Neuter();
+
+  if (!script_state_->ContextIsValid() || !error_callback)
+    return;
+
+  ScriptState::Scope scope(script_state_);
+  error_callback->InvokeAndReportException(nullptr, ex);
+}
+
+template <typename Traits>
+void EncoderBase<Traits>::EnqueueRequest(Request* request) {
+  requests_.push_back(request);
+  ProcessRequests();
+}
+
+template <typename Traits>
+void EncoderBase<Traits>::ProcessRequests() {
+  while (!requests_.empty() && !stall_request_processing_) {
+    Request* request = requests_.TakeFirst();
+    DCHECK(request);
+    switch (request->type) {
+      case Request::Type::kConfigure:
+        ProcessConfigure(request);
+        break;
+      case Request::Type::kReconfigure:
+        ProcessReconfigure(request);
+        break;
+      case Request::Type::kEncode:
+        ProcessEncode(request);
+        break;
+      case Request::Type::kFlush:
+        ProcessFlush(request);
+        break;
+      default:
+        NOTREACHED();
+    }
+  }
+}
+
+template <typename Traits>
+void EncoderBase<Traits>::ContextDestroyed() {
+  logger_->Neuter();
+}
+
+template <typename Traits>
+bool EncoderBase<Traits>::HasPendingActivity() const {
+  return stall_request_processing_ || !requests_.empty();
+}
+
+template <typename Traits>
+void EncoderBase<Traits>::Trace(Visitor* visitor) const {
+  visitor->Trace(active_config_);
+  visitor->Trace(script_state_);
+  visitor->Trace(output_callback_);
+  visitor->Trace(error_callback_);
+  visitor->Trace(requests_);
+  ScriptWrappable::Trace(visitor);
+  ExecutionContextLifecycleObserver::Trace(visitor);
+}
+
+template <typename Traits>
+void EncoderBase<Traits>::Request::Trace(Visitor* visitor) const {
+  visitor->Trace(frame);
+  visitor->Trace(encodeOpts);
+  visitor->Trace(resolver);
+}
+
+template class EncoderBase<VideoEncoderTraits>;
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/modules/webcodecs/encoder_base.h b/third_party/blink/renderer/modules/webcodecs/encoder_base.h
new file mode 100644
index 0000000..7442ef1c
--- /dev/null
+++ b/third_party/blink/renderer/modules/webcodecs/encoder_base.h
@@ -0,0 +1,136 @@
+// 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_WEBCODECS_ENCODER_BASE_H_
+#define THIRD_PARTY_BLINK_RENDERER_MODULES_WEBCODECS_ENCODER_BASE_H_
+
+#include <memory>
+
+#include "media/base/media_log.h"
+#include "media/base/status.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/platform/bindings/script_wrappable.h"
+#include "third_party/blink/renderer/platform/context_lifecycle_observer.h"
+
+namespace blink {
+
+class ExceptionState;
+enum class DOMExceptionCode;
+class Visitor;
+
+template <typename Traits>
+class MODULES_EXPORT EncoderBase
+    : public ScriptWrappable,
+      public ActiveScriptWrappable<EncoderBase<Traits>>,
+      public ExecutionContextLifecycleObserver {
+ public:
+  using InitType = typename Traits::Init;
+  using ConfigType = typename Traits::Config;
+  using InternalConfigType = typename Traits::InternalConfig;
+  using FrameType = typename Traits::Frame;
+  using EncodeOptionsType = typename Traits::EncodeOptions;
+  using OutputChunkType = typename Traits::OutputChunk;
+  using OutputCallbackType = typename Traits::OutputCallback;
+  using MediaEncoderType = typename Traits::MediaEncoder;
+
+  EncoderBase(ScriptState*, const InitType*, ExceptionState&);
+  ~EncoderBase() override;
+
+  // *_encoder.idl implementation.
+  virtual int32_t encodeQueueSize();
+
+  virtual void configure(const ConfigType*, ExceptionState&);
+
+  virtual void encode(FrameType* frame,
+                      const EncodeOptionsType* opts,
+                      ExceptionState& exception_state);
+
+  virtual ScriptPromise flush(ExceptionState&);
+
+  virtual void reset(ExceptionState&);
+
+  virtual void close(ExceptionState&);
+
+  virtual String state() { return state_; }
+
+  // ExecutionContextLifecycleObserver override.
+  void ContextDestroyed() override;
+
+  // ScriptWrappable override.
+  bool HasPendingActivity() const override;
+
+  // GarbageCollected override.
+  void Trace(Visitor*) const override;
+
+ protected:
+  struct Request final : public GarbageCollected<Request> {
+    enum class Type {
+      // Configure an encoder from scratch, possibly replacing the existing one.
+      kConfigure,
+      // Adjust options in the already configured encoder.
+      kReconfigure,
+      kEncode,
+      kFlush,
+    };
+
+    void Trace(Visitor*) const;
+
+    Type type;
+    // Current value of EncoderBase.reset_count_ when request was created.
+    uint32_t reset_count = 0;
+    Member<FrameType> frame;                     // used by kEncode
+    Member<const EncodeOptionsType> encodeOpts;  // used by kEncode
+    Member<ScriptPromiseResolver> resolver;      // used by kFlush
+  };
+
+  virtual void HandleError(DOMException* ex);
+  virtual void EnqueueRequest(Request* request);
+  virtual void ProcessRequests();
+  virtual void ProcessEncode(Request* request) = 0;
+  virtual void ProcessConfigure(Request* request) = 0;
+  virtual void ProcessReconfigure(Request* request) = 0;
+  virtual void ProcessFlush(Request* request) = 0;
+  virtual void ResetInternal();
+
+  virtual bool CanReconfigure(InternalConfigType& original_config,
+                              InternalConfigType& new_config) = 0;
+  virtual InternalConfigType* ParseConfig(const ConfigType*,
+                                          ExceptionState&) = 0;
+  virtual bool VerifyCodecSupport(InternalConfigType*, ExceptionState&) = 0;
+  virtual FrameType* CloneFrame(FrameType*, ExecutionContext*) = 0;
+
+  std::unique_ptr<CodecLogger> logger_;
+
+  std::unique_ptr<MediaEncoderType> media_encoder_;
+
+  V8CodecState state_;
+
+  Member<InternalConfigType> active_config_;
+  Member<ScriptState> script_state_;
+  Member<OutputCallbackType> output_callback_;
+  Member<V8WebCodecsErrorCallback> error_callback_;
+  HeapDeque<Member<Request>> requests_;
+  int32_t requested_encodes_ = 0;
+
+  // How many times reset() was called on the encoder. It's used to decide
+  // when a callback needs to be dismissed because reset() was called between
+  // an operation and its callback.
+  uint32_t reset_count_ = 0;
+
+  // Some kConfigure and kFlush requests can't be executed in parallel with
+  // kEncode. This flag stops processing of new requests in the requests_ queue
+  // till the current requests are finished.
+  bool stall_request_processing_ = false;
+
+  SEQUENCE_CHECKER(sequence_checker_);
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_MODULES_WEBCODECS_ENCODER_BASE_H_
diff --git a/third_party/blink/renderer/modules/webcodecs/video_decoder_broker_test.cc b/third_party/blink/renderer/modules/webcodecs/video_decoder_broker_test.cc
index d675c737..ea0d524ed 100644
--- a/third_party/blink/renderer/modules/webcodecs/video_decoder_broker_test.cc
+++ b/third_party/blink/renderer/modules/webcodecs/video_decoder_broker_test.cc
@@ -65,7 +65,7 @@
             media::VideoFrame::ReleaseMailboxCB(), current_config_.coded_size(),
             current_config_.visible_rect(), current_config_.natural_size(),
             buffer.timestamp());
-    frame->metadata()->power_efficient = true;
+    frame->metadata().power_efficient = true;
     return frame;
   }
 
diff --git a/third_party/blink/renderer/modules/webcodecs/video_encoder.cc b/third_party/blink/renderer/modules/webcodecs/video_encoder.cc
index 1d0bf1f..ccb0716 100644
--- a/third_party/blink/renderer/modules/webcodecs/video_encoder.cc
+++ b/third_party/blink/renderer/modules/webcodecs/video_encoder.cc
@@ -177,6 +177,11 @@
 }  // namespace
 
 // static
+const char* VideoEncoderTraits::GetNameForDevTools() {
+  return "VideoEncoder(WebCodecs)";
+}
+
+// static
 VideoEncoder* VideoEncoder::Create(ScriptState* script_state,
                                    const VideoEncoderInit* init,
                                    ExceptionState& exception_state) {
@@ -187,40 +192,12 @@
 VideoEncoder::VideoEncoder(ScriptState* script_state,
                            const VideoEncoderInit* init,
                            ExceptionState& exception_state)
-    : ExecutionContextLifecycleObserver(ExecutionContext::From(script_state)),
-      state_(V8CodecState::Enum::kUnconfigured),
-      script_state_(script_state) {
+    : Base(script_state, init, exception_state) {
   UseCounter::Count(ExecutionContext::From(script_state),
                     WebFeature::kWebCodecs);
-
-  // TODO(crbug.com/1151005): Use a real MediaLog in worker contexts too.
-  if (IsMainThread()) {
-    logger_ = std::make_unique<CodecLogger>(
-        GetExecutionContext(), Thread::MainThread()->GetTaskRunner());
-  } else {
-    // This will create a logger backed by a NullMediaLog, which does nothing.
-    logger_ = std::make_unique<CodecLogger>();
-  }
-
-  media::MediaLog* log = logger_->log();
-
-  log->SetProperty<media::MediaLogProperty::kFrameTitle>(
-      std::string("VideoEncoder(WebCodecs)"));
-  log->SetProperty<media::MediaLogProperty::kFrameUrl>(
-      GetExecutionContext()->Url().GetString().Ascii());
-
-  output_callback_ = init->output();
-  if (init->hasError())
-    error_callback_ = init->error();
 }
 
-VideoEncoder::~VideoEncoder() {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-}
-
-int32_t VideoEncoder::encodeQueueSize() {
-  return requested_encodes_;
-}
+VideoEncoder::~VideoEncoder() = default;
 
 VideoEncoder::ParsedConfig* VideoEncoder::ParseConfig(
     const VideoEncoderConfig* config,
@@ -336,6 +313,11 @@
   return true;
 }
 
+VideoFrame* VideoEncoder::CloneFrame(VideoFrame* frame,
+                                     ExecutionContext* context) {
+  return frame->CloneFromNative(context);
+}
+
 void VideoEncoder::UpdateEncoderLog(std::string encoder_name,
                                     bool is_hw_accelerated) {
   // TODO(https://crbug.com/1139089) : Add encoder properties.
@@ -453,188 +435,6 @@
          original_config.acc_pref == new_config.acc_pref;
 }
 
-void VideoEncoder::configure(const VideoEncoderConfig* config,
-                             ExceptionState& exception_state) {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-
-  if (ThrowIfCodecStateClosed(state_, "configure", exception_state))
-    return;
-
-  auto* parsed_config = ParseConfig(config, exception_state);
-  if (!parsed_config) {
-    DCHECK(exception_state.HadException());
-    return;
-  }
-
-  if (!VerifyCodecSupport(parsed_config, exception_state)) {
-    DCHECK(exception_state.HadException());
-    return;
-  }
-
-  Request* request = MakeGarbageCollected<Request>();
-  request->reset_count = reset_count_;
-  if (media_encoder_ && active_config_ &&
-      state_.AsEnum() == V8CodecState::Enum::kConfigured &&
-      CanReconfigure(*active_config_, *parsed_config)) {
-    request->type = Request::Type::kReconfigure;
-  } else {
-    state_ = V8CodecState(V8CodecState::Enum::kConfigured);
-    request->type = Request::Type::kConfigure;
-  }
-  active_config_ = parsed_config;
-  EnqueueRequest(request);
-}
-
-void VideoEncoder::encode(VideoFrame* frame,
-                          const VideoEncoderEncodeOptions* opts,
-                          ExceptionState& exception_state) {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-
-  if (ThrowIfCodecStateClosed(state_, "encode", exception_state))
-    return;
-
-  if (ThrowIfCodecStateUnconfigured(state_, "encode", exception_state))
-    return;
-
-  DCHECK(active_config_);
-  auto* context = GetExecutionContext();
-  if (!context) {
-    exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
-                                      "Context is destroyed.");
-    return;
-  }
-
-  // This will fail if |frame| is already destroyed.
-  auto* internal_frame = frame->CloneFromNative(context);
-
-  if (!internal_frame) {
-    exception_state.ThrowDOMException(DOMExceptionCode::kOperationError,
-                                      "Cannot encode destroyed frame.");
-    return;
-  }
-
-  // At this point, we have "consumed" the frame, and will destroy the clone
-  // in ProcessEncode().
-  frame->destroy();
-
-  Request* request = MakeGarbageCollected<Request>();
-  request->reset_count = reset_count_;
-  request->type = Request::Type::kEncode;
-  request->frame = internal_frame;
-  request->encodeOpts = opts;
-  ++requested_encodes_;
-  EnqueueRequest(request);
-}
-
-void VideoEncoder::close(ExceptionState& exception_state) {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-
-  if (ThrowIfCodecStateClosed(state_, "close", exception_state))
-    return;
-
-  state_ = V8CodecState(V8CodecState::Enum::kClosed);
-
-  ResetInternal();
-  media_encoder_.reset();
-  output_callback_.Clear();
-  error_callback_.Clear();
-}
-
-ScriptPromise VideoEncoder::flush(ExceptionState& exception_state) {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  if (ThrowIfCodecStateClosed(state_, "flush", exception_state))
-    return ScriptPromise();
-
-  if (ThrowIfCodecStateUnconfigured(state_, "flush", exception_state))
-    return ScriptPromise();
-
-  Request* request = MakeGarbageCollected<Request>();
-  request->resolver =
-      MakeGarbageCollected<ScriptPromiseResolver>(script_state_);
-  request->reset_count = reset_count_;
-  request->type = Request::Type::kFlush;
-  EnqueueRequest(request);
-  return request->resolver->Promise();
-}
-
-void VideoEncoder::reset(ExceptionState& exception_state) {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  if (ThrowIfCodecStateClosed(state_, "reset", exception_state))
-    return;
-
-  state_ = V8CodecState(V8CodecState::Enum::kUnconfigured);
-  ResetInternal();
-  media_encoder_.reset();
-}
-
-void VideoEncoder::ResetInternal() {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  reset_count_++;
-  while (!requests_.empty()) {
-    Request* pending_req = requests_.TakeFirst();
-    DCHECK(pending_req);
-    if (pending_req->resolver)
-      pending_req->resolver.Release()->Resolve();
-    if (pending_req->frame)
-      pending_req->frame.Release()->destroy();
-  }
-  stall_request_processing_ = false;
-}
-
-void VideoEncoder::HandleError(DOMException* ex) {
-  if (state_.AsEnum() == V8CodecState::Enum::kClosed)
-    return;
-
-  // Save a temp before we clear the callback.
-  V8WebCodecsErrorCallback* error_callback = error_callback_.Get();
-
-  state_ = V8CodecState(V8CodecState::Enum::kClosed);
-
-  ResetInternal();
-
-  // Errors are permanent. Shut everything down.
-  error_callback_.Clear();
-  media_encoder_.reset();
-  output_callback_.Clear();
-
-  // Prevent further logging.
-  logger_->Neuter();
-
-  if (!script_state_->ContextIsValid() || !error_callback)
-    return;
-
-  ScriptState::Scope scope(script_state_);
-  error_callback->InvokeAndReportException(nullptr, ex);
-}
-
-void VideoEncoder::EnqueueRequest(Request* request) {
-  requests_.push_back(request);
-  ProcessRequests();
-}
-
-void VideoEncoder::ProcessRequests() {
-  while (!requests_.empty() && !stall_request_processing_) {
-    Request* request = requests_.TakeFirst();
-    DCHECK(request);
-    switch (request->type) {
-      case Request::Type::kConfigure:
-        ProcessConfigure(request);
-        break;
-      case Request::Type::kReconfigure:
-        ProcessReconfigure(request);
-        break;
-      case Request::Type::kEncode:
-        ProcessEncode(request);
-        break;
-      case Request::Type::kFlush:
-        ProcessFlush(request);
-        break;
-      default:
-        NOTREACHED();
-    }
-  }
-}
-
 void VideoEncoder::ProcessEncode(Request* request) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK_EQ(state_, V8CodecState::Enum::kConfigured);
@@ -859,14 +659,6 @@
   output_callback_->InvokeAndReportException(nullptr, chunk, decoder_config);
 }
 
-void VideoEncoder::ContextDestroyed() {
-  logger_->Neuter();
-}
-
-bool VideoEncoder::HasPendingActivity() const {
-  return stall_request_processing_ || !requests_.empty();
-}
-
 // This function reads pixel data from textures associated with |txt_frame|
 // and creates a new CPU memory backed frame. It's needed because
 // existing video encoders can't handle texture backed frames.
@@ -909,7 +701,7 @@
       result_format, txt_frame->coded_size(), txt_frame->visible_rect(),
       txt_frame->natural_size(), txt_frame->timestamp());
   result->set_color_space(txt_frame->ColorSpace());
-  result->metadata()->MergeMetadataFrom(txt_frame->metadata());
+  result->metadata().MergeMetadataFrom(txt_frame->metadata());
 
   size_t planes = media::VideoFrame::NumPlanes(result->format());
   for (size_t plane = 0; plane < planes; plane++) {
@@ -973,19 +765,4 @@
   return result;
 }
 
-void VideoEncoder::Trace(Visitor* visitor) const {
-  visitor->Trace(active_config_);
-  visitor->Trace(script_state_);
-  visitor->Trace(output_callback_);
-  visitor->Trace(error_callback_);
-  visitor->Trace(requests_);
-  ScriptWrappable::Trace(visitor);
-  ExecutionContextLifecycleObserver::Trace(visitor);
-}
-
-void VideoEncoder::Request::Trace(Visitor* visitor) const {
-  visitor->Trace(frame);
-  visitor->Trace(encodeOpts);
-  visitor->Trace(resolver);
-}
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/webcodecs/video_encoder.h b/third_party/blink/renderer/modules/webcodecs/video_encoder.h
index be6a5af4..8f6ac48 100644
--- a/third_party/blink/renderer/modules/webcodecs/video_encoder.h
+++ b/third_party/blink/renderer/modules/webcodecs/video_encoder.h
@@ -8,22 +8,13 @@
 #include <memory>
 
 #include "base/optional.h"
-#include "media/base/media_log.h"
-#include "media/base/status.h"
 #include "media/base/video_codecs.h"
 #include "media/base/video_color_space.h"
 #include "media/base/video_encoder.h"
 #include "media/base/video_frame_pool.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_video_encoder_output_callback.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/encoder_base.h"
 #include "third_party/blink/renderer/modules/webcodecs/video_frame.h"
-#include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
-#include "third_party/blink/renderer/platform/context_lifecycle_observer.h"
 
 namespace media {
 class GpuVideoAcceleratorFactories;
@@ -33,56 +24,13 @@
 
 namespace blink {
 
-class ExceptionState;
-enum class DOMExceptionCode;
 class VideoEncoderConfig;
 class VideoEncoderInit;
 class VideoEncoderEncodeOptions;
-class Visitor;
 
-class MODULES_EXPORT VideoEncoder final
-    : public ScriptWrappable,
-      public ActiveScriptWrappable<VideoEncoder>,
-      public ExecutionContextLifecycleObserver {
-  DEFINE_WRAPPERTYPEINFO();
-
+class MODULES_EXPORT VideoEncoderTraits {
  public:
-  static VideoEncoder* Create(ScriptState*,
-                              const VideoEncoderInit*,
-                              ExceptionState&);
-  VideoEncoder(ScriptState*, const VideoEncoderInit*, ExceptionState&);
-  ~VideoEncoder() override;
-
-  // video_encoder.idl implementation.
-  int32_t encodeQueueSize();
-
-  void encode(VideoFrame* frame,
-              const VideoEncoderEncodeOptions*,
-              ExceptionState&);
-
-  void configure(const VideoEncoderConfig*, ExceptionState&);
-
-  ScriptPromise flush(ExceptionState&);
-
-  void reset(ExceptionState&);
-
-  void close(ExceptionState&);
-
-  String state() { return state_; }
-
-  // ExecutionContextLifecycleObserver override.
-  void ContextDestroyed() override;
-
-  // ScriptWrappable override.
-  bool HasPendingActivity() const override;
-
-  // GarbageCollected override.
-  void Trace(Visitor*) const override;
-
- private:
   enum class AccelerationPreference { kAllow, kDeny, kRequire };
-
-  // TODO(ezemtsov): Replace this with a {Audio|Video}EncoderConfig.
   struct ParsedConfig final : public GarbageCollected<ParsedConfig> {
     media::VideoCodec codec;
     media::VideoCodecProfile profile;
@@ -97,48 +45,54 @@
     void Trace(Visitor*) const {}
   };
 
-  struct Request final : public GarbageCollected<Request> {
-    enum class Type {
-      // Configure an encoder from scratch, possibly replacing the existing one.
-      kConfigure,
-      // Adjust options in the already configured encoder.
-      kReconfigure,
-      kEncode,
-      kFlush,
-    };
+  using Init = VideoEncoderInit;
+  using Config = VideoEncoderConfig;
+  using InternalConfig = ParsedConfig;
+  using Frame = VideoFrame;
+  using EncodeOptions = VideoEncoderEncodeOptions;
+  using OutputChunk = EncodedVideoChunk;
+  using OutputCallback = V8VideoEncoderOutputCallback;
+  using MediaEncoder = media::VideoEncoder;
 
-    void Trace(Visitor*) const;
+  // Can't be a virtual method, because it's used from base ctor.
+  static const char* GetNameForDevTools();
+};
 
-    Type type;
-    // Current value of VideoEncoder.reset_count_ when request was created.
-    uint32_t reset_count = 0;
-    Member<VideoFrame> frame;                            // used by kEncode
-    Member<const VideoEncoderEncodeOptions> encodeOpts;  // used by kEncode
-    Member<ScriptPromiseResolver> resolver;              // used by kFlush
-  };
+class MODULES_EXPORT VideoEncoder final
+    : public EncoderBase<VideoEncoderTraits> {
+  DEFINE_WRAPPERTYPEINFO();
+
+ public:
+  static VideoEncoder* Create(ScriptState*,
+                              const VideoEncoderInit*,
+                              ExceptionState&);
+  VideoEncoder(ScriptState*, const VideoEncoderInit*, ExceptionState&);
+  ~VideoEncoder() override;
+
+ private:
+  using Base = EncoderBase<VideoEncoderTraits>;
+  using AccelerationPreference = VideoEncoderTraits::AccelerationPreference;
+  using ParsedConfig = VideoEncoderTraits::ParsedConfig;
 
   void CallOutputCallback(
       ParsedConfig* active_config,
       uint32_t reset_count,
       media::VideoEncoderOutput output,
       base::Optional<media::VideoEncoder::CodecDescription> codec_desc);
-  void HandleError(DOMException* ex);
-  void EnqueueRequest(Request* request);
-  void ProcessRequests();
-  void ProcessEncode(Request* request);
-  void ProcessConfigure(Request* request);
-  void ProcessReconfigure(Request* request);
-  void ProcessFlush(Request* request);
+  void ProcessEncode(Request* request) override;
+  void ProcessConfigure(Request* request) override;
+  void ProcessReconfigure(Request* request) override;
+  void ProcessFlush(Request* request) override;
 
   void UpdateEncoderLog(std::string encoder_name, bool is_hw_accelerated);
 
-  void ResetInternal();
-  ScriptPromiseResolver* MakePromise();
-
   void OnReceivedGpuFactories(Request*, media::GpuVideoAcceleratorFactories*);
 
-  ParsedConfig* ParseConfig(const VideoEncoderConfig*, ExceptionState&);
-  bool VerifyCodecSupport(ParsedConfig*, ExceptionState&);
+  ParsedConfig* ParseConfig(const VideoEncoderConfig*,
+                            ExceptionState&) override;
+  bool VerifyCodecSupport(ParsedConfig*, ExceptionState&) override;
+  VideoFrame* CloneFrame(VideoFrame*, ExecutionContext*) override;
+
   void CreateAndInitializeEncoderWithoutAcceleration(Request* request);
   void CreateAndInitializeEncoderOnEncoderSupportKnown(
       Request* request,
@@ -146,34 +100,12 @@
   std::unique_ptr<media::VideoEncoder> CreateMediaVideoEncoder(
       const ParsedConfig& config,
       media::GpuVideoAcceleratorFactories* gpu_factories);
-  bool CanReconfigure(ParsedConfig& original_config, ParsedConfig& new_config);
+  bool CanReconfigure(ParsedConfig& original_config,
+                      ParsedConfig& new_config) override;
   scoped_refptr<media::VideoFrame> ReadbackTextureBackedFrameToMemory(
       scoped_refptr<media::VideoFrame> txt_frame);
 
-  std::unique_ptr<CodecLogger> logger_;
-
-  std::unique_ptr<media::VideoEncoder> media_encoder_;
-
-  V8CodecState state_;
-
-  Member<ParsedConfig> active_config_;
-  Member<ScriptState> script_state_;
-  Member<V8VideoEncoderOutputCallback> output_callback_;
-  Member<V8WebCodecsErrorCallback> error_callback_;
-  HeapDeque<Member<Request>> requests_;
-  int32_t requested_encodes_ = 0;
-  // How many times reset() was called on the encoder. It's used to decide
-  // when a callback needs to be dismissed because reset() was called between
-  // an operation and its callback.
-  uint32_t reset_count_ = 0;
-
-  // Some kConfigure and kFlush requests can't be executed in parallel with
-  // kEncode. This flag stops processing of new requests in the requests_ queue
-  // till the current requests are finished.
-  bool stall_request_processing_ = false;
   media::VideoFramePool readback_frame_pool_;
-
-  SEQUENCE_CHECKER(sequence_checker_);
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/webcodecs/video_frame.cc b/third_party/blink/renderer/modules/webcodecs/video_frame.cc
index cd7467e..3cc39a3 100644
--- a/third_party/blink/renderer/modules/webcodecs/video_frame.cc
+++ b/third_party/blink/renderer/modules/webcodecs/video_frame.cc
@@ -362,9 +362,9 @@
 base::Optional<uint64_t> VideoFrame::duration() const {
   auto local_frame = handle_->frame();
   // TODO(sandersd): Can a duration be kNoTimestamp?
-  if (!local_frame || !local_frame->metadata()->frame_duration.has_value())
+  if (!local_frame || !local_frame->metadata().frame_duration.has_value())
     return base::nullopt;
-  return local_frame->metadata()->frame_duration->InMicroseconds();
+  return local_frame->metadata().frame_duration->InMicroseconds();
 }
 
 void VideoFrame::destroy() {
diff --git a/third_party/blink/renderer/modules/webgpu/dawn_conversions.cc b/third_party/blink/renderer/modules/webgpu/dawn_conversions.cc
index 348295f7..3dd420f9 100644
--- a/third_party/blink/renderer/modules/webgpu/dawn_conversions.cc
+++ b/third_party/blink/renderer/modules/webgpu/dawn_conversions.cc
@@ -54,6 +54,73 @@
 }
 
 template <>
+WGPUBufferBindingType AsDawnEnum<WGPUBufferBindingType>(
+    const WTF::String& webgpu_enum) {
+  if (webgpu_enum == "uniform") {
+    return WGPUBufferBindingType_Uniform;
+  }
+  if (webgpu_enum == "storage") {
+    return WGPUBufferBindingType_Storage;
+  }
+  if (webgpu_enum == "read-only-storage") {
+    return WGPUBufferBindingType_ReadOnlyStorage;
+  }
+  NOTREACHED();
+  return WGPUBufferBindingType_Force32;
+}
+
+template <>
+WGPUSamplerBindingType AsDawnEnum<WGPUSamplerBindingType>(
+    const WTF::String& webgpu_enum) {
+  if (webgpu_enum == "filtering") {
+    return WGPUSamplerBindingType_Filtering;
+  }
+  if (webgpu_enum == "non-filtering") {
+    return WGPUSamplerBindingType_NonFiltering;
+  }
+  if (webgpu_enum == "comparison") {
+    return WGPUSamplerBindingType_Comparison;
+  }
+  NOTREACHED();
+  return WGPUSamplerBindingType_Force32;
+}
+
+template <>
+WGPUTextureSampleType AsDawnEnum<WGPUTextureSampleType>(
+    const WTF::String& webgpu_enum) {
+  if (webgpu_enum == "float") {
+    return WGPUTextureSampleType_Float;
+  }
+  if (webgpu_enum == "unfilterable-float") {
+    return WGPUTextureSampleType_UnfilterableFloat;
+  }
+  if (webgpu_enum == "depth") {
+    return WGPUTextureSampleType_Depth;
+  }
+  if (webgpu_enum == "sint") {
+    return WGPUTextureSampleType_Sint;
+  }
+  if (webgpu_enum == "uint") {
+    return WGPUTextureSampleType_Uint;
+  }
+  NOTREACHED();
+  return WGPUTextureSampleType_Force32;
+}
+
+template <>
+WGPUStorageTextureAccess AsDawnEnum<WGPUStorageTextureAccess>(
+    const WTF::String& webgpu_enum) {
+  if (webgpu_enum == "read-only") {
+    return WGPUStorageTextureAccess_ReadOnly;
+  }
+  if (webgpu_enum == "write-only") {
+    return WGPUStorageTextureAccess_WriteOnly;
+  }
+  NOTREACHED();
+  return WGPUStorageTextureAccess_Force32;
+}
+
+template <>
 WGPUTextureComponentType AsDawnEnum<WGPUTextureComponentType>(
     const WTF::String& webgpu_enum) {
   if (webgpu_enum == "float") {
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_bind_group_layout.cc b/third_party/blink/renderer/modules/webgpu/gpu_bind_group_layout.cc
index 2d96000f..cc83e4e 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_bind_group_layout.cc
+++ b/third_party/blink/renderer/modules/webgpu/gpu_bind_group_layout.cc
@@ -6,6 +6,10 @@
 
 #include "third_party/blink/renderer/bindings/modules/v8/v8_gpu_bind_group_layout_descriptor.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_gpu_bind_group_layout_entry.h"
+#include "third_party/blink/renderer/bindings/modules/v8/v8_gpu_buffer_binding_layout.h"
+#include "third_party/blink/renderer/bindings/modules/v8/v8_gpu_sampler_binding_layout.h"
+#include "third_party/blink/renderer/bindings/modules/v8/v8_gpu_storage_texture_binding_layout.h"
+#include "third_party/blink/renderer/bindings/modules/v8/v8_gpu_texture_binding_layout.h"
 #include "third_party/blink/renderer/modules/webgpu/dawn_conversions.h"
 #include "third_party/blink/renderer/modules/webgpu/gpu_device.h"
 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
@@ -20,23 +24,65 @@
   dawn_binding.binding = webgpu_binding->binding();
   dawn_binding.visibility =
       AsDawnEnum<WGPUShaderStage>(webgpu_binding->visibility());
-  dawn_binding.type = AsDawnEnum<WGPUBindingType>(webgpu_binding->type());
 
-  dawn_binding.hasDynamicOffset = webgpu_binding->hasDynamicOffset();
+  if (webgpu_binding->hasBuffer()) {
+    dawn_binding.buffer.type =
+        AsDawnEnum<WGPUBufferBindingType>(webgpu_binding->buffer()->type());
+    dawn_binding.buffer.hasDynamicOffset =
+        webgpu_binding->buffer()->hasDynamicOffset();
+    dawn_binding.buffer.minBindingSize =
+        webgpu_binding->buffer()->minBindingSize();
+  }
 
-  dawn_binding.minBufferBindingSize =
-      webgpu_binding->hasMinBufferBindingSize()
-          ? webgpu_binding->minBufferBindingSize()
-          : 0;
+  if (webgpu_binding->hasSampler()) {
+    dawn_binding.sampler.type =
+        AsDawnEnum<WGPUSamplerBindingType>(webgpu_binding->sampler()->type());
+  }
 
-  dawn_binding.viewDimension =
-      AsDawnEnum<WGPUTextureViewDimension>(webgpu_binding->viewDimension());
+  if (webgpu_binding->hasTexture()) {
+    dawn_binding.texture.sampleType = AsDawnEnum<WGPUTextureSampleType>(
+        webgpu_binding->texture()->sampleType());
+    dawn_binding.texture.viewDimension = AsDawnEnum<WGPUTextureViewDimension>(
+        webgpu_binding->texture()->viewDimension());
+    dawn_binding.texture.multisampled =
+        webgpu_binding->texture()->multisampled();
+  }
 
-  dawn_binding.textureComponentType = AsDawnEnum<WGPUTextureComponentType>(
-      webgpu_binding->textureComponentType());
+  if (webgpu_binding->hasStorageTexture()) {
+    dawn_binding.storageTexture.access = AsDawnEnum<WGPUStorageTextureAccess>(
+        webgpu_binding->storageTexture()->access());
+    dawn_binding.storageTexture.format = AsDawnEnum<WGPUTextureFormat>(
+        webgpu_binding->storageTexture()->format());
+    dawn_binding.storageTexture.viewDimension =
+        AsDawnEnum<WGPUTextureViewDimension>(
+            webgpu_binding->storageTexture()->viewDimension());
+  }
 
-  dawn_binding.storageTextureFormat =
-      AsDawnEnum<WGPUTextureFormat>(webgpu_binding->storageTextureFormat());
+  // Deprecated values
+  if (webgpu_binding->hasType()) {
+    device->AddConsoleWarning(
+        "The format of GPUBindGroupLayoutEntry has changed, and will soon "
+        "require the buffer, sampler, texture, or storageTexture members be "
+        "set rather than setting type, etc. on the entry directly.");
+
+    dawn_binding.type = AsDawnEnum<WGPUBindingType>(webgpu_binding->type());
+
+    dawn_binding.hasDynamicOffset = webgpu_binding->hasDynamicOffset();
+
+    dawn_binding.minBufferBindingSize =
+        webgpu_binding->hasMinBufferBindingSize()
+            ? webgpu_binding->minBufferBindingSize()
+            : 0;
+
+    dawn_binding.viewDimension =
+        AsDawnEnum<WGPUTextureViewDimension>(webgpu_binding->viewDimension());
+
+    dawn_binding.textureComponentType = AsDawnEnum<WGPUTextureComponentType>(
+        webgpu_binding->textureComponentType());
+
+    dawn_binding.storageTextureFormat =
+        AsDawnEnum<WGPUTextureFormat>(webgpu_binding->storageTextureFormat());
+  }
 
   return dawn_binding;
 }
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_bind_group_layout_entry.idl b/third_party/blink/renderer/modules/webgpu/gpu_bind_group_layout_entry.idl
index 2733d66f..1cbd37d 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_bind_group_layout_entry.idl
+++ b/third_party/blink/renderer/modules/webgpu/gpu_bind_group_layout_entry.idl
@@ -1,13 +1,16 @@
-// 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.
 
-// https://gpuweb.github.io/gpuweb/
 
 dictionary GPUBindGroupLayoutEntry {
     required GPUIndex32 binding;
     required GPUShaderStageFlags visibility;
-    required GPUBindingType type;
+
+    GPUBufferBindingLayout buffer;
+    GPUSamplerBindingLayout sampler;
+    GPUTextureBindingLayout texture;
+    GPUStorageTextureBindingLayout storageTexture;
+
+    // Deprecated BindGroupLayout members.
+    GPUBindingType type;
 
     boolean hasDynamicOffset = false;
 
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_buffer_binding_layout.idl b/third_party/blink/renderer/modules/webgpu/gpu_buffer_binding_layout.idl
new file mode 100644
index 0000000..b87f2eb
--- /dev/null
+++ b/third_party/blink/renderer/modules/webgpu/gpu_buffer_binding_layout.idl
@@ -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.
+
+// https://gpuweb.github.io/gpuweb/
+
+dictionary GPUBufferBindingLayout {
+    GPUBufferBindingType type = "uniform";
+    boolean hasDynamicOffset = false;
+    GPUSize64 minBindingSize = 0;
+};
+
+enum GPUBufferBindingType {
+    "uniform",
+    "storage",
+    "read-only-storage",
+};
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_sampler_binding_layout.idl b/third_party/blink/renderer/modules/webgpu/gpu_sampler_binding_layout.idl
new file mode 100644
index 0000000..38a15152
--- /dev/null
+++ b/third_party/blink/renderer/modules/webgpu/gpu_sampler_binding_layout.idl
@@ -0,0 +1,15 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// https://gpuweb.github.io/gpuweb/
+
+dictionary GPUSamplerBindingLayout {
+    GPUSamplerBindingType type = "filtering";
+};
+
+enum GPUSamplerBindingType {
+    "filtering",
+    "non-filtering",
+    "comparison",
+};
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_storage_texture_binding_layout.idl b/third_party/blink/renderer/modules/webgpu/gpu_storage_texture_binding_layout.idl
new file mode 100644
index 0000000..f9903b30
--- /dev/null
+++ b/third_party/blink/renderer/modules/webgpu/gpu_storage_texture_binding_layout.idl
@@ -0,0 +1,16 @@
+// 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.
+
+// https://gpuweb.github.io/gpuweb/
+
+dictionary GPUStorageTextureBindingLayout {
+    required GPUStorageTextureAccess access;
+    required GPUTextureFormat format;
+    GPUTextureViewDimension viewDimension = "2d";
+};
+
+enum GPUStorageTextureAccess {
+    "read-only",
+    "write-only",
+};
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_texture_binding_layout.idl b/third_party/blink/renderer/modules/webgpu/gpu_texture_binding_layout.idl
new file mode 100644
index 0000000..e0945872
--- /dev/null
+++ b/third_party/blink/renderer/modules/webgpu/gpu_texture_binding_layout.idl
@@ -0,0 +1,19 @@
+// 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.
+
+// https://gpuweb.github.io/gpuweb/
+
+dictionary GPUTextureBindingLayout {
+    GPUTextureSampleType sampleType = "float";
+    GPUTextureViewDimension viewDimension = "2d";
+    boolean multisampled = false;
+};
+
+enum GPUTextureSampleType {
+    "float",
+    "unfilterable-float",
+    "depth",
+    "sint",
+    "uint",
+};
diff --git a/third_party/blink/renderer/modules/webgpu/idls.gni b/third_party/blink/renderer/modules/webgpu/idls.gni
index 31dad915..076cacb 100644
--- a/third_party/blink/renderer/modules/webgpu/idls.gni
+++ b/third_party/blink/renderer/modules/webgpu/idls.gni
@@ -45,6 +45,7 @@
   "gpu_bind_group_layout_entry.idl",
   "gpu_blend_descriptor.idl",
   "gpu_buffer_binding.idl",
+  "gpu_buffer_binding_layout.idl",
   "gpu_buffer_copy_view.idl",
   "gpu_buffer_descriptor.idl",
   "gpu_color_dict.idl",
@@ -74,10 +75,13 @@
   "gpu_render_pass_descriptor.idl",
   "gpu_render_pipeline_descriptor.idl",
   "gpu_request_adapter_options.idl",
+  "gpu_sampler_binding_layout.idl",
   "gpu_sampler_descriptor.idl",
   "gpu_shader_module_descriptor.idl",
   "gpu_stencil_state_face_descriptor.idl",
+  "gpu_storage_texture_binding_layout.idl",
   "gpu_swap_chain_descriptor.idl",
+  "gpu_texture_binding_layout.idl",
   "gpu_texture_copy_view.idl",
   "gpu_texture_data_layout.idl",
   "gpu_texture_descriptor.idl",
diff --git a/third_party/blink/renderer/platform/graphics/canvas_color_params.cc b/third_party/blink/renderer/platform/graphics/canvas_color_params.cc
index 6532f5a..9bd9885 100644
--- a/third_party/blink/renderer/platform/graphics/canvas_color_params.cc
+++ b/third_party/blink/renderer/platform/graphics/canvas_color_params.cc
@@ -82,12 +82,51 @@
       pixel_format_(pixel_format),
       opacity_mode_(opacity_mode) {}
 
+CanvasColorParams::CanvasColorParams(const WTF::String& color_space,
+                                     const WTF::String& pixel_format,
+                                     bool has_alpha) {
+  if (color_space == kRec2020CanvasColorSpaceName)
+    color_space_ = CanvasColorSpace::kRec2020;
+  else if (color_space == kP3CanvasColorSpaceName)
+    color_space_ = CanvasColorSpace::kP3;
+
+  if (pixel_format == kF16CanvasPixelFormatName)
+    pixel_format_ = CanvasPixelFormat::kF16;
+
+  if (!has_alpha)
+    opacity_mode_ = kOpaque;
+}
+
 CanvasResourceParams CanvasColorParams::GetAsResourceParams() const {
   SkAlphaType alpha_type =
       opacity_mode_ == kOpaque ? kOpaque_SkAlphaType : kPremul_SkAlphaType;
   return CanvasResourceParams(color_space_, GetSkColorType(), alpha_type);
 }
 
+const char* CanvasColorParams::GetColorSpaceAsString() const {
+  switch (color_space_) {
+    case CanvasColorSpace::kSRGB:
+      return kSRGBCanvasColorSpaceName;
+    case CanvasColorSpace::kRec2020:
+      return kRec2020CanvasColorSpaceName;
+    case CanvasColorSpace::kP3:
+      return kP3CanvasColorSpaceName;
+  };
+  CHECK(false);
+  return "";
+}
+
+const char* CanvasColorParams::GetPixelFormatAsString() const {
+  switch (pixel_format_) {
+    case CanvasPixelFormat::kF16:
+      return kF16CanvasPixelFormatName;
+    case CanvasPixelFormat::kUint8:
+      return kUint8CanvasPixelFormatName;
+  };
+  CHECK(false);
+  return "";
+}
+
 SkColorType CanvasColorParams::GetSkColorType() const {
   switch (pixel_format_) {
     case CanvasPixelFormat::kF16:
diff --git a/third_party/blink/renderer/platform/graphics/canvas_color_params.h b/third_party/blink/renderer/platform/graphics/canvas_color_params.h
index e1b38b7..e32f3d9 100644
--- a/third_party/blink/renderer/platform/graphics/canvas_color_params.h
+++ b/third_party/blink/renderer/platform/graphics/canvas_color_params.h
@@ -59,14 +59,16 @@
   // The default constructor will create an output-blended 8-bit surface.
   CanvasColorParams();
   CanvasColorParams(CanvasColorSpace, CanvasPixelFormat, OpacityMode);
+  CanvasColorParams(const WTF::String& color_space,
+                    const WTF::String& pixel_format,
+                    bool has_alpha);
 
   CanvasColorSpace ColorSpace() const { return color_space_; }
   CanvasPixelFormat PixelFormat() const { return pixel_format_; }
   OpacityMode GetOpacityMode() const { return opacity_mode_; }
 
-  void SetCanvasColorSpace(CanvasColorSpace c) { color_space_ = c; }
-  void SetCanvasPixelFormat(CanvasPixelFormat f) { pixel_format_ = f; }
-  void SetOpacityMode(OpacityMode m) { opacity_mode_ = m; }
+  const char* GetColorSpaceAsString() const;
+  const char* GetPixelFormatAsString() const;
 
   CanvasResourceParams GetAsResourceParams() const;
 
diff --git a/third_party/blink/renderer/platform/graphics/canvas_resource_dispatcher.cc b/third_party/blink/renderer/platform/graphics/canvas_resource_dispatcher.cc
index 8828439..86e16750 100644
--- a/third_party/blink/renderer/platform/graphics/canvas_resource_dispatcher.cc
+++ b/third_party/blink/renderer/platform/graphics/canvas_resource_dispatcher.cc
@@ -140,8 +140,6 @@
 void CanvasResourceDispatcher::PostImageToPlaceholder(
     scoped_refptr<CanvasResource> canvas_resource,
     viz::ResourceId resource_id) {
-  scoped_refptr<base::SingleThreadTaskRunner> dispatcher_task_runner =
-      Thread::Current()->GetTaskRunner();
   // After this point, |canvas_resource| can only be used on the main thread,
   // until it is returned.
   canvas_resource->Transfer();
diff --git a/third_party/blink/renderer/platform/graphics/compositing/property_tree_manager.cc b/third_party/blink/renderer/platform/graphics/compositing/property_tree_manager.cc
index 6787d402..d9b4bc7 100644
--- a/third_party/blink/renderer/platform/graphics/compositing/property_tree_manager.cc
+++ b/third_party/blink/renderer/platform/graphics/compositing/property_tree_manager.cc
@@ -19,7 +19,6 @@
 #include "third_party/blink/renderer/platform/graphics/paint/geometry_mapper.h"
 #include "third_party/blink/renderer/platform/graphics/paint/scroll_paint_property_node.h"
 #include "third_party/blink/renderer/platform/graphics/paint/transform_paint_property_node.h"
-#include "third_party/skia/include/effects/SkColorFilterImageFilter.h"
 #include "third_party/skia/include/effects/SkLumaColorFilter.h"
 
 namespace blink {
diff --git a/third_party/blink/renderer/platform/graphics/compositor_filter_operations.cc b/third_party/blink/renderer/platform/graphics/compositor_filter_operations.cc
index 9eec397..8217d75 100644
--- a/third_party/blink/renderer/platform/graphics/compositor_filter_operations.cc
+++ b/third_party/blink/renderer/platform/graphics/compositor_filter_operations.cc
@@ -6,7 +6,6 @@
 
 #include "third_party/blink/renderer/platform/geometry/int_rect.h"
 #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
-#include "third_party/skia/include/core/SkImageFilter.h"
 #include "ui/gfx/geometry/rect.h"
 
 namespace blink {
@@ -53,9 +52,8 @@
   filter_operations_.Append(cc::FilterOperation::CreateOpacityFilter(amount));
 }
 
-void CompositorFilterOperations::AppendBlurFilter(
-    float amount,
-    SkBlurImageFilter::TileMode tile_mode) {
+void CompositorFilterOperations::AppendBlurFilter(float amount,
+                                                  SkTileMode tile_mode) {
   filter_operations_.Append(
       cc::FilterOperation::CreateBlurFilter(amount, tile_mode));
 }
diff --git a/third_party/blink/renderer/platform/graphics/compositor_filter_operations.h b/third_party/blink/renderer/platform/graphics/compositor_filter_operations.h
index 4c3a334f..6265a90 100644
--- a/third_party/blink/renderer/platform/graphics/compositor_filter_operations.h
+++ b/third_party/blink/renderer/platform/graphics/compositor_filter_operations.h
@@ -31,8 +31,7 @@
   void AppendContrastFilter(float amount);
   void AppendOpacityFilter(float amount);
   void AppendBlurFilter(float amount,
-                        SkBlurImageFilter::TileMode tile_mode =
-                            SkBlurImageFilter::kClampToBlack_TileMode);
+                        SkTileMode tile_mode = SkTileMode::kDecal);
   void AppendDropShadowFilter(IntPoint offset, float std_deviation, Color);
   void AppendColorMatrixFilter(const cc::FilterOperation::Matrix&);
   void AppendZoomFilter(float amount, int inset);
diff --git a/third_party/blink/renderer/platform/graphics/filters/fe_blend.cc b/third_party/blink/renderer/platform/graphics/filters/fe_blend.cc
index 7b69fee..a261591 100644
--- a/third_party/blink/renderer/platform/graphics/filters/fe_blend.cc
+++ b/third_party/blink/renderer/platform/graphics/filters/fe_blend.cc
@@ -27,7 +27,6 @@
 #include "third_party/blink/renderer/platform/graphics/filters/paint_filter_builder.h"
 #include "third_party/blink/renderer/platform/graphics/skia/skia_utils.h"
 #include "third_party/blink/renderer/platform/wtf/text/text_stream.h"
-#include "third_party/skia/include/effects/SkXfermodeImageFilter.h"
 
 namespace blink {
 
@@ -48,9 +47,10 @@
       InputEffect(1), OperatingInterpolationSpace()));
   SkBlendMode mode =
       WebCoreCompositeToSkiaComposite(kCompositeSourceOver, mode_);
-  PaintFilter::CropRect crop_rect = GetCropRect();
+  base::Optional<PaintFilter::CropRect> crop_rect = GetCropRect();
   return sk_make_sp<XfermodePaintFilter>(mode, std::move(background),
-                                         std::move(foreground), &crop_rect);
+                                         std::move(foreground),
+                                         base::OptionalOrNullptr(crop_rect));
 }
 
 WTF::TextStream& FEBlend::ExternalRepresentation(WTF::TextStream& ts,
diff --git a/third_party/blink/renderer/platform/graphics/filters/fe_color_matrix.cc b/third_party/blink/renderer/platform/graphics/filters/fe_color_matrix.cc
index aa24b6a..968e929 100644
--- a/third_party/blink/renderer/platform/graphics/filters/fe_color_matrix.cc
+++ b/third_party/blink/renderer/platform/graphics/filters/fe_color_matrix.cc
@@ -25,7 +25,6 @@
 
 #include "third_party/blink/renderer/platform/graphics/filters/paint_filter_builder.h"
 #include "third_party/blink/renderer/platform/wtf/text/text_stream.h"
-#include "third_party/skia/include/effects/SkColorFilterImageFilter.h"
 #include "third_party/skia/include/effects/SkColorMatrixFilter.h"
 
 namespace blink {
@@ -146,9 +145,9 @@
   sk_sp<PaintFilter> input(paint_filter_builder::Build(
       InputEffect(0), OperatingInterpolationSpace()));
   sk_sp<SkColorFilter> filter = CreateColorFilter(type_, values_);
-  PaintFilter::CropRect rect = GetCropRect();
+  base::Optional<PaintFilter::CropRect> crop_rect = GetCropRect();
   return sk_make_sp<ColorFilterPaintFilter>(std::move(filter), std::move(input),
-                                            &rect);
+                                            base::OptionalOrNullptr(crop_rect));
 }
 
 static WTF::TextStream& operator<<(WTF::TextStream& ts,
diff --git a/third_party/blink/renderer/platform/graphics/filters/fe_component_transfer.cc b/third_party/blink/renderer/platform/graphics/filters/fe_component_transfer.cc
index a54b3ff2..0c1caba69 100644
--- a/third_party/blink/renderer/platform/graphics/filters/fe_component_transfer.cc
+++ b/third_party/blink/renderer/platform/graphics/filters/fe_component_transfer.cc
@@ -132,11 +132,12 @@
   unsigned char r_values[256], g_values[256], b_values[256], a_values[256];
   GetValues(r_values, g_values, b_values, a_values);
 
-  PaintFilter::CropRect crop_rect = GetCropRect();
+  base::Optional<PaintFilter::CropRect> crop_rect = GetCropRect();
   sk_sp<SkColorFilter> color_filter =
       SkTableColorFilter::MakeARGB(a_values, r_values, g_values, b_values);
   return sk_make_sp<ColorFilterPaintFilter>(std::move(color_filter),
-                                            std::move(input), &crop_rect);
+                                            std::move(input),
+                                            base::OptionalOrNullptr(crop_rect));
 }
 
 void FEComponentTransfer::GetValues(unsigned char r_values[256],
diff --git a/third_party/blink/renderer/platform/graphics/filters/fe_composite.cc b/third_party/blink/renderer/platform/graphics/filters/fe_composite.cc
index 6954edf..9b75b9a5 100644
--- a/third_party/blink/renderer/platform/graphics/filters/fe_composite.cc
+++ b/third_party/blink/renderer/platform/graphics/filters/fe_composite.cc
@@ -27,8 +27,6 @@
 #include "third_party/blink/renderer/platform/graphics/filters/paint_filter_builder.h"
 #include "third_party/blink/renderer/platform/graphics/skia/skia_utils.h"
 #include "third_party/blink/renderer/platform/wtf/text/text_stream.h"
-#include "third_party/skia/include/effects/SkArithmeticImageFilter.h"
-#include "third_party/skia/include/effects/SkXfermodeImageFilter.h"
 
 namespace blink {
 
@@ -186,18 +184,19 @@
   sk_sp<PaintFilter> background(
       paint_filter_builder::Build(InputEffect(1), OperatingInterpolationSpace(),
                                   !MayProduceInvalidPreMultipliedPixels()));
-  PaintFilter::CropRect crop_rect = GetCropRect();
+  base::Optional<PaintFilter::CropRect> crop_rect = GetCropRect();
 
   if (type_ == FECOMPOSITE_OPERATOR_ARITHMETIC) {
     return sk_make_sp<ArithmeticPaintFilter>(
         SkFloatToScalar(k1_), SkFloatToScalar(k2_), SkFloatToScalar(k3_),
         SkFloatToScalar(k4_), requires_pm_color_validation,
-        std::move(background), std::move(foreground), &crop_rect);
+        std::move(background), std::move(foreground),
+        base::OptionalOrNullptr(crop_rect));
   }
 
-  return sk_make_sp<XfermodePaintFilter>(ToBlendMode(type_),
-                                         std::move(background),
-                                         std::move(foreground), &crop_rect);
+  return sk_make_sp<XfermodePaintFilter>(
+      ToBlendMode(type_), std::move(background), std::move(foreground),
+      base::OptionalOrNullptr(crop_rect));
 }
 
 static WTF::TextStream& operator<<(WTF::TextStream& ts,
diff --git a/third_party/blink/renderer/platform/graphics/filters/fe_convolve_matrix.cc b/third_party/blink/renderer/platform/graphics/filters/fe_convolve_matrix.cc
index 940a798..0eb108c6 100644
--- a/third_party/blink/renderer/platform/graphics/filters/fe_convolve_matrix.cc
+++ b/third_party/blink/renderer/platform/graphics/filters/fe_convolve_matrix.cc
@@ -29,7 +29,6 @@
 #include "base/numerics/checked_math.h"
 #include "third_party/blink/renderer/platform/graphics/filters/paint_filter_builder.h"
 #include "third_party/blink/renderer/platform/wtf/text/text_stream.h"
-#include "third_party/skia/include/effects/SkMatrixConvolutionImageFilter.h"
 
 namespace blink {
 
@@ -94,17 +93,16 @@
   return true;
 }
 
-SkMatrixConvolutionImageFilter::TileMode ToSkiaTileMode(
-    EdgeModeType edge_mode) {
+static SkTileMode ToSkiaTileMode(EdgeModeType edge_mode) {
   switch (edge_mode) {
     case EDGEMODE_DUPLICATE:
-      return SkMatrixConvolutionImageFilter::kClamp_TileMode;
+      return SkTileMode::kClamp;
     case EDGEMODE_WRAP:
-      return SkMatrixConvolutionImageFilter::kRepeat_TileMode;
+      return SkTileMode::kRepeat;
     case EDGEMODE_NONE:
-      return SkMatrixConvolutionImageFilter::kClampToBlack_TileMode;
+      return SkTileMode::kDecal;
     default:
-      return SkMatrixConvolutionImageFilter::kClamp_TileMode;
+      return SkTileMode::kClamp;
   }
 }
 
@@ -138,15 +136,15 @@
   SkScalar gain = SkFloatToScalar(1.0f / divisor_);
   SkScalar bias = SkFloatToScalar(bias_ * 255);
   SkIPoint target = SkIPoint::Make(target_offset_.X(), target_offset_.Y());
-  MatrixConvolutionPaintFilter::TileMode tile_mode = ToSkiaTileMode(edge_mode_);
+  SkTileMode tile_mode = ToSkiaTileMode(edge_mode_);
   bool convolve_alpha = !preserve_alpha_;
   auto kernel = std::make_unique<SkScalar[]>(num_elements);
   for (int i = 0; i < num_elements; ++i)
     kernel[i] = SkFloatToScalar(kernel_matrix_[num_elements - 1 - i]);
-  PaintFilter::CropRect crop_rect = GetCropRect();
+  base::Optional<PaintFilter::CropRect> crop_rect = GetCropRect();
   return sk_make_sp<MatrixConvolutionPaintFilter>(
       kernel_size, kernel.get(), gain, bias, target, tile_mode, convolve_alpha,
-      std::move(input), &crop_rect);
+      std::move(input), base::OptionalOrNullptr(crop_rect));
 }
 
 static WTF::TextStream& operator<<(WTF::TextStream& ts,
diff --git a/third_party/blink/renderer/platform/graphics/filters/fe_displacement_map.cc b/third_party/blink/renderer/platform/graphics/filters/fe_displacement_map.cc
index da586678..f5e13fdd 100644
--- a/third_party/blink/renderer/platform/graphics/filters/fe_displacement_map.cc
+++ b/third_party/blink/renderer/platform/graphics/filters/fe_displacement_map.cc
@@ -27,7 +27,6 @@
 #include "third_party/blink/renderer/platform/graphics/filters/filter.h"
 #include "third_party/blink/renderer/platform/graphics/filters/paint_filter_builder.h"
 #include "third_party/blink/renderer/platform/wtf/text/text_stream.h"
-#include "third_party/skia/include/effects/SkDisplacementMapEffect.h"
 
 namespace blink {
 
@@ -86,20 +85,20 @@
   return true;
 }
 
-static SkDisplacementMapEffect::ChannelSelectorType ToSkiaMode(
-    ChannelSelectorType type) {
+static SkColorChannel ToSkiaMode(ChannelSelectorType type) {
   switch (type) {
     case CHANNEL_R:
-      return SkDisplacementMapEffect::kR_ChannelSelectorType;
+      return SkColorChannel::kR;
     case CHANNEL_G:
-      return SkDisplacementMapEffect::kG_ChannelSelectorType;
+      return SkColorChannel::kG;
     case CHANNEL_B:
-      return SkDisplacementMapEffect::kB_ChannelSelectorType;
+      return SkColorChannel::kB;
     case CHANNEL_A:
-      return SkDisplacementMapEffect::kA_ChannelSelectorType;
+      return SkColorChannel::kA;
     case CHANNEL_UNKNOWN:
     default:
-      return SkDisplacementMapEffect::kUnknown_ChannelSelectorType;
+      // Historically, Skia's raster backend treated unknown as blue.
+      return SkColorChannel::kB;
   }
 }
 
@@ -114,18 +113,16 @@
 
   sk_sp<PaintFilter> displ = paint_filter_builder::Build(
       InputEffect(1), OperatingInterpolationSpace());
-  SkDisplacementMapEffect::ChannelSelectorType type_x =
-      ToSkiaMode(x_channel_selector_);
-  SkDisplacementMapEffect::ChannelSelectorType type_y =
-      ToSkiaMode(y_channel_selector_);
-  PaintFilter::CropRect crop_rect = GetCropRect();
+  SkColorChannel type_x = ToSkiaMode(x_channel_selector_);
+  SkColorChannel type_y = ToSkiaMode(y_channel_selector_);
+  base::Optional<PaintFilter::CropRect> crop_rect = GetCropRect();
   // FIXME : Only applyHorizontalScale is used and applyVerticalScale is ignored
   // This can be fixed by adding a 2nd scale parameter to
   // DisplacementMapEffectPaintFilter.
   return sk_make_sp<DisplacementMapEffectPaintFilter>(
       type_x, type_y,
       SkFloatToScalar(GetFilter()->ApplyHorizontalScale(scale_)),
-      std::move(displ), std::move(color), &crop_rect);
+      std::move(displ), std::move(color), base::OptionalOrNullptr(crop_rect));
 }
 
 static WTF::TextStream& operator<<(WTF::TextStream& ts,
diff --git a/third_party/blink/renderer/platform/graphics/filters/fe_drop_shadow.cc b/third_party/blink/renderer/platform/graphics/filters/fe_drop_shadow.cc
index 1a5fb28e..74bae62 100644
--- a/third_party/blink/renderer/platform/graphics/filters/fe_drop_shadow.cc
+++ b/third_party/blink/renderer/platform/graphics/filters/fe_drop_shadow.cc
@@ -24,7 +24,6 @@
 #include "third_party/blink/renderer/platform/graphics/filters/filter.h"
 #include "third_party/blink/renderer/platform/graphics/filters/paint_filter_builder.h"
 #include "third_party/blink/renderer/platform/wtf/text/text_stream.h"
-#include "third_party/skia/include/effects/SkDropShadowImageFilter.h"
 
 namespace blink {
 
@@ -72,12 +71,12 @@
   float std_y = GetFilter()->ApplyVerticalScale(std_y_);
   Color color = AdaptColorToOperatingInterpolationSpace(
       shadow_color_.CombineWithAlpha(shadow_opacity_));
-  PaintFilter::CropRect crop_rect = GetCropRect();
+  base::Optional<PaintFilter::CropRect> crop_rect = GetCropRect();
   return sk_make_sp<DropShadowPaintFilter>(
       SkFloatToScalar(dx), SkFloatToScalar(dy), SkFloatToScalar(std_x),
       SkFloatToScalar(std_y), color.Rgb(),
-      SkDropShadowImageFilter::kDrawShadowAndForeground_ShadowMode,
-      std::move(input), &crop_rect);
+      DropShadowPaintFilter::ShadowMode::kDrawShadowAndForeground,
+      std::move(input), base::OptionalOrNullptr(crop_rect));
 }
 
 WTF::TextStream& FEDropShadow::ExternalRepresentation(WTF::TextStream& ts,
diff --git a/third_party/blink/renderer/platform/graphics/filters/fe_flood.cc b/third_party/blink/renderer/platform/graphics/filters/fe_flood.cc
index 3b38104..d3373a4 100644
--- a/third_party/blink/renderer/platform/graphics/filters/fe_flood.cc
+++ b/third_party/blink/renderer/platform/graphics/filters/fe_flood.cc
@@ -25,7 +25,6 @@
 
 #include "third_party/blink/renderer/platform/wtf/text/text_stream.h"
 #include "third_party/skia/include/core/SkColorFilter.h"
-#include "third_party/skia/include/effects/SkColorFilterImageFilter.h"
 
 namespace blink {
 
@@ -60,9 +59,10 @@
 
 sk_sp<PaintFilter> FEFlood::CreateImageFilter() {
   Color color = FloodColor().CombineWithAlpha(FloodOpacity());
-  PaintFilter::CropRect rect = GetCropRect();
+  base::Optional<PaintFilter::CropRect> crop_rect = GetCropRect();
   return sk_make_sp<ColorFilterPaintFilter>(
-      SkColorFilters::Blend(color.Rgb(), SkBlendMode::kSrc), nullptr, &rect);
+      SkColorFilters::Blend(color.Rgb(), SkBlendMode::kSrc), nullptr,
+      base::OptionalOrNullptr(crop_rect));
 }
 
 WTF::TextStream& FEFlood::ExternalRepresentation(WTF::TextStream& ts,
diff --git a/third_party/blink/renderer/platform/graphics/filters/fe_gaussian_blur.cc b/third_party/blink/renderer/platform/graphics/filters/fe_gaussian_blur.cc
index 64d77f9..e2bf85c 100644
--- a/third_party/blink/renderer/platform/graphics/filters/fe_gaussian_blur.cc
+++ b/third_party/blink/renderer/platform/graphics/filters/fe_gaussian_blur.cc
@@ -28,7 +28,6 @@
 #include "third_party/blink/renderer/platform/graphics/filters/filter.h"
 #include "third_party/blink/renderer/platform/graphics/filters/paint_filter_builder.h"
 #include "third_party/blink/renderer/platform/wtf/text/text_stream.h"
-#include "third_party/skia/include/effects/SkBlurImageFilter.h"
 
 namespace blink {
 
@@ -80,11 +79,10 @@
       InputEffect(0), OperatingInterpolationSpace()));
   float std_x = GetFilter()->ApplyHorizontalScale(std_x_);
   float std_y = GetFilter()->ApplyVerticalScale(std_y_);
-  PaintFilter::CropRect rect = GetCropRect();
+  base::Optional<PaintFilter::CropRect> crop_rect = GetCropRect();
   return sk_make_sp<BlurPaintFilter>(
-      SkFloatToScalar(std_x), SkFloatToScalar(std_y),
-      BlurPaintFilter::TileMode::kClampToBlack_TileMode, std::move(input),
-      &rect);
+      SkFloatToScalar(std_x), SkFloatToScalar(std_y), SkTileMode::kDecal,
+      std::move(input), base::OptionalOrNullptr(crop_rect));
 }
 
 WTF::TextStream& FEGaussianBlur::ExternalRepresentation(WTF::TextStream& ts,
diff --git a/third_party/blink/renderer/platform/graphics/filters/fe_lighting.cc b/third_party/blink/renderer/platform/graphics/filters/fe_lighting.cc
index 2a0c7dd..81a5df8 100644
--- a/third_party/blink/renderer/platform/graphics/filters/fe_lighting.cc
+++ b/third_party/blink/renderer/platform/graphics/filters/fe_lighting.cc
@@ -32,7 +32,6 @@
 #include "third_party/blink/renderer/platform/graphics/filters/point_light_source.h"
 #include "third_party/blink/renderer/platform/graphics/filters/spot_light_source.h"
 #include "third_party/skia/include/core/SkPoint3.h"
-#include "third_party/skia/include/effects/SkLightingImageFilter.h"
 
 namespace blink {
 
@@ -56,7 +55,8 @@
 sk_sp<PaintFilter> FELighting::CreateImageFilter() {
   if (!light_source_)
     return CreateTransparentBlack();
-  PaintFilter::CropRect rect = GetCropRect();
+  base::Optional<PaintFilter::CropRect> crop_rect = GetCropRect();
+  const PaintFilter::CropRect* rect = base::OptionalOrNullptr(crop_rect);
   Color light_color = AdaptColorToOperatingInterpolationSpace(lighting_color_);
   sk_sp<PaintFilter> input(paint_filter_builder::Build(
       InputEffect(0), OperatingInterpolationSpace()));
@@ -71,7 +71,7 @@
           sinf(azimuth_rad) * cosf(elevation_rad), sinf(elevation_rad));
       return sk_make_sp<LightingDistantPaintFilter>(
           GetLightingType(), direction, light_color.Rgb(), surface_scale_,
-          GetFilterConstant(), specular_exponent_, std::move(input), &rect);
+          GetFilterConstant(), specular_exponent_, std::move(input), rect);
     }
     case LS_POINT: {
       PointLightSource* point_light_source =
@@ -81,7 +81,7 @@
           SkPoint3::Make(position.X(), position.Y(), position.Z());
       return sk_make_sp<LightingPointPaintFilter>(
           GetLightingType(), sk_position, light_color.Rgb(), surface_scale_,
-          GetFilterConstant(), specular_exponent_, std::move(input), &rect);
+          GetFilterConstant(), specular_exponent_, std::move(input), rect);
     }
     case LS_SPOT: {
       SpotLightSource* spot_light_source =
@@ -102,7 +102,7 @@
       return sk_make_sp<LightingSpotPaintFilter>(
           GetLightingType(), location, target, specular_exponent,
           limiting_cone_angle, light_color.Rgb(), surface_scale_,
-          GetFilterConstant(), specular_exponent_, std::move(input), &rect);
+          GetFilterConstant(), specular_exponent_, std::move(input), rect);
     }
     default:
       NOTREACHED();
diff --git a/third_party/blink/renderer/platform/graphics/filters/fe_merge.cc b/third_party/blink/renderer/platform/graphics/filters/fe_merge.cc
index 6cd3d00..60b9a9c 100644
--- a/third_party/blink/renderer/platform/graphics/filters/fe_merge.cc
+++ b/third_party/blink/renderer/platform/graphics/filters/fe_merge.cc
@@ -26,7 +26,6 @@
 
 #include "third_party/blink/renderer/platform/graphics/filters/paint_filter_builder.h"
 #include "third_party/blink/renderer/platform/wtf/text/text_stream.h"
-#include "third_party/skia/include/effects/SkMergeImageFilter.h"
 
 namespace blink {
 
@@ -40,8 +39,9 @@
     input_refs[i] = paint_filter_builder::Build(InputEffect(i),
                                                 OperatingInterpolationSpace());
   }
-  PaintFilter::CropRect rect = GetCropRect();
-  return sk_make_sp<MergePaintFilter>(input_refs.get(), size, &rect);
+  base::Optional<PaintFilter::CropRect> crop_rect = GetCropRect();
+  return sk_make_sp<MergePaintFilter>(input_refs.get(), size,
+                                      base::OptionalOrNullptr(crop_rect));
 }
 
 WTF::TextStream& FEMerge::ExternalRepresentation(WTF::TextStream& ts,
diff --git a/third_party/blink/renderer/platform/graphics/filters/fe_morphology.cc b/third_party/blink/renderer/platform/graphics/filters/fe_morphology.cc
index e1bcdbc2..9bd3716 100644
--- a/third_party/blink/renderer/platform/graphics/filters/fe_morphology.cc
+++ b/third_party/blink/renderer/platform/graphics/filters/fe_morphology.cc
@@ -27,7 +27,6 @@
 #include "third_party/blink/renderer/platform/graphics/filters/filter.h"
 #include "third_party/blink/renderer/platform/graphics/filters/paint_filter_builder.h"
 #include "third_party/blink/renderer/platform/wtf/text/text_stream.h"
-#include "third_party/skia/include/effects/SkMorphologyImageFilter.h"
 
 namespace blink {
 
@@ -87,13 +86,14 @@
       InputEffect(0), OperatingInterpolationSpace()));
   float radius_x = GetFilter()->ApplyHorizontalScale(radius_x_);
   float radius_y = GetFilter()->ApplyVerticalScale(radius_y_);
-  PaintFilter::CropRect rect = GetCropRect();
+  base::Optional<PaintFilter::CropRect> crop_rect = GetCropRect();
   MorphologyPaintFilter::MorphType morph_type =
       type_ == FEMORPHOLOGY_OPERATOR_DILATE
           ? MorphologyPaintFilter::MorphType::kDilate
           : MorphologyPaintFilter::MorphType::kErode;
   return sk_make_sp<MorphologyPaintFilter>(morph_type, radius_x, radius_y,
-                                           std::move(input), &rect);
+                                           std::move(input),
+                                           base::OptionalOrNullptr(crop_rect));
 }
 
 static WTF::TextStream& operator<<(WTF::TextStream& ts,
diff --git a/third_party/blink/renderer/platform/graphics/filters/fe_offset.cc b/third_party/blink/renderer/platform/graphics/filters/fe_offset.cc
index 364a659..59924c0 100644
--- a/third_party/blink/renderer/platform/graphics/filters/fe_offset.cc
+++ b/third_party/blink/renderer/platform/graphics/filters/fe_offset.cc
@@ -27,7 +27,6 @@
 #include "third_party/blink/renderer/platform/graphics/filters/filter.h"
 #include "third_party/blink/renderer/platform/graphics/filters/paint_filter_builder.h"
 #include "third_party/blink/renderer/platform/wtf/text/text_stream.h"
-#include "third_party/skia/include/effects/SkOffsetImageFilter.h"
 
 namespace blink {
 
@@ -59,13 +58,13 @@
 
 sk_sp<PaintFilter> FEOffset::CreateImageFilter() {
   Filter* filter = this->GetFilter();
-  PaintFilter::CropRect crop_rect = GetCropRect();
+  base::Optional<PaintFilter::CropRect> crop_rect = GetCropRect();
   return sk_make_sp<OffsetPaintFilter>(
       SkFloatToScalar(filter->ApplyHorizontalScale(dx_)),
       SkFloatToScalar(filter->ApplyVerticalScale(dy_)),
       paint_filter_builder::Build(InputEffect(0),
                                   OperatingInterpolationSpace()),
-      &crop_rect);
+      base::OptionalOrNullptr(crop_rect));
 }
 
 WTF::TextStream& FEOffset::ExternalRepresentation(WTF::TextStream& ts,
diff --git a/third_party/blink/renderer/platform/graphics/filters/fe_tile.cc b/third_party/blink/renderer/platform/graphics/filters/fe_tile.cc
index b2bff044..5b4aa83 100644
--- a/third_party/blink/renderer/platform/graphics/filters/fe_tile.cc
+++ b/third_party/blink/renderer/platform/graphics/filters/fe_tile.cc
@@ -24,7 +24,6 @@
 #include "third_party/blink/renderer/platform/graphics/filters/filter.h"
 #include "third_party/blink/renderer/platform/graphics/filters/paint_filter_builder.h"
 #include "third_party/blink/renderer/platform/wtf/text/text_stream.h"
-#include "third_party/skia/include/effects/SkTileImageFilter.h"
 
 namespace blink {
 
diff --git a/third_party/blink/renderer/platform/graphics/filters/fe_turbulence.cc b/third_party/blink/renderer/platform/graphics/filters/fe_turbulence.cc
index f3a89fc..7684c13d 100644
--- a/third_party/blink/renderer/platform/graphics/filters/fe_turbulence.cc
+++ b/third_party/blink/renderer/platform/graphics/filters/fe_turbulence.cc
@@ -27,8 +27,6 @@
 
 #include "third_party/blink/renderer/platform/graphics/filters/filter.h"
 #include "third_party/blink/renderer/platform/wtf/text/text_stream.h"
-#include "third_party/skia/include/effects/SkPaintImageFilter.h"
-#include "third_party/skia/include/effects/SkPerlinNoiseShader.h"
 
 namespace blink {
 
@@ -123,7 +121,7 @@
     base_frequency_x = base_frequency_y = 0;
   }
 
-  PaintFilter::CropRect rect = GetCropRect();
+  base::Optional<PaintFilter::CropRect> crop_rect = GetCropRect();
   TurbulencePaintFilter::TurbulenceType type =
       GetType() == FETURBULENCE_TYPE_FRACTALNOISE
           ? TurbulencePaintFilter::TurbulenceType::kFractalNoise
@@ -144,7 +142,8 @@
   return sk_make_sp<TurbulencePaintFilter>(
       type, SkFloatToScalar(base_frequency_x),
       SkFloatToScalar(base_frequency_y), capped_num_octaves,
-      SkFloatToScalar(Seed()), StitchTiles() ? &size : nullptr, &rect);
+      SkFloatToScalar(Seed()), StitchTiles() ? &size : nullptr,
+      base::OptionalOrNullptr(crop_rect));
 }
 
 static WTF::TextStream& operator<<(WTF::TextStream& ts,
diff --git a/third_party/blink/renderer/platform/graphics/filters/filter_effect.cc b/third_party/blink/renderer/platform/graphics/filters/filter_effect.cc
index 63733cdf..4a1b5b0 100644
--- a/third_party/blink/renderer/platform/graphics/filters/filter_effect.cc
+++ b/third_party/blink/renderer/platform/graphics/filters/filter_effect.cc
@@ -25,7 +25,6 @@
 
 #include "third_party/blink/renderer/platform/graphics/filters/filter.h"
 #include "third_party/skia/include/core/SkColorFilter.h"
-#include "third_party/skia/include/effects/SkColorFilterImageFilter.h"
 
 namespace blink {
 
@@ -133,25 +132,23 @@
 }
 
 sk_sp<PaintFilter> FilterEffect::CreateTransparentBlack() const {
-  PaintFilter::CropRect rect = GetCropRect();
   sk_sp<SkColorFilter> color_filter =
       SkColorFilters::Blend(0, SkBlendMode::kClear);
-  return sk_make_sp<ColorFilterPaintFilter>(std::move(color_filter), nullptr,
-                                            &rect);
+  return sk_make_sp<ColorFilterPaintFilter>(
+      std::move(color_filter), nullptr, base::OptionalOrNullptr(GetCropRect()));
 }
 
-PaintFilter::CropRect FilterEffect::GetCropRect() const {
+base::Optional<PaintFilter::CropRect> FilterEffect::GetCropRect() const {
   if (!ClipsToBounds())
-    return PaintFilter::CropRect(SkRect::MakeEmpty(), 0);
+    return {};
   FloatRect computed_bounds = FilterPrimitiveSubregion();
   // This and the filter region check is a workaround for crbug.com/512453.
   if (computed_bounds.IsEmpty())
-    return PaintFilter::CropRect(SkRect::MakeEmpty(), 0);
+    return {};
   FloatRect filter_region = GetFilter()->FilterRegion();
   if (!filter_region.IsEmpty())
     computed_bounds.Intersect(filter_region);
-  return PaintFilter::CropRect(
-      GetFilter()->MapLocalRectToAbsoluteRect(computed_bounds));
+  return GetFilter()->MapLocalRectToAbsoluteRect(computed_bounds);
 }
 
 static int GetImageFilterIndex(InterpolationSpace interpolation_space,
diff --git a/third_party/blink/renderer/platform/graphics/filters/filter_effect.h b/third_party/blink/renderer/platform/graphics/filters/filter_effect.h
index 15c66cb..1b402e2a 100644
--- a/third_party/blink/renderer/platform/graphics/filters/filter_effect.h
+++ b/third_party/blink/renderer/platform/graphics/filters/filter_effect.h
@@ -139,7 +139,7 @@
 
   Color AdaptColorToOperatingInterpolationSpace(const Color& device_color);
 
-  PaintFilter::CropRect GetCropRect() const;
+  base::Optional<PaintFilter::CropRect> GetCropRect() const;
 
  private:
   FilterEffectVector input_effects_;
diff --git a/third_party/blink/renderer/platform/graphics/filters/paint_filter_builder.cc b/third_party/blink/renderer/platform/graphics/filters/paint_filter_builder.cc
index 34ce3217..1bea306 100644
--- a/third_party/blink/renderer/platform/graphics/filters/paint_filter_builder.cc
+++ b/third_party/blink/renderer/platform/graphics/filters/paint_filter_builder.cc
@@ -30,14 +30,8 @@
 #include "third_party/blink/renderer/platform/graphics/paint/paint_canvas.h"
 #include "third_party/blink/renderer/platform/graphics/paint/paint_record.h"
 #include "third_party/blink/renderer/platform/graphics/skia/skia_utils.h"
-#include "third_party/skia/include/effects/SkBlurImageFilter.h"
-#include "third_party/skia/include/effects/SkColorFilterImageFilter.h"
 #include "third_party/skia/include/effects/SkColorMatrixFilter.h"
-#include "third_party/skia/include/effects/SkImageSource.h"
-#include "third_party/skia/include/effects/SkOffsetImageFilter.h"
-#include "third_party/skia/include/effects/SkPictureImageFilter.h"
 #include "third_party/skia/include/effects/SkTableColorFilter.h"
-#include "third_party/skia/include/effects/SkXfermodeImageFilter.h"
 
 namespace blink {
 namespace paint_filter_builder {
@@ -167,9 +161,8 @@
   sk_sp<PaintFilter> flip_image_filter = sk_make_sp<MatrixPaintFilter>(
       reflection.ReflectionMatrix(), kLow_SkFilterQuality,
       std::move(masked_input));
-  return sk_make_sp<XfermodePaintFilter>(SkBlendMode::kSrcOver,
-                                         std::move(flip_image_filter),
-                                         std::move(input), nullptr);
+  return sk_make_sp<XfermodePaintFilter>(
+      SkBlendMode::kSrcOver, std::move(flip_image_filter), std::move(input));
 }
 
 }  // namespace paint_filter_builder
diff --git a/third_party/blink/renderer/platform/graphics/filters/paint_filter_effect.cc b/third_party/blink/renderer/platform/graphics/filters/paint_filter_effect.cc
index 042813a..9e9b03d 100644
--- a/third_party/blink/renderer/platform/graphics/filters/paint_filter_effect.cc
+++ b/third_party/blink/renderer/platform/graphics/filters/paint_filter_effect.cc
@@ -6,7 +6,6 @@
 
 #include "third_party/blink/renderer/platform/graphics/filters/filter.h"
 #include "third_party/blink/renderer/platform/wtf/text/text_stream.h"
-#include "third_party/skia/include/effects/SkPaintImageFilter.h"
 
 namespace blink {
 
diff --git a/third_party/blink/renderer/platform/graphics/filters/source_alpha.cc b/third_party/blink/renderer/platform/graphics/filters/source_alpha.cc
index a23b3fd..53b757b 100644
--- a/third_party/blink/renderer/platform/graphics/filters/source_alpha.cc
+++ b/third_party/blink/renderer/platform/graphics/filters/source_alpha.cc
@@ -24,7 +24,6 @@
 #include "third_party/blink/renderer/platform/graphics/filters/paint_filter_builder.h"
 #include "third_party/blink/renderer/platform/wtf/text/text_stream.h"
 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
-#include "third_party/skia/include/effects/SkColorFilterImageFilter.h"
 #include "third_party/skia/include/effects/SkColorMatrixFilter.h"
 
 namespace blink {
diff --git a/third_party/blink/renderer/platform/graphics/video_frame_submitter.cc b/third_party/blink/renderer/platform/graphics/video_frame_submitter.cc
index 7a51d095..0e809ec4 100644
--- a/third_party/blink/renderer/platform/graphics/video_frame_submitter.cc
+++ b/third_party/blink/renderer/platform/graphics/video_frame_submitter.cc
@@ -575,8 +575,8 @@
           ? video_frame_provider_->GetPreferredRenderInterval()
           : viz::BeginFrameArgs::MinInterval();
 
-  if (video_frame && video_frame->metadata()->decode_end_time.has_value()) {
-    base::TimeTicks value = *video_frame->metadata()->decode_end_time;
+  if (video_frame && video_frame->metadata().decode_end_time.has_value()) {
+    base::TimeTicks value = *video_frame->metadata().decode_end_time;
     TRACE_EVENT_NESTABLE_ASYNC_BEGIN_WITH_TIMESTAMP0(
         "media", "VideoFrameSubmitter", TRACE_ID_LOCAL(frame_token), value);
     TRACE_EVENT_NESTABLE_ASYNC_BEGIN_WITH_TIMESTAMP0(
diff --git a/third_party/blink/renderer/platform/graphics/video_frame_submitter_test.cc b/third_party/blink/renderer/platform/graphics/video_frame_submitter_test.cc
index af7d14a..54b5cc6d 100644
--- a/third_party/blink/renderer/platform/graphics/video_frame_submitter_test.cc
+++ b/third_party/blink/renderer/platform/graphics/video_frame_submitter_test.cc
@@ -992,7 +992,7 @@
     auto frame = media::VideoFrame::CreateFrame(
         media::PIXEL_FORMAT_YV12, gfx::Size(8, 8), gfx::Rect(gfx::Size(8, 8)),
         gfx::Size(8, 8), i * frame_duration);
-    frame->metadata()->wallclock_frame_duration = frame_duration;
+    frame->metadata().wallclock_frame_duration = frame_duration;
     EXPECT_CALL(*video_frame_provider_, GetCurrentFrame())
         .WillRepeatedly(Return(frame));
 
diff --git a/third_party/blink/renderer/platform/peerconnection/webrtc_video_track_source.cc b/third_party/blink/renderer/platform/peerconnection/webrtc_video_track_source.cc
index 28235bf..81cd83e 100644
--- a/third_party/blink/renderer/platform/peerconnection/webrtc_video_track_source.cc
+++ b/third_party/blink/renderer/platform/peerconnection/webrtc_video_track_source.cc
@@ -64,10 +64,10 @@
 }
 
 webrtc::VideoRotation GetFrameRotation(const media::VideoFrame* frame) {
-  if (!frame->metadata()->rotation) {
+  if (!frame->metadata().rotation) {
     return webrtc::kVideoRotation_0;
   }
-  switch (*frame->metadata()->rotation) {
+  switch (*frame->metadata().rotation) {
     case media::VIDEO_ROTATION_0:
       return webrtc::kVideoRotation_0;
     case media::VIDEO_ROTATION_90:
@@ -162,9 +162,8 @@
   // rtc::AdaptedVideoTrackSource::OnFrame(). This region is going to be
   // relative to the coded frame data, i.e.
   // [0, 0, frame->coded_size().width(), frame->coded_size().height()].
-  base::Optional<int> capture_counter = frame->metadata()->capture_counter;
-  base::Optional<gfx::Rect> update_rect =
-      frame->metadata()->capture_update_rect;
+  base::Optional<int> capture_counter = frame->metadata().capture_counter;
+  base::Optional<gfx::Rect> update_rect = frame->metadata().capture_update_rect;
 
   const bool has_valid_update_rect =
       update_rect.has_value() && capture_counter.has_value() &&
diff --git a/third_party/blink/renderer/platform/peerconnection/webrtc_video_track_source_test.cc b/third_party/blink/renderer/platform/peerconnection/webrtc_video_track_source_test.cc
index f8586e91..81c1a9b8 100644
--- a/third_party/blink/renderer/platform/peerconnection/webrtc_video_track_source_test.cc
+++ b/third_party/blink/renderer/platform/peerconnection/webrtc_video_track_source_test.cc
@@ -86,8 +86,8 @@
       media::VideoFrame::StorageType storage_type) {
     scoped_refptr<media::VideoFrame> frame =
         CreateTestFrame(coded_size, visible_rect, natural_size, storage_type);
-    frame->metadata()->capture_counter = capture_counter;
-    frame->metadata()->capture_update_rect = update_rect;
+    frame->metadata().capture_counter = capture_counter;
+    frame->metadata().capture_update_rect = update_rect;
     track_source_->OnFrameCaptured(frame);
   }
 
diff --git a/third_party/blink/renderer/platform/video_capture/video_capture_impl.cc b/third_party/blink/renderer/platform/video_capture/video_capture_impl.cc
index c7cbe56..135ba38 100644
--- a/third_party/blink/renderer/platform/video_capture/video_capture_impl.cc
+++ b/third_party/blink/renderer/platform/video_capture/video_capture_impl.cc
@@ -196,8 +196,8 @@
         mailbox_holder_array,
         base::BindOnce(&BufferContext::MailboxHolderReleased, buffer_context),
         info->timestamp);
-    frame->metadata()->allow_overlay = true;
-    frame->metadata()->read_lock_fences_enabled = true;
+    frame->metadata().allow_overlay = true;
+    frame->metadata().read_lock_fences_enabled = true;
 
     std::move(on_texture_bound)
         .Run(std::move(info), std::move(frame), std::move(buffer_context));
@@ -752,8 +752,7 @@
   if (info->color_space.has_value() && info->color_space->IsValid())
     frame->set_color_space(info->color_space.value());
 
-  media::VideoFrameMetadata metadata = info->metadata;
-  frame->metadata()->MergeMetadataFrom(&metadata);
+  frame->metadata().MergeMetadataFrom(info->metadata);
 
   // TODO(qiangchen): Dive into the full code path to let frame metadata hold
   // reference time rather than using an extra parameter.
diff --git a/third_party/blink/renderer/platform/webrtc/webrtc_video_frame_adapter.cc b/third_party/blink/renderer/platform/webrtc/webrtc_video_frame_adapter.cc
index e795d786..56654b11 100644
--- a/third_party/blink/renderer/platform/webrtc/webrtc_video_frame_adapter.cc
+++ b/third_party/blink/renderer/platform/webrtc/webrtc_video_frame_adapter.cc
@@ -211,7 +211,7 @@
     LOG(ERROR) << "Failed to create I420 frame from pool.";
     return nullptr;
   }
-  dst_frame->metadata()->MergeMetadataFrom(source_frame->metadata());
+  dst_frame->metadata().MergeMetadataFrom(source_frame->metadata());
   const auto& i420_planes = dst_frame->layout().planes();
   webrtc::NV12ToI420Scaler scaler;
   scaler.NV12ToI420Scale(
@@ -240,7 +240,7 @@
       media::PIXEL_FORMAT_NV12, source_frame->natural_size(),
       gfx::Rect(source_frame->natural_size()), source_frame->natural_size(),
       source_frame->timestamp());
-  dst_frame->metadata()->MergeMetadataFrom(source_frame->metadata());
+  dst_frame->metadata().MergeMetadataFrom(source_frame->metadata());
   const auto& nv12_planes = dst_frame->layout().planes();
   libyuv::NV12Scale(mapped_frame->visible_data(media::VideoFrame::kYPlane),
                     mapped_frame->stride(media::VideoFrame::kYPlane),
diff --git a/third_party/blink/web_tests/FlagExpectations/disable-layout-ng b/third_party/blink/web_tests/FlagExpectations/disable-layout-ng
index 6199b34..438881c 100644
--- a/third_party/blink/web_tests/FlagExpectations/disable-layout-ng
+++ b/third_party/blink/web_tests/FlagExpectations/disable-layout-ng
@@ -236,17 +236,8 @@
 crbug.com/591099 external/wpt/css/css-text/white-space/line-edge-white-space-collapse-001.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-text/white-space/line-edge-white-space-collapse-002.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-text/white-space/pre-line-with-space-and-newline.html [ Failure ]
-crbug.com/1155633 external/wpt/css/css-text/white-space/break-spaces-with-ideographic-space-001.html [ Failure ]
-crbug.com/1155633 external/wpt/css/css-text/white-space/break-spaces-with-ideographic-space-005.html [ Failure ]
 crbug.com/1151784 external/wpt/css/css-text/white-space/seg-break-transformation-016.tentative.html [ Failure ]
 crbug.com/1151784 external/wpt/css/css-text/white-space/seg-break-transformation-017.tentative.html [ Failure ]
-crbug.com/1155633 external/wpt/css/css-text/white-space/trailing-ideographic-space-break-spaces-001.html [ Failure ]
-crbug.com/1155633 external/wpt/css/css-text/white-space/trailing-ideographic-space-break-spaces-002.html [ Failure ]
-crbug.com/1155633 external/wpt/css/css-text/white-space/trailing-ideographic-space-break-spaces-003.html [ Failure ]
-crbug.com/1155633 external/wpt/css/css-text/white-space/trailing-ideographic-space-break-spaces-004.html [ Failure ]
-crbug.com/1155633 external/wpt/css/css-text/white-space/trailing-ideographic-space-break-spaces-005.html [ Failure ]
-crbug.com/1155633 external/wpt/css/css-text/white-space/trailing-ideographic-space-break-spaces-006.html [ Failure ]
-crbug.com/1155633 external/wpt/css/css-text/white-space/trailing-ideographic-space-break-spaces-007.html [ Failure ]
 crbug.com/1162836 external/wpt/css/css-text/white-space/trailing-ideographic-space-017.html [ Failure ]
 crbug.com/1162836 external/wpt/css/css-text/white-space/trailing-ideographic-space-020.html [ Failure ]
 crbug.com/1162836 external/wpt/css/css-text/white-space/trailing-ideographic-space-023.html [ Failure ]
diff --git a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
index 4353c29..8165cfb 100644
--- a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
+++ b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
@@ -73932,6 +73932,19 @@
         {}
        ]
       ],
+      "positioned-grid-items-018.html": [
+       "0c9d21642ef828911c04d937c48052bf297baaf3",
+       [
+        null,
+        [
+         [
+          "/css/reference/ref-filled-green-100px-square-only.html",
+          "=="
+         ]
+        ],
+        {}
+       ]
+      ],
       "positioned-grid-items-sizing-001.html": [
        "7fb303c0dd0df39d614f8bee8bd003494fcd233c",
        [
@@ -74870,6 +74883,32 @@
         {}
        ]
       ],
+      "grid-gutters-015.html": [
+       "ed1048fd69714a8d813b92103719f0171f165472",
+       [
+        null,
+        [
+         [
+          "/css/css-grid/alignment/grid-gutters-015-ref.html",
+          "=="
+         ]
+        ],
+        {}
+       ]
+      ],
+      "grid-gutters-016.html": [
+       "163787cfee6a9762ec018bd05ee2d3d7b4273114",
+       [
+        null,
+        [
+         [
+          "/css/css-grid/alignment/grid-gutters-016-ref.html",
+          "=="
+         ]
+        ],
+        {}
+       ]
+      ],
       "grid-inline-axis-alignment-auto-margins-008.html": [
        "eb9666465542a34cc29853dd71dc326ff4cbbd63",
        [
@@ -173931,7 +173970,7 @@
       []
      ],
      "bluetooth-test.js": [
-      "b99564bf9ec3c8262029cd36e6df4c67d12ae83f",
+      "f6e5fd0e4b5e272d87c4745fa65c78879a44b2ed",
       []
      ],
      "health-thermometer-iframe.html": [
@@ -195025,6 +195064,14 @@
        "a6ca7c5aa7a80313786abc97ffe4581e9a6d7341",
        []
       ],
+      "grid-gutters-015-ref.html": [
+       "dd4d8daf6a30c0536b0bd3a291c4207be5ac7830",
+       []
+      ],
+      "grid-gutters-016-ref.html": [
+       "a56ab07e182530ef98292fc4729884302d3bdc9e",
+       []
+      ],
       "grid-item-content-baseline-001-ref.html": [
        "e55fe0b9f96c3f3ae4a4f6b66bfd2283fb88659b",
        []
@@ -237415,7 +237462,7 @@
     ]
    },
    "lint.ignore": [
-    "27ba5497065c7858248f99a063ffe57b932c0df0",
+    "65d307e410bc25f1e9b64d5f16ce71e8a09fbe3a",
     []
    ],
    "loading": {
@@ -243837,7 +243884,7 @@
       []
      ],
      "web-bluetooth-test.js": [
-      "aa4b80d2831c3c69ef16d7016b41d818bd58bd7b",
+      "ee835c224b489068ad6a9f9fe8ffe1b56ab08daa",
       []
      ],
      "web-bluetooth-test.js.headers": [
@@ -315569,7 +315616,7 @@
       ]
      ],
      "focus-visible-011.html": [
-      "d45f5d8ed0f3903da5b5c37ea5a13ccbe690ceca",
+      "b0daf34ba8dd96f343e687cef852523c92ae2c39",
       [
        null,
        {
diff --git a/third_party/blink/web_tests/external/wpt/css/selectors/focus-visible-011.html b/third_party/blink/web_tests/external/wpt/css/selectors/focus-visible-011.html
index d45f5d8e..b0daf34 100644
--- a/third_party/blink/web_tests/external/wpt/css/selectors/focus-visible-011.html
+++ b/third_party/blink/web_tests/external/wpt/css/selectors/focus-visible-011.html
@@ -45,6 +45,7 @@
   <button id="next" tabindex="-1">Focus moves here.</button>
   <script>
     start.addEventListener('keydown', (e) => {
+      e.preventDefault();
       next.focus();
     });
 
diff --git a/third_party/blink/web_tests/external/wpt/lint.ignore b/third_party/blink/web_tests/external/wpt/lint.ignore
index 65d307e4..2e0fc8b 100644
--- a/third_party/blink/web_tests/external/wpt/lint.ignore
+++ b/third_party/blink/web_tests/external/wpt/lint.ignore
@@ -708,6 +708,7 @@
 WEB-PLATFORM.TEST:signed-exchange/resources/generate-test-sxgs.sh
 
 # Web Bundle files have hard-coded URLs
+WEB-PLATFORM.TEST:web-bundle/resources/*.har
 WEB-PLATFORM.TEST:web-bundle/resources/generate-test-wbns.sh
 WEB-PLATFORM.TEST:web-bundle/resources/nested/*.wbn
 WEB-PLATFORM.TEST:web-bundle/resources/wbn/*.wbn
diff --git a/third_party/blink/web_tests/external/wpt/mediacapture-record/idlharness.window-expected.txt b/third_party/blink/web_tests/external/wpt/mediacapture-record/idlharness.window-expected.txt
index 531f344..e559211 100644
--- a/third_party/blink/web_tests/external/wpt/mediacapture-record/idlharness.window-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/mediacapture-record/idlharness.window-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 70 tests; 58 PASS, 12 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 70 tests; 60 PASS, 10 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS idl_test setup
 PASS idl_test validation
 PASS MediaRecorder interface: existence and properties of interface object
@@ -19,7 +19,7 @@
 PASS MediaRecorder interface: attribute onerror
 PASS MediaRecorder interface: attribute videoBitsPerSecond
 PASS MediaRecorder interface: attribute audioBitsPerSecond
-FAIL MediaRecorder interface: attribute audioBitrateMode assert_true: The prototype object must have a property "audioBitrateMode" expected true got false
+PASS MediaRecorder interface: attribute audioBitrateMode
 PASS MediaRecorder interface: operation start(optional unsigned long)
 PASS MediaRecorder interface: operation stop()
 PASS MediaRecorder interface: operation pause()
@@ -39,7 +39,7 @@
 PASS MediaRecorder interface: [object MediaRecorder] must inherit property "onerror" with the proper type
 PASS MediaRecorder interface: [object MediaRecorder] must inherit property "videoBitsPerSecond" with the proper type
 PASS MediaRecorder interface: [object MediaRecorder] must inherit property "audioBitsPerSecond" with the proper type
-FAIL MediaRecorder interface: [object MediaRecorder] must inherit property "audioBitrateMode" with the proper type assert_inherits: property "audioBitrateMode" not found in prototype chain
+PASS MediaRecorder interface: [object MediaRecorder] must inherit property "audioBitrateMode" with the proper type
 PASS MediaRecorder interface: [object MediaRecorder] must inherit property "start(optional unsigned long)" with the proper type
 PASS MediaRecorder interface: calling start(optional unsigned long) on [object MediaRecorder] with too few arguments must throw TypeError
 PASS MediaRecorder interface: [object MediaRecorder] must inherit property "stop()" with the proper type
diff --git a/third_party/blink/web_tests/external/wpt/web-bundle/resources/cross-origin.har b/third_party/blink/web_tests/external/wpt/web-bundle/resources/cross-origin.har
new file mode 100644
index 0000000..7435393
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/web-bundle/resources/cross-origin.har
@@ -0,0 +1,48 @@
+{
+  "log": {
+    "entries": [
+      {
+        "request": {
+          "method": "GET",
+          "url": "https://web-platform.test:8444/web-bundle/resources/wbn/cors/resource.cors.json",
+          "headers": []
+        },
+        "response": {
+          "status": 200,
+          "headers": [
+            {
+              "name": "Content-type",
+              "value": "application/json"
+            },
+            {
+              "name": "Access-Control-Allow-Origin",
+              "value": "*"
+            }
+          ],
+          "content": {
+            "text": "{ cors: 1 }"
+          }
+        }
+      },
+      {
+        "request": {
+          "method": "GET",
+          "url": "https://web-platform.test:8444/web-bundle/resources/wbn/cors/resource.no-cors.json",
+          "headers": []
+        },
+        "response": {
+          "status": 200,
+          "headers": [
+            {
+              "name": "Content-type",
+              "value": "application/json"
+            }
+          ],
+          "content": {
+            "text": "{ no_cors: 1}"
+          }
+        }
+      }
+    ]
+  }
+}
diff --git a/third_party/blink/web_tests/external/wpt/web-bundle/resources/generate-test-wbns.sh b/third_party/blink/web_tests/external/wpt/web-bundle/resources/generate-test-wbns.sh
index 3c8a3ea..8c8fd45 100755
--- a/third_party/blink/web_tests/external/wpt/web-bundle/resources/generate-test-wbns.sh
+++ b/third_party/blink/web_tests/external/wpt/web-bundle/resources/generate-test-wbns.sh
@@ -70,3 +70,9 @@
   -har urn-uuid.har \
   -primaryURL urn:uuid:020111b3-437a-4c5c-ae07-adb6bbffb720 \
   -o wbn/urn-uuid.wbn
+
+gen-bundle \
+  -version b1 \
+  -har cross-origin.har \
+  -primaryURL $wpt_test_https_origin/web-bundle/resources/wbn/cors/resource.json \
+  -o wbn/cors/cross-origin.wbn
diff --git a/third_party/blink/web_tests/external/wpt/web-bundle/resources/wbn/cors/__dir__.headers b/third_party/blink/web_tests/external/wpt/web-bundle/resources/wbn/cors/__dir__.headers
new file mode 100644
index 0000000..383abc5
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/web-bundle/resources/wbn/cors/__dir__.headers
@@ -0,0 +1,2 @@
+Content-Type: application/webbundle
+Access-Control-Allow-Origin: *
diff --git a/third_party/blink/web_tests/external/wpt/web-bundle/resources/wbn/cors/cross-origin.wbn b/third_party/blink/web_tests/external/wpt/web-bundle/resources/wbn/cors/cross-origin.wbn
new file mode 100644
index 0000000..bed9bf2
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/web-bundle/resources/wbn/cors/cross-origin.wbn
Binary files differ
diff --git a/third_party/blink/web_tests/external/wpt/web-bundle/subresource-loading/subresource-loading-cross-origin.tentative.html b/third_party/blink/web_tests/external/wpt/web-bundle/subresource-loading/subresource-loading-cross-origin.tentative.html
new file mode 100644
index 0000000..3991aef2
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/web-bundle/subresource-loading/subresource-loading-cross-origin.tentative.html
@@ -0,0 +1,66 @@
+<!DOCTYPE html>
+<title>Cross-origin WebBundle subresource loading</title>
+<link
+  rel="help"
+  href="https://github.com/WICG/webpackage/blob/master/explainers/subresource-loading.md"
+/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+  <!--
+       This wpt should run on an origin which is different than https://web-platform.test:8444/,
+       from where cross-orign WebBundles are served.
+
+       This test uses the two cross-origin WebBundles:
+
+       1. https://web-platform.test:8444/web-bundle/resources/wbn/cors/cross-origin.wbn,
+          which is served with an Access-Control-Allow-Origin response header.
+       2. http://web-platform.test:8444/web-bundle/resources/wbn/subreource.wbn,
+          which is served *without* an Access-Control-Allow-Origin response header.
+
+      `cross-origin.wbn` includes two subresources:
+       a. `resource.cors.json`, which includes an Access-Control-Allow-Origin response header.
+       b. `resource.no-cors.json`, which doesn't include an Access-Control-Allow-Origin response header.
+  -->
+  <link
+    rel="webbundle"
+    href="https://web-platform.test:8444/web-bundle/resources/wbn/cors/cross-origin.wbn"
+    resources="https://web-platform.test:8444/web-bundle/resources/wbn/cors/resource.cors.json
+         https://web-platform.test:8444/web-bundle/resources/wbn/cors/resource.no-cors.json"
+  />
+  <script>
+    promise_test(async () => {
+      const response = await fetch(
+        "https://web-platform.test:8444/web-bundle/resources/wbn/cors/resource.cors.json"
+      );
+      assert_true(response.ok);
+    }, "A subresource which includes an Access-Control-Allow-Origin response header can be fetched");
+
+    promise_test(async (t) => {
+      return promise_rejects_js(
+        t,
+        TypeError,
+        fetch(
+          "https://web-platform.test:8444/web-bundle/resources/wbn/cors/resource.no-cors.json"
+        )
+      );
+    }, "A subresource which does not include an Access-Control-Allow-Origin response header can not be fetched");
+
+    promise_test(async () => {
+      return addLinkAndWaitForError(
+        "http://web-platform.test:8444/web-bundle/resources/wbn/subreource.wbn"
+      );
+    }, "A cross-origin WebBundle which does not include an Access-Control-Allow-Origin response header should fire an error event on load");
+
+    function addLinkAndWaitForError(url) {
+      return new Promise((resolve, reject) => {
+        const link = document.createElement("link");
+        link.rel = "webbundle";
+        link.href = url;
+        link.onload = reject;
+        link.onerror = () => resolve(link);
+        document.body.appendChild(link);
+      });
+    }
+  </script>
+</body>
diff --git a/third_party/blink/web_tests/fast/forms/range/range-keyoperation-expected.txt b/third_party/blink/web_tests/fast/forms/range/range-keyoperation-expected.txt
index 1997ab7e..ec73310 100644
--- a/third_party/blink/web_tests/fast/forms/range/range-keyoperation-expected.txt
+++ b/third_party/blink/web_tests/fast/forms/range/range-keyoperation-expected.txt
@@ -34,8 +34,14 @@
 PASS changeEventCounter is lastChangeEventCounter
 
 
-RTL
+Tests for a horizontal range with RTL
+Press the up arrow key:
 PASS input.value is "51"
+Press the down arrow key:
+PASS input.value is "50"
+Press the left arrow key:
+PASS input.value is "51"
+Press the right arrow key:
 PASS input.value is "50"
 
 
@@ -45,6 +51,25 @@
 Press the down arrow key:
 PASS input.value is "50"
 Press the left arrow key:
+PASS input.value is "49"
+Press the right arrow key:
+PASS input.value is "50"
+Press the PageUp key:
+PASS input.value is "60"
+Press the PageDown key:
+PASS input.value is "50"
+Press the Home key:
+PASS input.value is "0"
+Press the End key:
+PASS input.value is "100"
+
+
+Tests for a vertical range with rtl
+Press the up arrow key:
+PASS input.value is "51"
+Press the down arrow key:
+PASS input.value is "50"
+Press the left arrow key:
 PASS input.value is "51"
 Press the right arrow key:
 PASS input.value is "50"
@@ -53,9 +78,9 @@
 Press the PageDown key:
 PASS input.value is "50"
 Press the Home key:
-PASS input.value is "100"
-Press the End key:
 PASS input.value is "0"
+Press the End key:
+PASS input.value is "100"
 
 
 step=any cases
@@ -72,10 +97,10 @@
 PASS input.value is "100"
 PASS changeEventCounter is lastChangeEventCounter + 1
 Press the home key:
-PASS input.value is "200"
+PASS input.value is "0"
 PASS changeEventCounter is lastChangeEventCounter + 1
 Press the end key:
-PASS input.value is "0"
+PASS input.value is "200"
 PASS changeEventCounter is lastChangeEventCounter + 1
 Edge cases
 PASS input.value is "200"
@@ -99,9 +124,9 @@
 small range
 PASS input.value is "9"
 PASS input.value is "6"
-PASS input.value is "0"
 PASS input.value is "9"
 PASS input.value is "0"
+PASS input.value is "9"
 
 
 Disabled and read-only
@@ -117,3 +142,4 @@
 
 TEST COMPLETE
 
+      
diff --git a/third_party/blink/web_tests/fast/forms/range/range-keyoperation.html b/third_party/blink/web_tests/fast/forms/range/range-keyoperation.html
index 630043e6..d528254 100644
--- a/third_party/blink/web_tests/fast/forms/range/range-keyoperation.html
+++ b/third_party/blink/web_tests/fast/forms/range/range-keyoperation.html
@@ -6,6 +6,15 @@
 <body>
 <p id="description">Test for keyboard operations of &lt;input type=range></p>
 <div id="console"></div>
+
+<input type=range id=horiz-range min=0 max=100 value=50 onchange='handleChange()'>
+<input type=range id=horiz-range-rtl dir=rtl min=0 max=100 value=50 onchange='handleChange()'>
+<input type=range id=vert-range min=0 max=100 value=50 onchange='handleChange()' style='-webkit-appearance:slider-vertical;'>
+<input type=range id=vert-range-rtl dir=rtl min=0 max=100 value=50 onchange='handleChange()' style='-webkit-appearance:slider-vertical;'>
+<input type=range id=step-any min=0 max=200 value=100 step='any' onchange='handleChange()' style='-webkit-appearance:slider-vertical;'>
+<input type=range id=small-range min=0 max=10 value=6 step=3 onchange='handleChange()' style='-webkit-appearance:slider-vertical;'>
+<input type=range id=disabled disabled min=0 max=100 value=1 step=1 onchange='handleChange()' style='-webkit-appearance:slider-vertical;'>
+
 <script>
 
 function sendKey(element, keyName) {
@@ -17,15 +26,10 @@
   changeEventCounter++;
 }
 
-var parent = document.createElement('div');
-document.body.appendChild(parent);
-parent.innerHTML = '<input type=range id=range min=0 max=100 value=50>';
-
-var input = document.getElementById('range');
-input.onchange = handleChange;
+debug('Tests for a horizontal range');
+var input = document.getElementById('horiz-range');
 input.focus();
 
-debug('Tests for a horizontal range');
 debug('Press the up arrow key:');
 var lastChangeEventCounter = changeEventCounter;
 sendKey(input, 'ArrowUp');
@@ -92,23 +96,75 @@
 shouldBe('input.value', '"100"');
 shouldBe('changeEventCounter', 'lastChangeEventCounter');
 
-input.dir = 'rtl';
-input.value = '50';
-input.offsetLeft;
+
+
 debug('');
-debug('RTL');
+debug('Tests for a horizontal range with RTL');
+var input = document.getElementById('horiz-range-rtl');
+input.focus();
+
+debug('Press the up arrow key:');
+sendKey(input, 'ArrowUp');
+shouldBe('input.value', '"51"');
+
+debug('Press the down arrow key:');
+sendKey(input, 'ArrowDown');
+shouldBe('input.value', '"50"');
+
+debug('Press the left arrow key:');
 sendKey(input, 'ArrowLeft');
-shouldBeEqualToString('input.value', '51');
+shouldBe('input.value', '"51"');
+
+debug('Press the right arrow key:');
 sendKey(input, 'ArrowRight');
-shouldBeEqualToString('input.value', '50');
-input.dir = 'ltr';
+shouldBe('input.value', '"50"');
+
+
 
 debug('');
-input.setAttribute('style', '-webkit-appearance:slider-vertical; height: 40px;');
-input.offsetLeft; // force layout
-
-input.valueAsNumber = 50;
 debug('Tests for a vertical range');
+var input = document.getElementById('vert-range');
+input.focus();
+
+debug('Press the up arrow key:');
+sendKey(input, 'ArrowUp');
+shouldBe('input.value', '"51"');
+
+debug('Press the down arrow key:');
+sendKey(input, 'ArrowDown');
+shouldBe('input.value', '"50"');
+
+debug('Press the left arrow key:');
+sendKey(input, 'ArrowLeft');
+shouldBe('input.value', '"49"');
+
+debug('Press the right arrow key:');
+sendKey(input, 'ArrowRight');
+shouldBe('input.value', '"50"');
+
+debug('Press the PageUp key:');
+sendKey(input, 'PageUp');
+shouldBe('input.value', '"60"');
+
+debug('Press the PageDown key:');
+sendKey(input, 'PageDown');
+shouldBe('input.value', '"50"');
+
+debug('Press the Home key:');
+sendKey(input, 'Home');
+shouldBe('input.value', '"0"');
+
+debug('Press the End key:');
+sendKey(input, 'End');
+shouldBe('input.value', '"100"');
+
+
+
+debug('');
+debug('Tests for a vertical range with rtl');
+var input = document.getElementById('vert-range-rtl');
+input.focus();
+
 debug('Press the up arrow key:');
 sendKey(input, 'ArrowUp');
 shouldBe('input.value', '"51"');
@@ -135,18 +191,18 @@
 
 debug('Press the Home key:');
 sendKey(input, 'Home');
-shouldBe('input.value', '"100"');
+shouldBe('input.value', '"0"');
 
 debug('Press the End key:');
 sendKey(input, 'End');
-shouldBe('input.value', '"0"');
+shouldBe('input.value', '"100"');
+
+
 
 debug('');
 debug('step=any cases');
-input.step = 'any';
-input.min = '0';
-input.max = '200';
-input.valueAsNumber = 100;
+var input = document.getElementById('step-any');
+input.focus();
 
 debug('Press the up arrow key:');
 lastChangeEventCounter = changeEventCounter;
@@ -175,13 +231,13 @@
 debug('Press the home key:');
 lastChangeEventCounter = changeEventCounter;
 sendKey(input, 'Home');
-shouldBe('input.value', '"200"');
+shouldBe('input.value', '"0"');
 shouldBe('changeEventCounter', 'lastChangeEventCounter + 1');
 
 debug('Press the end key:');
 lastChangeEventCounter = changeEventCounter;
 sendKey(input, 'End');
-shouldBe('input.value', '"0"');
+shouldBe('input.value', '"200"');
 shouldBe('changeEventCounter', 'lastChangeEventCounter + 1');
 
 debug('Edge cases');
@@ -225,12 +281,12 @@
 sendKey(input, 'PageUp');
 shouldBe('input.value', '"20"');
 
+
+
 debug('');
 debug('small range');
-input.min = '0';
-input.max = '10';
-input.step = '3';
-input.valueAsNumber = 6;
+var input = document.getElementById('small-range');
+input.focus();
 
 sendKey(input, 'PageUp');
 shouldBe('input.value', '"9"');
@@ -239,23 +295,21 @@
 shouldBe('input.value', '"6"');
 
 sendKey(input, 'End');
-shouldBe('input.value', '"0"');
-
-sendKey(input, 'Home');
 shouldBe('input.value', '"9"');
 
-sendKey(input, 'End');
+sendKey(input, 'Home');
 shouldBe('input.value', '"0"');
 
+sendKey(input, 'End');
+shouldBe('input.value', '"9"');
+
+
 
 debug('');
 debug('Disabled and read-only');
-input.min = '0';
-input.max = '100';
-input.step = '1';
-input.value = '1';
+var input = document.getElementById('disabled');
+input.focus();
 
-input.disabled = true;
 sendKey(input, 'ArrowUp');
 shouldBe('input.value', '"1"');
 sendKey(input, 'Home');
diff --git a/third_party/blink/web_tests/virtual/plz-dedicated-worker/external/wpt/service-workers/service-worker/claim-worker-fetch.https-expected.txt b/third_party/blink/web_tests/virtual/plz-dedicated-worker/external/wpt/service-workers/service-worker/claim-worker-fetch.https-expected.txt
deleted file mode 100644
index 55d23be..0000000
--- a/third_party/blink/web_tests/virtual/plz-dedicated-worker/external/wpt/service-workers/service-worker/claim-worker-fetch.https-expected.txt
+++ /dev/null
@@ -1,6 +0,0 @@
-This is a testharness.js-based test.
-PASS fetch() in Worker should be intercepted after the client is claimed.
-PASS fetch() in nested Worker should be intercepted after the client is claimed.
-FAIL fetch() in blob URL Worker should be intercepted after the client is claimed. assert_equals: fetch() in the worker should be intercepted. expected "Intercepted!" but got "a simple text file\n"
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-expected.txt b/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-expected.txt
index 75d90f4..b93246c 100644
--- a/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-expected.txt
+++ b/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-expected.txt
@@ -4306,6 +4306,7 @@
 interface MediaRecorder : EventTarget
     static method isTypeSupported
     attribute @@toStringTag
+    getter audioBitrateMode
     getter audioBitsPerSecond
     getter mimeType
     getter ondataavailable
diff --git a/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt b/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
index efef17d..9118339 100644
--- a/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
+++ b/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
@@ -5101,6 +5101,7 @@
 interface MediaRecorder : EventTarget
     static method isTypeSupported
     attribute @@toStringTag
+    getter audioBitrateMode
     getter audioBitsPerSecond
     getter mimeType
     getter ondataavailable
diff --git a/third_party/crashpad/README.chromium b/third_party/crashpad/README.chromium
index 488c051..12614047 100644
--- a/third_party/crashpad/README.chromium
+++ b/third_party/crashpad/README.chromium
@@ -2,7 +2,7 @@
 Short Name: crashpad
 URL: https://crashpad.chromium.org/
 Version: unknown
-Revision: 8bf3cdd977a0d6542963c704cadc85e03bbe4e94
+Revision: 0f70d9477ea844a7a88c9449d231fc5a71f2cc4e
 License: Apache 2.0
 License File: crashpad/LICENSE
 Security Critical: yes
@@ -39,3 +39,6 @@
  - MultiprocessExec.MultiprocessExec is disabled when OS_POSIX and
    BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC) are defined. crbug.com/1153544
    (third_party/crashpad/crashpad/test/multiprocess_exec_test.cc)
+ - MemoryMap.SelfLargeMapFile, SelfBasic, SelfLargeFiles are disabled when
+   BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC) are defined. crbug.com/1163794
+   (third_party/crashpad/crashpad/util/linux/memory_map_test.cc)
diff --git a/third_party/crashpad/crashpad/DEPS b/third_party/crashpad/crashpad/DEPS
index db8fd8d..11add5e 100644
--- a/third_party/crashpad/crashpad/DEPS
+++ b/third_party/crashpad/crashpad/DEPS
@@ -42,7 +42,7 @@
       '7bde79cc274d06451bf65ae82c012a5d3e476b5a',
   'crashpad/third_party/mini_chromium/mini_chromium':
       Var('chromium_git') + '/chromium/mini_chromium@' +
-      'c748b289b825056985f3dd3b36dc86c766d787ad',
+      '12ea507eb719a54698e1429e91e84c65284805ab',
   'crashpad/third_party/libfuzzer/src':
       Var('chromium_git') + '/chromium/llvm-project/compiler-rt/lib/fuzzer.git@' +
       'fda403cf93ecb8792cb1d061564d89a6553ca020',
diff --git a/third_party/crashpad/crashpad/client/BUILD.gn b/third_party/crashpad/crashpad/client/BUILD.gn
index 1df95ee..007b6f9 100644
--- a/third_party/crashpad/crashpad/client/BUILD.gn
+++ b/third_party/crashpad/crashpad/client/BUILD.gn
@@ -88,7 +88,7 @@
     "../util",
   ]
 
-  deps = [ "//build:chromeos_buildflags" ]
+  deps = [ "../third_party/mini_chromium:chromeos_buildflags" ]
 
   if (crashpad_is_win) {
     libs = [ "rpcrt4.lib" ]
diff --git a/third_party/crashpad/crashpad/handler/BUILD.gn b/third_party/crashpad/crashpad/handler/BUILD.gn
index 623c833..1d5951e 100644
--- a/third_party/crashpad/crashpad/handler/BUILD.gn
+++ b/third_party/crashpad/crashpad/handler/BUILD.gn
@@ -75,8 +75,8 @@
   deps = [
     "../minidump",
     "../snapshot",
+    "../third_party/mini_chromium:chromeos_buildflags",
     "../tools:tool_support",
-    "//build:chromeos_buildflags",
   ]
 
   if (crashpad_is_win) {
diff --git a/third_party/crashpad/crashpad/handler/handler_main.cc b/third_party/crashpad/crashpad/handler/handler_main.cc
index 83e5562..737ee4e 100644
--- a/third_party/crashpad/crashpad/handler/handler_main.cc
+++ b/third_party/crashpad/crashpad/handler/handler_main.cc
@@ -57,7 +57,7 @@
 #include "util/string/split_string.h"
 #include "util/synchronization/semaphore.h"
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
+#if BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS)
 #include "handler/linux/cros_crash_report_exception_handler.h"
 #endif
 
@@ -163,7 +163,7 @@
 #endif  // OS_LINUX || OS_CHROMEOS || OS_ANDROID
 "      --url=URL               send crash reports to this Breakpad server URL,\n"
 "                              only if uploads are enabled for the database\n"
-#if BUILDFLAG(IS_CHROMEOS_ASH)
+#if BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS)
 "      --use-cros-crash-reporter\n"
 "                              pass crash reports to /sbin/crash_reporter\n"
 "                              instead of storing them in the database\n"
@@ -174,7 +174,7 @@
 "                              pass the --always_allow_feedback flag to\n"
 "                              crash_reporter, thus skipping metrics consent\n"
 "                              checks\n"
-#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS)
 #if defined(OS_ANDROID)
 "      --write-minidump-to-log write minidump to log\n"
 #endif  // OS_ANDROID
@@ -213,11 +213,11 @@
   bool periodic_tasks;
   bool rate_limit;
   bool upload_gzip;
-#if BUILDFLAG(IS_CHROMEOS_ASH)
+#if BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS)
   bool use_cros_crash_reporter = false;
   base::FilePath minidump_dir_for_tests;
   bool always_allow_feedback = false;
-#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS)
 #if defined(ATTACHMENTS_SUPPORTED)
   std::vector<base::FilePath> attachments;
 #endif  // ATTACHMENTS_SUPPORTED
@@ -577,11 +577,11 @@
     kOptionTraceParentWithException,
 #endif
     kOptionURL,
-#if BUILDFLAG(IS_CHROMEOS_ASH)
+#if BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS)
     kOptionUseCrosCrashReporter,
     kOptionMinidumpDirForTests,
     kOptionAlwaysAllowFeedback,
-#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS)
 #if defined(OS_ANDROID)
     kOptionWriteMinidumpToLog,
 #endif  // OS_ANDROID
@@ -659,7 +659,7 @@
      kOptionTraceParentWithException},
 #endif  // OS_LINUX || OS_CHROMEOS || OS_ANDROID
     {"url", required_argument, nullptr, kOptionURL},
-#if BUILDFLAG(IS_CHROMEOS_ASH)
+#if BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS)
     {"use-cros-crash-reporter",
       no_argument,
       nullptr,
@@ -672,7 +672,7 @@
       no_argument,
       nullptr,
       kOptionAlwaysAllowFeedback},
-#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS)
 #if defined(OS_ANDROID)
     {"write-minidump-to-log", no_argument, nullptr, kOptionWriteMinidumpToLog},
 #endif  // OS_ANDROID
@@ -833,7 +833,7 @@
         options.url = optarg;
         break;
       }
-#if BUILDFLAG(IS_CHROMEOS_ASH)
+#if BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS)
       case kOptionUseCrosCrashReporter: {
         options.use_cros_crash_reporter = true;
         break;
@@ -847,7 +847,7 @@
         options.always_allow_feedback = true;
         break;
       }
-#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS)
 #if defined(OS_ANDROID)
       case kOptionWriteMinidumpToLog: {
         options.write_minidump_to_log = true;
@@ -993,7 +993,7 @@
   std::unique_ptr<CrashReportExceptionHandler> exception_handler;
 #endif
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
+#if BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS)
   if (options.use_cros_crash_reporter) {
     auto cros_handler = std::make_unique<CrosCrashReportExceptionHandler>(
         database.get(),
@@ -1036,7 +1036,7 @@
       false,
 #endif  // OS_LINUX
       user_stream_sources);
-#endif  // BUILDFLAG(IS_CHROMEOS_ASH) 
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS)
 
 #if defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(OS_ANDROID)
   if (options.exception_information_address) {
diff --git a/third_party/crashpad/crashpad/snapshot/minidump/process_snapshot_minidump.cc b/third_party/crashpad/crashpad/snapshot/minidump/process_snapshot_minidump.cc
index c42e7ccf..4f85b77 100644
--- a/third_party/crashpad/crashpad/snapshot/minidump/process_snapshot_minidump.cc
+++ b/third_party/crashpad/crashpad/snapshot/minidump/process_snapshot_minidump.cc
@@ -318,7 +318,7 @@
 #else
       full_version_ = base::UTF16ToUTF8(info.BuildString);
 #endif
-      full_version_ = full_version_.substr(0, full_version_.find(";"));
+      full_version_ = full_version_.substr(0, full_version_.find(';'));
       FALLTHROUGH;
     case sizeof(MINIDUMP_MISC_INFO_3):
     case sizeof(MINIDUMP_MISC_INFO_2):
diff --git a/third_party/crashpad/crashpad/third_party/mini_chromium/BUILD.gn b/third_party/crashpad/crashpad/third_party/mini_chromium/BUILD.gn
index 3efd710..c1eb588 100644
--- a/third_party/crashpad/crashpad/third_party/mini_chromium/BUILD.gn
+++ b/third_party/crashpad/crashpad/third_party/mini_chromium/BUILD.gn
@@ -45,3 +45,16 @@
     public_deps = [ "//third_party/mini_chromium/mini_chromium/build" ]
   }
 }
+
+group("chromeos_buildflags") {
+  if (crashpad_is_in_chromium) {
+    public_deps = [ "//build:chromeos_buildflags" ]
+  } else if (crashpad_is_standalone || crashpad_is_in_fuchsia) {
+    public_deps = [ "mini_chromium/build:chromeos_buildflags" ]
+  } else if (crashpad_is_external) {
+    public_deps = [ "../../../../mini_chromium/mini_chromium/build:chromeos_buildflags" ]
+  } else if (crashpad_is_in_dart) {
+    public_deps = [ "//third_party/mini_chromium/mini_chromium/build:chromeos_buildflags" ]
+  }
+}
+
diff --git a/third_party/crashpad/crashpad/util/BUILD.gn b/third_party/crashpad/crashpad/util/BUILD.gn
index a8daea8..adedf95 100644
--- a/third_party/crashpad/crashpad/util/BUILD.gn
+++ b/third_party/crashpad/crashpad/util/BUILD.gn
@@ -379,7 +379,7 @@
     ]
   }
 
-  deps = [ "//build:chromeos_buildflags" ]
+  deps = []
 
   if (crashpad_is_linux || crashpad_is_fuchsia || crashpad_is_android) {
     sources += [ "net/http_transport_socket.cc" ]
diff --git a/third_party/crashpad/crashpad/util/linux/memory_map_test.cc b/third_party/crashpad/crashpad/util/linux/memory_map_test.cc
index 0ee90800..6c5ae314 100644
--- a/third_party/crashpad/crashpad/util/linux/memory_map_test.cc
+++ b/third_party/crashpad/crashpad/util/linux/memory_map_test.cc
@@ -20,6 +20,7 @@
 #include <sys/stat.h>
 #include <unistd.h>
 
+#include "base/allocator/buildflags.h"
 #include "base/files/file_path.h"
 #include "base/strings/stringprintf.h"
 #include "build/build_config.h"
@@ -41,7 +42,20 @@
 namespace test {
 namespace {
 
-TEST(MemoryMap, SelfLargeFiles) {
+// TODO(tasak): Disable SelfLargeFiles when PartitionAlloc is used as malloc.
+// Because malloc() will cause new mmap() in the case. So while
+// reading /proc/self/maps, any memory allocation will update the maps file and
+// will cause "format_error". (e.g. GetDelim uses std::string. If std::string
+// allocates memory internally (e.g. append and so on), map.Initialize() will
+// fail.) To avoid this failue, firstly allocate a large buffer and read entire
+// /proc/self/maps into the buffer. Next will parse data from the buffer and
+// initialize MemoryMap. crbug.com/1163794.
+#if BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
+#define MAYBE_SelfLargeFiles DISABLED_SelfLargeFiles
+#else
+#define MAYBE_SelfLargeFiles SelfLargeFiles
+#endif
+TEST(MemoryMap, MAYBE_SelfLargeFiles) {
   // This test is meant to test the handler's ability to understand files
   // mapped from large offsets, even if the handler wasn't built with
   // _FILE_OFFSET_BITS=64. ScopedTempDir needs to stat files to determine
@@ -73,7 +87,14 @@
   ASSERT_TRUE(map.Initialize(&connection));
 }
 
-TEST(MemoryMap, SelfBasic) {
+// TODO(tasak): Disable SelfBasic when PartitionAlloc is used as malloc.
+// crbug.com/1163794. See SelfLargeFiles' comment.
+#if BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
+#define MAYBE_SelfBasic DISABLED_SelfBasic
+#else
+#define MAYBE_SelfBasic SelfBasic
+#endif
+TEST(MemoryMap, MAYBE_SelfBasic) {
   ScopedMmap mmapping;
   ASSERT_TRUE(mmapping.ResetMmap(nullptr,
                                  getpagesize(),
@@ -305,7 +326,14 @@
   }
 }
 
-TEST(MemoryMap, SelfLargeMapFile) {
+// TODO(tasak): Disable SelfLargeMapFile when PartitionAlloc is used as malloc.
+// crbug.com/1163794. See SelfLargeFiles' comment.
+#if BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
+#define MAYBE_SelfLargeMapFile DISABLED_SelfLargeMapFile
+#else
+#define MAYBE_SelfLargeMapFile SelfLargeMapFile
+#endif
+TEST(MemoryMap, MAYBE_SelfLargeMapFile) {
   constexpr size_t kNumMappings = 1024;
   const size_t page_size = getpagesize();
   ScopedMmap mappings;
diff --git a/tools/determinism/deterministic_build_ignorelist.pyl b/tools/determinism/deterministic_build_ignorelist.pyl
index cebd7451..36a2de6 100644
--- a/tools/determinism/deterministic_build_ignorelist.pyl
+++ b/tools/determinism/deterministic_build_ignorelist.pyl
@@ -203,6 +203,10 @@
     'mini_installer.exe.pdb',
     'previous_version_mini_installer.exe',
 
+    # https://crbug.com/1163310
+    r'swiftshader\libGLESv2.dll',
+    r'swiftshader\libGLESv2.dll.pdb',
+
     # These probably have mtimes in the zip headers and the scripts creating
     # them probably should use build_utils.ZipDir() instead.
     'mini_installer_tests.zip',
diff --git a/tools/lldb/OWNERS b/tools/lldb/OWNERS
deleted file mode 100644
index 03f127c..0000000
--- a/tools/lldb/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-jzw@chromium.org
-ichikawa@chromium.org
diff --git a/tools/lldb/lldb_chrome.py b/tools/lldb/lldb_chrome.py
deleted file mode 100644
index b85d5a5..0000000
--- a/tools/lldb/lldb_chrome.py
+++ /dev/null
@@ -1,39 +0,0 @@
-# Copyright (c) 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.
-
-"""
-    LLDB Support for Chromium types in Xcode
-
-    Add the following to your ~/.lldbinit:
-    command script import {Path to SRC Root}/tools/lldb/lldb_chrome.py
-"""
-
-import lldb
-
-
-def __lldb_init_module(debugger, internal_dict):
-    debugger.HandleCommand('type summary add -F ' +
-        'lldb_chrome.basestring16_SummaryProvider base::string16')
-
-
-def basestring16_SummaryProvider(valobj, internal_dict):
-    s = valobj.GetValueForExpressionPath('.__r_.__value_.__s')
-    l = valobj.GetValueForExpressionPath('.__r_.__value_.__l')
-    size = s.GetChildMemberWithName('__size_').GetValueAsUnsigned(0)
-    is_short_string = size & 128 == 0  # Assumes _LIBCPP_BIG_ENDIAN is defined.
-    if is_short_string:
-        length = size >> 1
-        data = s.GetChildMemberWithName('__data_').GetPointeeData(0, length)
-    else:
-        length = l.GetChildMemberWithName('__size_').GetValueAsUnsigned(0)
-        data = l.GetChildMemberWithName('__data_').GetPointeeData(0, length)
-    error = lldb.SBError()
-    bytes_to_read = 2 * length
-    if not bytes_to_read:
-        return '""'
-    byte_string = data.ReadRawData(error, 0, bytes_to_read)
-    if error.fail:
-        return 'Summary error: %s' % error.description
-    else:
-        return '"' + byte_string.decode('utf-16') + '"'
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 59269df..6272f65 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -9941,6 +9941,16 @@
   <int value="2" label="First service disabled"/>
 </enum>
 
+<enum name="ChromeOSCameraClientType">
+  <int value="0" label="Unknown"/>
+  <int value="1" label="Testing"/>
+  <int value="2" label="Chrome"/>
+  <int value="3" label="Android"/>
+  <int value="4" label="PluginVM"/>
+  <int value="5" label="AshChrome"/>
+  <int value="6" label="LacrosChrome"/>
+</enum>
+
 <enum name="ChromeOSCameraErrorType">
   <int value="1" label="Device"/>
   <int value="2" label="Request"/>
@@ -11255,6 +11265,13 @@
   <int value="3" label="Web Smart Paste"/>
 </enum>
 
+<enum name="ClipboardHistoryTriggerType">
+  <int value="0" label="Accelerator"/>
+  <int value="1" label="Render View Context Menu"/>
+  <int value="2" label="Textfield Context Menu"/>
+  <int value="3" label="Virtual Keyboard"/>
+</enum>
+
 <enum name="ClockdriftLevel">
   <int value="0" label="None"/>
   <int value="1" label="Probable"/>
@@ -20536,6 +20553,13 @@
   <int value="8" label="MediaKeySession.remove()"/>
 </enum>
 
+<enum name="EmeSessionType">
+  <int value="0" label="Unknown"/>
+  <int value="1" label="Temporary"/>
+  <int value="2" label="Persistent License"/>
+  <int value="3" label="Persistent Usage Record"/>
+</enum>
+
 <enum name="EmmcLifeUsed">
   <int value="0" label="Not defined"/>
   <int value="1" label="0% - 10% device life time used"/>
@@ -30869,6 +30893,10 @@
   <int value="17" label="Failed - feed is disabled by enterprise policy"/>
   <int value="18"
       label="Failed - network fetch failed or returned a non-200 status"/>
+  <int value="19" label="Failed - cannot load more, no next page token"/>
+  <int value="20" label="Failed - data in store is stale, last refresh missed"/>
+  <int value="21" label="Success loading stale data, after network failure"/>
+  <int value="22" label="Failed - data in store is expired"/>
 </enum>
 
 <enum name="FeedRequestReason">
@@ -42779,6 +42807,8 @@
       label="AutofillUpstreamEditableCardholderName:disabled"/>
   <int value="-1624854957" label="enable-es3-apis"/>
   <int value="-1624593106" label="NewTabPageBackgrounds:enabled"/>
+  <int value="-1623003151"
+      label="SafeBrowsingkRealTimeUrlLookupEnterpriseGaEndpoint:disabled"/>
   <int value="-1621963267" label="EnableAssistantLauncherUI:enabled"/>
   <int value="-1620804800" label="NoScriptPreviews:disabled"/>
   <int value="-1620568042" label="FeaturePolicy:disabled"/>
@@ -45679,6 +45709,8 @@
   <int value="1105439588" label="enable-swipe-selection"/>
   <int value="1106307305" label="AutofillPrimaryInfoStyleExperiment:enabled"/>
   <int value="1106579567" label="ProfilesUIRevamp:enabled"/>
+  <int value="1106890743"
+      label="SafeBrowsingkRealTimeUrlLookupEnterpriseGaEndpoint:enabled"/>
   <int value="1107543566" label="enable-one-copy"/>
   <int value="1108663108" label="disable-device-discovery-notifications"/>
   <int value="1109907837"
diff --git a/tools/metrics/histograms/histograms_index.txt b/tools/metrics/histograms/histograms_index.txt
index 3bd5f8c..d377604 100644
--- a/tools/metrics/histograms/histograms_index.txt
+++ b/tools/metrics/histograms/histograms_index.txt
@@ -43,6 +43,7 @@
 tools/metrics/histograms/histograms_xml/google/histograms.xml
 tools/metrics/histograms/histograms_xml/gpu/histograms.xml
 tools/metrics/histograms/histograms_xml/hang_watcher/histograms.xml
+tools/metrics/histograms/histograms_xml/help_app/histograms.xml
 tools/metrics/histograms/histograms_xml/histogram_suffixes_list.xml
 tools/metrics/histograms/histograms_xml/history/histograms.xml
 tools/metrics/histograms/histograms_xml/holding_space/histograms.xml
@@ -117,4 +118,4 @@
 tools/metrics/histograms/histograms_xml/web_core/histograms.xml
 tools/metrics/histograms/histograms_xml/web_rtc/histograms.xml
 tools/metrics/histograms/histograms_xml/weblayer/histograms.xml
-tools/metrics/histograms/histograms_xml/windows/histograms.xml
+tools/metrics/histograms/histograms_xml/windows/histograms.xml
\ No newline at end of file
diff --git a/tools/metrics/histograms/histograms_xml/ash/histograms.xml b/tools/metrics/histograms/histograms_xml/ash/histograms.xml
index a36e572..5d06113 100644
--- a/tools/metrics/histograms/histograms_xml/ash/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/ash/histograms.xml
@@ -407,6 +407,16 @@
   </summary>
 </histogram>
 
+<histogram name="Ash.ClipboardHistory.ContextMenu.ShowMenu"
+    enum="ClipboardHistoryTriggerType" expires_after="2021-09-01">
+  <owner>andrewxu@chromium.org</owner>
+  <owner>multipaste@google.com</owner>
+  <summary>
+    It records how many times users trigger the clipboard history menu through
+    different ways respecitvely. Recorded when the clipboard history menu shows.
+  </summary>
+</histogram>
+
 <histogram name="Ash.ClipboardHistory.ContextMenu.ShowPlaceholderString"
     enum="ClipboardHistoryPlaceholderStringType" expires_after="2021-09-01">
   <owner>newcomer@chromium.org</owner>
diff --git a/tools/metrics/histograms/histograms_xml/blink/histograms.xml b/tools/metrics/histograms/histograms_xml/blink/histograms.xml
index 8b5d95e..442f981 100644
--- a/tools/metrics/histograms/histograms_xml/blink/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/blink/histograms.xml
@@ -65,7 +65,7 @@
 </histogram>
 
 <histogram name="Blink.Canvas.2DPrintingAsVector" enum="BooleanSuccess"
-    expires_after="2021-01-31">
+    expires_after="2022-01-31">
   <owner>fserb@chromium.org</owner>
   <owner>juanmihd@chromium.org</owner>
   <summary>
@@ -154,7 +154,7 @@
 </histogram>
 
 <histogram name="Blink.Canvas.HasRendered" enum="Boolean"
-    expires_after="2021-01-31">
+    expires_after="2022-01-31">
   <owner>juanmihd@chromium.org</owner>
   <owner>fserb@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/histograms_xml/chromeos/histograms.xml b/tools/metrics/histograms/histograms_xml/chromeos/histograms.xml
index bb22df5..d60f13f0 100644
--- a/tools/metrics/histograms/histograms_xml/chromeos/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/chromeos/histograms.xml
@@ -181,6 +181,16 @@
   </summary>
 </histogram>
 
+<histogram name="ChromeOS.Camera.OpenDeviceClientType"
+    enum="ChromeOSCameraClientType" expires_after="2021-05-16">
+  <owner>lnishan@chromium.org</owner>
+  <owner>chromeos-camera-eng@google.com</owner>
+  <summary>
+    Records which client type (e.g., Chrome, Android) is opening a camera
+    device.
+  </summary>
+</histogram>
+
 <histogram name="ChromeOS.Camera.OpenDeviceLatency" units="microseconds"
     expires_after="2021-05-16">
   <owner>wtlee@chromium.org</owner>
diff --git a/tools/metrics/histograms/histograms_xml/content/histograms.xml b/tools/metrics/histograms/histograms_xml/content/histograms.xml
index 4a9a67a..69d8ef7 100644
--- a/tools/metrics/histograms/histograms_xml/content/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/content/histograms.xml
@@ -656,6 +656,34 @@
   </summary>
 </histogram>
 
+<histogram name="ContentSuggestions.Feed.ContentAgeOnLoad.BlockingRefresh"
+    units="ms" expires_after="2022-01-20">
+  <owner>carlosk@chromium.org</owner>
+  <owner>harringtond@chromium.org</owner>
+  <owner>feed@chromium.org</owner>
+  <summary>
+    Android: The time since locally stored content was fetched from the server.
+    Reported when the feed surface is shown, and content is refreshed from the
+    network. This typically only happens when content is stale, so this is a
+    measurement of how stale content is before it's replaced by a blocking
+    refresh. Feed v2 only.
+  </summary>
+</histogram>
+
+<histogram name="ContentSuggestions.Feed.ContentAgeOnLoad.NotRefreshed"
+    units="ms" expires_after="2022-01-20">
+  <owner>carlosk@chromium.org</owner>
+  <owner>harringtond@chromium.org</owner>
+  <owner>feed@chromium.org</owner>
+  <summary>
+    Android: The time since locally stored content was fetched from the server.
+    Reported when the feed surface is shown, and the local persisted feed
+    content is loaded instead of a direct network refresh. This provides a
+    measure of how stale feed content is when the feed loads without a blocking
+    network request. Feed v2 only.
+  </summary>
+</histogram>
+
 <histogram name="ContentSuggestions.Feed.Controls.Actions"
     enum="FeedControlsActions" expires_after="never">
 <!-- expires-never: tracked as an important feed metric. -->
diff --git a/tools/metrics/histograms/histograms_xml/dev/histograms.xml b/tools/metrics/histograms/histograms_xml/dev/histograms.xml
index 79a8ecb..d6a5fb7 100644
--- a/tools/metrics/histograms/histograms_xml/dev/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/dev/histograms.xml
@@ -30,7 +30,7 @@
 </histogram>
 
 <histogram name="DevTools.BackgroundService.ClearEvents"
-    enum="ServiceWorkerStatusCode" expires_after="M87">
+    enum="ServiceWorkerStatusCode" expires_after="M92">
   <owner>yangguo@chromium.org</owner>
   <owner>rayankans@chromium.org</owner>
   <owner>bmeurer@chromium.org</owner>
@@ -42,7 +42,7 @@
 </histogram>
 
 <histogram name="DevTools.BackgroundService.GetEvents"
-    enum="ServiceWorkerStatusCode" expires_after="M87">
+    enum="ServiceWorkerStatusCode" expires_after="M92">
   <owner>rayankans@chromium.org</owner>
   <owner>yangguo@chromium.org</owner>
   <owner>bmeurer@chromium.org</owner>
@@ -53,7 +53,7 @@
 </histogram>
 
 <histogram name="DevTools.BackgroundService.LogEvent"
-    enum="ServiceWorkerStatusCode" expires_after="M87">
+    enum="ServiceWorkerStatusCode" expires_after="M92">
   <owner>rayankans@chromium.org</owner>
   <owner>yangguo@chromium.org</owner>
   <owner>bmeurer@chromium.org</owner>
@@ -65,7 +65,7 @@
 </histogram>
 
 <histogram name="DevTools.BackgroundService.StartRecording"
-    enum="DevToolsBackgroundService" expires_after="M87">
+    enum="DevToolsBackgroundService" expires_after="M92">
   <owner>yangguo@chromium.org</owner>
   <owner>bmeurer@chromium.org</owner>
   <owner>rayankans@chromium.org</owner>
diff --git a/tools/metrics/histograms/histograms_xml/help_app/histograms.xml b/tools/metrics/histograms/histograms_xml/help_app/histograms.xml
new file mode 100644
index 0000000..32f69273
--- /dev/null
+++ b/tools/metrics/histograms/histograms_xml/help_app/histograms.xml
@@ -0,0 +1,38 @@
+<!--
+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.
+-->
+
+<!--
+This file is used to generate a comprehensive list of History histograms
+along with a detailed description for each histogram.
+
+For best practices on writing histogram descriptions, see
+https://chromium.googlesource.com/chromium/src.git/+/HEAD/tools/metrics/histograms/README.md
+
+Please send CLs to chromium-metrics-reviews@google.com rather than to specific
+individuals. These CLs will be automatically reassigned to a reviewer within
+about 5 minutes. This approach helps the metrics team to load-balance incoming
+reviews. Googlers can read more about this at go/gwsq-gerrit.
+-->
+
+<histogram-configuration>
+
+<histograms>
+
+<histogram name="Discover.Search.SearchStatus"
+    enum="LocalSearchServiceResponseStatus" expires_after="2022-01-08">
+  <owner>callistus@google.com</owner>
+  <owner>showoff-eng@google.com</owner>
+  <summary>
+    Records the search status when searching with the Local Search Service in
+    the Discover (internally &quot;Showoff&quot;) app. This is logged once per
+    user search and will count the number of searches while the index is still
+    empty, successful searches, etc.
+  </summary>
+</histogram>
+
+</histograms>
+
+</histogram-configuration>
diff --git a/tools/metrics/histograms/histograms_xml/histogram_suffixes_list.xml b/tools/metrics/histograms/histograms_xml/histogram_suffixes_list.xml
index 10f294c..a8afe61 100644
--- a/tools/metrics/histograms/histograms_xml/histogram_suffixes_list.xml
+++ b/tools/metrics/histograms/histograms_xml/histogram_suffixes_list.xml
@@ -8027,6 +8027,7 @@
   <affected-histogram name="Media.EME.cancelKeyRequest"/>
   <affected-histogram name="Media.EME.CreateCdm"/>
   <affected-histogram name="Media.EME.CreateCdmTime"/>
+  <affected-histogram name="Media.EME.CreateSession.SessionType"/>
   <affected-histogram name="Media.EME.generateKeyRequest"/>
   <affected-histogram name="Media.EME.KeyAdded"/>
   <affected-histogram name="Media.EME.KeyError"/>
diff --git a/tools/metrics/histograms/histograms_xml/media/histograms.xml b/tools/metrics/histograms/histograms_xml/media/histograms.xml
index 4b5c3dd88..4d6e068 100644
--- a/tools/metrics/histograms/histograms_xml/media/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/media/histograms.xml
@@ -1513,6 +1513,13 @@
   <summary>The time it takes to create the CDM instance.</summary>
 </histogram>
 
+<histogram base="true" name="Media.EME.CreateSession.SessionType"
+    enum="EmeSessionType" expires_after="2021-05-07">
+  <owner>xhwang@chromium.org</owner>
+  <owner>media-dev@chromium.org</owner>
+  <summary>EME session type passed into CreateSession.</summary>
+</histogram>
+
 <histogram name="Media.EME.CrosCdmType" enum="CrosCdmType"
     expires_after="2022-01-15">
   <owner>jkardatzke@chromium.org</owner>
diff --git a/tools/metrics/histograms/histograms_xml/web_rtc/histograms.xml b/tools/metrics/histograms/histograms_xml/web_rtc/histograms.xml
index 1d650a3b..d08d64f 100644
--- a/tools/metrics/histograms/histograms_xml/web_rtc/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/web_rtc/histograms.xml
@@ -772,6 +772,9 @@
 
 <histogram name="WebRTC.Audio.ResidualEchoDetector.EchoLikelihood" units="%"
     expires_after="2021-05-09">
+  <obsolete>
+    No longer reported. Removed in M84.
+  </obsolete>
   <owner>hlundin@chromium.org</owner>
   <owner>ivoc@chromium.org</owner>
   <summary>
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps.json b/tools/perf/core/perfetto_binary_roller/binary_deps.json
index 79760a7..02beb766 100644
--- a/tools/perf/core/perfetto_binary_roller/binary_deps.json
+++ b/tools/perf/core/perfetto_binary_roller/binary_deps.json
@@ -1,16 +1,16 @@
 {
     "trace_processor_shell": {
         "win": {
-            "hash": "f3800b3c8e2857a159879265b67126c6ab2495e7",
-            "remote_path": "perfetto_binaries/trace_processor_shell/win/e3e59aac750e507e91be98ff4944a7bdd601a08e/trace_processor_shell.exe"
+            "hash": "e1f08368dcce2a5fae6b8997393ce4172e26ffe0",
+            "remote_path": "perfetto_binaries/trace_processor_shell/win/2881d6b22d1eba089a772a15733151b67685cdaf/trace_processor_shell.exe"
         },
         "mac": {
-            "hash": "d9a52ed5c07fbc0eb8c051b372fb462788267b3c",
-            "remote_path": "perfetto_binaries/trace_processor_shell/mac/c60dda78e4abc2a16052ac325dcc0e2fab39d54c/trace_processor_shell"
+            "hash": "b25230d11856547e55e67c4b28dfb2948a5e6473",
+            "remote_path": "perfetto_binaries/trace_processor_shell/mac/44ba8e1708abec70b1c85015aae59026fb214c83/trace_processor_shell"
         },
         "linux": {
             "hash": "dc8c4a04caf836aca794cd8e8f19207ca3116bc7",
-            "remote_path": "perfetto_binaries/trace_processor_shell/linux/002c03c4b6ed27434ff35dbb31bd8c3288b0ccbd/trace_processor_shell"
+            "remote_path": "perfetto_binaries/trace_processor_shell/linux/51e1fea7385f810e7d08c2fb333112d00fbfcd0e/trace_processor_shell"
         }
     },
     "power_profile.sql": {
diff --git a/ui/accessibility/accessibility_features.cc b/ui/accessibility/accessibility_features.cc
index 2a80d11..1f2a913 100644
--- a/ui/accessibility/accessibility_features.cc
+++ b/ui/accessibility/accessibility_features.cc
@@ -78,13 +78,6 @@
 #endif  // defined(OS_WIN)
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
-const base::Feature kAccessibilityCursorColor{"AccessibilityCursorColor",
-                                              base::FEATURE_ENABLED_BY_DEFAULT};
-
-bool IsAccessibilityCursorColorEnabled() {
-  return base::FeatureList::IsEnabled(::features::kAccessibilityCursorColor);
-}
-
 const base::Feature kMagnifierNewFocusFollowing{
     "MagnifierNewFocusFollowing", base::FEATURE_ENABLED_BY_DEFAULT};
 
diff --git a/ui/accessibility/accessibility_features.h b/ui/accessibility/accessibility_features.h
index 8ce561b..f913209 100644
--- a/ui/accessibility/accessibility_features.h
+++ b/ui/accessibility/accessibility_features.h
@@ -64,12 +64,6 @@
 #endif  // defined(OS_WIN)
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
-AX_BASE_EXPORT extern const base::Feature kAccessibilityCursorColor;
-
-// Returns true if the accessibility cursor color feature is enabled, letting
-// users pick a custom cursor color.
-AX_BASE_EXPORT bool IsAccessibilityCursorColorEnabled();
-
 // Enables new magnifier focus following feature, which provides a richer
 // focus following experience.
 AX_BASE_EXPORT extern const base::Feature kMagnifierNewFocusFollowing;
diff --git a/ui/compositor/layer.cc b/ui/compositor/layer.cc
index fb7c472..e6217f58 100644
--- a/ui/compositor/layer.cc
+++ b/ui/compositor/layer.cc
@@ -636,8 +636,8 @@
   if (layer_inverted_)
     filters.Append(cc::FilterOperation::CreateInvertFilter(1.0));
   if (layer_blur_sigma_) {
-    filters.Append(cc::FilterOperation::CreateBlurFilter(
-        layer_blur_sigma_, SkBlurImageFilter::kClamp_TileMode));
+    filters.Append(cc::FilterOperation::CreateBlurFilter(layer_blur_sigma_,
+                                                         SkTileMode::kClamp));
   }
   // Brightness goes last, because the resulting colors neeed clamping, which
   // cause further color matrix filters to be applied separately. In this order,
@@ -660,8 +660,8 @@
     filters.Append(cc::FilterOperation::CreateZoomFilter(zoom_, zoom_inset_));
 
   if (background_blur_sigma_) {
-    filters.Append(cc::FilterOperation::CreateBlurFilter(
-        background_blur_sigma_, SkBlurImageFilter::kClamp_TileMode));
+    filters.Append(cc::FilterOperation::CreateBlurFilter(background_blur_sigma_,
+                                                         SkTileMode::kClamp));
   }
   cc_layer_->SetBackdropFilters(filters);
 }
diff --git a/ui/file_manager/file_manager/background/js/BUILD.gn b/ui/file_manager/file_manager/background/js/BUILD.gn
index ae5f2b50..3fa67a5 100644
--- a/ui/file_manager/file_manager/background/js/BUILD.gn
+++ b/ui/file_manager/file_manager/background/js/BUILD.gn
@@ -428,6 +428,9 @@
     "//ui/file_manager/externs/background:drive_sync_handler.m",
     "//ui/webui/resources/js/cr:event_target.m",
   ]
+  visibility += [
+    "//ui/file_manager/file_manager/foreground/js:actions_model_unittest.m",
+  ]
 
   extra_deps = [ ":modulize" ]
 }
diff --git a/ui/file_manager/file_manager/foreground/js/BUILD.gn b/ui/file_manager/file_manager/foreground/js/BUILD.gn
index 302fe1e..50e902ea 100644
--- a/ui/file_manager/file_manager/foreground/js/BUILD.gn
+++ b/ui/file_manager/file_manager/foreground/js/BUILD.gn
@@ -28,6 +28,7 @@
 js_type_check("closure_compile_jsmodules") {
   uses_js_modules = true
   deps = [
+    ":actions_model.m",
     ":android_app_list_model.m",
     ":constants.m",
     ":crossover_search_utils.m",
@@ -38,18 +39,23 @@
     ":drop_effect_and_label.m",
     ":empty_folder_controller.m",
     ":fake_android_app_list_model.m",
+    ":fake_file_selection_handler.m",
     ":file_list_model.m",
+    ":file_selection.m",
+    ":file_transfer_controller.m",
     ":file_type_filters_controller.m",
     ":file_watcher.m",
     ":folder_shortcuts_data_model.m",
+    ":holding_space_util.m",
     ":launch_param.m",
-    ":metrics_start.m",
-    ":mock_actions_model.m",
+    ":list_thumbnail_loader.m",
     ":mock_directory_model.m",
     ":mock_folder_shortcut_data_model.m",
     ":mock_navigation_list_model.m",
+    ":mock_thumbnail_loader.m",
     ":navigation_list_model.m",
     ":navigation_uma.m",
+    ":path_component.m",
     ":providers_model.m",
     ":spinner_controller.m",
     ":thumbnail_loader.m",
@@ -185,6 +191,25 @@
       [ "//ui/file_manager/externs/background/drive_sync_handler.js" ]
 }
 
+js_library("actions_model.m") {
+  sources = [ "$root_gen_dir/ui/file_manager/file_manager/foreground/js/actions_model.m.js" ]
+  deps = [
+    ":folder_shortcuts_data_model.m",
+    "metadata:metadata_model.m",
+    "ui:action_model_ui.m",
+    "//ui/file_manager/base/js:volume_manager_types.m",
+    "//ui/file_manager/externs:volume_manager.m",
+    "//ui/file_manager/externs/background:drive_sync_handler.m",
+    "//ui/file_manager/file_manager/common/js:metrics.m",
+    "//ui/file_manager/file_manager/common/js:util.m",
+    "//ui/webui/resources/js:assert.m",
+    "//ui/webui/resources/js:cr.m",
+    "//ui/webui/resources/js/cr:event_target.m",
+  ]
+
+  extra_deps = [ ":modulize" ]
+}
+
 js_library("mock_actions_model") {
   testonly = true
   deps = [
@@ -211,15 +236,34 @@
   ]
 }
 
-js_unittest("actions_model_unittest") {
+js_library("fake_file_selection_handler.m") {
+  sources = [ "$root_gen_dir/ui/file_manager/file_manager/foreground/js/fake_file_selection_handler.m.js" ]
   deps = [
-    ":actions_model",
-    "metadata:mock_metadata",
-    "//ui/file_manager/base/js:mock_chrome",
-    "//ui/file_manager/base/js:test_error_reporting",
-    "//ui/file_manager/file_manager/background/js:mock_drive_sync_handler",
-    "//ui/file_manager/file_manager/background/js:mock_volume_manager",
-    "//ui/file_manager/file_manager/foreground/js/ui:action_model_ui",
+    ":file_selection.m",
+    "//ui/webui/resources/js/cr:event_target.m",
+  ]
+
+  extra_deps = [ ":modulize" ]
+}
+
+js_unittest("actions_model_unittest.m") {
+  deps = [
+    ":actions_model.m",
+    ":folder_shortcuts_data_model.m",
+    "metadata:mock_metadata.m",
+    "ui:action_model_ui.m",
+    "ui:files_alert_dialog.m",
+    "ui:list_container.m",
+    "//chrome/test/data/webui:chai_assert",
+    "//ui/file_manager/base/js:mock_chrome.m",
+    "//ui/file_manager/base/js:test_error_reporting.m",
+    "//ui/file_manager/base/js:volume_manager_types.m",
+    "//ui/file_manager/file_manager/background/js:mock_drive_sync_handler.m",
+    "//ui/file_manager/file_manager/background/js:mock_volume_manager.m",
+    "//ui/file_manager/file_manager/common/js:metrics.m",
+    "//ui/file_manager/file_manager/common/js:mock_entry.m",
+    "//ui/webui/resources/js:assert.m",
+    "//ui/webui/resources/js/cr:event_target.m",
   ]
 }
 
@@ -301,6 +345,13 @@
   deps = [ ":thumbnail_loader" ]
 }
 
+js_library("mock_thumbnail_loader.m") {
+  sources = [ "$root_gen_dir/ui/file_manager/file_manager/foreground/js/mock_thumbnail_loader.m.js" ]
+  deps = [ ":thumbnail_loader.m" ]
+
+  extra_deps = [ ":modulize" ]
+}
+
 js_library("android_app_list_model") {
   deps = [ "//ui/webui/resources/js/cr:event_target" ]
 }
@@ -673,6 +724,26 @@
   ]
 }
 
+js_library("file_selection.m") {
+  sources = [ "$root_gen_dir/ui/file_manager/file_manager/foreground/js/file_selection.m.js" ]
+  deps = [
+    ":constants.m",
+    ":directory_model.m",
+    "metadata:metadata_model.m",
+    "ui:list_container.m",
+    "//ui/file_manager/base/js:volume_manager_types.m",
+    "//ui/file_manager/externs:volume_manager.m",
+    "//ui/file_manager/externs/background:file_operation_manager.m",
+    "//ui/file_manager/file_manager/common/js:file_type.m",
+    "//ui/file_manager/file_manager/common/js:util.m",
+    "//ui/webui/resources/js:assert.m",
+    "//ui/webui/resources/js:cr.m",
+    "//ui/webui/resources/js/cr:event_target.m",
+  ]
+
+  extra_deps = [ ":modulize" ]
+}
+
 js_library("file_tasks") {
   deps = [
     ":directory_model",
@@ -718,15 +789,66 @@
   externs_list = [ "//ui/file_manager/externs/background/progress_center.js" ]
 }
 
-js_unittest("file_transfer_controller_unittest") {
+js_library("file_transfer_controller.m") {
+  sources = [ "$root_gen_dir/ui/file_manager/file_manager/foreground/js/file_transfer_controller.m.js" ]
   deps = [
-    ":fake_file_selection_handler",
-    ":file_transfer_controller",
-    "//ui/file_manager/base/js:mock_chrome",
-    "//ui/file_manager/file_manager/background/js:mock_volume_manager",
-    "//ui/file_manager/file_manager/foreground/js:mock_directory_model",
-    "//ui/file_manager/file_manager/foreground/js/metadata:mock_metadata",
-    "//ui/webui/resources/js:webui_resource_test",
+    ":directory_model.m",
+    ":drop_effect_and_label.m",
+    ":file_selection.m",
+    ":thumbnail_loader.m",
+    "metadata:metadata_model.m",
+    "metadata:thumbnail_model.m",
+    "ui:directory_tree.m",
+    "ui:drag_selector.m",
+    "ui:list_container.m",
+    "//ui/file_manager/base/js:volume_manager_types.m",
+    "//ui/file_manager/externs:entry_location.m",
+    "//ui/file_manager/externs:files_app_entry_interfaces.m",
+    "//ui/file_manager/externs:volume_info.m",
+    "//ui/file_manager/externs:volume_manager.m",
+    "//ui/file_manager/externs/background:file_operation_manager.m",
+    "//ui/file_manager/externs/background:progress_center.m",
+    "//ui/file_manager/file_manager/common/js:file_type.m",
+    "//ui/file_manager/file_manager/common/js:progress_center_common.m",
+    "//ui/file_manager/file_manager/common/js:util.m",
+    "//ui/webui/resources/js:assert.m",
+    "//ui/webui/resources/js:util.m",
+    "//ui/webui/resources/js/cr/ui:command.m",
+    "//ui/webui/resources/js/cr/ui:list.m",
+    "//ui/webui/resources/js/cr/ui:tree.m",
+  ]
+
+  extra_deps = [ ":modulize" ]
+}
+
+js_unittest("file_transfer_controller_unittest.m") {
+  deps = [
+    ":dialog_type.m",
+    ":fake_file_selection_handler.m",
+    ":file_list_model.m",
+    ":file_selection.m",
+    ":file_transfer_controller.m",
+    ":mock_directory_model.m",
+    "metadata:mock_metadata.m",
+    "metadata:thumbnail_model.m",
+    "ui:a11y_announce.m",
+    "ui:directory_tree.m",
+    "ui:file_grid.m",
+    "ui:file_table.m",
+    "ui:list_container.m",
+    "//chrome/test/data/webui:chai_assert",
+    "//ui/file_manager/base/js:mock_chrome.m",
+    "//ui/file_manager/base/js:volume_manager_types.m",
+    "//ui/file_manager/externs:volume_manager.m",
+    "//ui/file_manager/externs/background:file_operation_manager.m",
+    "//ui/file_manager/externs/background:import_history.m",
+    "//ui/file_manager/externs/background:progress_center.m",
+    "//ui/file_manager/file_manager/background/js:mock_volume_manager.m",
+    "//ui/file_manager/file_manager/common/js:mock_entry.m",
+    "//ui/webui/resources/js:util.m",
+    "//ui/webui/resources/js/cr:ui.m",
+    "//ui/webui/resources/js/cr/ui:command.m",
+    "//ui/webui/resources/js/cr/ui:list_selection_model.m",
   ]
 }
 
@@ -826,6 +948,17 @@
   ]
 }
 
+js_library("holding_space_util.m") {
+  sources = [ "$root_gen_dir/ui/file_manager/file_manager/foreground/js/holding_space_util.m.js" ]
+  deps = [
+    "//ui/file_manager/base/js:volume_manager_types.m",
+    "//ui/file_manager/file_manager/common/js:metrics.m",
+    "//ui/webui/resources/js:load_time_data.m",
+  ]
+
+  extra_deps = [ ":modulize" ]
+}
+
 js_library("import_controller") {
   deps = [
     ":actions_controller",
@@ -894,12 +1027,38 @@
   ]
 }
 
-js_unittest("list_thumbnail_loader_unittest") {
+js_library("list_thumbnail_loader.m") {
+  sources = [ "$root_gen_dir/ui/file_manager/file_manager/foreground/js/list_thumbnail_loader.m.js" ]
   deps = [
-    ":list_thumbnail_loader",
-    ":mock_thumbnail_loader",
-    "//ui/file_manager/base/js:test_error_reporting",
-    "//ui/file_manager/file_manager/common/js:mock_entry",
+    ":directory_model.m",
+    ":file_list_model.m",
+    ":thumbnail_loader.m",
+    "metadata:thumbnail_model.m",
+    "//ui/file_manager/base/js:volume_manager_types.m",
+    "//ui/file_manager/externs:volume_manager.m",
+    "//ui/file_manager/file_manager/common/js:lru_cache.m",
+    "//ui/webui/resources/js:assert.m",
+    "//ui/webui/resources/js/cr:event_target.m",
+  ]
+
+  extra_deps = [ ":modulize" ]
+}
+
+js_unittest("list_thumbnail_loader_unittest.m") {
+  deps = [
+    ":directory_model.m",
+    ":file_list_model.m",
+    ":list_thumbnail_loader.m",
+    ":mock_thumbnail_loader.m",
+    "metadata:metadata_model.m",
+    "metadata:thumbnail_model.m",
+    "//chrome/test/data/webui:chai_assert",
+    "//ui/file_manager/base/js:test_error_reporting.m",
+    "//ui/file_manager/base/js:volume_manager_types.m",
+    "//ui/file_manager/externs:volume_manager.m",
+    "//ui/file_manager/file_manager/common/js:mock_entry.m",
+    "//ui/webui/resources/js:assert.m",
+    "//ui/webui/resources/js/cr:event_target.m",
   ]
 }
 
@@ -1022,6 +1181,17 @@
   deps = [ "//ui/file_manager/base/js:volume_manager_types" ]
 }
 
+js_library("path_component.m") {
+  sources = [ "$root_gen_dir/ui/file_manager/file_manager/foreground/js/path_component.m.js" ]
+  deps = [
+    "//ui/file_manager/base/js:volume_manager_types.m",
+    "//ui/file_manager/externs:files_app_entry_interfaces.m",
+    "//ui/file_manager/file_manager/common/js:util.m",
+  ]
+
+  extra_deps = [ ":modulize" ]
+}
+
 js_library("providers_model") {
   deps = [ "//ui/webui/resources/js:assert" ]
 }
@@ -1227,9 +1397,12 @@
 
 js_test_gen_html("js_test_gen_html_modules") {
   deps = [
+    ":actions_model_unittest.m",
     ":directory_contents_unittest.m",
     ":file_list_model_unittest.m",
+    ":file_transfer_controller_unittest.m",
     ":file_type_filters_controller_unittest.m",
+    ":list_thumbnail_loader_unittest.m",
     ":navigation_list_model_unittest.m",
     ":providers_model_unittest.m",
     ":spinner_controller_unittest.m",
@@ -1241,6 +1414,7 @@
       strict_error_checking_closure_args + [
         "js_module_root=./gen/ui",
         "js_module_root=../../ui",
+        "jscomp_off=duplicate",
         "browser_resolver_prefix_replacements=\"chrome://test/=./\"",
         "hide_warnings_for=third_party/",
       ]
@@ -1248,12 +1422,9 @@
 
 js_test_gen_html("js_test_gen_html") {
   deps = [
-    ":actions_model_unittest",
     ":file_manager_commands_unittest",
     ":file_tasks_unittest",
-    ":file_transfer_controller_unittest",
     ":import_controller_unittest",
-    ":list_thumbnail_loader_unittest",
     ":task_controller_unittest",
   ]
   mocks = [ "$externs_path/file_manager_private.js" ]
@@ -1261,6 +1432,7 @@
 
 js_modulizer("modulize") {
   input_files = [
+    "actions_model.js",
     "android_app_list_model.js",
     "constants.js",
     "crossover_search_utils.js",
@@ -1271,14 +1443,21 @@
     "drop_effect_and_label.js",
     "empty_folder_controller.js",
     "fake_android_app_list_model.js",
+    "fake_file_selection_handler.js",
     "file_list_model.js",
+    "file_selection.js",
+    "file_transfer_controller.js",
     "file_watcher.js",
     "folder_shortcuts_data_model.js",
+    "holding_space_util.js",
     "launch_param.js",
+    "list_thumbnail_loader.js",
     "mock_directory_model.js",
     "mock_folder_shortcut_data_model.js",
     "mock_navigation_list_model.js",
+    "mock_thumbnail_loader.js",
     "navigation_list_model.js",
+    "path_component.js",
     "providers_model.js",
     "thumbnail_loader.js",
     "web_store_utils.js",
@@ -1289,5 +1468,6 @@
     "spinner_controller.js",
   ]
 
-  namespace_rewrites = cr_namespace_rewrites
+  namespace_rewrites =
+      cr_namespace_rewrites + [ "cr.ui.FilesMenuItem|FilesMenuItem" ]
 }
diff --git a/ui/file_manager/file_manager/foreground/js/actions_model.js b/ui/file_manager/file_manager/foreground/js/actions_model.js
index b829f94..1e7c957 100644
--- a/ui/file_manager/file_manager/foreground/js/actions_model.js
+++ b/ui/file_manager/file_manager/foreground/js/actions_model.js
@@ -2,11 +2,25 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// clang-format off
+// #import {ActionModelUI} from './ui/action_model_ui.m.js';
+// #import {FolderShortcutsDataModel} from './folder_shortcuts_data_model.m.js';
+// #import {DriveSyncHandler} from '../../../externs/background/drive_sync_handler.m.js';
+// #import {VolumeManager} from '../../../externs/volume_manager.m.js';
+// #import {MetadataModel} from './metadata/metadata_model.m.js';
+// #import {VolumeManagerCommon} from '../../../base/js/volume_manager_types.m.js';
+// #import {util, str, strf} from '../../common/js/util.m.js';
+// #import {NativeEventTarget as EventTarget} from 'chrome://resources/js/cr/event_target.m.js';
+// #import {metrics} from '../../common/js/metrics.m.js';
+// #import {dispatchSimpleEvent} from 'chrome://resources/js/cr.m.js';
+// #import {assert} from 'chrome://resources/js/assert.m.js';
+// clang-format on
+
 /**
  * A single action, that can be taken on a set of entries.
  * @interface
  */
-class Action {
+/* #export */ class Action {
   /**
    * Executes this action on the set of entries.
    */
@@ -643,7 +657,7 @@
  * Represents a set of actions for a set of entries. Includes actions set
  * locally in JS, as well as those retrieved from the FSP API.
  */
-class ActionsModel extends cr.EventTarget {
+/* #export */ class ActionsModel extends cr.EventTarget {
   /**
    * @param {!VolumeManager} volumeManager
    * @param {!MetadataModel} metadataModel
diff --git a/ui/file_manager/file_manager/foreground/js/actions_model_unittest.js b/ui/file_manager/file_manager/foreground/js/actions_model_unittest.m.js
similarity index 91%
rename from ui/file_manager/file_manager/foreground/js/actions_model_unittest.js
rename to ui/file_manager/file_manager/foreground/js/actions_model_unittest.m.js
index dde8567..10cfd8f 100644
--- a/ui/file_manager/file_manager/foreground/js/actions_model_unittest.js
+++ b/ui/file_manager/file_manager/foreground/js/actions_model_unittest.m.js
@@ -2,7 +2,22 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-'use strict';
+import {assert} from 'chrome://resources/js/assert.m.js';
+import {NativeEventTarget as EventTarget} from 'chrome://resources/js/cr/event_target.m.js';
+import {assertEquals, assertFalse, assertTrue} from 'chrome://test/chai_assert.js';
+import {installMockChrome, MockCommandLinePrivate} from '../../../base/js/mock_chrome.m.js';
+import {reportPromise} from '../../../base/js/test_error_reporting.m.js';
+import {VolumeManagerCommon} from '../../../base/js/volume_manager_types.m.js';
+import {MockDriveSyncHandler} from '../../background/js/mock_drive_sync_handler.m.js';
+import {MockVolumeManager} from '../../background/js/mock_volume_manager.m.js';
+import {metrics} from '../../common/js/metrics.m.js';
+import {MockDirectoryEntry, MockFileEntry} from '../../common/js/mock_entry.m.js';
+import {ActionsModel} from './actions_model.m.js';
+import {FolderShortcutsDataModel} from './folder_shortcuts_data_model.m.js';
+import {MockMetadataModel} from './metadata/mock_metadata.m.js';
+import {ActionModelUI} from './ui/action_model_ui.m.js';
+import {FilesAlertDialog} from './ui/files_alert_dialog.m.js';
+import {ListContainer} from './ui/list_container.m.js';
 
 /**
  * @type {!MockVolumeManager}
@@ -29,7 +44,7 @@
  * @returns {!FolderShortcutsDataModel}
  */
 function createFakeFolderShortcutsDataModel() {
-  class FakeFolderShortcutsModel extends cr.EventTarget {
+  class FakeFolderShortcutsModel extends EventTarget {
     constructor() {
       super();
       this.has = false;
@@ -79,7 +94,7 @@
  */
 let ui;
 
-function setUp() {
+export function setUp() {
   // Mock loadTimeData strings.
   window.loadTimeData.getString = id => id;
   window.loadTimeData.data = {};
@@ -109,15 +124,18 @@
   installMockChrome(mockChrome);
   new MockCommandLinePrivate();
 
-  // Mock metrics.
-  window.metrics = {
-    calls: {
-      DrivePinSuccess: 0,
-      DriveHostedFilePinSuccess: 0,
-    },
-    recordBoolean: function(name) {
-      window.metrics.calls[name]++;
-    },
+  /**
+   * Mock metrics.recordBoolean.
+   * @param {string} name Short metric name.
+   * @param {boolean} value The value to be recorded.
+   */
+  metrics.recordBoolean = (name, value) => {
+    metrics.calls[name]++;
+  };
+
+  metrics.calls = {
+    DrivePinSuccess: 0,
+    DriveHostedFilePinSuccess: 0,
   };
 
   // Setup Drive file system.
@@ -141,7 +159,7 @@
 /**
  * Tests that the correct actions are available for a Google Drive directory.
  */
-function testDriveDirectoryEntry(callback) {
+export function testDriveDirectoryEntry(callback) {
   driveFileSystem.entries['/test'] =
       MockDirectoryEntry.create(driveFileSystem, '/test');
 
@@ -222,7 +240,7 @@
 /**
  * Tests that the correct actions are available for a Google Drive file.
  */
-function testDriveFileEntry(callback) {
+export function testDriveFileEntry(callback) {
   driveFileSystem.entries['/test.txt'] =
       MockFileEntry.create(driveFileSystem, '/test.txt');
 
@@ -276,8 +294,8 @@
             assertTrue(metadataModel.properties.pinned);
             assertEquals(1, invalidated);
 
-            assertEquals(1, window.metrics.calls['DrivePinSuccess']);
-            assertEquals(0, window.metrics.calls['DriveHostedFilePinSuccess']);
+            assertEquals(1, metrics.calls['DrivePinSuccess']);
+            assertEquals(0, metrics.calls['DriveHostedFilePinSuccess']);
 
             // The model is invalidated, as list of actions have changed.
             // Recreated the model and check that the actions are updated.
@@ -328,7 +346,7 @@
 /**
  * Tests that the correct actions are available for a Google Drive hosted file.
  */
-function testDriveHostedFileEntry(callback) {
+export function testDriveHostedFileEntry(callback) {
   const testDocument = MockFileEntry.create(driveFileSystem, '/test.gdoc');
   const testFile = MockFileEntry.create(driveFileSystem, '/test.txt');
   driveFileSystem.entries['/test.gdoc'] = testDocument;
@@ -396,8 +414,8 @@
             assertTrue(!!metadataModel.getCache([testDocument])[0].pinned);
             assertTrue(!!metadataModel.getCache([testFile])[0].pinned);
 
-            assertEquals(2, window.metrics.calls['DrivePinSuccess']);
-            assertEquals(1, window.metrics.calls['DriveHostedFilePinSuccess']);
+            assertEquals(2, metrics.calls['DrivePinSuccess']);
+            assertEquals(1, metrics.calls['DriveHostedFilePinSuccess']);
 
             model = new ActionsModel(
                 volumeManager, metadataModel, shortcutsModel, driveSyncHandler,
@@ -437,7 +455,7 @@
  * Tests that the correct actions are available for a Google Drive hosted file
  * when the user does not have the required extension installed.
  */
-function testDriveHostedFileEntryWithoutExtension(callback) {
+export function testDriveHostedFileEntryWithoutExtension(callback) {
   const testDocument = MockFileEntry.create(driveFileSystem, '/test.gdoc');
   const testFile = MockFileEntry.create(driveFileSystem, '/test.txt');
   driveFileSystem.entries['/test.gdoc'] = testDocument;
@@ -506,8 +524,8 @@
             assertFalse(!!metadataModel.getCache([testDocument])[0].pinned);
             assertTrue(!!metadataModel.getCache([testFile])[0].pinned);
 
-            assertEquals(1, window.metrics.calls['DrivePinSuccess']);
-            assertEquals(0, window.metrics.calls['DriveHostedFilePinSuccess']);
+            assertEquals(1, metrics.calls['DrivePinSuccess']);
+            assertEquals(0, metrics.calls['DriveHostedFilePinSuccess']);
 
             model = new ActionsModel(
                 volumeManager, metadataModel, shortcutsModel, driveSyncHandler,
@@ -548,7 +566,7 @@
 /**
  * Tests that a Team Drive Root entry has the correct actions available.
  */
-function testTeamDriveRootEntry(callback) {
+export function testTeamDriveRootEntry(callback) {
   driveFileSystem.entries['/team_drives/ABC Team'] =
       MockDirectoryEntry.create(driveFileSystem, '/team_drives/ABC Team');
 
@@ -583,7 +601,7 @@
 /**
  * Tests that a Team Drive directory entry has the correct actions available.
  */
-function testTeamDriveDirectoryEntry(callback) {
+export function testTeamDriveDirectoryEntry(callback) {
   driveFileSystem.entries['/team_drives/ABC Team/Folder 1'] =
       MockDirectoryEntry.create(
           driveFileSystem, '/team_drives/ABC Team/Folder 1');
@@ -636,7 +654,7 @@
 /**
  * Tests that a Team Drive file entry has the correct actions available.
  */
-function testTeamDriveFileEntry(callback) {
+export function testTeamDriveFileEntry(callback) {
   driveFileSystem.entries['/team_drives/ABC Team/Folder 1/test.txt'] =
       MockFileEntry.create(
           driveFileSystem, '/team_drives/ABC Team/Folder 1/test.txt');
@@ -679,7 +697,7 @@
  * Tests that if actions are provided with getCustomActions(), they appear
  * correctly for the file.
  */
-function testProvidedEntry(callback) {
+export function testProvidedEntry(callback) {
   providedFileSystem.entries['/test'] =
       MockDirectoryEntry.create(providedFileSystem, '/test');
 
@@ -758,7 +776,7 @@
 /**
  * Tests that no actions are available when getCustomActions() throws an error.
  */
-function testProvidedEntryWithError(callback) {
+export function testProvidedEntryWithError(callback) {
   providedFileSystem.entries['/test'] =
       MockDirectoryEntry.create(providedFileSystem, '/test');
 
diff --git a/ui/file_manager/file_manager/foreground/js/fake_file_selection_handler.js b/ui/file_manager/file_manager/foreground/js/fake_file_selection_handler.js
index 78f4700..4d1b583 100644
--- a/ui/file_manager/file_manager/foreground/js/fake_file_selection_handler.js
+++ b/ui/file_manager/file_manager/foreground/js/fake_file_selection_handler.js
@@ -2,11 +2,16 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// clang-format off
+// #import {FileSelectionHandler, FileSelection} from './file_selection.m.js';
+// #import {NativeEventTarget as EventTarget} from 'chrome://resources/js/cr/event_target.m.js';
+// clang-format on
+
 /**
  * Mock FileSelectionHandler.
  * @extends {FileSelectionHandler}
  */
-class FakeFileSelectionHandler {
+/* #export */ class FakeFileSelectionHandler {
   constructor() {
     this.selection = /** @type {!FileSelection} */ ({});
     this.updateSelection([], []);
diff --git a/ui/file_manager/file_manager/foreground/js/file_selection.js b/ui/file_manager/file_manager/foreground/js/file_selection.js
index 5bba28a..624e5f18 100644
--- a/ui/file_manager/file_manager/foreground/js/file_selection.js
+++ b/ui/file_manager/file_manager/foreground/js/file_selection.js
@@ -2,10 +2,25 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// clang-format off
+// #import {MetadataModel} from './metadata/metadata_model.m.js';
+// #import {ListContainer} from './ui/list_container.m.js';
+// #import {FileOperationManager} from '../../../externs/background/file_operation_manager.m.js';
+// #import {DirectoryModel} from './directory_model.m.js';
+// #import {VolumeManager} from '../../../externs/volume_manager.m.js';
+// #import {AllowedPaths} from '../../../base/js/volume_manager_types.m.js';
+// #import {util} from '../../common/js/util.m.js';
+// #import {constants} from './constants.m.js';
+// #import {FileType} from '../../common/js/file_type.m.js';
+// #import {NativeEventTarget as EventTarget} from 'chrome://resources/js/cr/event_target.m.js';
+// #import {dispatchSimpleEvent} from 'chrome://resources/js/cr.m.js';
+// #import {assert} from 'chrome://resources/js/assert.m.js';
+// clang-format on
+
 /**
  * The current selection object.
  */
-class FileSelection {
+/* #export */ class FileSelection {
   /**
    * @param {!Array<number>} indexes
    * @param {!Array<Entry>} entries
@@ -131,7 +146,7 @@
 /**
  * This object encapsulates everything related to current selection.
  */
-class FileSelectionHandler extends cr.EventTarget {
+/* #export */ class FileSelectionHandler extends cr.EventTarget {
   /**
    * @param {!DirectoryModel} directoryModel
    * @param {!FileOperationManager} fileOperationManager
diff --git a/ui/file_manager/file_manager/foreground/js/file_transfer_controller.js b/ui/file_manager/file_manager/foreground/js/file_transfer_controller.js
index dffe64e..5960d61 100644
--- a/ui/file_manager/file_manager/foreground/js/file_transfer_controller.js
+++ b/ui/file_manager/file_manager/foreground/js/file_transfer_controller.js
@@ -2,6 +2,33 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// clang-format off
+// #import {EntryLocation} from '../../../externs/entry_location.m.js';
+// #import {VolumeInfo} from '../../../externs/volume_info.m.js';
+// #import {List} from 'chrome://resources/js/cr/ui/list.m.js';
+// #import {FilesAppDirEntry, FakeEntry, FilesAppEntry} from '../../../externs/files_app_entry_interfaces.m.js';
+// #import {Command} from 'chrome://resources/js/cr/ui/command.m.js';
+// #import {VolumeManager} from '../../../externs/volume_manager.m.js';
+// #import {DirectoryModel} from './directory_model.m.js';
+// #import {ThumbnailModel} from './metadata/thumbnail_model.m.js';
+// #import {MetadataModel} from './metadata/metadata_model.m.js';
+// #import {FileOperationManager} from '../../../externs/background/file_operation_manager.m.js';
+// #import {ProgressCenter} from '../../../externs/background/progress_center.m.js';
+// #import {ListContainer} from './ui/list_container.m.js';
+// #import {DropEffectAndLabel, DropEffectType} from './drop_effect_and_label.m.js';
+// #import {FileSelectionHandler} from './file_selection.m.js';
+// #import {DragSelector} from './ui/drag_selector.m.js';
+// #import {assert, assertNotReached} from 'chrome://resources/js/assert.m.js';
+// #import {VolumeManagerCommon} from '../../../base/js/volume_manager_types.m.js';
+// #import {DirectoryItem, DirectoryTree} from './ui/directory_tree.m.js';
+// #import {TreeItem} from 'chrome://resources/js/cr/ui/tree.m.js';
+// #import {ThumbnailLoader} from './thumbnail_loader.m.js';
+// #import {ProgressCenterItem, ProgressItemType, ProgressItemState} from '../../common/js/progress_center_common.m.js';
+// #import {FileType} from '../../common/js/file_type.m.js';
+// #import {util, strf} from '../../common/js/util.m.js';
+// #import {queryRequiredElement} from 'chrome://resources/js/util.m.js';
+// clang-format on
+
 /**
  * Global (placed in the window object) variable name to hold internal
  * file dragging information. Needed to show visual feedback while dragging
@@ -15,7 +42,7 @@
  */
 let FileAsyncData;
 
-class FileTransferController {
+/* #export */ class FileTransferController {
   /**
    * @param {!Document} doc Owning document.
    * @param {!ListContainer} listContainer List container.
diff --git a/ui/file_manager/file_manager/foreground/js/file_transfer_controller_unittest.js b/ui/file_manager/file_manager/foreground/js/file_transfer_controller_unittest.m.js
similarity index 81%
rename from ui/file_manager/file_manager/foreground/js/file_transfer_controller_unittest.js
rename to ui/file_manager/file_manager/foreground/js/file_transfer_controller_unittest.m.js
index aeea8957..0013048 100644
--- a/ui/file_manager/file_manager/foreground/js/file_transfer_controller_unittest.js
+++ b/ui/file_manager/file_manager/foreground/js/file_transfer_controller_unittest.m.js
@@ -2,6 +2,33 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import {decorate} from 'chrome://resources/js/cr/ui.m.js';
+import {Command} from 'chrome://resources/js/cr/ui/command.m.js';
+import {ListSelectionModel} from 'chrome://resources/js/cr/ui/list_selection_model.m.js';
+import {queryRequiredElement} from 'chrome://resources/js/util.m.js';
+import {assertEquals, assertFalse, assertTrue} from 'chrome://test/chai_assert.js';
+import {installMockChrome} from '../../../base/js/mock_chrome.m.js';
+import {VolumeManagerCommon} from '../../../base/js/volume_manager_types.m.js';
+import {FileOperationManager} from '../../../externs/background/file_operation_manager.m.js';
+import {importerHistoryInterfaces} from '../../../externs/background/import_history.m.js';
+import {ProgressCenter} from '../../../externs/background/progress_center.m.js';
+import {VolumeManager} from '../../../externs/volume_manager.m.js';
+import {MockVolumeManager} from '../../background/js/mock_volume_manager.m.js';
+import {MockDirectoryEntry, MockFileEntry, MockFileSystem} from '../../common/js/mock_entry.m.js';
+import {DialogType} from './dialog_type.m.js';
+import {FakeFileSelectionHandler} from './fake_file_selection_handler.m.js';
+import {FileListModel} from './file_list_model.m.js';
+import {FileSelectionHandler} from './file_selection.m.js';
+import {FileTransferController} from './file_transfer_controller.m.js';
+import {MockMetadataModel} from './metadata/mock_metadata.m.js';
+import {ThumbnailModel} from './metadata/thumbnail_model.m.js';
+import {createFakeDirectoryModel} from './mock_directory_model.m.js';
+import {A11yAnnounce} from './ui/a11y_announce.m.js';
+import {DirectoryTree} from './ui/directory_tree.m.js';
+import {FileGrid} from './ui/file_grid.m.js';
+import {FileTable} from './ui/file_table.m.js';
+import {ListContainer} from './ui/list_container.m.js';
+
 /** @type {!ListContainer} */
 let listContainer;
 
@@ -22,7 +49,7 @@
  */
 let mockChrome;
 
-function setUp() {
+export function setUp() {
   // Setup page DOM.
   document.body.innerHTML = [
     '<style>',
@@ -74,8 +101,8 @@
   };
   installMockChrome(mockChrome);
 
-  // Initialize cr.ui.Command with the <command>s.
-  cr.ui.decorate('command', cr.ui.Command);
+  // Initialize Command with the <command>s.
+  decorate('command', Command);
 
   // Fake confirmation callback.
   const confirmationDialog = (isMove, messages) => Promise.resolve(true);
@@ -126,7 +153,6 @@
   FileTable.decorate(
       table, metadataModel, volumeManager, historyLoader, a11y,
       true /* fullPage */);
-  table.list = document.querySelector('#file-list');
   const dataModel = new FileListModel(metadataModel);
   table.list.dataModel = dataModel;
 
@@ -139,7 +165,7 @@
       queryRequiredElement('#list-container'), table, grid,
       DialogType.FULL_PAGE);
   listContainer.dataModel = dataModel;
-  listContainer.selectionModel = new cr.ui.ListSelectionModel();
+  listContainer.selectionModel = new ListSelectionModel();
   listContainer.setCurrentListType(ListContainer.ListType.DETAIL);
 
   // Setup DirectoryTree elements.
@@ -168,7 +194,7 @@
  * @suppress {accessControls} To be able to access private method
  * isDocumentWideEvent_
  */
-function testIsDocumentWideEvent() {
+export function testIsDocumentWideEvent() {
   const input = document.querySelector('#free-text');
   const crInput = document.querySelector('#test-input');
   const button = document.querySelector('#button');
@@ -207,7 +233,7 @@
 /**
  * Tests canCutOrDrag() respects non-modifiable entries like Downloads.
  */
-function testCanMoveDownloads() {
+export function testCanMoveDownloads() {
   // Item 1 of the volume info list should be Downloads volume type.
   assertEquals(
       VolumeManagerCommon.VolumeType.DOWNLOADS,
@@ -243,7 +269,7 @@
 /**
  * Tests preparePaste() with FilesApp fs/sources and standard DataTransfer.
  */
-async function testPreparePaste(done) {
+export async function testPreparePaste(done) {
   const myFilesVolume = volumeManager.volumeInfoList.item(1);
   const myFilesMockFs =
       /** @type {!MockFileSystem} */ (myFilesVolume.fileSystem);
diff --git a/ui/file_manager/file_manager/foreground/js/holding_space_util.js b/ui/file_manager/file_manager/foreground/js/holding_space_util.js
index 97b6276..19c53d7 100644
--- a/ui/file_manager/file_manager/foreground/js/holding_space_util.js
+++ b/ui/file_manager/file_manager/foreground/js/holding_space_util.js
@@ -6,7 +6,13 @@
  * @fileoverview Utility methods for the holding space feature.
  */
 
-class HoldingSpaceUtil {
+// clang-format off
+// #import {VolumeManagerCommon} from '../../../base/js/volume_manager_types.m.js';
+// #import {metrics} from '../../common/js/metrics.m.js';
+// #import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
+// clang-format on
+
+/* #export */ class HoldingSpaceUtil {
   /**
    * Returns the key in localStorage to store the time (in milliseconds) of the
    * first pin to holding space.
diff --git a/ui/file_manager/file_manager/foreground/js/list_thumbnail_loader.js b/ui/file_manager/file_manager/foreground/js/list_thumbnail_loader.js
index 76b9e40..945031b7 100644
--- a/ui/file_manager/file_manager/foreground/js/list_thumbnail_loader.js
+++ b/ui/file_manager/file_manager/foreground/js/list_thumbnail_loader.js
@@ -2,6 +2,18 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// clang-format off
+// #import {FileListModel} from './file_list_model.m.js';
+// #import {VolumeManager} from '../../../externs/volume_manager.m.js';
+// #import {ThumbnailModel} from './metadata/thumbnail_model.m.js';
+// #import {DirectoryModel} from './directory_model.m.js';
+// #import {VolumeManagerCommon} from '../../../base/js/volume_manager_types.m.js';
+// #import {LRUCache} from '../../common/js/lru_cache.m.js';
+// #import {ThumbnailLoader} from './thumbnail_loader.m.js';
+// #import {assert} from 'chrome://resources/js/assert.m.js';
+// #import {NativeEventTarget as EventTarget} from 'chrome://resources/js/cr/event_target.m.js';
+// clang-format on
+
 /**
  * A thumbnail loader for list style UI.
  *
@@ -10,7 +22,7 @@
  * is responsible to return dataUrls of thumbnails and fetch them with proper
  * priority.
  */
-class ListThumbnailLoader extends cr.EventTarget {
+/* #export */ class ListThumbnailLoader extends cr.EventTarget {
   /**
    * @param {!DirectoryModel} directoryModel A directory model.
    * @param {!ThumbnailModel} thumbnailModel Thumbnail metadata model.
diff --git a/ui/file_manager/file_manager/foreground/js/list_thumbnail_loader_unittest.js b/ui/file_manager/file_manager/foreground/js/list_thumbnail_loader_unittest.m.js
similarity index 90%
rename from ui/file_manager/file_manager/foreground/js/list_thumbnail_loader_unittest.js
rename to ui/file_manager/file_manager/foreground/js/list_thumbnail_loader_unittest.m.js
index 52f46ac..4a9b8d2 100644
--- a/ui/file_manager/file_manager/foreground/js/list_thumbnail_loader_unittest.js
+++ b/ui/file_manager/file_manager/foreground/js/list_thumbnail_loader_unittest.m.js
@@ -2,6 +2,20 @@
 // 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 {NativeEventTarget as EventTarget} from 'chrome://resources/js/cr/event_target.m.js';
+import {assertEquals, assertFalse, assertTrue} from 'chrome://test/chai_assert.js';
+import {reportPromise, waitUntil} from '../../../base/js/test_error_reporting.m.js';
+import {VolumeManagerCommon} from '../../../base/js/volume_manager_types.m.js';
+import {VolumeManager} from '../../../externs/volume_manager.m.js';
+import {MockDirectoryEntry, MockEntry, MockFileSystem} from '../../common/js/mock_entry.m.js';
+import {DirectoryModel} from './directory_model.m.js';
+import {FileListModel} from './file_list_model.m.js';
+import {ListThumbnailLoader} from './list_thumbnail_loader.m.js';
+import {MetadataModel} from './metadata/metadata_model.m.js';
+import {ThumbnailModel} from './metadata/thumbnail_model.m.js';
+import {MockThumbnailLoader} from './mock_thumbnail_loader.m.js';
+
 /** @type {string} */
 let currentVolumeType;
 
@@ -48,7 +62,7 @@
 /** @type {!MockEntry} */
 const entry6 = new MockEntry(fileSystem, '/Test6.jpg');
 
-function setUp() {
+export function setUp() {
   currentVolumeType = ListThumbnailLoader.TEST_VOLUME_TYPE;
   /** @suppress {const} */
   ListThumbnailLoader.CACHE_SIZE = 5;
@@ -97,7 +111,7 @@
 
   isScanningForTest = false;
 
-  class TestDirectoryModel extends cr.EventTarget {
+  class TestDirectoryModel extends EventTarget {
     getFileList() {
       return fileListModel;
     }
@@ -157,7 +171,7 @@
 /**
  * Story test for list thumbnail loader.
  */
-function testStory(callback) {
+export function testStory(callback) {
   fileListModel.push(directory1, entry1, entry2, entry3, entry4, entry5);
 
   // Set high priority range to 0 - 2.
@@ -227,7 +241,7 @@
 /**
  * Do not enqueue prefetch task when high priority range is at the end of list.
  */
-function testRangeIsAtTheEndOfList() {
+export function testRangeIsAtTheEndOfList() {
   // Set high priority range to 5 - 6.
   listThumbnailLoader.setHighPriorityRange(5, 6);
 
@@ -238,7 +252,7 @@
   assertEquals(1, Object.keys(getCallbacks).length);
 }
 
-function testCache(callback) {
+export function testCache(callback) {
   ListThumbnailLoader.numOfMaxActiveTasksForTest = 5;
 
   // Set high priority range to 0 - 2.
@@ -301,7 +315,7 @@
  * Test case for thumbnail fetch error. In this test case, thumbnail fetch for
  * entry 2 is failed.
  */
-function testErrorHandling(callback) {
+export function testErrorHandling(callback) {
   MockThumbnailLoader.errorUrls = [entry2.toURL()];
 
   listThumbnailLoader.setHighPriorityRange(0, 2);
@@ -320,7 +334,7 @@
 /**
  * Test case for handling sorted event in data model.
  */
-function testSortedEvent(callback) {
+export function testSortedEvent(callback) {
   listThumbnailLoader.setHighPriorityRange(0, 2);
   fileListModel.push(directory1, entry1, entry2, entry3, entry4, entry5);
 
@@ -349,7 +363,7 @@
 /**
  * Test case for handling change event in data model.
  */
-function testChangeEvent(callback) {
+export function testChangeEvent(callback) {
   listThumbnailLoader.setHighPriorityRange(0, 2);
   fileListModel.push(directory1, entry1, entry2, entry3);
 
@@ -383,7 +397,7 @@
 /**
  * Test case for MTP volume.
  */
-function testMTPVolume() {
+export function testMTPVolume() {
   currentVolumeType = VolumeManagerCommon.VolumeType.MTP;
 
   listThumbnailLoader.setHighPriorityRange(0, 2);
@@ -396,7 +410,7 @@
 /**
  * Test case that directory scan is running.
  */
-function testDirectoryScanIsRunning() {
+export function testDirectoryScanIsRunning() {
   // Items are added during directory scan.
   isScanningForTest = true;
 
@@ -415,7 +429,7 @@
 /**
  * Test case for EXIF IO error and retrying logic.
  */
-function testExifIOError(callback) {
+export function testExifIOError(callback) {
   const task = new ListThumbnailLoader.Task(
       entry1,
       /** @type {!VolumeManager} */ ({
diff --git a/ui/file_manager/file_manager/foreground/js/mock_thumbnail_loader.js b/ui/file_manager/file_manager/foreground/js/mock_thumbnail_loader.js
index b7f76ec..cc664b69 100644
--- a/ui/file_manager/file_manager/foreground/js/mock_thumbnail_loader.js
+++ b/ui/file_manager/file_manager/foreground/js/mock_thumbnail_loader.js
@@ -2,10 +2,12 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// #import {ThumbnailLoader} from './thumbnail_loader.m.js';
+
 /**
  * Mock thumbnail loader.
  */
-class MockThumbnailLoader {
+/* #export */ class MockThumbnailLoader {
   /**
    * @param {Entry} entry An entry.
    * @param {ThumbnailLoader.LoaderType=} opt_loaderType Loader type.
diff --git a/ui/file_manager/file_manager/foreground/js/path_component.js b/ui/file_manager/file_manager/foreground/js/path_component.js
index b5002b5..02a49d9 100644
--- a/ui/file_manager/file_manager/foreground/js/path_component.js
+++ b/ui/file_manager/file_manager/foreground/js/path_component.js
@@ -2,6 +2,12 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// clang-format off
+// #import {FilesAppEntry, FakeEntry} from '../../../externs/files_app_entry_interfaces.m.js';
+// #import {VolumeManagerCommon} from '../../../base/js/volume_manager_types.m.js';
+// #import {util, str} from '../../common/js/util.m.js';
+// clang-format on
+
 /**
  * File path component.
  *
@@ -11,7 +17,7 @@
  * PathComponent.computeComponentsFromEntry computes an array of PathComponent
  * of the given entry.
  */
-class PathComponent {
+/* #export */ class PathComponent {
   /**
    * @param {string} name Name.
    * @param {string} url Url.
@@ -161,4 +167,4 @@
 
     return components;
   }
-}
\ No newline at end of file
+}
diff --git a/ui/file_manager/file_manager/foreground/js/ui/BUILD.gn b/ui/file_manager/file_manager/foreground/js/ui/BUILD.gn
index f69daea..bcbba01 100644
--- a/ui/file_manager/file_manager/foreground/js/ui/BUILD.gn
+++ b/ui/file_manager/file_manager/foreground/js/ui/BUILD.gn
@@ -24,16 +24,28 @@
 js_type_check("closure_compile_jsmodules") {
   uses_js_modules = true
   deps = [
+    ":a11y_announce.m",
+    ":action_model_ui.m",
+    ":combobutton.m",
+    ":commandbutton.m",
+    ":default_task_dialog.m",
     ":directory_tree.m",
     ":drag_selector.m",
     ":empty_folder.m",
+    ":file_grid.m",
     ":file_list_selection_model.m",
     ":file_manager_dialog_base.m",
+    ":file_metadata_formatter.m",
+    ":file_table.m",
+    ":file_table_list.m",
+    ":file_tap_handler.m",
     ":files_alert_dialog.m",
     ":files_confirm_dialog.m",
     ":files_menu.m",
+    ":list_container.m",
     ":multi_menu.m",
     ":multi_menu_button.m",
+    ":progress_center_panel.m",
     ":suggest_apps_dialog.m",
     "table:table.m",
     "table:table_column.m",
@@ -115,6 +127,12 @@
 js_library("a11y_announce") {
 }
 
+js_library("a11y_announce.m") {
+  sources = [ "$root_gen_dir/ui/file_manager/file_manager/foreground/js/ui/a11y_announce.m.js" ]
+
+  extra_deps = [ ":modulize" ]
+}
+
 js_library("action_model_ui") {
   deps = [
     ":files_alert_dialog",
@@ -122,6 +140,16 @@
   ]
 }
 
+js_library("action_model_ui.m") {
+  sources = [ "$root_gen_dir/ui/file_manager/file_manager/foreground/js/ui/action_model_ui.m.js" ]
+  deps = [
+    ":files_alert_dialog.m",
+    ":list_container.m",
+  ]
+
+  extra_deps = [ ":modulize" ]
+}
+
 js_library("actions_submenu") {
   deps = [
     "//ui/file_manager/file_manager/foreground/js:actions_model",
@@ -173,6 +201,21 @@
   externs_list = [ "//ui/file_manager/externs/paper_elements.js" ]
 }
 
+js_library("combobutton.m") {
+  sources = [ "$root_gen_dir/ui/file_manager/file_manager/foreground/js/ui/combobutton.m.js" ]
+  deps = [
+    ":files_menu.m",
+    ":multi_menu_button.m",
+    "//ui/file_manager/file_manager/common/js:util.m",
+    "//ui/webui/resources/js/cr:ui.m",
+    "//ui/webui/resources/js/cr/ui:menu_item.m",
+  ]
+
+  externs_list = [ "//ui/file_manager/externs/paper_elements.js" ]
+
+  extra_deps = [ ":modulize" ]
+}
+
 js_library("commandbutton") {
   deps = [
     "//ui/webui/resources/js:assert",
@@ -181,6 +224,18 @@
   ]
 }
 
+js_library("commandbutton.m") {
+  sources = [ "$root_gen_dir/ui/file_manager/file_manager/foreground/js/ui/commandbutton.m.js" ]
+  deps = [
+    "//ui/webui/resources/js:assert.m",
+    "//ui/webui/resources/js:cr.m",
+    "//ui/webui/resources/js/cr:ui.m",
+    "//ui/webui/resources/js/cr/ui:command.m",
+  ]
+
+  extra_deps = [ ":modulize" ]
+}
+
 js_library("default_task_dialog") {
   deps = [
     ":file_manager_dialog_base",
@@ -190,6 +245,19 @@
   ]
 }
 
+js_library("default_task_dialog.m") {
+  sources = [ "$root_gen_dir/ui/file_manager/file_manager/foreground/js/ui/default_task_dialog.m.js" ]
+
+  deps = [
+    ":file_manager_dialog_base.m",
+    "//ui/webui/resources/js:cr.m",
+    "//ui/webui/resources/js/cr/ui:array_data_model.m",
+    "//ui/webui/resources/js/cr/ui:list.m",
+    "//ui/webui/resources/js/cr/ui:list_single_selection_model.m",
+  ]
+  extra_deps = [ ":modulize" ]
+}
+
 js_library("dialog_footer") {
   deps = [
     "//ui/file_manager/file_manager/common/js:file_type",
@@ -305,6 +373,34 @@
   externs_list = [ "//ui/file_manager/externs/background/import_history.js" ]
 }
 
+js_library("file_grid.m") {
+  sources = [ "$root_gen_dir/ui/file_manager/file_manager/foreground/js/ui/file_grid.m.js" ]
+  deps = [
+    ":a11y_announce.m",
+    ":drag_selector.m",
+    ":file_table_list.m",
+    ":file_tap_handler.m",
+    "//ui/file_manager/externs:files_app_entry_interfaces.m",
+    "//ui/file_manager/externs:volume_manager.m",
+    "//ui/file_manager/externs/background:import_history.m",
+    "//ui/file_manager/file_manager/common/js:async_util.m",
+    "//ui/file_manager/file_manager/common/js:file_type.m",
+    "//ui/file_manager/file_manager/common/js:importer_common.m",
+    "//ui/file_manager/file_manager/common/js:util.m",
+    "//ui/file_manager/file_manager/foreground/js:list_thumbnail_loader.m",
+    "//ui/file_manager/file_manager/foreground/js/metadata:metadata_model.m",
+    "//ui/webui/resources/js:assert.m",
+    "//ui/webui/resources/js:cr.m",
+    "//ui/webui/resources/js:util.m",
+    "//ui/webui/resources/js/cr/ui:grid.m",
+    "//ui/webui/resources/js/cr/ui:list.m",
+    "//ui/webui/resources/js/cr/ui:list_item.m",
+    "//ui/webui/resources/js/cr/ui:list_selection_model.m",
+  ]
+
+  extra_deps = [ ":modulize" ]
+}
+
 js_library("file_list_selection_model") {
   deps = [
     "//ui/webui/resources/js/cr/ui:list_selection_model",
@@ -412,6 +508,17 @@
   ]
 }
 
+js_library("file_metadata_formatter.m") {
+  sources = [ "$root_gen_dir/ui/file_manager/file_manager/foreground/js/ui/file_metadata_formatter.m.js" ]
+  deps = [
+    "//ui/file_manager/file_manager/common/js:util.m",
+    "//ui/webui/resources/js:cr.m",
+    "//ui/webui/resources/js/cr:event_target.m",
+  ]
+
+  extra_deps = [ ":modulize" ]
+}
+
 js_library("file_table") {
   deps = [
     ":a11y_announce",
@@ -426,10 +533,44 @@
   externs_list = [ "//ui/file_manager/externs/background/import_history.js" ]
 }
 
-js_unittest("file_table_unittest") {
+js_library("file_table.m") {
+  sources = [ "$root_gen_dir/ui/file_manager/file_manager/foreground/js/ui/file_table.m.js" ]
   deps = [
-    ":file_table",
-    "//ui/webui/resources/js:webui_resource_test",
+    ":a11y_announce.m",
+    ":drag_selector.m",
+    ":file_list_selection_model.m",
+    ":file_metadata_formatter.m",
+    ":file_table_list.m",
+    "table:table.m",
+    "table:table_column.m",
+    "table:table_column_model.m",
+    "table:table_list.m",
+    "//ui/file_manager/externs:entry_location.m",
+    "//ui/file_manager/externs:files_app_entry_interfaces.m",
+    "//ui/file_manager/externs:volume_manager.m",
+    "//ui/file_manager/externs/background:import_history.m",
+    "//ui/file_manager/file_manager/common/js:async_util.m",
+    "//ui/file_manager/file_manager/common/js:file_type.m",
+    "//ui/file_manager/file_manager/common/js:importer_common.m",
+    "//ui/file_manager/file_manager/common/js:util.m",
+    "//ui/file_manager/file_manager/foreground/js:file_list_model.m",
+    "//ui/file_manager/file_manager/foreground/js:list_thumbnail_loader.m",
+    "//ui/file_manager/file_manager/foreground/js/metadata:metadata_model.m",
+    "//ui/webui/resources/js:assert.m",
+    "//ui/webui/resources/js:cr.m",
+    "//ui/webui/resources/js/cr/ui:list.m",
+    "//ui/webui/resources/js/cr/ui:list_item.m",
+    "//ui/webui/resources/js/cr/ui:list_selection_model.m",
+  ]
+
+  extra_deps = [ ":modulize" ]
+}
+
+js_unittest("file_table_unittest.m") {
+  deps = [
+    ":file_table.m",
+    "table:table_column.m",
+    "//chrome/test/data/webui:chai_assert",
   ]
 }
 
@@ -445,15 +586,44 @@
   ]
 }
 
-js_unittest("file_table_list_unittest") {
+js_library("file_table_list.m") {
+  sources = [ "$root_gen_dir/ui/file_manager/file_manager/foreground/js/ui/file_table_list.m.js" ]
   deps = [
-    ":file_table",
-    ":file_table_list",
-    "//ui/file_manager/base/js:test_error_reporting",
-    "//ui/file_manager/file_manager/background/js:mock_volume_manager",
-    "//ui/file_manager/file_manager/common/js:util",
-    "//ui/file_manager/file_manager/foreground/js/metadata:mock_metadata",
-    "//ui/webui/resources/js:webui_resource_test",
+    ":a11y_announce.m",
+    ":file_list_selection_model.m",
+    ":file_tap_handler.m",
+    "table:table_list.m",
+    "//ui/file_manager/externs:entry_location.m",
+    "//ui/file_manager/externs:files_app_entry_interfaces.m",
+    "//ui/file_manager/file_manager/common/js:file_type.m",
+    "//ui/file_manager/file_manager/common/js:util.m",
+    "//ui/file_manager/file_manager/foreground/js/metadata:metadata_model.m",
+    "//ui/webui/resources/js:assert.m",
+    "//ui/webui/resources/js:cr.m",
+    "//ui/webui/resources/js/cr/ui:list.m",
+    "//ui/webui/resources/js/cr/ui:list_item.m",
+    "//ui/webui/resources/js/cr/ui:list_selection_controller.m",
+    "//ui/webui/resources/js/cr/ui:list_selection_model.m",
+  ]
+
+  extra_deps = [ ":modulize" ]
+}
+
+js_unittest("file_table_list_unittest.m") {
+  deps = [
+    ":a11y_announce.m",
+    ":file_list_selection_model.m",
+    ":file_table.m",
+    ":file_table_list.m",
+    "//chrome/test/data/webui:chai_assert",
+    "//ui/file_manager/base/js:volume_manager_types.m",
+    "//ui/file_manager/externs/background:import_history.m",
+    "//ui/file_manager/file_manager/background/js:mock_volume_manager.m",
+    "//ui/file_manager/file_manager/common/js:files_app_entry_types.m",
+    "//ui/file_manager/file_manager/foreground/js:directory_model.m",
+    "//ui/file_manager/file_manager/foreground/js:file_list_model.m",
+    "//ui/file_manager/file_manager/foreground/js/metadata:metadata_model.m",
+    "//ui/file_manager/file_manager/foreground/js/metadata:mock_metadata.m",
   ]
 }
 
@@ -461,11 +631,18 @@
   deps = [ "//ui/file_manager/file_manager/common/js:util" ]
 }
 
-js_unittest("file_tap_handler_unittest") {
+js_library("file_tap_handler.m") {
+  sources = [ "$root_gen_dir/ui/file_manager/file_manager/foreground/js/ui/file_tap_handler.m.js" ]
+  deps = [ "//ui/webui/resources/js:assert.m" ]
+
+  extra_deps = [ ":modulize" ]
+}
+
+js_unittest("file_tap_handler_unittest.m") {
   deps = [
-    ":file_tap_handler",
-    "//ui/file_manager/base/js:test_error_reporting",
-    "//ui/webui/resources/js:webui_resource_test",
+    ":file_tap_handler.m",
+    "//chrome/test/data/webui:chai_assert",
+    "//ui/file_manager/base/js:test_error_reporting.m",
   ]
 }
 
@@ -559,6 +736,27 @@
   ]
 }
 
+js_library("list_container.m") {
+  sources = [ "$root_gen_dir/ui/file_manager/file_manager/foreground/js/ui/list_container.m.js" ]
+  deps = [
+    ":file_grid.m",
+    ":file_table.m",
+    "//ui/file_manager/file_manager/common/js:util.m",
+    "//ui/file_manager/file_manager/foreground/js:dialog_type.m",
+    "//ui/file_manager/file_manager/foreground/js:file_list_model.m",
+    "//ui/file_manager/file_manager/foreground/js:list_thumbnail_loader.m",
+    "//ui/webui/resources/js:assert.m",
+    "//ui/webui/resources/js:cr.m",
+    "//ui/webui/resources/js:util.m",
+    "//ui/webui/resources/js/cr/ui:list.m",
+    "//ui/webui/resources/js/cr/ui:list_item.m",
+    "//ui/webui/resources/js/cr/ui:list_selection_model.m",
+    "//ui/webui/resources/js/cr/ui:list_single_selection_model.m",
+  ]
+
+  extra_deps = [ ":modulize" ]
+}
+
 js_library("location_line") {
   deps = [
     "//ui/file_manager/base/js:volume_manager_types",
@@ -646,6 +844,18 @@
   externs_list = [ "//ui/file_manager/externs/progress_center_panel.js" ]
 }
 
+js_library("progress_center_panel.m") {
+  sources = [ "$root_gen_dir/ui/file_manager/file_manager/foreground/js/ui/progress_center_panel.m.js" ]
+  deps = [
+    "//ui/file_manager/externs:progress_center_panel.m",
+    "//ui/file_manager/file_manager/common/js:progress_center_common.m",
+    "//ui/file_manager/file_manager/common/js:util.m",
+    "//ui/webui/resources/js:assert.m",
+  ]
+
+  extra_deps = [ ":modulize" ]
+}
+
 js_library("providers_menu") {
   deps = [
     ":directory_tree",
@@ -720,6 +930,9 @@
     ":directory_tree_unittest.m",
     ":file_list_selection_model_unittest.m",
     ":file_manager_dialog_base_unittest.m",
+    ":file_table_list_unittest.m",
+    ":file_table_unittest.m",
+    ":file_tap_handler_unittest.m",
     ":multi_menu_unittest.m",
   ]
   js_module = true
@@ -738,31 +951,42 @@
   deps = [
     ":actions_submenu_unittest",
     ":breadcrumb_unittest",
-    ":file_table_list_unittest",
-    ":file_table_unittest",
-    ":file_tap_handler_unittest",
     ":install_linux_package_dialog_unittest",
   ]
 }
 
 js_modulizer("modulize") {
   input_files = [
+    "a11y_announce.js",
+    "action_model_ui.js",
+    "combobutton.js",
+    "commandbutton.js",
+    "default_task_dialog.js",
     "directory_tree.js",
     "drag_selector.js",
     "empty_folder.js",
+    "file_grid.js",
     "file_list_selection_model.js",
     "file_manager_dialog_base.js",
+    "file_metadata_formatter.js",
+    "file_table.js",
+    "file_table_list.js",
+    "file_tap_handler.js",
     "files_alert_dialog.js",
     "files_confirm_dialog.js",
     "files_menu.js",
+    "list_container.js",
     "multi_menu.js",
     "multi_menu_button.js",
+    "progress_center_panel.js",
     "suggest_apps_dialog.js",
   ]
 
   namespace_rewrites = cr_namespace_rewrites
   namespace_rewrites += [
-    "cr.ui.MultiMenu|MultiMenu",
+    "cr.ui.ComboButton|ComboButton",
     "cr.ui.FilesMenuItem|FilesMenuItem",
+    "cr.ui.MultiMenu|MultiMenu",
+    "cr.ui.table.Table|Table",
   ]
 }
diff --git a/ui/file_manager/file_manager/foreground/js/ui/a11y_announce.js b/ui/file_manager/file_manager/foreground/js/ui/a11y_announce.js
index 97d7aef..684ca6f 100644
--- a/ui/file_manager/file_manager/foreground/js/ui/a11y_announce.js
+++ b/ui/file_manager/file_manager/foreground/js/ui/a11y_announce.js
@@ -3,7 +3,7 @@
 // found in the LICENSE file.
 
 /** @interface */
-class A11yAnnounce {
+/* #export */ class A11yAnnounce {
   /**
    * @param {string} text Text to be announced by screen reader, which should be
    * already translated.
diff --git a/ui/file_manager/file_manager/foreground/js/ui/action_model_ui.js b/ui/file_manager/file_manager/foreground/js/ui/action_model_ui.js
index e6c3c8fa..ad6d13719 100644
--- a/ui/file_manager/file_manager/foreground/js/ui/action_model_ui.js
+++ b/ui/file_manager/file_manager/foreground/js/ui/action_model_ui.js
@@ -2,8 +2,11 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// #import {ListContainer} from './list_container.m.js';
+// #import {FilesAlertDialog} from './files_alert_dialog.m.js';
+
 /** @interface */
-class ActionModelUI {
+/* #export */ class ActionModelUI {
   constructor() {
     /** @type {!FilesAlertDialog} */
     this.alertDialog;
diff --git a/ui/file_manager/file_manager/foreground/js/ui/combobutton.js b/ui/file_manager/file_manager/foreground/js/ui/combobutton.js
index d5f433575..0f400d47 100644
--- a/ui/file_manager/file_manager/foreground/js/ui/combobutton.js
+++ b/ui/file_manager/file_manager/foreground/js/ui/combobutton.js
@@ -2,6 +2,15 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// clang-format off
+// #import {MenuItem} from 'chrome://resources/js/cr/ui/menu_item.m.js';
+// #import {util} from '../../../common/js/util.m.js';
+// #import {FilesMenuItem} from './files_menu.m.js';
+// #import {decorate} from 'chrome://resources/js/cr/ui.m.js';
+// #import {MultiMenuButton} from './multi_menu_button.m.js';
+// #import {getPropertyDescriptor, PropertyKind} from 'chrome://resources/js/cr.m.js';
+// clang-format on
+
 /**
  * @fileoverview This implements a combobutton control.
  */
@@ -9,7 +18,7 @@
   /**
    * Creates a new combo button element.
    */
-  class ComboButton extends cr.ui.MultiMenuButton {
+  /* #export */ class ComboButton extends cr.ui.MultiMenuButton {
     constructor() {
       super();
 
@@ -21,6 +30,15 @@
 
       /** @private {?Element} */
       this.actionNode_ = null;
+
+      /** @private {?Element} */
+      this.filesToggleRipple_ = null;
+
+      /** @private {boolean} */
+      this.disabled = false;
+
+      /** @private {boolean} */
+      this.multiple = false;
     }
 
     /**
@@ -177,9 +195,8 @@
         ripplesLayer.setAttribute('hidden', '');
       }
 
-      /** @private {!FilesToggleRippleElement} */
-      this.filesToggleRipple_ = /** @type {!FilesToggleRippleElement} */
-          (this.ownerDocument.createElement('files-toggle-ripple'));
+      this.filesToggleRipple_ =
+          this.ownerDocument.createElement('files-toggle-ripple');
       ripplesLayer.appendChild(this.filesToggleRipple_);
 
       /** @private {!PaperRipple} */
@@ -260,9 +277,7 @@
     }
   }
 
-  cr.defineProperty(ComboButton, 'disabled', cr.PropertyKind.BOOL_ATTR);
-  cr.defineProperty(ComboButton, 'multiple', cr.PropertyKind.BOOL_ATTR);
-
+  // #cr_define_end
   return {
     ComboButton: ComboButton,
   };
diff --git a/ui/file_manager/file_manager/foreground/js/ui/commandbutton.js b/ui/file_manager/file_manager/foreground/js/ui/commandbutton.js
index 0776707..f46d7909 100644
--- a/ui/file_manager/file_manager/foreground/js/ui/commandbutton.js
+++ b/ui/file_manager/file_manager/foreground/js/ui/commandbutton.js
@@ -6,11 +6,18 @@
  * @fileoverview This implements a common button control, bound to command.
  */
 
+// clang-format off
+// #import {Command} from 'chrome://resources/js/cr/ui/command.m.js';
+// #import {decorate} from 'chrome://resources/js/cr/ui.m.js';
+// #import {getPropertyDescriptor, PropertyKind} from 'chrome://resources/js/cr.m.js';
+// #import {assert} from 'chrome://resources/js/assert.m.js';
+// clang-format on
+
 /**
  * Creates a new button element.
  * @extends {HTMLButtonElement}
  */
-class CommandButton {
+/* #export */ class CommandButton {
   constructor() {
     /**
      * Associated command.
@@ -160,16 +167,16 @@
   }
 }
 
-CommandButton.prototype.__proto__ = HTMLButtonElement.prototype;
-
 /**
  * Whether the button is disabled or not.
  * @type {boolean}
  */
-cr.defineProperty(CommandButton, 'disabled', cr.PropertyKind.BOOL_ATTR);
+CommandButton.prototype.disabled;
 
 /**
  * Whether the button is hidden or not.
  * @type {boolean}
  */
-cr.defineProperty(CommandButton, 'hidden', cr.PropertyKind.BOOL_ATTR);
+CommandButton.prototype.hidden;
+
+CommandButton.prototype.__proto__ = HTMLButtonElement.prototype;
diff --git a/ui/file_manager/file_manager/foreground/js/ui/default_task_dialog.js b/ui/file_manager/file_manager/foreground/js/ui/default_task_dialog.js
index b6e1356..28bd409 100644
--- a/ui/file_manager/file_manager/foreground/js/ui/default_task_dialog.js
+++ b/ui/file_manager/file_manager/foreground/js/ui/default_task_dialog.js
@@ -2,6 +2,14 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// clang-format off
+// #import {ArrayDataModel} from 'chrome://resources/js/cr/ui/array_data_model.m.js';
+// #import {ListSingleSelectionModel} from 'chrome://resources/js/cr/ui/list_single_selection_model.m.js';
+// #import {List} from 'chrome://resources/js/cr/ui/list.m.js';
+// #import {FileManagerDialogBase} from './file_manager_dialog_base.m.js';
+// #import {getPropertyDescriptor, PropertyKind} from 'chrome://resources/js/cr.m.js';
+// clang-format on
+
 /**
  * DefaultTaskDialog contains a message, a list box, an ok button, and a
  * cancel button.
@@ -11,7 +19,7 @@
   /**
    * Creates dialog in DOM tree.
    */
-  class DefaultTaskDialog extends FileManagerDialogBase {
+  /* #export */ class DefaultTaskDialog extends FileManagerDialogBase {
     /**
      * @param {HTMLElement} parentNode Node to be parent for this dialog.
      */
@@ -98,8 +106,12 @@
       // A11y - make it focusable and readable.
       result.setAttribute('tabindex', '-1');
 
-      cr.defineProperty(result, 'lead', cr.PropertyKind.BOOL_ATTR);
-      cr.defineProperty(result, 'selected', cr.PropertyKind.BOOL_ATTR);
+      Object.defineProperty(
+          result, 'lead',
+          cr.getPropertyDescriptor('lead', cr.PropertyKind.BOOL_ATTR));
+      Object.defineProperty(
+          result, 'selected',
+          cr.getPropertyDescriptor('selected', cr.PropertyKind.BOOL_ATTR));
 
       return result;
     }
@@ -189,5 +201,6 @@
     }
   }
 
+  // #cr_define_end
   return {DefaultTaskDialog: DefaultTaskDialog};
 });
diff --git a/ui/file_manager/file_manager/foreground/js/ui/file_grid.js b/ui/file_manager/file_manager/foreground/js/ui/file_grid.js
index 9e1c569..4dd239e 100644
--- a/ui/file_manager/file_manager/foreground/js/ui/file_grid.js
+++ b/ui/file_manager/file_manager/foreground/js/ui/file_grid.js
@@ -2,13 +2,36 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// clang-format off
+// #import {A11yAnnounce} from './a11y_announce.m.js';
+// #import {ListSelectionModel} from 'chrome://resources/js/cr/ui/list_selection_model.m.js';
+// #import {FilesAppEntry} from '../../../../externs/files_app_entry_interfaces.m.js';
+// #import {importerHistoryInterfaces} from '../../../../externs/background/import_history.m.js';
+// #import {VolumeManager} from '../../../../externs/volume_manager.m.js';
+// #import {ListThumbnailLoader} from '../list_thumbnail_loader.m.js';
+// #import {MetadataModel} from '../metadata/metadata_model.m.js';
+// #import {FileTapHandler} from './file_tap_handler.m.js';
+// #import {ListItem} from 'chrome://resources/js/cr/ui/list_item.m.js';
+// #import {DragSelector} from './drag_selector.m.js';
+// #import {FileType} from '../../../common/js/file_type.m.js';
+// #import {importer} from '../../../common/js/importer_common.m.js';
+// #import {filelist} from './file_table_list.m.js';
+// #import {assert, assertInstanceof} from 'chrome://resources/js/assert.m.js';
+// #import {util, str} from '../../../common/js/util.m.js';
+// #import {isRTL} from 'chrome://resources/js/util.m.js';
+// #import {AsyncUtil} from '../../../common/js/async_util.m.js';
+// #import {List} from 'chrome://resources/js/cr/ui/list.m.js';
+// #import {Grid, GridSelectionController} from 'chrome://resources/js/cr/ui/grid.m.js';
+// #import {dispatchSimpleEvent} from 'chrome://resources/js/cr.m.js';
+// clang-format on
+
 /**
  * FileGrid constructor.
  *
  * Represents grid for the Grid View in the File Manager.
  */
 
-class FileGrid extends cr.ui.Grid {
+/* #export */ class FileGrid extends cr.ui.Grid {
   constructor() {
     super();
 
@@ -108,7 +131,9 @@
    * @param {!A11yAnnounce} a11y
    */
   static decorate(element, metadataModel, volumeManager, historyLoader, a11y) {
-    cr.ui.Grid.decorate(element);
+    if (cr.ui.Grid.decorate) {
+      cr.ui.Grid.decorate(element);
+    }
     const self = /** @type {!FileGrid} */ (element);
     self.__proto__ = FileGrid.prototype;
     self.setAttribute('aria-multiselectable', true);
@@ -134,7 +159,7 @@
       let item = self.ownerDocument.createElement('li');
       item.__proto__ = FileGrid.Item.prototype;
       item = /** @type {!FileGrid.Item} */ (item);
-      self.decorateThumbnail_(item, entry);
+      self.decorateThumbnail_(item, /** @type {!Entry} */ (entry));
       return item;
     };
 
@@ -1033,7 +1058,8 @@
 /**
  * Selection controller for the file grid.
  */
-class FileGridSelectionController extends cr.ui.GridSelectionController {
+/* #export */ class FileGridSelectionController extends
+    cr.ui.GridSelectionController {
   /**
    * @param {!cr.ui.ListSelectionModel} selectionModel The selection model to
    *     interact with.
@@ -1057,7 +1083,7 @@
   /** @override */
   handleTouchEvents(e, index) {
     if (this.tapHandler_.handleTouchEvents(
-            e, index, filelist.handleTap.bind(this))) {
+            assert(e), index, filelist.handleTap.bind(this))) {
       filelist.focusParentList(e);
     }
   }
diff --git a/ui/file_manager/file_manager/foreground/js/ui/file_metadata_formatter.js b/ui/file_manager/file_manager/foreground/js/ui/file_metadata_formatter.js
index 8788df4..fba4c14 100644
--- a/ui/file_manager/file_manager/foreground/js/ui/file_metadata_formatter.js
+++ b/ui/file_manager/file_manager/foreground/js/ui/file_metadata_formatter.js
@@ -2,10 +2,16 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// clang-format off
+// #import {strf, util} from '../../../common/js/util.m.js';
+// #import {NativeEventTarget as EventTarget} from 'chrome://resources/js/cr/event_target.m.js';
+// #import {dispatchSimpleEvent} from 'chrome://resources/js/cr.m.js';
+// clang-format on
+
 /**
  * Formatter class for file metadatas.
  */
-class FileMetadataFormatter extends cr.EventTarget {
+/* #export */ class FileMetadataFormatter extends cr.EventTarget {
   constructor() {
     super();
     this.setDateTimeFormat(true);
diff --git a/ui/file_manager/file_manager/foreground/js/ui/file_table.js b/ui/file_manager/file_manager/foreground/js/ui/file_table.js
index 03a65f1..9c62abc 100644
--- a/ui/file_manager/file_manager/foreground/js/ui/file_table.js
+++ b/ui/file_manager/file_manager/foreground/js/ui/file_table.js
@@ -2,10 +2,38 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// clang-format off
+// #import {A11yAnnounce} from './a11y_announce.m.js';
+// #import {FileListSelectionModel, FileListSingleSelectionModel} from './file_list_selection_model.m.js';
+// #import {EntryLocation} from '../../../../externs/entry_location.m.js';
+// #import {ListItem} from 'chrome://resources/js/cr/ui/list_item.m.js';
+// #import {ListSelectionModel} from 'chrome://resources/js/cr/ui/list_selection_model.m.js';
+// #import {FilesAppEntry} from '../../../../externs/files_app_entry_interfaces.m.js';
+// #import {List} from 'chrome://resources/js/cr/ui/list.m.js';
+// #import {VolumeManager} from '../../../../externs/volume_manager.m.js';
+// #import {importerHistoryInterfaces} from '../../../../externs/background/import_history.m.js';
+// #import {MetadataModel} from '../metadata/metadata_model.m.js';
+// #import {ListThumbnailLoader} from '../list_thumbnail_loader.m.js';
+// #import {TableColumn} from './table/table_column.m.js';
+// #import {TableColumnModel} from './table/table_column_model.m.js';
+// #import {TableList} from './table/table_list.m.js';
+// #import {Table} from './table/table.m.js';
+// #import {FileMetadataFormatter} from './file_metadata_formatter.m.js';
+// #import {FileTableList, filelist} from './file_table_list.m.js';
+// #import {FileListModel} from '../file_list_model.m.js';
+// #import {importer} from '../../../common/js/importer_common.m.js';
+// #import {FileType} from '../../../common/js/file_type.m.js';
+// #import {DragSelector} from './drag_selector.m.js';
+// #import {assert, assertInstanceof} from 'chrome://resources/js/assert.m.js';
+// #import {AsyncUtil} from '../../../common/js/async_util.m.js';
+// #import {util, str, strf} from '../../../common/js/util.m.js';
+// #import {dispatchSimpleEvent} from 'chrome://resources/js/cr.m.js';
+// clang-format on
+
 /**
  * Custom column model for advanced auto-resizing.
  */
-class FileTableColumnModel extends cr.ui.table.TableColumnModel {
+/* #export */ class FileTableColumnModel extends cr.ui.table.TableColumnModel {
   /**
    * @param {!Array<cr.ui.table.TableColumn>} tableColumns Table columns.
    */
@@ -244,7 +272,7 @@
  * @param {Element} table Table being rendered.
  * @return {Element}
  */
-function renderHeader_(table) {
+/* #export */ function renderHeader_(table) {
   const column = /** @type {cr.ui.table.TableColumn} */ (this);
   const container = table.ownerDocument.createElement('div');
   container.classList.add('table-label-container');
@@ -355,7 +383,7 @@
 /**
  * File list Table View.
  */
-class FileTable extends cr.ui.Table {
+/* #export */ class FileTable extends cr.ui.Table {
   constructor() {
     super();
 
@@ -411,6 +439,8 @@
    *     messages.
    * @param {boolean} fullPage True if it's full page File Manager, False if a
    *    file open/save dialog.
+   * @suppress {checkPrototypalTypes} Closure was failing because the signature
+   * of this decorate() doesn't match the base class.
    */
   static decorate(
       self, metadataModel, volumeManager, historyLoader, a11y, fullPage) {
diff --git a/ui/file_manager/file_manager/foreground/js/ui/file_table_list.js b/ui/file_manager/file_manager/foreground/js/ui/file_table_list.js
index f9ce4d30..b934116f 100644
--- a/ui/file_manager/file_manager/foreground/js/ui/file_table_list.js
+++ b/ui/file_manager/file_manager/foreground/js/ui/file_table_list.js
@@ -3,6 +3,29 @@
 // found in the LICENSE file.
 
 /**
+ * @fileoverview
+ * @suppress {uselessCode} Temporary suppress because of the line exporting.
+ */
+
+// clang-format off
+// #import {A11yAnnounce} from './a11y_announce.m.js';
+// #import {FileListSelectionModel, FileListSingleSelectionModel} from './file_list_selection_model.m.js';
+// #import {EntryLocation} from '../../../../externs/entry_location.m.js';
+// #import {MetadataModel} from '../metadata/metadata_model.m.js';
+// #import {FilesAppEntry} from '../../../../externs/files_app_entry_interfaces.m.js';
+// #import {ListItem} from 'chrome://resources/js/cr/ui/list_item.m.js';
+// #import {ListSelectionModel} from 'chrome://resources/js/cr/ui/list_selection_model.m.js';
+// #import {TableList} from './table/table_list.m.js';
+// #import {FileTapHandler} from './file_tap_handler.m.js';
+// #import {List} from 'chrome://resources/js/cr/ui/list.m.js';
+// #import {FileType} from '../../../common/js/file_type.m.js';
+// #import {util, str, strf} from '../../../common/js/util.m.js';
+// #import {ListSelectionController} from 'chrome://resources/js/cr/ui/list_selection_controller.m.js';
+// #import {isMac} from 'chrome://resources/js/cr.m.js';
+// #import {assert} from 'chrome://resources/js/assert.m.js';
+// clang-format on
+
+/**
  * Namespace for utility functions.
  */
 const filelist = {};
@@ -10,7 +33,7 @@
 /**
  * File table list.
  */
-class FileTableList extends cr.ui.table.TableList {
+/* #export */ class FileTableList extends cr.ui.table.TableList {
   constructor() {
     // To silence closure compiler.
     super();
@@ -111,7 +134,7 @@
   /** @override */
   handleTouchEvents(e, index) {
     if (this.tapHandler_.handleTouchEvents(
-            e, index, filelist.handleTap.bind(this))) {
+            assert(e), index, filelist.handleTap.bind(this))) {
       // If a tap event is processed, FileTapHandler cancels the event to
       // prevent triggering click events. Then it results not moving the focus
       // to the list. So we do that here explicitly.
@@ -675,3 +698,6 @@
     element.focus();
   }
 };
+
+// eslint-disable-next-line semi,no-extra-semi
+/* #export */ {filelist};
diff --git a/ui/file_manager/file_manager/foreground/js/ui/file_table_list_unittest.js b/ui/file_manager/file_manager/foreground/js/ui/file_table_list_unittest.m.js
similarity index 90%
rename from ui/file_manager/file_manager/foreground/js/ui/file_table_list_unittest.js
rename to ui/file_manager/file_manager/foreground/js/ui/file_table_list_unittest.m.js
index 7a751e75..1b55b14 100644
--- a/ui/file_manager/file_manager/foreground/js/ui/file_table_list_unittest.js
+++ b/ui/file_manager/file_manager/foreground/js/ui/file_table_list_unittest.m.js
@@ -2,7 +2,19 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-'use strict';
+import {assertEquals, assertFalse, assertTrue} from 'chrome://test/chai_assert.js';
+import {VolumeManagerCommon} from '../../../../base/js/volume_manager_types.m.js';
+import {importerHistoryInterfaces} from '../../../../externs/background/import_history.m.js';
+import {MockVolumeManager} from '../../../background/js/mock_volume_manager.m.js';
+import {FakeEntryImpl} from '../../../common/js/files_app_entry_types.m.js';
+import {DirectoryModel} from '../directory_model.m.js';
+import {FileListModel} from '../file_list_model.m.js';
+import {MetadataModel} from '../metadata/metadata_model.m.js';
+import {MockMetadataModel} from '../metadata/mock_metadata.m.js';
+import {A11yAnnounce} from './a11y_announce.m.js';
+import {FileListSelectionModel} from './file_list_selection_model.m.js';
+import {FileTable} from './file_table.m.js';
+import {FileTableList} from './file_table_list.m.js';
 
 /** @type {!MockVolumeManager} */
 let volumeManager;
@@ -23,7 +35,7 @@
 let a11y;
 
 // Set up test components.
-function setUp() {
+export function setUp() {
   // Mock LoadTimeData strings.
   window.loadTimeData.getString = id => id;
   window.loadTimeData.data = {};
@@ -103,7 +115,7 @@
 /**
  * Tests that the keyboard can be used to navigate the FileTableList.
  */
-function testMultipleSelectionWithKeyboard() {
+export function testMultipleSelectionWithKeyboard() {
   // Render the FileTable on |element|.
   const fullPage = true;
   FileTable.decorate(
@@ -234,7 +246,7 @@
   }
 }
 
-function testKeyboardOperations() {
+export function testKeyboardOperations() {
   // Render the FileTable on |element|.
   const fullPage = true;
   FileTable.decorate(
diff --git a/ui/file_manager/file_manager/foreground/js/ui/file_table_unittest.js b/ui/file_manager/file_manager/foreground/js/ui/file_table_unittest.m.js
similarity index 87%
rename from ui/file_manager/file_manager/foreground/js/ui/file_table_unittest.js
rename to ui/file_manager/file_manager/foreground/js/ui/file_table_unittest.m.js
index 9cfe90f..ba09261d 100644
--- a/ui/file_manager/file_manager/foreground/js/ui/file_table_unittest.js
+++ b/ui/file_manager/file_manager/foreground/js/ui/file_table_unittest.m.js
@@ -2,22 +2,26 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import {assertArrayEquals, assertEquals, assertFalse, assertTrue} from 'chrome://test/chai_assert.js';
+import {FileTableColumnModel} from './file_table.m.js';
+import {TableColumn} from './table/table_column.m.js';
+
 /** @type {!FileTableColumnModel} */
 let model;
 
-/** @type {!Array<cr.ui.table.TableColumn>} */
+/** @type {!Array<TableColumn>} */
 let columns;
 
-function setUp() {
+export function setUp() {
   window.loadTimeData.getString = id => id;
   window.loadTimeData.getBoolean = id => false;
 
   columns = [
-    new cr.ui.table.TableColumn('col0', 'col0', 100),
-    new cr.ui.table.TableColumn('col1', 'col1', 100),
-    new cr.ui.table.TableColumn('col2', 'col2', 100),
-    new cr.ui.table.TableColumn('col3', 'col3', 100),
-    new cr.ui.table.TableColumn('col4', 'col4', 100)
+    new TableColumn('col0', 'col0', 100),
+    new TableColumn('col1', 'col1', 100),
+    new TableColumn('col2', 'col2', 100),
+    new TableColumn('col3', 'col3', 100),
+    new TableColumn('col4', 'col4', 100),
   ];
 
   model = new FileTableColumnModel(columns);
@@ -37,7 +41,7 @@
 
 // Verify that column visibility and width are correctly set when the visibility
 // setting is toggled.
-function testToggleVisibility() {
+export function testToggleVisibility() {
   // The column under test.
   const INDEX = 2;
   const width = model.getWidth(INDEX);
@@ -59,7 +63,7 @@
 
 // Verify that the table layout does not drift when a column is repeatedly shown
 // and hidden.
-function testToggleVisibilityColumnLayout() {
+export function testToggleVisibilityColumnLayout() {
   // The index of the column under test.
   const INDEX = 2;
   // Capture column widths.
@@ -85,7 +89,7 @@
 
 // Verify that table layout stays constant when the column config is exported
 // and then restored, with no hidden columns.
-function testExportAndRestoreColumnConfigWithNoHiddenColumns() {
+export function testExportAndRestoreColumnConfigWithNoHiddenColumns() {
   // Change some column widths, then capture then.
   for (let i = 0; i < model.size; i++) {
     model.setWidth(i, i * 50);
@@ -104,7 +108,7 @@
 
 // Verify that table layout stays constant when the column config is exported
 // and then restored, with a hidden column.
-function testExportAndRestoreColumnConfigWithHiddenColumns() {
+export function testExportAndRestoreColumnConfigWithHiddenColumns() {
   // The index of the column under test.
   const INDEX = 2;
 
@@ -131,7 +135,7 @@
 
 // Verify that table layout stays constant when the column config is exported
 // with a hidden column but then restored with the column visible.
-function testExportAndRestoreColumnConfigWithShowingColumn() {
+export function testExportAndRestoreColumnConfigWithShowingColumn() {
   // The index of the column under test.
   const INDEX = 2;
 
@@ -160,7 +164,7 @@
 
 // Verify that table layout stays constant when the column config is exported
 // with all columns visible but then restored with a hidden column.
-function testExportAndRestoreColumnConfigWithHidingColumn() {
+export function testExportAndRestoreColumnConfigWithHidingColumn() {
   // The index of the column under test.
   const INDEX = 2;
 
@@ -187,7 +191,7 @@
   assertEquals(expectedTotalWidth, newModel.totalWidth);
 }
 
-function testNormalizeWidth() {
+export function testNormalizeWidth() {
   let newContentWidth = 0;
   const initialWidths = [
     10 * 17,
@@ -220,7 +224,7 @@
   assertEquals(newContentWidth, model.totalWidth);
 }
 
-function testNormalizeWidthWithSmallWidth() {
+export function testNormalizeWidthWithSmallWidth() {
   model.normalizeWidths(10);  // not enough width to contain all columns
 
   // Should keep the minimum width.
@@ -229,7 +233,7 @@
   });
 }
 
-function testSetWidthAndKeepTotal() {
+export function testSetWidthAndKeepTotal() {
   // Make sure to take column snapshot. Required for setWidthAndKeepTotal.
   model.initializeColumnPos();
 
diff --git a/ui/file_manager/file_manager/foreground/js/ui/file_tap_handler.js b/ui/file_manager/file_manager/foreground/js/ui/file_tap_handler.js
index 35ab509..7c2cd3c 100644
--- a/ui/file_manager/file_manager/foreground/js/ui/file_tap_handler.js
+++ b/ui/file_manager/file_manager/foreground/js/ui/file_tap_handler.js
@@ -2,6 +2,8 @@
 // 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';
+
 /**
  * Processes touch events and calls back upon tap, longpress and longtap.
  * This class is similar to cr.ui.TouchHandler. The major difference is that,
@@ -9,7 +11,7 @@
  * distincted from mouse clicks, or leave it handled by the mouse event
  * handlers by default.
  */
-class FileTapHandler {
+/* #export */ class FileTapHandler {
   constructor() {
     /**
      * Whether the pointer is currently down and at the same place as the
diff --git a/ui/file_manager/file_manager/foreground/js/ui/file_tap_handler_unittest.js b/ui/file_manager/file_manager/foreground/js/ui/file_tap_handler_unittest.m.js
similarity index 94%
rename from ui/file_manager/file_manager/foreground/js/ui/file_tap_handler_unittest.js
rename to ui/file_manager/file_manager/foreground/js/ui/file_tap_handler_unittest.m.js
index a4efe66..6fd878e 100644
--- a/ui/file_manager/file_manager/foreground/js/ui/file_tap_handler_unittest.js
+++ b/ui/file_manager/file_manager/foreground/js/ui/file_tap_handler_unittest.m.js
@@ -2,6 +2,10 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import {assertEquals} from 'chrome://test/chai_assert.js';
+import {reportPromise} from '../../../../base/js/test_error_reporting.m.js';
+import {FileTapHandler} from './file_tap_handler.m.js';
+
 /** @type {!FileTapHandler} handler the handler. */
 let handler;
 
@@ -37,13 +41,13 @@
   });
 }
 
-function setUp() {
+export function setUp() {
   handler = new FileTapHandler();
   dummyTarget = document.body;
   events = [];
 }
 
-function testTap() {
+export function testTap() {
   const touch = createTouch(0, 300, 400);
   handler.handleTouchEvents(
       new TouchEvent('touchstart', {targetTouches: [touch], touches: [touch]}),
@@ -61,7 +65,7 @@
   assertEquals(0, events[0].index);
 }
 
-function testIgnoreSlide() {
+export function testIgnoreSlide() {
   const touch0 = createTouch(0, 300, 400);
   const touch1 = createTouch(0, 320, 450);
   handler.handleTouchEvents(
@@ -110,7 +114,7 @@
   assertEquals(FileTapHandler.TapEvent.TAP, events[0].eventType);
 }
 
-function testTapMoveTolerance() {
+export function testTapMoveTolerance() {
   const touch0 = createTouch(0, 300, 400);
   const touch1 = createTouch(0, 300, 405);  // moved slightly
   const touch2 = createTouch(0, 302, 405);  // moved slightly
@@ -148,7 +152,7 @@
   assertEquals(0, events[0].index);
 }
 
-function testLongTap(callback) {
+export function testLongTap(callback) {
   const touch0 = createTouch(0, 300, 400);
   const touch1 = createTouch(0, 303, 404);
   handler.handleTouchEvents(
@@ -196,7 +200,7 @@
       callback);
 }
 
-function testCancelLongTapBySlide(callback) {
+export function testCancelLongTapBySlide(callback) {
   const touch0 = createTouch(0, 300, 400);
   const touch1 = createTouch(0, 330, 450);
   handler.handleTouchEvents(
@@ -230,7 +234,7 @@
       callback);
 }
 
-function testTwoFingerTap() {
+export function testTwoFingerTap() {
   const touch0_0 = createTouch(0, 300, 400);
   const touch0_1 = createTouch(0, 303, 404);
   const touch1_0 = createTouch(1, 350, 400);
diff --git a/ui/file_manager/file_manager/foreground/js/ui/list_container.js b/ui/file_manager/file_manager/foreground/js/ui/list_container.js
index 0d22878..2b24813 100644
--- a/ui/file_manager/file_manager/foreground/js/ui/list_container.js
+++ b/ui/file_manager/file_manager/foreground/js/ui/list_container.js
@@ -2,6 +2,22 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// clang-format off
+// #import {FileGrid} from './file_grid.m.js';
+// #import {FileTable} from './file_table.m.js';
+// #import {ListThumbnailLoader} from '../list_thumbnail_loader.m.js';
+// #import {List} from 'chrome://resources/js/cr/ui/list.m.js';
+// #import {ListSingleSelectionModel} from 'chrome://resources/js/cr/ui/list_single_selection_model.m.js';
+// #import {FileListModel} from '../file_list_model.m.js';
+// #import {ListItem} from 'chrome://resources/js/cr/ui/list_item.m.js';
+// #import {DialogType} from '../dialog_type.m.js';
+// #import {ListSelectionModel} from 'chrome://resources/js/cr/ui/list_selection_model.m.js';
+// #import {queryRequiredElement} from 'chrome://resources/js/util.m.js';
+// #import {util} from '../../../common/js/util.m.js';
+// #import {assert, assertInstanceof, assertNotReached} from 'chrome://resources/js/assert.m.js';
+// #import {dispatchSimpleEvent} from 'chrome://resources/js/cr.m.js';
+// clang-format on
+
 class TextSearchState {
   constructor() {
     /** @public {string} */
@@ -15,7 +31,7 @@
 /**
  * List container for the file table and the grid view.
  */
-class ListContainer {
+/* #export */ class ListContainer {
   /**
    * @param {!HTMLElement} element Element of the container.
    * @param {!FileTable} table File table.
diff --git a/ui/file_manager/file_manager/foreground/js/ui/progress_center_panel.js b/ui/file_manager/file_manager/foreground/js/ui/progress_center_panel.js
index 3735dde..74a9ad30 100644
--- a/ui/file_manager/file_manager/foreground/js/ui/progress_center_panel.js
+++ b/ui/file_manager/file_manager/foreground/js/ui/progress_center_panel.js
@@ -2,11 +2,18 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// clang-format off
+// #import {ProgressCenterPanelInterface} from '../../../../externs/progress_center_panel.m.js';
+// #import {assertNotReached} from 'chrome://resources/js/assert.m.js';
+// #import {strf, str} from '../../../common/js/util.m.js';
+// #import {ProgressItemType, ProgressItemState, ProgressCenterItem} from '../../../common/js/progress_center_common.m.js';
+// clang-format on
+
 /**
  * Progress center panel.
  * @implements {ProgressCenterPanelInterface}
  */
-class ProgressCenterPanel {
+/* #export */ class ProgressCenterPanel {
   constructor() {
     /**
      * Reference to the feedback panel host.
diff --git a/ui/gfx/skbitmap_operations.cc b/ui/gfx/skbitmap_operations.cc
index ed96883..65dafaf 100644
--- a/ui/gfx/skbitmap_operations.cc
+++ b/ui/gfx/skbitmap_operations.cc
@@ -15,7 +15,7 @@
 #include "third_party/skia/include/core/SkColorFilter.h"
 #include "third_party/skia/include/core/SkColorPriv.h"
 #include "third_party/skia/include/core/SkUnPreMultiply.h"
-#include "third_party/skia/include/effects/SkBlurImageFilter.h"
+#include "third_party/skia/include/effects/SkImageFilters.h"
 #include "ui/gfx/geometry/insets.h"
 #include "ui/gfx/geometry/point.h"
 #include "ui/gfx/geometry/size.h"
@@ -727,7 +727,7 @@
     // The blur is halved to produce a shadow that correctly fits within the
     // |shadow_margin|.
     SkScalar sigma = SkDoubleToScalar(shadow.blur() / 2);
-    paint.setImageFilter(SkBlurImageFilter::Make(sigma, sigma, nullptr));
+    paint.setImageFilter(SkImageFilters::Blur(sigma, sigma, nullptr));
 
     canvas.saveLayer(0, &paint);
     canvas.drawBitmap(shadow_image,
diff --git a/ui/views/metadata/metadata_types.cc b/ui/views/metadata/metadata_types.cc
index 1a461bee..9d97704f 100644
--- a/ui/views/metadata/metadata_types.cc
+++ b/ui/views/metadata/metadata_types.cc
@@ -137,7 +137,7 @@
   type_name_ = type_name;
 }
 
-void MemberMetaDataBase::SetValueAsString(void* obj,
+void MemberMetaDataBase::SetValueAsString(View* obj,
                                           const base::string16& new_value) {
   NOTREACHED();
 }
diff --git a/ui/views/metadata/metadata_types.h b/ui/views/metadata/metadata_types.h
index af10a631..9473f45 100644
--- a/ui/views/metadata/metadata_types.h
+++ b/ui/views/metadata/metadata_types.h
@@ -14,6 +14,9 @@
 #include "ui/views/views_export.h"
 
 namespace views {
+
+class View;
+
 namespace metadata {
 
 enum class PropertyFlags : uint32_t {
@@ -151,12 +154,12 @@
   // Access the value of this member and return it as a string.
   // |obj| is the instance on which to obtain the value of the property this
   // metadata represents.
-  virtual base::string16 GetValueAsString(void* obj) const = 0;
+  virtual base::string16 GetValueAsString(View* obj) const = 0;
 
   // Set the value of this member through a string on a specified object.
   // |obj| is the instance on which to set the value of the property this
   // metadata represents.
-  virtual void SetValueAsString(void* obj, const base::string16& new_value);
+  virtual void SetValueAsString(View* obj, const base::string16& new_value);
 
   // Return various information flags about the property.
   virtual PropertyFlags GetPropertyFlags() const = 0;
diff --git a/ui/views/metadata/property_metadata.h b/ui/views/metadata/property_metadata.h
index 76cb8b5c..11a3fd8 100644
--- a/ui/views/metadata/property_metadata.h
+++ b/ui/views/metadata/property_metadata.h
@@ -14,6 +14,7 @@
 #include "ui/views/metadata/metadata_cache.h"
 #include "ui/views/metadata/metadata_types.h"
 #include "ui/views/metadata/type_conversion.h"
+#include "ui/views/view.h"
 #include "ui/views/views_export.h"
 
 namespace views {
@@ -32,7 +33,7 @@
   using MemberMetaDataBase::MemberMetaDataBase;
   ~ClassPropertyReadOnlyMetaData() override = default;
 
-  base::string16 GetValueAsString(void* obj) const override {
+  base::string16 GetValueAsString(View* obj) const override {
     if (!kIsSerializable)
       return base::string16();
     return TypeConverter<TValue>::ToString((static_cast<TClass*>(obj)->*Get)());
@@ -69,7 +70,7 @@
       ClassPropertyReadOnlyMetaData;
   ~ClassPropertyMetaData() override = default;
 
-  void SetValueAsString(void* obj, const base::string16& new_value) override {
+  void SetValueAsString(View* obj, const base::string16& new_value) override {
     if (!kIsSerializable)
       return;
     if (base::Optional<TValue> result =
diff --git a/ui/views/metadata/type_conversion.h b/ui/views/metadata/type_conversion.h
index 87d79fb7..eef5b62c 100644
--- a/ui/views/metadata/type_conversion.h
+++ b/ui/views/metadata/type_conversion.h
@@ -163,9 +163,7 @@
 #undef DECLARE_CONVERSIONS
 
 template <>
-struct VIEWS_EXPORT TypeConverter<bool> {
-  static constexpr bool is_serializable = true;
-  static bool IsSerializable() { return is_serializable; }
+struct VIEWS_EXPORT TypeConverter<bool> : BaseTypeConverter<true> {
   static base::string16 ToString(bool source_value);
   static base::Optional<bool> FromString(const base::string16& source_value);
   static ValidStrings GetValidStrings();
diff --git a/ui/webui/resources/js/list_property_update_behavior.js b/ui/webui/resources/js/list_property_update_behavior.js
index 6812f541..6ac160b 100644
--- a/ui/webui/resources/js/list_property_update_behavior.js
+++ b/ui/webui/resources/js/list_property_update_behavior.js
@@ -21,44 +21,60 @@
 /* #export */ const ListPropertyUpdateBehavior = {
   /**
    * @param {string} propertyPath
-   * @param {function(!Object): string} itemUidGetter
+   * @param {function(!Object): (!Object|string)} identityGetter
    * @param {!Array<!Object>} updatedList
-   * @param {boolean} uidBasedUpdate
+   * @param {boolean=} identityBasedUpdate
    * @returns {boolean} True if notifySplices was called.
    */
-  updateList(propertyPath, itemUidGetter, updatedList, uidBasedUpdate = false) {
-    const list = this.get(propertyPath);
-    const splices = Polymer.ArraySplice.calculateSplices(
-        updatedList.map(itemUidGetter), list.map(itemUidGetter));
-
-    splices.forEach(splice => {
-      const index = splice.index;
-      const deleteCount = splice.removed.length;
-      // Transform splices to the expected format of notifySplices().
-      // Convert !Array<string> to !Array<!Object>.
-      splice.removed = list.slice(index, index + deleteCount);
-      splice.object = list;
-      splice.type = 'splice';
-
-      const added = updatedList.slice(index, index + splice.addedCount);
-      const spliceParams = [index, deleteCount].concat(added);
-      list.splice.apply(list, spliceParams);
-    });
-
-    let updated = splices.length > 0;
-    if (!uidBasedUpdate) {
-      list.forEach((item, index) => {
-        const updatedItem = updatedList[index];
-        if (JSON.stringify(item) !== JSON.stringify(updatedItem)) {
-          this.set([propertyPath, index], updatedItem);
-          updated = true;
-        }
-      });
-    }
-
-    if (splices.length > 0) {
-      this.notifySplices(propertyPath, splices);
-    }
-    return updated;
+  updateList(
+      propertyPath, identityGetter, updatedList, identityBasedUpdate = false) {
+    return updateListProperty(
+        this, propertyPath, identityGetter, updatedList, identityBasedUpdate);
   },
 };
+
+/**
+ * @param {Object} instance
+ * @param {string} propertyPath
+ * @param {function(!Object): (!Object|string)} identityGetter
+ * @param {!Array<!Object>} updatedList
+ * @param {boolean=} identityBasedUpdate
+ * @returns {boolean} True if notifySplices was called.
+ */
+/* #export */ function updateListProperty(
+    instance, propertyPath, identityGetter, updatedList,
+    identityBasedUpdate = false) {
+  const list = instance.get(propertyPath);
+  const splices = Polymer.ArraySplice.calculateSplices(
+      updatedList.map(identityGetter), list.map(identityGetter));
+
+  splices.forEach(splice => {
+    const index = splice.index;
+    const deleteCount = splice.removed.length;
+    // Transform splices to the expected format of notifySplices().
+    // Convert !Array<string> to !Array<!Object>.
+    splice.removed = list.slice(index, index + deleteCount);
+    splice.object = list;
+    splice.type = 'splice';
+
+    const added = updatedList.slice(index, index + splice.addedCount);
+    const spliceParams = [index, deleteCount].concat(added);
+    list.splice.apply(list, spliceParams);
+  });
+
+  let updated = splices.length > 0;
+  if (!identityBasedUpdate) {
+    list.forEach((item, index) => {
+      const updatedItem = updatedList[index];
+      if (JSON.stringify(item) !== JSON.stringify(updatedItem)) {
+        instance.set([propertyPath, index], updatedItem);
+        updated = true;
+      }
+    });
+  }
+
+  if (splices.length > 0) {
+    instance.notifySplices(propertyPath, splices);
+  }
+  return updated;
+}
diff --git a/ui/webui/webui_config.h b/ui/webui/webui_config.h
index 29990bd..9664e1ff 100644
--- a/ui/webui/webui_config.h
+++ b/ui/webui/webui_config.h
@@ -5,6 +5,7 @@
 #ifndef UI_WEBUI_WEBUI_CONFIG_H_
 #define UI_WEBUI_WEBUI_CONFIG_H_
 
+#include <memory>
 #include <string>
 
 #include "base/strings/string_piece.h"