diff --git a/BUILD.gn b/BUILD.gn
index e705d6b..6d73d23 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -1607,7 +1607,7 @@
       "chrome/test:closure_compile",
       "components/flags_ui/resources:closure_compile",
       "components/neterror/resources:closure_compile",
-      "components/security_interstitials/core/common/resources:closure_compile",
+      "components/security_interstitials:closure_compile",
       "components/sync/driver/resources:closure_compile",
       "components/ukm/debug:closure_compile",
       "content/browser/resources:closure_compile",
diff --git a/DEPS b/DEPS
index ee1ebb40..aebce120 100644
--- a/DEPS
+++ b/DEPS
@@ -257,11 +257,11 @@
   # 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': '4a29e433a8ca9aae93e896b17c5b802a941573a5',
+  'v8_revision': 'eef950da8dd11f337e13a68123fc24b7a1e47ea1',
   # 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': '3154b00bab776e366538e712558a7f8c2967d4ca',
+  'angle_revision': '223a25f0133d18935ef507108b7320e8d7024f96',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
@@ -320,7 +320,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling catapult
   # and whatever else without interference from each other.
-  'catapult_revision': '4ac0eac51b57114d5a50793dee6ffbda9859850f',
+  'catapult_revision': '58ce6f9e689bd01661c62e7464497f03e8ec003f',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
@@ -328,7 +328,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling devtools-frontend
   # and whatever else without interference from each other.
-  'devtools_frontend_revision': '38e1e62439337f92c85c952ebafbd1f6e99bacf2',
+  'devtools_frontend_revision': 'e0e85d0a5a6c4337ff3b496b20257ab41e09aaec',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libprotobuf-mutator
   # and whatever else without interference from each other.
@@ -368,7 +368,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'dawn_revision': 'bf44da52b98e7b3d8bd3a8b8aae39c38ec480ad9',
+  'dawn_revision': 'b573ec0cda64b62f14de7732f424970a90865752',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -1131,7 +1131,7 @@
   },
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '64f5f26f1a0c8b8333514cdb861847f02d405f36',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '306b03b1912affcae0dbecbe5aa36b414fb4ae2a',
 
   'src/third_party/devtools-frontend/src':
     Var('chromium_git') + '/devtools/devtools-frontend' + '@' + Var('devtools_frontend_revision'),
@@ -1592,7 +1592,7 @@
       'packages': [
           {
               'package': 'fuchsia/third_party/android/aemu/release/linux-amd64',
-              'version': 'j_0klEfkeAK-jFzzDKYCntyRDJ8Kv5mtaXHQhMA2vzIC'
+              'version': 'IA5TOLWvQAyOg7VT0EEiRUO0WmTSicdQksvsnBBcIUEC'
           },
       ],
       'condition': 'host_os == "linux" and checkout_fuchsia',
@@ -1735,7 +1735,7 @@
     Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + '30f22785e9b13ea82b2a9f67208a13fcc7b017c3',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + '36cf4787d064635578cde107c9b993379b17fec6',
+    Var('webrtc_git') + '/src.git' + '@' + '3b393ec991ed26a36195c9f41e74b0d7d72e6953',
 
   'src/third_party/libgifcodec':
      Var('skia_git') + '/libgifcodec' + '@'+  Var('libgifcodec_revision'),
@@ -1762,7 +1762,7 @@
       'packages': [
         {
           'package': 'skia/tools/goldctl/linux-amd64',
-          'version': 'td7IhN6Q3eTDLXn6p5jlbeSIDYl7rI75dlX0qj8fEEsC',
+          'version': 'eZ3k373CYgRxlu4JKph6e-_7xkP02swy_jePFFMiyIQC',
         },
       ],
       'dep_type': 'cipd',
@@ -1772,7 +1772,7 @@
       'packages': [
         {
           'package': 'skia/tools/goldctl/windows-amd64',
-          'version': '5k9ZnDE42Xoqs07enkcOdWOf9jT-bhW-OXOp2fY-IR4C',
+          'version': 'iEqqRADI7znrc6pG-MVnc5pBZwD25koILREPC6x2AFAC',
         },
       ],
       'dep_type': 'cipd',
@@ -1783,7 +1783,7 @@
       'packages': [
         {
           'package': 'skia/tools/goldctl/mac-amd64',
-          'version': 'im5u9GiTMHxNcLH_Nc2X3RqzjfDs2oDmC0VhkLgUCeYC',
+          'version': 'nHUjLIViYsLxRjv-zDdmzqT8p1R3VoyHq5gdGkKeMYwC',
         },
       ],
       'dep_type': 'cipd',
@@ -1794,7 +1794,7 @@
       'packages': [
         {
           'package': 'skia/tools/goldctl/mac-arm64',
-          'version': 'edDMT5wDXf_HjD5qeNPgIEmYXDGUB1BswQ0G84CQBdUC',
+          'version': '-mc865SGfJAqreLZM6fkn8tgCJ7u5QLk5zm7r-ZRJ9gC',
         },
       ],
       'dep_type': 'cipd',
@@ -1805,7 +1805,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@a0657139bdfb2bb07c4046abab66c0a2c6c3ff27',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@37960f1892877ac789c8d8eed734048272e60b4c',
     'condition': 'checkout_src_internal',
   },
 
@@ -1857,7 +1857,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/projector_app/app',
-        'version': 'G3YbquTP38UsyZKhNKvJa-ujaz4g3GiPsi7WjcaA3ucC',
+        'version': 'khvsZHSVS5sYkhT--IuQFMPYy_A6D2forFACfSue-OQC',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
diff --git a/ash/system/accessibility/dictation_bubble_view.cc b/ash/system/accessibility/dictation_bubble_view.cc
index 3be13c9..01d6ce01 100644
--- a/ash/system/accessibility/dictation_bubble_view.cc
+++ b/ash/system/accessibility/dictation_bubble_view.cc
@@ -39,24 +39,42 @@
 constexpr SkColor kDefaultTextAndIconColorPrimary = SK_ColorBLACK;
 constexpr SkColor kDefaultTextAndIconColorSecondary = SK_ColorDKGRAY;
 
+SkColor text_color_primary() {
+  if (!features::IsDarkLightModeEnabled())
+    return kDefaultTextAndIconColorPrimary;
+
+  return AshColorProvider::Get()->GetContentLayerColor(
+      AshColorProvider::ContentLayerType::kTextColorPrimary);
+}
+
+SkColor icon_color_primary() {
+  if (!features::IsDarkLightModeEnabled())
+    return kDefaultTextAndIconColorPrimary;
+
+  return AshColorProvider::Get()->GetContentLayerColor(
+      AshColorProvider::ContentLayerType::kIconColorPrimary);
+}
+
+SkColor text_color_secondary() {
+  if (!features::IsDarkLightModeEnabled())
+    return kDefaultTextAndIconColorSecondary;
+
+  return AshColorProvider::Get()->GetContentLayerColor(
+      AshColorProvider::ContentLayerType::kTextColorSecondary);
+}
+
 std::unique_ptr<views::ImageView> CreateImageView(
     views::ImageView** destination_view,
     const gfx::VectorIcon& icon) {
-  SkColor color =
-      features::IsDarkLightModeEnabled()
-          ? AshColorProvider::Get()->GetContentLayerColor(
-                AshColorProvider::ContentLayerType::kIconColorPrimary)
-          : kDefaultTextAndIconColorPrimary;
   return views::Builder<views::ImageView>()
       .CopyAddressTo(destination_view)
-      .SetImage(gfx::CreateVectorIcon(icon, kIconSizeDip, color))
+      .SetImage(gfx::CreateVectorIcon(icon, kIconSizeDip, icon_color_primary()))
       .Build();
 }
 
-void SetImageHelper(views::ImageView* image_view,
-                    const gfx::VectorIcon& icon,
-                    SkColor color) {
-  image_view->SetImage(gfx::CreateVectorIcon(icon, kIconSizeDip, color));
+void SetImageHelper(views::ImageView* image_view, const gfx::VectorIcon& icon) {
+  image_view->SetImage(
+      gfx::CreateVectorIcon(icon, kIconSizeDip, icon_color_primary()));
 }
 
 std::unique_ptr<views::Label> CreateLabelView(views::Label** destination_view,
@@ -109,13 +127,8 @@
                                  kDictationBubbleMacroSucceededIcon));
     AddChildView(
         CreateImageView(&macro_failed_image_, kDictationBubbleMacroFailedIcon));
-
-    SkColor text_color =
-        features::IsDarkLightModeEnabled()
-            ? AshColorProvider::Get()->GetContentLayerColor(
-                  AshColorProvider::ContentLayerType::kTextColorPrimary)
-            : kDefaultTextAndIconColorPrimary;
-    AddChildView(CreateLabelView(&label_, std::u16string(), text_color));
+    AddChildView(
+        CreateLabelView(&label_, std::u16string(), text_color_primary()));
   }
 
   TopRowView(const TopRowView&) = delete;
@@ -127,12 +140,12 @@
   void Update(DictationBubbleIconType icon,
               const absl::optional<std::u16string>& text) {
     // Update visibility.
+    bool is_standby = icon == DictationBubbleIconType::kStandby;
     if (use_standby_animation_) {
-      standby_animation_->SetVisible(icon == DictationBubbleIconType::kStandby);
-      icon == DictationBubbleIconType::kStandby ? standby_animation_->Play()
-                                                : standby_animation_->Stop();
+      standby_animation_->SetVisible(is_standby);
+      is_standby ? standby_animation_->Play() : standby_animation_->Stop();
     } else {
-      standby_image_->SetVisible(icon == DictationBubbleIconType::kStandby);
+      standby_image_->SetVisible(is_standby);
     }
 
     macro_succeeded_image_->SetVisible(icon ==
@@ -148,21 +161,12 @@
 
   // Updates this view so that it respects the global dark mode setting.
   void OnColorModeChanged(bool dark_mode_enabled) {
-    AshColorProvider* color_provider = AshColorProvider::Get();
-    if (!color_provider)
-      return;
-
-    SkColor icon_color = color_provider->GetContentLayerColor(
-        AshColorProvider::ContentLayerType::kIconColorPrimary);
-    SkColor text_color = color_provider->GetContentLayerColor(
-        AshColorProvider::ContentLayerType::kTextColorPrimary);
     if (!use_standby_animation_)
-      SetImageHelper(standby_image_, kDictationBubbleIcon, icon_color);
-    SetImageHelper(macro_succeeded_image_, kDictationBubbleMacroSucceededIcon,
-                   icon_color);
-    SetImageHelper(macro_failed_image_, kDictationBubbleMacroFailedIcon,
-                   icon_color);
-    label_->SetEnabledColor(text_color);
+      SetImageHelper(standby_image_, kDictationBubbleIcon);
+
+    SetImageHelper(macro_succeeded_image_, kDictationBubbleMacroSucceededIcon);
+    SetImageHelper(macro_failed_image_, kDictationBubbleMacroFailedIcon);
+    label_->SetEnabledColor(text_color_primary());
   }
 
   // views::View:
@@ -171,7 +175,7 @@
   }
 
  private:
-  friend DictationBubbleView;
+  friend class ash::DictationBubbleView;
 
   // Returns a std::unique_ptr<AnimatedImageView> if the standby animation
   // can successfully be loaded. Otherwise, returns a std::unique_ptr<ImageView>
@@ -223,18 +227,11 @@
     layout->set_between_child_spacing(kSpaceBetweenHintLabelsDip);
     SetLayoutManager(std::move(layout));
 
-    bool is_dark_light_mode_enabled = features::IsDarkLightModeEnabled();
-    SkColor primary =
-        is_dark_light_mode_enabled
-            ? AshColorProvider::Get()->GetContentLayerColor(
-                  AshColorProvider::ContentLayerType::kTextColorPrimary)
-            : kDefaultTextAndIconColorPrimary;
-    SkColor secondary =
-        is_dark_light_mode_enabled
-            ? AshColorProvider::Get()->GetContentLayerColor(
-                  AshColorProvider::ContentLayerType::kTextColorSecondary)
-            : kDefaultTextAndIconColorSecondary;
+    SkColor primary = text_color_primary();
+    SkColor secondary = text_color_secondary();
     for (size_t i = 0; i < labels_.size(); ++i) {
+      // The first label should use the secondary text color. All other labels
+      // should use the primary text color.
       SkColor color = i == 0 ? secondary : primary;
       AddChildView(CreateLabelView(&labels_[i], std::u16string(), color));
     }
@@ -270,8 +267,6 @@
     // hints to the user.
     if (num_visible_hints > 0) {
       SetVisible(true);
-      // TODO(crbug.com/1252037): Write a DictationUITest to verify that
-      // ChromeVox announces hints.
       NotifyAccessibilityEvent(ax::mojom::Event::kAlert, true);
     } else {
       SetVisible(false);
@@ -281,15 +276,11 @@
 
   // Updates this view so that it respects the global dark mode setting.
   void OnColorModeChanged(bool dark_mode_enabled) {
-    AshColorProvider* color_provider = AshColorProvider::Get();
-    if (!color_provider)
-      return;
-
-    SkColor primary = color_provider->GetContentLayerColor(
-        AshColorProvider::ContentLayerType::kTextColorPrimary);
-    SkColor secondary = color_provider->GetContentLayerColor(
-        AshColorProvider::ContentLayerType::kTextColorSecondary);
+    SkColor primary = text_color_primary();
+    SkColor secondary = text_color_secondary();
     for (size_t i = 0; i < labels_.size(); ++i) {
+      // The first label should use the secondary text color. All other labels
+      // should use the primary text color.
       labels_[i]->SetEnabledColor(i == 0 ? secondary : primary);
     }
   }
@@ -300,7 +291,7 @@
   }
 
  private:
-  friend DictationBubbleView;
+  friend class ash::DictationBubbleView;
 
   // Labels containing hints for users of Dictation. A max of five hints can be
   // shown at any given time.
diff --git a/ash/webui/camera_app_ui/resources/.eslintrc.js b/ash/webui/camera_app_ui/resources/.eslintrc.js
index 8825ca3c..4a9a308 100644
--- a/ash/webui/camera_app_ui/resources/.eslintrc.js
+++ b/ash/webui/camera_app_ui/resources/.eslintrc.js
@@ -478,5 +478,38 @@
 
     // This is covered by @typescript-eslint/naming-convention.
     'camelcase': 'off',
+
+    // go/tsstyle#arrayt-type
+    '@typescript-eslint/array-type': [
+      'error',
+      {
+        'default': 'array-simple',
+      },
+    ],
+
+    // go/tsstyle#type-assertions-syntax
+    // go/tsstyle#type-assertions-and-object-literals
+    '@typescript-eslint/consistent-type-assertions': [
+      'error',
+      {
+        assertionStyle: 'as',
+        objectLiteralTypeAssertions: 'never',
+      },
+    ],
+
+    // go/tsstyle#interfaces-vs-type-aliases
+    '@typescript-eslint/consistent-type-definitions': ['error', 'interface'],
   }),
+  'overrides': [{
+    files: ['**/*.ts'],
+    parserOptions: {
+      // eslint-disable-next-line no-undef
+      tsconfigRootDir: __dirname,
+      project: './tsconfig_base.json',
+    },
+    rules: {
+      // go/tsstyle#use-readonly
+      '@typescript-eslint/prefer-readonly': 'error',
+    },
+  }],
 };
diff --git a/ash/webui/camera_app_ui/resources/js/app_window.ts b/ash/webui/camera_app_ui/resources/js/app_window.ts
index 57157d0..af7a61a 100644
--- a/ash/webui/camera_app_ui/resources/js/app_window.ts
+++ b/ash/webui/camera_app_ui/resources/js/app_window.ts
@@ -52,7 +52,7 @@
   private inClosingItself = false;
   private readonly errors: ErrorInfo[] = [];
   private readonly perfs: PerfEntry[] = [];
-  private launchedTime = performance.now();
+  private readonly launchedTime = performance.now();
   /**
    * @param fromColdStart Whether this app is launched from a cold start. It is
    *     used for performance measurement.
diff --git a/ash/webui/camera_app_ui/resources/js/device/camera_operation.ts b/ash/webui/camera_app_ui/resources/js/device/camera_operation.ts
index 97ea1919..35b77bfc 100644
--- a/ash/webui/camera_app_ui/resources/js/device/camera_operation.ts
+++ b/ash/webui/camera_app_ui/resources/js/device/camera_operation.ts
@@ -322,12 +322,12 @@
 export class OperationScheduler {
   public cameraInfo: CameraInfo|null = null;
   private pendingUpdateInfo: CameraInfo|null = null;
-  private firstInfoUpdate = new WaitableEvent();
+  private readonly firstInfoUpdate = new WaitableEvent();
 
   readonly reconfigurer: Reconfigurer;
   readonly capturer: Capturer;
   private ongoingOperationType: OperationType|null = null;
-  private pendingReconfigureWaiters: CancelableEvent<boolean>[] = [];
+  private pendingReconfigureWaiters: Array<CancelableEvent<boolean>> = [];
   public readonly photoPreferrer = new PhotoConstraintsPreferrer();
   public readonly videoPreferrer = new VideoConstraintsPreferrer();
   public readonly modes: Modes;
diff --git a/ash/webui/camera_app_ui/resources/js/device/device_info_updater.ts b/ash/webui/camera_app_ui/resources/js/device/device_info_updater.ts
index 6d0ea98b..9ef595f 100644
--- a/ash/webui/camera_app_ui/resources/js/device/device_info_updater.ts
+++ b/ash/webui/camera_app_ui/resources/js/device/device_info_updater.ts
@@ -164,7 +164,7 @@
   /**
    * Gets Camera3DeviceInfo for all available video devices.
    */
-  getCamera3DevicesInfo(): Array<Camera3DeviceInfo>|null {
+  getCamera3DevicesInfo(): Camera3DeviceInfo[]|null {
     return this.camera3DevicesInfo;
   }
 }
diff --git a/ash/webui/camera_app_ui/resources/js/device/mode/video.ts b/ash/webui/camera_app_ui/resources/js/device/mode/video.ts
index c415cfb5..609f244 100644
--- a/ash/webui/camera_app_ui/resources/js/device/mode/video.ts
+++ b/ash/webui/camera_app_ui/resources/js/device/mode/video.ts
@@ -179,7 +179,7 @@
   /**
    * Record-time for the elapsed gif recording time.
    */
-  private gifRecordTime: GifRecordTime;
+  private readonly gifRecordTime: GifRecordTime;
 
   /**
    * Record type of ongoing recording.
diff --git a/ash/webui/camera_app_ui/resources/js/device/stream_manager.ts b/ash/webui/camera_app_ui/resources/js/device/stream_manager.ts
index 1674b05..31a9115 100644
--- a/ash/webui/camera_app_ui/resources/js/device/stream_manager.ts
+++ b/ash/webui/camera_app_ui/resources/js/device/stream_manager.ts
@@ -85,7 +85,7 @@
   /**
    * Filter out lagging 720p on grunt. See https://crbug.com/1122852.
    */
-  private videoConfigFilter: (config: VideoConfig) => boolean;
+  private readonly videoConfigFilter: (config: VideoConfig) => boolean;
 
   private constructor() {
     this.videoConfigFilter = (() => {
diff --git a/ash/webui/camera_app_ui/resources/js/device/type.ts b/ash/webui/camera_app_ui/resources/js/device/type.ts
index 7819819..e59f0be 100644
--- a/ash/webui/camera_app_ui/resources/js/device/type.ts
+++ b/ash/webui/camera_app_ui/resources/js/device/type.ts
@@ -17,8 +17,8 @@
 export type CameraViewUI = CaptureHandler;
 
 export class CameraInfo {
-  readonly devicesInfo: Array<MediaDeviceInfo>;
-  readonly camera3DevicesInfo: Array<Camera3DeviceInfo>|null;
+  readonly devicesInfo: MediaDeviceInfo[];
+  readonly camera3DevicesInfo: Camera3DeviceInfo[]|null;
 
   private readonly idToDeviceInfo: Map<string, MediaDeviceInfo>;
   private readonly idToCamera3DeviceInfo: Map<string, Camera3DeviceInfo>|null;
diff --git a/ash/webui/camera_app_ui/resources/js/externs/types.d.ts b/ash/webui/camera_app_ui/resources/js/externs/types.d.ts
index fc01b20..c1e5fbd7 100644
--- a/ash/webui/camera_app_ui/resources/js/externs/types.d.ts
+++ b/ash/webui/camera_app_ui/resources/js/externs/types.d.ts
@@ -85,7 +85,7 @@
 interface Window {
   loadTimeData: {
     getBoolean(id: string): boolean; getString(id: string): string;
-    getStringF(id: string, ...args: (number|string)[]): string;
+    getStringF(id: string, ...args: Array<number|string>): string;
   }
 }
 
@@ -104,16 +104,16 @@
 
 // Chrome private API for crash report.
 declare namespace chrome.crashReportPrivate {
-  export type ErrorInfo = {
-    message: string,
-    url: string,
-    columnNumber?: number,
-    debugId?: string,
-    lineNumber?: number,
-    product?: string,
-    stackTrace?: string,
-    version?: string,
-  };
+  export interface ErrorInfo {
+    message: string;
+    url: string;
+    columnNumber?: number;
+    debugId?: string;
+    lineNumber?: number;
+    product?: string;
+    stackTrace?: string;
+    version?: string;
+  }
   export const reportError: (info: ErrorInfo, callback: () => void) => void;
 }
 
@@ -152,7 +152,7 @@
 type LaunchConsumer = (params: LaunchParams) => void;
 
 interface LaunchParams {
-  readonly files: ReadonlyArray<FileSystemHandle>;
+  readonly files: readonly FileSystemHandle[];
 }
 
 // HTMLVideoElement.requestVideoFrameCallback, this is currently available in
@@ -196,7 +196,7 @@
   boundingBox: DOMRectReadOnly;
   rawValue: string;
   format: BarcodeFormat;
-  cornerPoints: ReadonlyArray<Point2D>;
+  cornerPoints: readonly Point2D[];
 }
 
 type BarcodeFormat =
diff --git a/ash/webui/camera_app_ui/resources/js/main.ts b/ash/webui/camera_app_ui/resources/js/main.ts
index aa7188b..174106f6 100644
--- a/ash/webui/camera_app_ui/resources/js/main.ts
+++ b/ash/webui/camera_app_ui/resources/js/main.ts
@@ -52,11 +52,11 @@
  * Creates the Camera App main object.
  */
 export class App {
-  private perfLogger: PerfLogger;
-  private intent: Intent|null;
+  private readonly perfLogger: PerfLogger;
+  private readonly intent: Intent|null;
   private readonly cameraManager: CameraManager;
-  private galleryButton = new GalleryButton();
-  private cameraView: Camera;
+  private readonly galleryButton = new GalleryButton();
+  private readonly cameraView: Camera;
 
   constructor({perfLogger, intent, facing, mode: defaultMode}: {
     perfLogger: PerfLogger,
diff --git a/ash/webui/camera_app_ui/resources/js/models/load_time_data.ts b/ash/webui/camera_app_ui/resources/js/models/load_time_data.ts
index 75cb4d5..cb70c45 100644
--- a/ash/webui/camera_app_ui/resources/js/models/load_time_data.ts
+++ b/ash/webui/camera_app_ui/resources/js/models/load_time_data.ts
@@ -23,7 +23,7 @@
  * Returns the I18N message generated by given |name| and |substitutions|.
  */
 export function getI18nMessage(
-    name: I18nString, ...substitutions: (string|number)[]): string {
+    name: I18nString, ...substitutions: Array<string|number>): string {
   return window.loadTimeData.getStringF(name, ...substitutions);
 }
 
diff --git a/ash/webui/camera_app_ui/resources/js/mojo/chrome_helper.ts b/ash/webui/camera_app_ui/resources/js/mojo/chrome_helper.ts
index 975c583b..24498bbc 100644
--- a/ash/webui/camera_app_ui/resources/js/mojo/chrome_helper.ts
+++ b/ash/webui/camera_app_ui/resources/js/mojo/chrome_helper.ts
@@ -64,7 +64,7 @@
   /**
    * An interface remote that is used to communicate with Chrome.
    */
-  private remote: CameraAppHelperRemote =
+  private readonly remote: CameraAppHelperRemote =
       wrapEndpoint(CameraAppHelper.getRemote());
 
   /**
diff --git a/ash/webui/camera_app_ui/resources/js/views/crop_document.ts b/ash/webui/camera_app_ui/resources/js/views/crop_document.ts
index 3e175f2..ffa4398 100644
--- a/ash/webui/camera_app_ui/resources/js/views/crop_document.ts
+++ b/ash/webui/camera_app_ui/resources/js/views/crop_document.ts
@@ -119,7 +119,7 @@
  * View controller for review document crop area page.
  */
 export class CropDocument extends Review<boolean> {
-  private imageFrame: HTMLDivElement;
+  private readonly imageFrame: HTMLDivElement;
 
   /**
    * Size of image frame.
@@ -137,8 +137,8 @@
    */
   private cornerSpaceSize: Size|null = null;
 
-  private cropAreaContainer: SVGElement;
-  private cropArea: SVGPolygonElement;
+  private readonly cropAreaContainer: SVGElement;
+  private readonly cropArea: SVGPolygonElement;
 
   /**
    * Index of |ROTATION| as current photo rotation.
@@ -146,7 +146,7 @@
   private rotation = 0;
 
   private initialCorners: Point[] = [];
-  private corners: Corner[];
+  private readonly corners: Corner[];
 
   constructor() {
     super(ViewName.CROP_DOCUMENT);
diff --git a/ash/webui/camera_app_ui/resources/js/views/dialog.ts b/ash/webui/camera_app_ui/resources/js/views/dialog.ts
index 36748ffa..0645d734 100644
--- a/ash/webui/camera_app_ui/resources/js/views/dialog.ts
+++ b/ash/webui/camera_app_ui/resources/js/views/dialog.ts
@@ -12,7 +12,7 @@
  * Creates the Dialog view controller.
  */
 export class Dialog extends View {
-  private positiveButton: HTMLButtonElement;
+  private readonly positiveButton: HTMLButtonElement;
   private negativeButton: HTMLButtonElement|null;
   private messageHolder: HTMLElement;
 
diff --git a/ash/webui/camera_app_ui/resources/js/views/review.ts b/ash/webui/camera_app_ui/resources/js/views/review.ts
index dfcda98..6bedacf 100644
--- a/ash/webui/camera_app_ui/resources/js/views/review.ts
+++ b/ash/webui/camera_app_ui/resources/js/views/review.ts
@@ -57,7 +57,7 @@
  * Group of review options.
  */
 export class OptionGroup<T> {
-  readonly options: Option<T>[];
+  readonly options: Array<Option<T>>;
   readonly template: ButtonGroupTemplate;
 
   /** Constructs Options. */
diff --git a/ash/webui/camera_app_ui/resources/js/waitable_event.ts b/ash/webui/camera_app_ui/resources/js/waitable_event.ts
index 0365bcd..593308a 100644
--- a/ash/webui/camera_app_ui/resources/js/waitable_event.ts
+++ b/ash/webui/camera_app_ui/resources/js/waitable_event.ts
@@ -12,7 +12,7 @@
   // recognize that. Disable the check by adding "!" to the property name.
   protected resolve!: (val: T) => void;
   protected reject!: (val: Error) => void;
-  private promise: Promise<T>;
+  private readonly promise: Promise<T>;
 
   constructor() {
     this.promise = new Promise((resolve, reject) => {
diff --git a/ash/webui/eche_app_ui/eche_message_receiver_impl_unittest.cc b/ash/webui/eche_app_ui/eche_message_receiver_impl_unittest.cc
index 2f79bd9..a03362d 100644
--- a/ash/webui/eche_app_ui/eche_message_receiver_impl_unittest.cc
+++ b/ash/webui/eche_app_ui/eche_message_receiver_impl_unittest.cc
@@ -23,6 +23,8 @@
 
   size_t apps_setup_response_num_calls() const { return apps_setup_response_; }
 
+  size_t status_change_num_calls() const { return status_change_num_calls_; }
+
   proto::GetAppsAccessStateResponse get_last_apps_access_state() const {
     return last_apps_access_state_response_;
   }
@@ -31,6 +33,10 @@
     return last_apps_setup_reponse_;
   }
 
+  proto::StatusChangeType get_last_status_change_type() const {
+    return last_status_change_type_;
+  }
+
   // EcheMessageReceiver::Observer:
   void OnGetAppsAccessStateResponseReceived(
       proto::GetAppsAccessStateResponse apps_access_state_response) override {
@@ -42,13 +48,18 @@
     last_apps_setup_reponse_ = apps_setup_response;
     ++apps_setup_response_;
   }
-  void OnStatusChange(proto::StatusChangeType status_change_type) override {}
+  void OnStatusChange(proto::StatusChangeType status_change_type) override {
+    last_status_change_type_ = status_change_type;
+    ++status_change_num_calls_;
+  }
 
  private:
   size_t apps_access_state_response_num_calls_ = 0;
   size_t apps_setup_response_ = 0;
+  size_t status_change_num_calls_ = 0;
   proto::GetAppsAccessStateResponse last_apps_access_state_response_;
   proto::SendAppsSetupResponse last_apps_setup_reponse_;
+  proto::StatusChangeType last_status_change_type_;
 };
 }  // namespace
 
@@ -81,6 +92,10 @@
     return fake_observer_.apps_setup_response_num_calls();
   }
 
+  size_t GetNumStatusChangeCalls() const {
+    return fake_observer_.status_change_num_calls();
+  }
+
   proto::GetAppsAccessStateResponse GetLastAppsAccessState() const {
     return fake_observer_.get_last_apps_access_state();
   }
@@ -89,6 +104,10 @@
     return fake_observer_.get_last_apps_setup_response();
   }
 
+  proto::StatusChangeType GetLastStatusChangeType() const {
+    return fake_observer_.get_last_status_change_type();
+  }
+
   FakeObserver fake_observer_;
   std::unique_ptr<secure_channel::FakeConnectionManager>
       fake_connection_manager_;
@@ -110,6 +129,7 @@
 
   EXPECT_EQ(1u, GetNumAppsAccessStateResponseCalls());
   EXPECT_EQ(0u, GetNumAppsSetupResponseCalls());
+  EXPECT_EQ(0u, GetNumStatusChangeCalls());
   EXPECT_EQ(eche_app::proto::Result::RESULT_ERROR_ACTION_FAILED,
             actual_apps_state.result());
   EXPECT_EQ(eche_app::proto::AppsAccessState::ACCESS_GRANTED,
@@ -131,11 +151,28 @@
 
   EXPECT_EQ(0u, GetNumAppsAccessStateResponseCalls());
   EXPECT_EQ(1u, GetNumAppsSetupResponseCalls());
+  EXPECT_EQ(0u, GetNumStatusChangeCalls());
   EXPECT_EQ(eche_app::proto::Result::RESULT_ERROR_ACTION_FAILED,
             actual_apps_setup_response.result());
   EXPECT_EQ(eche_app::proto::AppsAccessState::ACCESS_GRANTED,
             actual_apps_setup_response.apps_access_state());
 }
 
+TEST_F(EcheMessageReceiverImplTest, OnStatusChangeReceived) {
+  proto::StatusChange status_change;
+  status_change.set_type(proto::StatusChangeType::TYPE_STREAM_START);
+  proto::ExoMessage message;
+  *message.mutable_status_change() = std::move(status_change);
+
+  fake_connection_manager_->NotifyMessageReceived(message.SerializeAsString());
+
+  proto::StatusChangeType status_change_type = GetLastStatusChangeType();
+
+  EXPECT_EQ(0u, GetNumAppsAccessStateResponseCalls());
+  EXPECT_EQ(0u, GetNumAppsSetupResponseCalls());
+  EXPECT_EQ(1u, GetNumStatusChangeCalls());
+  EXPECT_EQ(proto::StatusChangeType::TYPE_STREAM_START, status_change_type);
+}
+
 }  // namespace eche_app
 }  // namespace ash
diff --git a/ash/webui/shimless_rma/backend/shimless_rma_service.cc b/ash/webui/shimless_rma/backend/shimless_rma_service.cc
index ffabc6b3..d5a0e6e 100644
--- a/ash/webui/shimless_rma/backend/shimless_rma_service.cc
+++ b/ash/webui/shimless_rma/backend/shimless_rma_service.cc
@@ -273,6 +273,20 @@
   TransitionNextStateGeneric(std::move(callback));
 }
 
+void ShimlessRmaService::SetWipeDevice(bool should_wipe_device,
+                                       SetWipeDeviceCallback callback) {
+  if (state_proto_.state_case() != rmad::RmadState::kWipeSelection) {
+    LOG(ERROR) << "SetWipeDevice called from incorrect state "
+               << state_proto_.state_case();
+    std::move(callback).Run(RmadStateToMojo(state_proto_.state_case()),
+                            can_abort_, can_go_back_,
+                            rmad::RmadErrorCode::RMAD_ERROR_REQUEST_INVALID);
+    return;
+  }
+  state_proto_.mutable_wipe_selection()->set_wipe_device(should_wipe_device);
+  TransitionNextStateGeneric(std::move(callback));
+}
+
 void ShimlessRmaService::ChooseManuallyDisableWriteProtect(
     ChooseManuallyDisableWriteProtectCallback callback) {
   if (state_proto_.state_case() != rmad::RmadState::kWpDisableMethod) {
diff --git a/ash/webui/shimless_rma/backend/shimless_rma_service.h b/ash/webui/shimless_rma/backend/shimless_rma_service.h
index 3b7f747be..f909a85 100644
--- a/ash/webui/shimless_rma/backend/shimless_rma_service.h
+++ b/ash/webui/shimless_rma/backend/shimless_rma_service.h
@@ -52,6 +52,8 @@
   void SetSameOwner(SetSameOwnerCallback callback) override;
   void SetDifferentOwner(SetDifferentOwnerCallback callback) override;
 
+  void SetWipeDevice(bool wipe_device, SetWipeDeviceCallback) override;
+
   void ChooseManuallyDisableWriteProtect(
       ChooseManuallyDisableWriteProtectCallback callback) override;
   void ChooseRsuDisableWriteProtect(
diff --git a/ash/webui/shimless_rma/backend/shimless_rma_service_unittest.cc b/ash/webui/shimless_rma/backend/shimless_rma_service_unittest.cc
index 1a26025..5c97e3d 100644
--- a/ash/webui/shimless_rma/backend/shimless_rma_service_unittest.cc
+++ b/ash/webui/shimless_rma/backend/shimless_rma_service_unittest.cc
@@ -123,6 +123,9 @@
         state->set_allocated_device_destination(
             new rmad::DeviceDestinationState());
         break;
+      case rmad::RmadState::kWipeSelection:
+        state->set_allocated_wipe_selection(new rmad::WipeSelectionState());
+        break;
       case rmad::RmadState::kWpDisableMethod:
         state->set_allocated_wp_disable_method(
             new rmad::WriteProtectDisableMethodState());
@@ -630,6 +633,39 @@
   run_loop.Run();
 }
 
+TEST_F(ShimlessRmaServiceTest, SetWipeDevice) {
+  const std::vector<rmad::GetStateReply> fake_states = {
+      CreateStateReply(rmad::RmadState::kWipeSelection, rmad::RMAD_ERROR_OK),
+      CreateStateReply(rmad::RmadState::kDeviceDestination,
+                       rmad::RMAD_ERROR_OK)};
+  fake_rmad_client_()->SetFakeStateReplies(std::move(fake_states));
+  fake_rmad_client_()->check_state_callback =
+      base::BindRepeating([](const rmad::RmadState& state) {
+        EXPECT_EQ(state.state_case(), rmad::RmadState::kWipeSelection);
+        EXPECT_TRUE(state.wipe_selection().wipe_device());
+      });
+  base::RunLoop run_loop;
+  shimless_rma_provider_->GetCurrentState(base::BindLambdaForTesting(
+      [&](mojom::State state, bool can_cancel, bool can_go_back,
+          rmad::RmadErrorCode error) {
+        EXPECT_EQ(state, mojom::State::kChooseWipeDevice);
+        EXPECT_EQ(error, rmad::RmadErrorCode::RMAD_ERROR_OK);
+      }));
+  run_loop.RunUntilIdle();
+
+  const bool expected_wipe_device = true;
+  shimless_rma_provider_->SetWipeDevice(
+      expected_wipe_device,
+      base::BindLambdaForTesting([&](mojom::State state, bool can_cancel,
+                                     bool can_go_back,
+                                     rmad::RmadErrorCode error) {
+        EXPECT_EQ(state, mojom::State::kChooseDestination);
+        EXPECT_EQ(error, rmad::RmadErrorCode::RMAD_ERROR_OK);
+        run_loop.Quit();
+      }));
+  run_loop.Run();
+}
+
 TEST_F(ShimlessRmaServiceTest, SetDifferentOwnerFromWrongStateFails) {
   const std::vector<rmad::GetStateReply> fake_states = {
       CreateStateReply(rmad::RmadState::kRestock, rmad::RMAD_ERROR_OK)};
diff --git a/ash/webui/shimless_rma/mojom/shimless_rma.mojom b/ash/webui/shimless_rma/mojom/shimless_rma.mojom
index 35afc98..436b4822 100644
--- a/ash/webui/shimless_rma/mojom/shimless_rma.mojom
+++ b/ash/webui/shimless_rma/mojom/shimless_rma.mojom
@@ -489,6 +489,14 @@
           RmadErrorCode error);
 
   ///////////////////////////////////////
+  // Method for kChooseWipeDevice state.
+  //
+  // Set the RMA state to wipe or preserve the device data on RMA completion.
+  SetWipeDevice(bool should_wipe_device)
+      => (State state, bool can_cancel, bool can_go_back,
+          RmadErrorCode error);
+
+  ///////////////////////////////////////
   // Methods for kChooseWriteProtectDisableMethod state.
   //
   // Choose to disabled HWWP manually e.g. by disconnecting the battery
diff --git a/ash/webui/shimless_rma/resources/fake_shimless_rma_service.js b/ash/webui/shimless_rma/resources/fake_shimless_rma_service.js
index 871082d..e2cb3f26 100644
--- a/ash/webui/shimless_rma/resources/fake_shimless_rma_service.js
+++ b/ash/webui/shimless_rma/resources/fake_shimless_rma_service.js
@@ -278,6 +278,15 @@
   }
 
   /**
+   * @param {boolean} shouldWipeDevice
+   * @return {!Promise<!StateResult>}
+   */
+  setWipeDevice(shouldWipeDevice) {
+    return this.getNextStateForMethod_(
+        'setWipeDevice', State.kChooseWipeDevice);
+  }
+
+  /**
    * @return {!Promise<!{available: boolean}>}
    */
   manualDisableWriteProtectAvailable() {
diff --git a/ash/webui/shimless_rma/resources/onboarding_choose_wipe_device_page.html b/ash/webui/shimless_rma/resources/onboarding_choose_wipe_device_page.html
index 4b7f702..951ccef 100644
--- a/ash/webui/shimless_rma/resources/onboarding_choose_wipe_device_page.html
+++ b/ash/webui/shimless_rma/resources/onboarding_choose_wipe_device_page.html
@@ -4,14 +4,15 @@
   <div slot="left-pane">
     <h1>[[i18n('wipeDeviceTitleText')]]</h1>
     <!-- TODO(gavinwill): Replace |disabled| with variable -->
-    <cr-radio-group id="wipeDeviceRadioGroup" disabled>
-      <cr-radio-button name="wipeDevice">
+    <cr-radio-group id="wipeDeviceRadioGroup" disabled="[[allButtonsDisabled]]"
+        on-selected-changed="onOptionChanged_">
+      <cr-radio-button name="[[wipeDeviceOption_.WIPE_DEVICE]]">
         [[i18n('wipeDeviceRemoveDataLabel')]]
       </cr-radio-button>
       <div class="indented-description">
         [[i18n('wipeDeviceRemoveDataDescription')]]
       </div>
-      <cr-radio-button name="preserveData">
+      <cr-radio-button name="[[wipeDeviceOption_.PRESERVE_DATA]]">
         [[i18n('wipeDevicePreserveDataLabel')]]
       </cr-radio-button>
       <div class="indented-description">
diff --git a/ash/webui/shimless_rma/resources/onboarding_choose_wipe_device_page.js b/ash/webui/shimless_rma/resources/onboarding_choose_wipe_device_page.js
index 962d6ee..ea553d0 100644
--- a/ash/webui/shimless_rma/resources/onboarding_choose_wipe_device_page.js
+++ b/ash/webui/shimless_rma/resources/onboarding_choose_wipe_device_page.js
@@ -8,9 +8,13 @@
 import '//resources/cr_elements/cr_radio_button/cr_radio_button.m.js';
 import '//resources/cr_elements/cr_radio_group/cr_radio_group.m.js';
 
+import {assert} from 'chrome://resources/js/assert.m.js';
 import {I18nBehavior, I18nBehaviorInterface} from 'chrome://resources/js/i18n_behavior.m.js';
 import {html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
+import {getShimlessRmaService} from './mojo_interface_provider.js';
+import {ShimlessRmaServiceInterface, StateResult} from './shimless_rma_types.js';
+
 /**
  * @fileoverview
  * 'onboarding-choose-wipe-device-page' allows user to select between wiping all
@@ -25,6 +29,15 @@
 const OnboardingChooseWipeDevicePageBase =
     mixinBehaviors([I18nBehavior], PolymerElement);
 
+/**
+ * Supported options for the wipe device state.
+ * @enum {string}
+ */
+const WipeDeviceOption = {
+  WIPE_DEVICE: 'wipeDevice',
+  PRESERVE_DATA: 'preserveData',
+};
+
 /** @polymer */
 export class OnboardingChooseWipeDevicePage extends
     OnboardingChooseWipeDevicePageBase {
@@ -35,6 +48,57 @@
   static get template() {
     return html`{__html_template__}`;
   }
+
+  static get properties() {
+    return {
+      /**
+       * Used to refer to the enum values in HTML file.
+       * @protected {?WipeDeviceOption}
+       */
+      wipeDeviceOption_: {
+        type: Object,
+        value: WipeDeviceOption,
+      },
+
+      // Set by shimless_rma.js.
+      allButtonsDisabled: Boolean,
+
+      /** @protected */
+      selectedWipeDeviceOption_: {
+        type: String,
+        value: '',
+      },
+    };
+  }
+
+  constructor() {
+    super();
+    /** @private {ShimlessRmaServiceInterface} */
+    this.shimlessRmaService_ = getShimlessRmaService();
+  }
+
+
+  /**
+   * @param {!CustomEvent<{value: string}>} event
+   * @protected
+   */
+  onOptionChanged_(event) {
+    this.selectedWipeDeviceOption_ =
+        /** @type {!WipeDeviceOption} */ (event.detail.value);
+
+    // Enable the next button when an option is chosen.
+    this.dispatchEvent(new CustomEvent(
+        'disable-next-button',
+        {bubbles: true, composed: true, detail: false},
+        ));
+  }
+
+  /** @return {!Promise<!StateResult>} */
+  onNextButtonClick() {
+    assert(!!this.selectedWipeDeviceOption_);
+    return this.shimlessRmaService_.setWipeDevice(
+        this.selectedWipeDeviceOption_ === WipeDeviceOption.WIPE_DEVICE);
+  }
 }
 
 customElements.define(
diff --git a/build/fuchsia/linux.sdk.sha1 b/build/fuchsia/linux.sdk.sha1
index 138b620..e817926 100644
--- a/build/fuchsia/linux.sdk.sha1
+++ b/build/fuchsia/linux.sdk.sha1
@@ -1 +1 @@
-7.20220222.2.2
+7.20220223.0.1
diff --git a/build/fuchsia/linux_internal.sdk.sha1 b/build/fuchsia/linux_internal.sdk.sha1
index 7079ef7..e817926 100644
--- a/build/fuchsia/linux_internal.sdk.sha1
+++ b/build/fuchsia/linux_internal.sdk.sha1
@@ -1 +1 @@
-7.20220222.4.1
+7.20220223.0.1
diff --git a/build/fuchsia/mac.sdk.sha1 b/build/fuchsia/mac.sdk.sha1
index 37c86238..e817926 100644
--- a/build/fuchsia/mac.sdk.sha1
+++ b/build/fuchsia/mac.sdk.sha1
@@ -1 +1 @@
-7.20220222.2.1
+7.20220223.0.1
diff --git a/cc/paint/image_transfer_cache_entry.cc b/cc/paint/image_transfer_cache_entry.cc
index 62e33bb1..c641fca 100644
--- a/cc/paint/image_transfer_cache_entry.cc
+++ b/cc/paint/image_transfer_cache_entry.cc
@@ -26,10 +26,6 @@
 namespace cc {
 namespace {
 
-// TODO(https://crbug.com/1286076): Plumb the true parameters in here.
-constexpr float kTempMaxLuminanceNits = 100.f;
-constexpr float kTempHDRMaxLuminanceRelative = 1.f;
-
 struct Context {
   const std::vector<sk_sp<SkImage>> sk_planes_;
 };
@@ -92,42 +88,104 @@
   return image;
 }
 
-// TODO(ericrk): Replace calls to this with calls to SkImage::makeTextureImage,
-// once that function handles colorspaces. https://crbug.com/834837
-sk_sp<SkImage> MakeTextureImage(
+sk_sp<SkImage> MakeGpuSkImage(
     GrDirectContext* context,
-    sk_sp<SkImage> source_image,
-    absl::optional<TargetColorParams> target_color_params,
-    GrMipMapped mip_mapped) {
-  // Step 1: Upload image and generate mips if necessary. If we will be applying
-  // a color-space conversion, don't generate mips yet, instead do it after
-  // conversion, in step 3.
-  // NOTE: |target_color_space| is only passed over the transfer cache if needed
-  // (non-null, different from the source color space).
-  bool add_mips_after_color_conversion =
-      target_color_params && mip_mapped == GrMipMapped::kYes;
-  sk_sp<SkImage> uploaded_image = source_image->makeTextureImage(
-      context, add_mips_after_color_conversion ? GrMipMapped::kNo : mip_mapped,
-      SkBudgeted::kNo);
+    const SkPixmap& pixmap,
+    uint32_t width,
+    uint32_t height,
+    bool needs_mips,
+    absl::optional<TargetColorParams> target_color_params) {
+  sk_sp<SkImage> source_image =
+      SkImage::MakeFromRaster(pixmap, nullptr, nullptr);
+  if (!source_image)
+    return nullptr;
 
-  // Step 2: Apply a color-space conversion if necessary.
-  if (uploaded_image && target_color_params) {
+  // If we are going to be applying color space conversion, then we defer mipmap
+  // generation until after the conversion.
+  bool add_mips_after_color_conversion = target_color_params && needs_mips;
+  GrMipMapped mip_mapped_for_upload =
+      needs_mips && !add_mips_after_color_conversion ? GrMipMapped::kYes
+                                                     : GrMipMapped::kNo;
+
+  // Upload the image.
+  sk_sp<SkImage> image = source_image->makeTextureImage(
+      context, mip_mapped_for_upload, SkBudgeted::kNo);
+  if (!image) {
+    DLOG(ERROR) << "Image upload failed.";
+    return nullptr;
+  }
+
+  // Apply a color-space conversion if necessary.
+  if (target_color_params) {
     // TODO(https://crbug.com/1286088): Pass a shared cache as a parameter.
     gfx::ColorConversionSkFilterCache cache;
-    uploaded_image = cache.ConvertImage(
-        uploaded_image, target_color_params->color_space.ToSkColorSpace(),
+    image = cache.ConvertImage(
+        image, target_color_params->color_space.ToSkColorSpace(),
         target_color_params->sdr_max_luminance_nits,
         target_color_params->hdr_max_luminance_relative, context);
+    if (!image) {
+      DLOG(ERROR) << "Color conversion failed.";
+      return nullptr;
+    }
+
+    // Perform the deferred mipmap generation.
+    if (add_mips_after_color_conversion) {
+      image =
+          image->makeTextureImage(context, GrMipMapped::kYes, SkBudgeted::kNo);
+      if (!image) {
+        DLOG(ERROR) << "Mipmap generation using makeTextureImage failed.";
+        return nullptr;
+      }
+    }
   }
 
-  // Step 3: If we had a colorspace conversion, we couldn't mipmap in step 1, so
-  // add mips here.
-  if (uploaded_image && add_mips_after_color_conversion) {
-    uploaded_image = uploaded_image->makeTextureImage(
-        context, GrMipMapped::kYes, SkBudgeted::kNo);
-  }
+  // Make sure the GPU work to create the backing texture is issued.
+  image->getBackendTexture(true /* flushPendingGrContextIO */);
+  return image;
+}
 
-  return uploaded_image;
+sk_sp<SkImage> MakeCpuSkImage(
+    const SkPixmap& pixmap,
+    uint32_t width,
+    uint32_t height,
+    absl::optional<TargetColorParams> target_color_params) {
+  sk_sp<SkImage> original = SkImage::MakeFromRaster(
+      pixmap, [](const void*, void*) {}, nullptr);
+  if (!original)
+    return nullptr;
+  sk_sp<SkImage> image;
+  if (target_color_params) {
+    // TODO(https://crbug.com/1286088): Pass a shared cache as a parameter.
+    gfx::ColorConversionSkFilterCache cache;
+    image = cache.ConvertImage(
+        original, target_color_params->color_space.ToSkColorSpace(),
+        target_color_params->sdr_max_luminance_nits,
+        target_color_params->hdr_max_luminance_relative, /*context=*/nullptr);
+    // If color space conversion is a noop, use original data.
+    if (image == original)
+      image = SkImage::MakeRasterCopy(pixmap);
+  } else {
+    // No color conversion to do, use original data.
+    image = SkImage::MakeRasterCopy(pixmap);
+  }
+  return image;
+}
+
+size_t TargetColorParamsSize(
+    const absl::optional<TargetColorParams>& target_color_params) {
+  // uint32 for whether or not there are going to be parameters.
+  size_t target_color_params_size = sizeof(uint32_t);
+  if (target_color_params) {
+    // The target color space.
+    target_color_params_size +=
+        sizeof(uint64_t) +
+        target_color_params->color_space.ToSkColorSpace()->writeToMemory(
+            nullptr);
+    // floats for the SDR and HDR maximum luminance.
+    target_color_params_size += sizeof(float);
+    target_color_params_size += sizeof(float);
+  }
+  return target_color_params_size;
 }
 
 }  // namespace
@@ -148,18 +206,19 @@
 
 ClientImageTransferCacheEntry::ClientImageTransferCacheEntry(
     const SkPixmap* pixmap,
-    const SkColorSpace* target_color_space,
+    absl::optional<TargetColorParams> target_color_params,
     bool needs_mips)
     : needs_mips_(needs_mips),
+      target_color_params_(target_color_params),
       id_(GetNextId()),
       pixmap_(pixmap),
-      target_color_space_(target_color_space),
       decoded_color_space_(nullptr) {
-  size_t target_color_space_size =
-      target_color_space ? target_color_space->writeToMemory(nullptr) : 0u;
-  size_t pixmap_color_space_size =
-      pixmap_->colorSpace() ? pixmap_->colorSpace()->writeToMemory(nullptr)
-                            : 0u;
+  const size_t target_color_params_size =
+      TargetColorParamsSize(target_color_params_);
+  const size_t pixmap_color_space_size =
+      sizeof(uint64_t) + (pixmap_->colorSpace()
+                              ? pixmap_->colorSpace()->writeToMemory(nullptr)
+                              : 0);
 
   // x64 has 8-byte alignment for uint64_t even though x86 has 4-byte
   // alignment.  Always use 8 byte alignment.
@@ -175,8 +234,8 @@
   safe_size += sizeof(uint32_t);  // has mips
   safe_size += sizeof(uint64_t) + align;  // pixels size + alignment
   safe_size += sizeof(uint64_t) + align;  // row bytes + alignment
-  safe_size += target_color_space_size + sizeof(uint64_t) + align;
-  safe_size += pixmap_color_space_size + sizeof(uint64_t) + align;
+  safe_size += target_color_params_size + align;
+  safe_size += pixmap_color_space_size + align;
   // Include 4 bytes of padding so we can always align our data pointer to a
   // 4-byte boundary.
   safe_size += 4;
@@ -190,12 +249,13 @@
     SkYUVAInfo::Subsampling subsampling,
     const SkColorSpace* decoded_color_space,
     SkYUVColorSpace yuv_color_space,
+    absl::optional<TargetColorParams> target_color_params,
     bool needs_mips)
     : needs_mips_(needs_mips),
       plane_config_(plane_config),
+      target_color_params_(target_color_params),
       id_(GetNextId()),
       pixmap_(nullptr),
-      target_color_space_(nullptr),
       decoded_color_space_(decoded_color_space),
       subsampling_(subsampling),
       yuv_color_space_(yuv_color_space) {
@@ -208,8 +268,13 @@
     yuv_pixmaps_->at(i) = &yuva_pixmaps[i];
   }
   DCHECK(IsYuv());
-  size_t decoded_color_space_size =
-      decoded_color_space ? decoded_color_space->writeToMemory(nullptr) : 0u;
+
+  const size_t target_color_params_size =
+      TargetColorParamsSize(target_color_params_);
+  const size_t decoded_color_space_size =
+      sizeof(uint64_t) + (decoded_color_space_
+                              ? decoded_color_space_->writeToMemory(nullptr)
+                              : 0u);
 
   // x64 has 8-byte alignment for uint64_t even though x86 has 4-byte
   // alignment.  Always use 8 byte alignment.
@@ -223,6 +288,7 @@
   safe_size += sizeof(uint32_t);  // has mips
   safe_size += sizeof(uint32_t);  // yuv_color_space
   safe_size += sizeof(uint32_t);  // yuv_color_type
+  safe_size += target_color_params_size + align;
   safe_size += decoded_color_space_size + align;
   safe_size += num_yuva_pixmaps * sizeof(uint64_t);  // plane widths
   safe_size += num_yuva_pixmaps * sizeof(uint64_t);  // plane heights
@@ -274,6 +340,17 @@
   PaintOpWriter writer(data.data(), data.size(), options);
   writer.Write(plane_config_);
 
+  if (target_color_params_) {
+    const uint32_t has_target_color_params = 1;
+    writer.Write(has_target_color_params);
+    writer.Write(target_color_params_->color_space.ToSkColorSpace().get());
+    writer.Write(target_color_params_->sdr_max_luminance_nits);
+    writer.Write(target_color_params_->hdr_max_luminance_relative);
+  } else {
+    const uint32_t has_target_color_params = 0;
+    writer.Write(has_target_color_params);
+  }
+
   if (plane_config_ != SkYUVAInfo::PlaneConfig::kUnknown) {
     ValidateYUVDataBeforeSerializing();
     writer.Write(subsampling_);
@@ -317,7 +394,6 @@
   writer.WriteSize(pixmap_size);
   writer.WriteSize(pixmap_->rowBytes());
   writer.Write(pixmap_->colorSpace());
-  writer.Write(target_color_space_);
   writer.AlignMemory(4);
   writer.WriteData(pixmap_size, pixmap_->addr());
 
@@ -403,92 +479,120 @@
   PaintOpReader reader(data.data(), data.size(), options);
   plane_config_ = SkYUVAInfo::PlaneConfig::kUnknown;
   reader.Read(&plane_config_);
-  if (plane_config_ != SkYUVAInfo::PlaneConfig::kUnknown) {
-    SkYUVAInfo::Subsampling subsampling = SkYUVAInfo::Subsampling::kUnknown;
-    reader.Read(&subsampling);
-    if (subsampling == SkYUVAInfo::Subsampling::kUnknown)
+
+  uint32_t has_target_color_params;
+  reader.Read(&has_target_color_params);
+  absl::optional<TargetColorParams> target_color_params;
+  if (has_target_color_params) {
+    sk_sp<SkColorSpace> target_color_space;
+    reader.Read(&target_color_space);
+    if (!target_color_space)
       return false;
-    subsampling_ = subsampling;
-    uint32_t needs_mips;
-    reader.Read(&needs_mips);
-    has_mips_ = needs_mips;
-    SkYUVColorSpace yuv_color_space;
-    reader.Read(&yuv_color_space);
-    yuv_color_space_ = yuv_color_space;
-    sk_sp<SkColorSpace> decoded_color_space;
-    reader.Read(&decoded_color_space);
-    SkColorType yuv_plane_color_type = kUnknown_SkColorType;
-    reader.Read(&yuv_plane_color_type);
-
-    int num_planes = SkYUVAInfo::NumPlanes(plane_config_);
-    // Read in each plane and reconstruct pixmaps.
-    for (int i = 0; i < num_planes; i++) {
-      uint32_t plane_width = 0;
-      reader.Read(&plane_width);
-      uint32_t plane_height = 0;
-      reader.Read(&plane_height);
-      size_t plane_stride = 0;
-      reader.ReadSize(&plane_stride);
-      // Because Skia does not support YUV rasterization from software planes,
-      // we require that each pixmap fits in a GPU texture. In the
-      // GpuImageDecodeCache, we veto YUV decoding if the planes would be too
-      // big.
-      uint32_t max_size = static_cast<uint32_t>(context_->maxTextureSize());
-      // We compute this for each plane in case a malicious renderer tries to
-      // send very large U or V planes.
-      fits_on_gpu_ = plane_stride <= max_size && plane_width <= max_size &&
-                     plane_height <= max_size;
-      if (!fits_on_gpu_ || plane_width == 0 || plane_height == 0 ||
-          plane_stride == 0)
-        return false;
-
-      size_t plane_bytes = 0;
-      reader.ReadSize(&plane_bytes);
-      SkImageInfo plane_pixmap_info =
-          SkImageInfo::Make(plane_width, plane_height, yuv_plane_color_type,
-                            kPremul_SkAlphaType, decoded_color_space);
-      if (plane_pixmap_info.computeMinByteSize() > plane_bytes)
-        return false;
-      // Align data to a 4-byte boundary, to match what we did when writing.
-      reader.AlignMemory(4);
-      const volatile void* plane_pixel_data =
-          reader.ExtractReadableMemory(plane_bytes);
-      if (!reader.valid())
-        return false;
-      DCHECK(SkIsAlign4(reinterpret_cast<uintptr_t>(plane_pixel_data)));
-
-      // Const-cast away the "volatile" on |pixel_data|. We specifically
-      // understand that a malicious caller may change our pixels under us, and
-      // are OK with this as the worst case scenario is visual corruption.
-      SkPixmap plane_pixmap(plane_pixmap_info,
-                            const_cast<const void*>(plane_pixel_data),
-                            plane_stride);
-      if (plane_pixmap.computeByteSize() > plane_bytes)
-        return false;
-
-      // Nothing should read the colorspace of individual planes because that
-      // information is stored in image_, so we pass nullptr.
-      sk_sp<SkImage> plane =
-          MakeSkImage(plane_pixmap, plane_width, plane_height,
-                      /*target_color_params=*/absl::nullopt);
-      if (!plane)
-        return false;
-      DCHECK(plane->isTextureBacked());
-
-      plane_sizes_.push_back(plane->textureSize());
-      size_ += plane_sizes_.back();
-
-      // |plane_images_| must be set for use in EnsureMips(), memory dumps, and
-      // unit tests.
-      plane_images_.push_back(std::move(plane));
-    }
-    DCHECK(yuv_color_space_.has_value());
-    image_ = MakeYUVImageFromUploadedPlanes(
-        context_, plane_images_, plane_config_, subsampling_.value(),
-        yuv_color_space_.value(), decoded_color_space);
-    return !!image_;
+    target_color_params = TargetColorParams();
+    target_color_params->color_space = gfx::ColorSpace(*target_color_space);
+    reader.Read(&target_color_params->sdr_max_luminance_nits);
+    reader.Read(&target_color_params->hdr_max_luminance_relative);
   }
 
+  if (plane_config_ != SkYUVAInfo::PlaneConfig::kUnknown)
+    return DeserializeYUVA(reader);
+  return DeserializeRGBA(reader, target_color_params);
+}
+
+bool ServiceImageTransferCacheEntry::DeserializeYUVA(PaintOpReader& reader) {
+  SkYUVAInfo::Subsampling subsampling = SkYUVAInfo::Subsampling::kUnknown;
+  reader.Read(&subsampling);
+  if (subsampling == SkYUVAInfo::Subsampling::kUnknown)
+    return false;
+  subsampling_ = subsampling;
+  uint32_t needs_mips;
+  reader.Read(&needs_mips);
+  has_mips_ = needs_mips;
+  SkYUVColorSpace yuv_color_space;
+  reader.Read(&yuv_color_space);
+  yuv_color_space_ = yuv_color_space;
+  sk_sp<SkColorSpace> decoded_color_space;
+  reader.Read(&decoded_color_space);
+  SkColorType yuv_plane_color_type = kUnknown_SkColorType;
+  reader.Read(&yuv_plane_color_type);
+
+  // In the GpuImageDecodeCache, we veto YUV decoding if the planes would be
+  // too big. Below, we will reject the image if any plane is too large.
+  fits_on_gpu_ = true;
+
+  int num_planes = SkYUVAInfo::NumPlanes(plane_config_);
+  // Read in each plane and reconstruct pixmaps.
+  for (int i = 0; i < num_planes; i++) {
+    uint32_t plane_width = 0;
+    reader.Read(&plane_width);
+    uint32_t plane_height = 0;
+    reader.Read(&plane_height);
+    size_t plane_stride = 0;
+    reader.ReadSize(&plane_stride);
+    // Because Skia does not support YUV rasterization from software planes,
+    // we require that each pixmap fits in a GPU texture. In the
+    // GpuImageDecodeCache, we veto YUV decoding if the planes would be too
+    // big.
+    const uint32_t max_texture_size =
+        static_cast<uint32_t>(context_->maxTextureSize());
+    // We compute this for each plane in case a malicious renderer tries to
+    // send very large U or V planes.
+    if (plane_width > max_texture_size || plane_width == 0 ||
+        plane_height > max_texture_size || plane_height == 0 ||
+        plane_stride > max_texture_size || plane_stride == 0) {
+      return false;
+    }
+
+    size_t plane_bytes = 0;
+    reader.ReadSize(&plane_bytes);
+    SkImageInfo plane_pixmap_info =
+        SkImageInfo::Make(plane_width, plane_height, yuv_plane_color_type,
+                          kPremul_SkAlphaType, decoded_color_space);
+    if (plane_pixmap_info.computeMinByteSize() > plane_bytes)
+      return false;
+    // Align data to a 4-byte boundary, to match what we did when writing.
+    reader.AlignMemory(4);
+    const volatile void* plane_pixel_data =
+        reader.ExtractReadableMemory(plane_bytes);
+    if (!reader.valid())
+      return false;
+    DCHECK(SkIsAlign4(reinterpret_cast<uintptr_t>(plane_pixel_data)));
+
+    // Const-cast away the "volatile" on |pixel_data|. We specifically
+    // understand that a malicious caller may change our pixels under us, and
+    // are OK with this as the worst case scenario is visual corruption.
+    SkPixmap plane_pixmap(plane_pixmap_info,
+                          const_cast<const void*>(plane_pixel_data),
+                          plane_stride);
+    if (plane_pixmap.computeByteSize() > plane_bytes)
+      return false;
+
+    // Nothing should read the colorspace of individual planes because that
+    // information is stored in image_, so we pass nullptr.
+    sk_sp<SkImage> plane = MakeGpuSkImage(
+        context_, plane_pixmap, plane_width, plane_height, has_mips_,
+        /*target_color_params=*/absl::nullopt);
+    if (!plane)
+      return false;
+    DCHECK(plane->isTextureBacked());
+
+    plane_sizes_.push_back(plane->textureSize());
+    size_ += plane_sizes_.back();
+
+    // |plane_images_| must be set for use in EnsureMips(), memory dumps, and
+    // unit tests.
+    plane_images_.push_back(std::move(plane));
+  }
+  DCHECK(yuv_color_space_.has_value());
+  image_ = MakeYUVImageFromUploadedPlanes(
+      context_, plane_images_, plane_config_, subsampling_.value(),
+      yuv_color_space_.value(), decoded_color_space);
+  return !!image_;
+}
+
+bool ServiceImageTransferCacheEntry::DeserializeRGBA(
+    PaintOpReader& reader,
+    absl::optional<TargetColorParams> target_color_params) {
   SkColorType color_type = kUnknown_SkColorType;
   reader.Read(&color_type);
 
@@ -510,17 +614,6 @@
   reader.ReadSize(&row_bytes);
   sk_sp<SkColorSpace> pixmap_color_space;
   reader.Read(&pixmap_color_space);
-  sk_sp<SkColorSpace> target_color_space;
-  reader.Read(&target_color_space);
-
-  absl::optional<TargetColorParams> target_color_params;
-  if (target_color_space) {
-    target_color_params = TargetColorParams();
-    target_color_params->color_space = gfx::ColorSpace(*target_color_space);
-    target_color_params->sdr_max_luminance_nits = kTempMaxLuminanceNits;
-    target_color_params->hdr_max_luminance_relative =
-        kTempHDRMaxLuminanceRelative;
-  }
 
   if (!reader.valid())
     return false;
@@ -547,7 +640,17 @@
   // that a malicious caller may change our pixels under us, and are OK with
   // this as the worst case scenario is visual corruption.
   SkPixmap pixmap(image_info, const_cast<const void*>(pixel_data), row_bytes);
-  image_ = MakeSkImage(pixmap, width, height, target_color_params);
+  const uint32_t max_texture_size =
+      static_cast<uint32_t>(context_->maxTextureSize());
+  fits_on_gpu_ = width <= max_texture_size && height <= max_texture_size;
+  if (fits_on_gpu_) {
+    image_ = MakeGpuSkImage(context_, pixmap, width, height, has_mips_,
+                            target_color_params);
+  } else {
+    // If the image is on the CPU, no work is needed to generate mips.
+    has_mips_ = true;
+    image_ = MakeCpuSkImage(pixmap, width, height, target_color_params);
+  }
 
   if (image_)
     size_ = image_->textureSize();
@@ -555,54 +658,6 @@
   return !!image_;
 }
 
-sk_sp<SkImage> ServiceImageTransferCacheEntry::MakeSkImage(
-    const SkPixmap& pixmap,
-    uint32_t width,
-    uint32_t height,
-    absl::optional<TargetColorParams> target_color_params) {
-  DCHECK(context_);
-
-  // Depending on whether the pixmap will fit in a GPU texture, either create
-  // a software or GPU SkImage.
-  uint32_t max_size = context_->maxTextureSize();
-  fits_on_gpu_ = width <= max_size && height <= max_size;
-  sk_sp<SkImage> image;
-  if (fits_on_gpu_) {
-    image = SkImage::MakeFromRaster(pixmap, nullptr, nullptr);
-    if (!image)
-      return nullptr;
-    image = MakeTextureImage(context_, std::move(image), target_color_params,
-                             has_mips_ ? GrMipMapped::kYes : GrMipMapped::kNo);
-  } else {
-    // If the image is on the CPU, no work is needed to generate mips.
-    has_mips_ = true;
-    sk_sp<SkImage> original =
-        SkImage::MakeFromRaster(pixmap, [](const void*, void*) {}, nullptr);
-    if (!original)
-      return nullptr;
-    if (target_color_params) {
-      // TODO(https://crbug.com/1286088): Pass a shared cache as a parameter.
-      gfx::ColorConversionSkFilterCache cache;
-      image = cache.ConvertImage(
-          original, target_color_params->color_space.ToSkColorSpace(),
-          target_color_params->sdr_max_luminance_nits,
-          target_color_params->hdr_max_luminance_relative, /*context=*/nullptr);
-      // If color space conversion is a noop, use original data.
-      if (image == original)
-        image = SkImage::MakeRasterCopy(pixmap);
-    } else {
-      // No color conversion to do, use original data.
-      image = SkImage::MakeRasterCopy(pixmap);
-    }
-  }
-
-  // Make sure the GPU work to create the backing texture is issued.
-  if (image)
-    image->getBackendTexture(true /* flushPendingGrContextIO */);
-
-  return image;
-}
-
 const sk_sp<SkImage>& ServiceImageTransferCacheEntry::GetPlaneImage(
     size_t index) const {
   DCHECK_GE(index, 0u);
diff --git a/cc/paint/image_transfer_cache_entry.h b/cc/paint/image_transfer_cache_entry.h
index 1e61c8a..b233f51d 100644
--- a/cc/paint/image_transfer_cache_entry.h
+++ b/cc/paint/image_transfer_cache_entry.h
@@ -27,6 +27,8 @@
 
 namespace cc {
 
+class PaintOpReader;
+
 static constexpr uint32_t kInvalidImageTransferCacheEntryId =
     static_cast<uint32_t>(-1);
 
@@ -47,15 +49,17 @@
 class CC_PAINT_EXPORT ClientImageTransferCacheEntry final
     : public ClientTransferCacheEntryBase<TransferCacheEntryType::kImage> {
  public:
-  explicit ClientImageTransferCacheEntry(const SkPixmap* pixmap,
-                                         const SkColorSpace* target_color_space,
-                                         bool needs_mips);
+  explicit ClientImageTransferCacheEntry(
+      const SkPixmap* pixmap,
+      absl::optional<TargetColorParams> target_color_params,
+      bool needs_mips);
   explicit ClientImageTransferCacheEntry(
       const SkPixmap yuva_pixmaps[],
       SkYUVAInfo::PlaneConfig plane_config,
       SkYUVAInfo::Subsampling subsampling,
       const SkColorSpace* decoded_color_space,
       SkYUVColorSpace yuv_color_space,
+      absl::optional<TargetColorParams> target_color_params,
       bool needs_mips);
   ~ClientImageTransferCacheEntry() final;
 
@@ -72,15 +76,13 @@
  private:
   const bool needs_mips_ = false;
   SkYUVAInfo::PlaneConfig plane_config_ = SkYUVAInfo::PlaneConfig::kUnknown;
+  absl::optional<TargetColorParams> target_color_params_;
   uint32_t id_;
   uint32_t size_ = 0;
   static base::AtomicSequenceNumber s_next_id_;
 
   // RGBX-only members.
   const raw_ptr<const SkPixmap> pixmap_;
-  const raw_ptr<const SkColorSpace>
-      target_color_space_;  // Unused for YUV because Skia handles colorspaces
-                            // at raster.
 
   // YUVA-only members.
   absl::optional<std::array<const SkPixmap*, SkYUVAInfo::kMaxPlanes>>
@@ -151,11 +153,9 @@
   }
 
  private:
-  sk_sp<SkImage> MakeSkImage(
-      const SkPixmap& pixmap,
-      uint32_t width,
-      uint32_t height,
-      absl::optional<TargetColorParams> target_color_params);
+  bool DeserializeYUVA(PaintOpReader& reader);
+  bool DeserializeRGBA(PaintOpReader& reader,
+                       absl::optional<TargetColorParams> target_color_params);
 
   raw_ptr<GrDirectContext> context_ = nullptr;
   std::vector<sk_sp<SkImage>> plane_images_;
diff --git a/cc/paint/image_transfer_cache_entry_unittest.cc b/cc/paint/image_transfer_cache_entry_unittest.cc
index e57ab84..92064810 100644
--- a/cc/paint/image_transfer_cache_entry_unittest.cc
+++ b/cc/paint/image_transfer_cache_entry_unittest.cc
@@ -228,7 +228,8 @@
   auto client_entry(std::make_unique<ClientImageTransferCacheEntry>(
       yuva_pixmaps.planes().data(), yuva_info.planeConfig(),
       yuva_info.subsampling(), nullptr /* decoded color space*/,
-      yuva_info.yuvColorSpace(), true /* needs_mips */));
+      yuva_info.yuvColorSpace(), absl::nullopt /* target_color_params */,
+      true /* needs_mips */));
   uint32_t size = client_entry->SerializedSize();
   std::vector<uint8_t> data(size);
   ASSERT_TRUE(client_entry->Serialize(
@@ -378,7 +379,8 @@
   SkBitmap bitmap;
   bitmap.allocPixels(
       SkImageInfo::MakeN32Premul(gr_context->maxTextureSize() + 1, 10));
-  ClientImageTransferCacheEntry client_entry(&bitmap.pixmap(), nullptr, true);
+  ClientImageTransferCacheEntry client_entry(&bitmap.pixmap(), absl::nullopt,
+                                             true);
   std::vector<uint8_t> storage(client_entry.SerializedSize());
   client_entry.Serialize(base::make_span(storage.data(), storage.size()));
 
@@ -404,7 +406,8 @@
   SkBitmap bitmap;
   bitmap.allocPixels(
       SkImageInfo::MakeN32Premul(gr_context->maxTextureSize() + 1, 10));
-  ClientImageTransferCacheEntry client_entry(&bitmap.pixmap(), nullptr, false);
+  ClientImageTransferCacheEntry client_entry(&bitmap.pixmap(), absl::nullopt,
+                                             false);
   std::vector<uint8_t> storage(client_entry.SerializedSize());
   client_entry.Serialize(base::make_span(storage.data(), storage.size()));
 
diff --git a/cc/test/test_options_provider.cc b/cc/test/test_options_provider.cc
index e239f9a..88e63cd 100644
--- a/cc/test/test_options_provider.cc
+++ b/cc/test/test_options_provider.cc
@@ -100,9 +100,9 @@
       SkBitmap::kZeroPixels_AllocFlag);
 
   // Create a transfer cache entry for this image.
-  auto color_space = SkColorSpace::MakeSRGB();
-  ClientImageTransferCacheEntry cache_entry(&bitmap.pixmap(), color_space.get(),
-                                            false /* needs_mips */);
+  TargetColorParams target_color_params;
+  ClientImageTransferCacheEntry cache_entry(
+      &bitmap.pixmap(), target_color_params, false /* needs_mips */);
   std::vector<uint8_t> data;
   data.resize(cache_entry.SerializedSize());
   if (!cache_entry.Serialize(base::span<uint8_t>(data.data(), data.size()))) {
diff --git a/cc/tiles/gpu_image_decode_cache.cc b/cc/tiles/gpu_image_decode_cache.cc
index d54d1123..3cc7134 100644
--- a/cc/tiles/gpu_image_decode_cache.cc
+++ b/cc/tiles/gpu_image_decode_cache.cc
@@ -2131,6 +2131,12 @@
     color_space = nullptr;
   }
 
+  absl::optional<TargetColorParams> target_color_params;
+  if (color_space) {
+    target_color_params = draw_image.target_color_params();
+    target_color_params->color_space = gfx::ColorSpace(*color_space);
+  }
+
   // Will be nullptr for non-HDR images or when we're using the default level.
   const bool needs_adjusted_color_space =
       NeedsColorSpaceAdjustedForUpload(draw_image);
@@ -2144,11 +2150,11 @@
           draw_image, image_data, color_space);
     } else if (image_data->yuva_pixmap_info.has_value()) {
       UploadImageIfNecessary_TransferCache_SoftwareDecode_YUVA(
-          draw_image, image_data, decoded_target_colorspace);
+          draw_image, image_data, decoded_target_colorspace, absl::nullopt);
     } else {
       UploadImageIfNecessary_TransferCache_SoftwareDecode_RGBA(
           draw_image, image_data, needs_adjusted_color_space,
-          decoded_target_colorspace, color_space);
+          decoded_target_colorspace, target_color_params);
     }
   } else {
     // Grab a reference to our decoded image. For the kCpu path, we will use
@@ -2217,7 +2223,8 @@
     UploadImageIfNecessary_TransferCache_SoftwareDecode_YUVA(
         const DrawImage& draw_image,
         ImageData* image_data,
-        sk_sp<SkColorSpace> decoded_target_colorspace) {
+        sk_sp<SkColorSpace> decoded_target_colorspace,
+        absl::optional<TargetColorParams> target_color_params) {
   DCHECK_EQ(image_data->mode, DecodedDataMode::kTransferCache);
   DCHECK(use_transfer_cache_);
   DCHECK(!image_data->decode.do_hardware_accelerated_decode());
@@ -2234,7 +2241,7 @@
       image_data->yuva_pixmap_info->yuvaInfo().subsampling(),
       decoded_target_colorspace.get(),
       image_data->yuva_pixmap_info->yuvaInfo().yuvColorSpace(),
-      image_data->needs_mips);
+      target_color_params, image_data->needs_mips);
   if (!image_entry.IsValid())
     return;
   InsertTransferCacheEntry(image_entry, image_data);
@@ -2246,7 +2253,7 @@
         ImageData* image_data,
         bool needs_adjusted_color_space,
         sk_sp<SkColorSpace> decoded_target_colorspace,
-        sk_sp<SkColorSpace> color_space) {
+        absl::optional<TargetColorParams> target_color_params) {
   DCHECK_EQ(image_data->mode, DecodedDataMode::kTransferCache);
   DCHECK(use_transfer_cache_);
   DCHECK(!image_data->decode.do_hardware_accelerated_decode());
@@ -2258,7 +2265,7 @@
   if (needs_adjusted_color_space)
     pixmap.setColorSpace(decoded_target_colorspace);
 
-  ClientImageTransferCacheEntry image_entry(&pixmap, color_space.get(),
+  ClientImageTransferCacheEntry image_entry(&pixmap, target_color_params,
                                             image_data->needs_mips);
   if (!image_entry.IsValid())
     return;
diff --git a/cc/tiles/gpu_image_decode_cache.h b/cc/tiles/gpu_image_decode_cache.h
index 127723d..51d3a9b9 100644
--- a/cc/tiles/gpu_image_decode_cache.h
+++ b/cc/tiles/gpu_image_decode_cache.h
@@ -696,13 +696,14 @@
   void UploadImageIfNecessary_TransferCache_SoftwareDecode_YUVA(
       const DrawImage& draw_image,
       ImageData* image_data,
-      sk_sp<SkColorSpace> decoded_target_colorspace);
+      sk_sp<SkColorSpace> decoded_target_colorspace,
+      absl::optional<TargetColorParams> target_color_params);
   void UploadImageIfNecessary_TransferCache_SoftwareDecode_RGBA(
       const DrawImage& draw_image,
       ImageData* image_data,
       bool needs_adjusted_color_space,
       sk_sp<SkColorSpace> decoded_target_colorspace,
-      sk_sp<SkColorSpace> color_space);
+      absl::optional<TargetColorParams> target_color_params);
   void UploadImageIfNecessary_GpuCpu_YUVA(
       const DrawImage& draw_image,
       ImageData* image_data,
diff --git a/chrome/VERSION b/chrome/VERSION
index 385316a..26f2a3e6 100644
--- a/chrome/VERSION
+++ b/chrome/VERSION
@@ -1,4 +1,4 @@
 MAJOR=101
 MINOR=0
-BUILD=4905
+BUILD=4906
 PATCH=0
diff --git a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/user_data/AssistantCollectUserDataModel.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/user_data/AssistantCollectUserDataModel.java
index d79a4a5..3f0f4a5 100644
--- a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/user_data/AssistantCollectUserDataModel.java
+++ b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/user_data/AssistantCollectUserDataModel.java
@@ -339,11 +339,12 @@
     }
 
     @CalledByNative
-    private void setSelectedShippingAddress(
-            @Nullable AssistantAutofillProfile shippingAddress, String[] errors) {
+    private void setSelectedShippingAddress(@Nullable AssistantAutofillProfile shippingAddress,
+            String fullDescription, String summaryDescription, String[] errors) {
         set(SELECTED_SHIPPING_ADDRESS,
                 shippingAddress == null ? null
-                                        : new AddressModel(shippingAddress, Arrays.asList(errors)));
+                                        : new AddressModel(shippingAddress, fullDescription,
+                                                summaryDescription, Arrays.asList(errors)));
     }
 
     @CalledByNative
@@ -484,9 +485,11 @@
     }
 
     @CalledByNative
-    private static void addShippingAddress(
-            List<AddressModel> addresses, AssistantAutofillProfile address, String[] errors) {
-        addresses.add(new AddressModel(address, Arrays.asList(errors)));
+    private static void addShippingAddress(List<AddressModel> addresses,
+            AssistantAutofillProfile address, String fullDescription, String summaryDescription,
+            String[] errors) {
+        addresses.add(new AddressModel(
+                address, fullDescription, summaryDescription, Arrays.asList(errors)));
     }
 
     @CalledByNative
diff --git a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/user_data/AssistantShippingAddressSection.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/user_data/AssistantShippingAddressSection.java
index 9e69a5e..3627a9d 100644
--- a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/user_data/AssistantShippingAddressSection.java
+++ b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/user_data/AssistantShippingAddressSection.java
@@ -15,7 +15,6 @@
 
 import org.chromium.base.Callback;
 import org.chromium.chrome.autofill_assistant.R;
-import org.chromium.chrome.browser.autofill_assistant.AssistantAutofillUtilChrome;
 import org.chromium.chrome.browser.autofill_assistant.AssistantEditor.AssistantAddressEditor;
 import org.chromium.chrome.browser.autofill_assistant.AssistantOptionModel.AddressModel;
 
@@ -70,9 +69,7 @@
         hideIfEmpty(fullNameView);
 
         TextView fullAddressView = fullView.findViewById(R.id.full_address);
-        // TODO(b/211748133): Remove dependency to AutofillUtilChrome.
-        fullAddressView.setText(AssistantAutofillUtilChrome.getShippingAddressLabel(
-                model.mOption, /* withCountry= */ true));
+        fullAddressView.setText(model.getFullDescription());
         hideIfEmpty(fullAddressView);
 
         TextView errorView = fullView.findViewById(R.id.incomplete_error);
@@ -95,9 +92,7 @@
         hideIfEmpty(fullNameView);
 
         TextView shortAddressView = summaryView.findViewById(R.id.short_address);
-        // TODO(b/211748133): Remove dependency to AutofillUtilChrome.
-        shortAddressView.setText(AssistantAutofillUtilChrome.getShippingAddressLabel(
-                model.mOption, /* withCountry= */ false));
+        shortAddressView.setText(model.getSummaryDescription());
         hideIfEmpty(shortAddressView);
 
         TextView errorView = summaryView.findViewById(R.id.incomplete_error);
diff --git a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantCollectUserDataIntegrationTest.java b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantCollectUserDataIntegrationTest.java
index 05b1bde..c505f1d0 100644
--- a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantCollectUserDataIntegrationTest.java
+++ b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantCollectUserDataIntegrationTest.java
@@ -71,13 +71,13 @@
 import org.chromium.chrome.browser.autofill_assistant.proto.ClickType;
 import org.chromium.chrome.browser.autofill_assistant.proto.CollectUserDataProto;
 import org.chromium.chrome.browser.autofill_assistant.proto.CollectUserDataProto.TermsAndConditionsState;
-import org.chromium.chrome.browser.autofill_assistant.proto.CollectUserDataProto.UserDataProto;
 import org.chromium.chrome.browser.autofill_assistant.proto.CollectUserDataResultProto;
 import org.chromium.chrome.browser.autofill_assistant.proto.ContactDetailsProto;
 import org.chromium.chrome.browser.autofill_assistant.proto.DropdownSelectStrategy;
 import org.chromium.chrome.browser.autofill_assistant.proto.ElementAreaProto;
 import org.chromium.chrome.browser.autofill_assistant.proto.ElementAreaProto.Rectangle;
 import org.chromium.chrome.browser.autofill_assistant.proto.ElementConditionProto;
+import org.chromium.chrome.browser.autofill_assistant.proto.GetUserDataResponseProto;
 import org.chromium.chrome.browser.autofill_assistant.proto.IntList;
 import org.chromium.chrome.browser.autofill_assistant.proto.KeyboardValueFillStrategy;
 import org.chromium.chrome.browser.autofill_assistant.proto.LoginDetailsProto;
@@ -915,14 +915,17 @@
     @Test
     @MediumTest
     public void testEnterBackendContact() throws Exception {
-        UserDataProto
-                .Builder data = UserDataProto.newBuilder().setLocale("en-US").addAvailableContacts(
-                ProfileProto.newBuilder()
-                        .putValues(7, AutofillEntryProto.newBuilder().setValue("John Doe").build())
-                        .putValues(9,
-                                AutofillEntryProto.newBuilder()
-                                        .setValue("johndoe@google.com")
-                                        .build()));
+        GetUserDataResponseProto.Builder data =
+                GetUserDataResponseProto.newBuilder().setLocale("en-US").addAvailableContacts(
+                        ProfileProto.newBuilder()
+                                .putValues(7,
+                                        AutofillEntryProto.newBuilder()
+                                                .setValue("John Doe")
+                                                .build())
+                                .putValues(9,
+                                        AutofillEntryProto.newBuilder()
+                                                .setValue("johndoe@google.com")
+                                                .build()));
 
         ArrayList<ActionProto> list = new ArrayList<>();
         list.add(ActionProto.newBuilder()
@@ -999,27 +1002,49 @@
     @Test
     @MediumTest
     public void testShowBackendCard() throws Exception {
-        UserDataProto.Builder
-                data = UserDataProto.newBuilder().setLocale("en-US").addAvailablePaymentInstruments(
-                PaymentInstrumentProto.newBuilder()
-                        .putCardValues(55, AutofillEntryProto.newBuilder().setValue("2050").build())
-                        .putCardValues(53, AutofillEntryProto.newBuilder().setValue("7").build())
-                        .putCardValues(
-                                51, AutofillEntryProto.newBuilder().setValue("John Doe").build())
-                        .setNetwork("visaCC")
-                        .setLastFourDigits("1111")
-                        .putAddressValues(
-                                35, AutofillEntryProto.newBuilder().setValue("80302").build())
-                        .putAddressValues(
-                                36, AutofillEntryProto.newBuilder().setValue("US").build())
-                        .putAddressValues(
-                                33, AutofillEntryProto.newBuilder().setValue("Boulder").build())
-                        .putAddressValues(30,
-                                AutofillEntryProto.newBuilder().setValue("123 Broadway St").build())
-                        .putAddressValues(
-                                34, AutofillEntryProto.newBuilder().setValue("CO").build())
-                        .putAddressValues(
-                                7, AutofillEntryProto.newBuilder().setValue("John Doe").build()));
+        GetUserDataResponseProto.Builder data =
+                GetUserDataResponseProto.newBuilder()
+                        .setLocale("en-US")
+                        .addAvailablePaymentInstruments(
+                                PaymentInstrumentProto.newBuilder()
+                                        .putCardValues(55,
+                                                AutofillEntryProto.newBuilder()
+                                                        .setValue("2050")
+                                                        .build())
+                                        .putCardValues(53,
+                                                AutofillEntryProto.newBuilder()
+                                                        .setValue("7")
+                                                        .build())
+                                        .putCardValues(51,
+                                                AutofillEntryProto.newBuilder()
+                                                        .setValue("John Doe")
+                                                        .build())
+                                        .setNetwork("visaCC")
+                                        .setLastFourDigits("1111")
+                                        .putAddressValues(35,
+                                                AutofillEntryProto.newBuilder()
+                                                        .setValue("80302")
+                                                        .build())
+                                        .putAddressValues(36,
+                                                AutofillEntryProto.newBuilder()
+                                                        .setValue("US")
+                                                        .build())
+                                        .putAddressValues(33,
+                                                AutofillEntryProto.newBuilder()
+                                                        .setValue("Boulder")
+                                                        .build())
+                                        .putAddressValues(30,
+                                                AutofillEntryProto.newBuilder()
+                                                        .setValue("123 Broadway St")
+                                                        .build())
+                                        .putAddressValues(34,
+                                                AutofillEntryProto.newBuilder()
+                                                        .setValue("CO")
+                                                        .build())
+                                        .putAddressValues(7,
+                                                AutofillEntryProto.newBuilder()
+                                                        .setValue("John Doe")
+                                                        .build()));
 
         ArrayList<ActionProto> list = new ArrayList<>();
         list.add(ActionProto.newBuilder()
@@ -1053,8 +1078,8 @@
     @Test
     @MediumTest
     public void testEnterBackendPhoneNumber() throws Exception {
-        UserDataProto.Builder data =
-                UserDataProto.newBuilder().setLocale("en-US").addAvailablePhoneNumbers(
+        GetUserDataResponseProto.Builder data =
+                GetUserDataResponseProto.newBuilder().setLocale("en-US").addAvailablePhoneNumbers(
                         PhoneNumberProto.newBuilder().setValue(
                                 AutofillEntryProto.newBuilder().setValue("+41234567890").build()));
 
@@ -1119,8 +1144,8 @@
     @Test
     @MediumTest
     public void testMergeBackendPhoneNumberIntoContact() throws Exception {
-        UserDataProto.Builder data =
-                UserDataProto.newBuilder()
+        GetUserDataResponseProto.Builder data =
+                GetUserDataResponseProto.newBuilder()
                         .setLocale("en-US")
                         .addAvailableContacts(
                                 ProfileProto.newBuilder()
diff --git a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantCollectUserDataUiTest.java b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantCollectUserDataUiTest.java
index 932d453..91990b33 100644
--- a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantCollectUserDataUiTest.java
+++ b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantCollectUserDataUiTest.java
@@ -539,14 +539,13 @@
                     new ContactModel(contact));
             AssistantAutofillProfile phone_number = createDummyContact(profile);
             model.set(AssistantCollectUserDataModel.AVAILABLE_PHONE_NUMBERS,
-                    Collections.singletonList(new ContactModel(contact)));
+                    Collections.singletonList(new ContactModel(phone_number)));
             model.set(
                     AssistantCollectUserDataModel.SELECTED_PHONE_NUMBER, new ContactModel(contact));
-            AssistantAutofillProfile address = createDummyAddress(profile);
             model.set(AssistantCollectUserDataModel.AVAILABLE_SHIPPING_ADDRESSES,
-                    Collections.singletonList(new AddressModel(address)));
+                    Collections.singletonList(createAddressModel(profile)));
             model.set(AssistantCollectUserDataModel.SELECTED_SHIPPING_ADDRESS,
-                    new AddressModel(address));
+                    createAddressModel(profile));
             AssistantPaymentInstrument paymentInstrument =
                     AssistantCollectUserDataModel.createAssistantPaymentInstrument(
                             createDummyCreditCard(creditCard), createDummyAddress(profile));
@@ -628,7 +627,7 @@
             model.set(AssistantCollectUserDataModel.AVAILABLE_CONTACTS,
                     Collections.singletonList(new ContactModel(contact)));
             model.set(AssistantCollectUserDataModel.AVAILABLE_SHIPPING_ADDRESSES,
-                    Collections.singletonList(new AddressModel(createDummyAddress(profile))));
+                    Collections.singletonList(createAddressModel(profile)));
             AssistantPaymentInstrument paymentInstrument =
                     AssistantCollectUserDataModel.createAssistantPaymentInstrument(
                             createDummyCreditCard(creditCard), createDummyAddress(profile));
@@ -1139,4 +1138,14 @@
                 creditCard.getServerId(), creditCard.getInstrumentId(), creditCard.getNickname(),
                 creditCard.getCardArtUrl(), creditCard.getVirtualCardEnrollmentState());
     }
+
+    private AddressModel createAddressModel(PersonalDataManager.AutofillProfile profile) {
+        String fullDescription =
+                PersonalDataManager.getInstance()
+                        .getShippingAddressLabelWithCountryForPaymentRequest(profile);
+        String summaryDescription =
+                PersonalDataManager.getInstance()
+                        .getShippingAddressLabelWithoutCountryForPaymentRequest(profile);
+        return new AddressModel(createDummyAddress(profile), fullDescription, summaryDescription);
+    }
 }
diff --git a/chrome/android/features/autofill_assistant/public/java/src/org/chromium/chrome/browser/autofill_assistant/AssistantAddressEditorAutofill.java b/chrome/android/features/autofill_assistant/public/java/src/org/chromium/chrome/browser/autofill_assistant/AssistantAddressEditorAutofill.java
index 91df573..62e2b9f 100644
--- a/chrome/android/features/autofill_assistant/public/java/src/org/chromium/chrome/browser/autofill_assistant/AssistantAddressEditorAutofill.java
+++ b/chrome/android/features/autofill_assistant/public/java/src/org/chromium/chrome/browser/autofill_assistant/AssistantAddressEditorAutofill.java
@@ -10,6 +10,7 @@
 import androidx.annotation.Nullable;
 
 import org.chromium.base.Callback;
+import org.chromium.chrome.browser.autofill.PersonalDataManager;
 import org.chromium.chrome.browser.autofill.prefeditor.EditorDialog;
 import org.chromium.chrome.browser.autofill.settings.AddressEditor;
 import org.chromium.chrome.browser.autofill_assistant.AssistantEditor.AssistantAddressEditor;
@@ -46,9 +47,17 @@
         Callback<AutofillAddress> editorDoneCallback = editedAddress -> {
             assert (editedAddress != null && editedAddress.isComplete()
                     && editedAddress.getProfile() != null);
+            String fullDescription = PersonalDataManager.getInstance()
+                                             .getShippingAddressLabelWithCountryForPaymentRequest(
+                                                     editedAddress.getProfile());
+            String summaryDescription =
+                    PersonalDataManager.getInstance()
+                            .getShippingAddressLabelWithoutCountryForPaymentRequest(
+                                    editedAddress.getProfile());
             doneCallback.onResult(new AddressModel(
                     AssistantAutofillUtilChrome.autofillProfileToAssistantAutofillProfile(
-                            editedAddress.getProfile())));
+                            editedAddress.getProfile()),
+                    fullDescription, summaryDescription));
         };
 
         Callback<AutofillAddress> editorCancelCallback =
diff --git a/chrome/android/features/autofill_assistant/public/java/src/org/chromium/chrome/browser/autofill_assistant/AssistantAutofillUtilChrome.java b/chrome/android/features/autofill_assistant/public/java/src/org/chromium/chrome/browser/autofill_assistant/AssistantAutofillUtilChrome.java
index ffd36c2..20b0616 100644
--- a/chrome/android/features/autofill_assistant/public/java/src/org/chromium/chrome/browser/autofill_assistant/AssistantAutofillUtilChrome.java
+++ b/chrome/android/features/autofill_assistant/public/java/src/org/chromium/chrome/browser/autofill_assistant/AssistantAutofillUtilChrome.java
@@ -8,7 +8,6 @@
 
 import androidx.annotation.Nullable;
 
-import org.chromium.chrome.browser.autofill.PersonalDataManager;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.AutofillProfile;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.CreditCard;
 import org.chromium.chrome.browser.payments.AutofillAddress;
@@ -88,26 +87,6 @@
                 CompletenessCheckType.IGNORE_PHONE);
     }
 
-    /**
-     * Get the label for an {@link AssistantAutofillProfile} used as a shipping address.
-     *
-     * @param profile The {@link AssistantAutofillProfile}.
-     * @param withCountry Flag to add country.
-     * @return The label.
-     */
-    public static String getShippingAddressLabel(
-            AssistantAutofillProfile profile, boolean withCountry) {
-        if (withCountry) {
-            return PersonalDataManager.getInstance()
-                    .getShippingAddressLabelWithCountryForPaymentRequest(
-                            assistantAutofillProfileToAutofillProfile(profile));
-        } else {
-            return PersonalDataManager.getInstance()
-                    .getShippingAddressLabelWithoutCountryForPaymentRequest(
-                            assistantAutofillProfileToAutofillProfile(profile));
-        }
-    }
-
     private static CreditCard assistantAutofillCreditCardToAutofillCreditCard(
             AssistantAutofillCreditCard creditCard) {
         return new CreditCard(creditCard.getGUID(), creditCard.getOrigin(), creditCard.getIsLocal(),
@@ -165,15 +144,4 @@
                         ? null
                         : autofillProfileToAssistantAutofillProfile(autofillBillingProfile));
     }
-
-    /**
-     * Get the label for an {@link AssistantAutofillProfile} used as a billing address.
-     *
-     * @param profile The {@link AssistantAutofillProfile}.
-     * @return The label.
-     */
-    public static String getBillingAddressLabel(AssistantAutofillProfile profile) {
-        return PersonalDataManager.getInstance().getBillingAddressLabelForPaymentRequest(
-                assistantAutofillProfileToAutofillProfile(profile));
-    }
 }
diff --git a/chrome/android/features/autofill_assistant/public/java/src/org/chromium/chrome/browser/autofill_assistant/AssistantOptionModel.java b/chrome/android/features/autofill_assistant/public/java/src/org/chromium/chrome/browser/autofill_assistant/AssistantOptionModel.java
index b2cab6ad..3c7def03 100644
--- a/chrome/android/features/autofill_assistant/public/java/src/org/chromium/chrome/browser/autofill_assistant/AssistantOptionModel.java
+++ b/chrome/android/features/autofill_assistant/public/java/src/org/chromium/chrome/browser/autofill_assistant/AssistantOptionModel.java
@@ -5,6 +5,7 @@
 package org.chromium.chrome.browser.autofill_assistant;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 
 /**
@@ -52,12 +53,27 @@
 
     /** Model wrapper for an {@link AssistantAutofillProfile}. */
     public static class AddressModel extends AssistantOptionModel<AssistantAutofillProfile> {
-        public AddressModel(AssistantAutofillProfile address, List<String> errors) {
+        private final String mFullDescription;
+        private final String mSummaryDescription;
+
+        public AddressModel(AssistantAutofillProfile address, String fullDescription,
+                String summaryDescription, List<String> errors) {
             super(address, errors);
+            mFullDescription = fullDescription;
+            mSummaryDescription = summaryDescription;
         }
 
-        public AddressModel(AssistantAutofillProfile address) {
-            super(address);
+        public AddressModel(AssistantAutofillProfile address, String fullDescription,
+                String summaryDescription) {
+            this(address, fullDescription, summaryDescription, Collections.emptyList());
+        }
+
+        public String getFullDescription() {
+            return mFullDescription;
+        }
+
+        public String getSummaryDescription() {
+            return mSummaryDescription;
         }
     }
 
diff --git a/chrome/android/features/autofill_assistant/public/java/src/org/chromium/chrome/browser/autofill_assistant/AssistantPaymentInstrumentEditorAutofill.java b/chrome/android/features/autofill_assistant/public/java/src/org/chromium/chrome/browser/autofill_assistant/AssistantPaymentInstrumentEditorAutofill.java
index 3647fd1..9a4db1a 100644
--- a/chrome/android/features/autofill_assistant/public/java/src/org/chromium/chrome/browser/autofill_assistant/AssistantPaymentInstrumentEditorAutofill.java
+++ b/chrome/android/features/autofill_assistant/public/java/src/org/chromium/chrome/browser/autofill_assistant/AssistantPaymentInstrumentEditorAutofill.java
@@ -10,6 +10,7 @@
 import androidx.annotation.Nullable;
 
 import org.chromium.base.Callback;
+import org.chromium.chrome.browser.autofill.PersonalDataManager;
 import org.chromium.chrome.browser.autofill.prefeditor.EditorDialog;
 import org.chromium.chrome.browser.autofill.settings.AddressEditor;
 import org.chromium.chrome.browser.autofill.settings.CardEditor;
@@ -142,7 +143,8 @@
                         address, mContext);
         if (autofillAddress.getProfile().getLabel() == null) {
             autofillAddress.getProfile().setLabel(
-                    AssistantAutofillUtilChrome.getBillingAddressLabel(address));
+                    PersonalDataManager.getInstance().getBillingAddressLabelForPaymentRequest(
+                            autofillAddress.getProfile()));
         }
         mEditor.updateBillingAddressIfComplete(autofillAddress);
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java
index 304f41d..545f34b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java
@@ -58,6 +58,8 @@
 import org.chromium.chrome.browser.fullscreen.FullscreenManager;
 import org.chromium.chrome.browser.identity_disc.IdentityDiscController;
 import org.chromium.chrome.browser.image_descriptions.ImageDescriptionsController;
+import org.chromium.chrome.browser.incognito.reauth.IncognitoReauthController;
+import org.chromium.chrome.browser.incognito.reauth.IncognitoReauthManager;
 import org.chromium.chrome.browser.layouts.LayoutStateProvider;
 import org.chromium.chrome.browser.layouts.LayoutType;
 import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher;
@@ -180,6 +182,12 @@
     protected LayoutStateProvider mLayoutStateProvider;
     private LayoutStateProvider.LayoutStateObserver mLayoutStateObserver;
 
+    /**
+     * A controller which is used to show an Incognito re-auth dialog when the feature is
+     * available.
+     */
+    private @Nullable IncognitoReauthController mIncognitoReauthController;
+
     /** A means of providing the theme color to different features. */
     private TopUiThemeColorProvider mTopUiThemeColorProvider;
 
@@ -542,6 +550,10 @@
             mScrollCaptureManager = null;
         }
 
+        if (mIncognitoReauthController != null) {
+            mIncognitoReauthController.destroy();
+        }
+
         mActivity = null;
     }
 
@@ -672,6 +684,13 @@
                                 .getSubscriptionsManager());
             });
         }
+
+        if (IncognitoReauthManager.isIncognitoReauthFeatureAvailable()) {
+            mIncognitoReauthController =
+                    new IncognitoReauthController(mActivity, mTabModelSelectorSupplier.get(),
+                            mActivityLifecycleDispatcher, mModalDialogManagerSupplier.get(),
+                            mLayoutStateProviderOneShotSupplier, mProfileSupplier);
+        }
     }
 
     private void initMerchantTrustSignals() {
diff --git a/chrome/app/settings_chromium_strings.grdp b/chrome/app/settings_chromium_strings.grdp
index 3ac31b5..78d760de 100644
--- a/chrome/app/settings_chromium_strings.grdp
+++ b/chrome/app/settings_chromium_strings.grdp
@@ -128,31 +128,31 @@
   </message>
 
   <!-- Privacy Guide -->
-  <message name="IDS_SETTINGS_PRIVACY_GUIDE_PROMO_BODY" desc="Body text of a card in the settings page that explains what the privacy guide feature is.">
+  <message name="IDS_SETTINGS_PRIVACY_GUIDE_PROMO_BODY" desc="Body text of a card in the settings page that explains what the 'Privacy Guide' feature is.">
     Review key privacy and security controls in Chromium
   </message>
-  <message name="IDS_SETTINGS_PRIVACY_GUIDE_CLEAR_ON_EXIT_FEATURE_DESCRIPTION1" desc="A part of the feature description of 'clear cookies on exit' card in the privacy guide.">
+  <message name="IDS_SETTINGS_PRIVACY_GUIDE_CLEAR_ON_EXIT_FEATURE_DESCRIPTION1" desc="A part of the feature description of 'clear cookies on exit' card in the 'Privacy Guide'.">
     When you close all Chromium windows, cookies and site data are automatically cleared
   </message>
-  <message name="IDS_SETTINGS_PRIVACY_GUIDE_CLEAR_ON_EXIT_FEATURE_DESCRIPTION2" desc="A part of the feature description of 'clear cookies on exit' card in the privacy guide.">
+  <message name="IDS_SETTINGS_PRIVACY_GUIDE_CLEAR_ON_EXIT_FEATURE_DESCRIPTION2" desc="A part of the feature description of 'clear cookies on exit' card in the 'Privacy Guide'.">
     You will be <ph name="BEGIN_BOLD">&lt;b&gt;</ph>signed out of most sites<ph name="END_BOLD">&lt;/b&gt;</ph> when you close Chromium. If sync is off, you will also be <ph name="BEGIN_BOLD">&lt;b&gt;</ph>signed out of Google services and Chromium<ph name="END_BOLD">&lt;/b&gt;</ph>.
   </message>
-  <message name="IDS_SETTINGS_PRIVACY_GUIDE_CLEAR_ON_EXIT_FEATURE_DESCRIPTION3" desc="A part of the feature description of 'clear cookies on exit' card in the privacy guide.">
+  <message name="IDS_SETTINGS_PRIVACY_GUIDE_CLEAR_ON_EXIT_FEATURE_DESCRIPTION3" desc="A part of the feature description of 'clear cookies on exit' card in the 'Privacy Guide'.">
     Sites you visit remember your information until you close Chromium
   </message>
-  <message name="IDS_SETTINGS_PRIVACY_GUIDE_SAFE_BROWSING_CARD_STANDARD_PROTECTION_FEATURE_DESCRIPTION2" desc="A part of the feature description of the standard protection section of the safe browsing card in the privacy guide.">
+  <message name="IDS_SETTINGS_PRIVACY_GUIDE_SAFE_BROWSING_CARD_STANDARD_PROTECTION_FEATURE_DESCRIPTION2" desc="A part of the feature description of the standard protection section of the safe browsing card in the 'Privacy Guide'.">
     Checks URLs with a list of unsafe sites stored in Chromium
   </message>
-  <message name="IDS_SETTINGS_PRIVACY_GUIDE_SAFE_BROWSING_CARD_STANDARD_PROTECTION_PRIVACY_DESCRIPTION1" desc="A part of the privacy description of the standard protection section of the safe browsing card in the privacy guide.">
+  <message name="IDS_SETTINGS_PRIVACY_GUIDE_SAFE_BROWSING_CARD_STANDARD_PROTECTION_PRIVACY_DESCRIPTION1" desc="A part of the privacy description of the standard protection section of the safe browsing card in the 'Privacy Guide'.">
     If a site tries to steal your password, or when you download a harmful file, Chromium may also send URLs, including bits of page content, to Safe Browsing
   </message>
-  <message name="IDS_SETTINGS_PRIVACY_GUIDE_COMPLETION_CARD_PRIVACY_SANDBOX_SUB_LABEL" desc="Text of the Privacy Sandbox sublabel in the completion card of the privacy guide.">
+  <message name="IDS_SETTINGS_PRIVACY_GUIDE_COMPLETION_CARD_PRIVACY_SANDBOX_SUB_LABEL" desc="Text of the Privacy Sandbox sublabel in the completion card of the 'Privacy Guide'.">
     Chromium is exploring new features that allow sites to deliver the same browsing experience using less of your data
   </message>
-  <message name="IDS_SETTINGS_PRIVACY_GUIDE_COMPLETION_CARD_WAA_SUB_LABEL" desc="Text of the Web and App Activity sublabel in the completion card of the privacy guide.">
+  <message name="IDS_SETTINGS_PRIVACY_GUIDE_COMPLETION_CARD_WAA_SUB_LABEL" desc="Text of the Web and App Activity sublabel in the completion card of the 'Privacy Guide'.">
     Choose whether to include Chromium history for more personalized experiences in Google services
   </message>
-  <message name="IDS_SETTINGS_PRIVACY_GUIDE_MSBB_PRIVACY_DESCRIPTION2" desc="A part of the privacy description of 'make searches and browsing better' in the privacy guide.">
+  <message name="IDS_SETTINGS_PRIVACY_GUIDE_MSBB_PRIVACY_DESCRIPTION2" desc="A part of the privacy description of 'make searches and browsing better' in the 'Privacy Guide'.">
     If you also share Chromium usage reports, those reports include the URLs you visit
   </message>
 
diff --git a/chrome/app/settings_google_chrome_strings.grdp b/chrome/app/settings_google_chrome_strings.grdp
index 5066d0bd..aa4c0d9c5 100644
--- a/chrome/app/settings_google_chrome_strings.grdp
+++ b/chrome/app/settings_google_chrome_strings.grdp
@@ -154,31 +154,31 @@
   </message>
 
   <!-- Privacy Guide -->
-  <message name="IDS_SETTINGS_PRIVACY_GUIDE_PROMO_BODY" desc="Body text of a card in the settings page that explains what the privacy guide feature is.">
+  <message name="IDS_SETTINGS_PRIVACY_GUIDE_PROMO_BODY" desc="Body text of a card in the settings page that explains what the 'Privacy Guide' feature is.">
     Review key privacy and security controls in Chrome
   </message>
-  <message name="IDS_SETTINGS_PRIVACY_GUIDE_CLEAR_ON_EXIT_FEATURE_DESCRIPTION1" desc="A part of the feature description of 'clear cookies on exit' card in the privacy guide.">
+  <message name="IDS_SETTINGS_PRIVACY_GUIDE_CLEAR_ON_EXIT_FEATURE_DESCRIPTION1" desc="A part of the feature description of 'clear cookies on exit' card in the 'Privacy Guide'.">
     When you close all Chrome windows, cookies and site data are automatically cleared
   </message>
-  <message name="IDS_SETTINGS_PRIVACY_GUIDE_CLEAR_ON_EXIT_FEATURE_DESCRIPTION2" desc="A part of the feature description of 'clear cookies on exit' card in the privacy guide.">
+  <message name="IDS_SETTINGS_PRIVACY_GUIDE_CLEAR_ON_EXIT_FEATURE_DESCRIPTION2" desc="A part of the feature description of 'clear cookies on exit' card in the 'Privacy Guide'.">
     You will be <ph name="BEGIN_BOLD">&lt;b&gt;</ph>signed out of most sites<ph name="END_BOLD">&lt;/b&gt;</ph> when you close Chrome. If sync is off, you will also be <ph name="BEGIN_BOLD">&lt;b&gt;</ph>signed out of Google services and Chrome<ph name="END_BOLD">&lt;/b&gt;</ph>.
   </message>
-  <message name="IDS_SETTINGS_PRIVACY_GUIDE_CLEAR_ON_EXIT_FEATURE_DESCRIPTION3" desc="A part of the feature description of 'clear cookies on exit' card in the privacy guide.">
+  <message name="IDS_SETTINGS_PRIVACY_GUIDE_CLEAR_ON_EXIT_FEATURE_DESCRIPTION3" desc="A part of the feature description of 'clear cookies on exit' card in the 'Privacy Guide'.">
     Sites you visit remember your information until you close Chrome
   </message>
-  <message name="IDS_SETTINGS_PRIVACY_GUIDE_SAFE_BROWSING_CARD_STANDARD_PROTECTION_FEATURE_DESCRIPTION2" desc="A part of the feature description of the standard protection section of the safe browsing card in the privacy guide.">
+  <message name="IDS_SETTINGS_PRIVACY_GUIDE_SAFE_BROWSING_CARD_STANDARD_PROTECTION_FEATURE_DESCRIPTION2" desc="A part of the feature description of the standard protection section of the safe browsing card in the 'Privacy Guide'.">
     Checks URLs with a list of unsafe sites stored in Chrome
   </message>
-  <message name="IDS_SETTINGS_PRIVACY_GUIDE_SAFE_BROWSING_CARD_STANDARD_PROTECTION_PRIVACY_DESCRIPTION1" desc="A part of the privacy description of the standard protection section of the safe browsing card in the privacy guide.">
+  <message name="IDS_SETTINGS_PRIVACY_GUIDE_SAFE_BROWSING_CARD_STANDARD_PROTECTION_PRIVACY_DESCRIPTION1" desc="A part of the privacy description of the standard protection section of the safe browsing card in the 'Privacy Guide'.">
     If a site tries to steal your password, or when you download a harmful file, Chrome may also send URLs, including bits of page content, to Safe Browsing
   </message>
-  <message name="IDS_SETTINGS_PRIVACY_GUIDE_COMPLETION_CARD_PRIVACY_SANDBOX_SUB_LABEL" desc="Text of the Privacy Sandbox sublabel in the completion card of the privacy guide.">
+  <message name="IDS_SETTINGS_PRIVACY_GUIDE_COMPLETION_CARD_PRIVACY_SANDBOX_SUB_LABEL" desc="Text of the Privacy Sandbox sublabel in the completion card of the 'Privacy Guide'.">
     Chrome is exploring new features that allow sites to deliver the same browsing experience using less of your data
   </message>
-  <message name="IDS_SETTINGS_PRIVACY_GUIDE_COMPLETION_CARD_WAA_SUB_LABEL" desc="Text of the Web and App Activity sublabel in the completion card of the privacy guide.">
+  <message name="IDS_SETTINGS_PRIVACY_GUIDE_COMPLETION_CARD_WAA_SUB_LABEL" desc="Text of the Web and App Activity sublabel in the completion card of the 'Privacy Guide'.">
     Choose whether to include Chrome history for more personalized experiences in Google services
   </message>
-  <message name="IDS_SETTINGS_PRIVACY_GUIDE_MSBB_PRIVACY_DESCRIPTION2" desc="A part of the privacy description of 'make searches and browsing better' in the privacy guide.">
+  <message name="IDS_SETTINGS_PRIVACY_GUIDE_MSBB_PRIVACY_DESCRIPTION2" desc="A part of the privacy description of 'make searches and browsing better' in the 'Privacy Guide'.">
     If you also share Chrome usage reports, those reports include the URLs you visit
   </message>
 
diff --git a/chrome/app/settings_strings.grdp b/chrome/app/settings_strings.grdp
index c17cab0..a7ed516 100644
--- a/chrome/app/settings_strings.grdp
+++ b/chrome/app/settings_strings.grdp
@@ -1498,127 +1498,127 @@
   </message>
 
   <!-- Privacy Guide -->
-  <message name="IDS_SETTINGS_PRIVACY_GUIDE_LABEL" desc="Label of the row in the Chrome privacy and security settings page that leads to the privacy guide.">
+  <message name="IDS_SETTINGS_PRIVACY_GUIDE_LABEL" desc="Label of the row in the Chrome privacy and security settings page that leads to the 'Privacy Guide'. 'Privacy Guide' is a term, see go/glossarymanager/termset?gid=27517723&amp;tsid=28b420b3.">
     Privacy Guide
   </message>
-  <message name="IDS_SETTINGS_PRIVACY_GUIDE_SUBLABEL" desc="Sublabel of the row in the Chrome privacy and security of Chrome settings that leads to the privacy guide.">
+  <message name="IDS_SETTINGS_PRIVACY_GUIDE_SUBLABEL" desc="Sublabel of the row in the Chrome privacy and security of Chrome settings that leads to the 'Privacy Guide'.">
     Review key privacy and security controls
   </message>
-  <message name="IDS_SETTINGS_PRIVACY_GUIDE_PROMO_HEADER" desc="Text of a button on a card in the settings page that starts the privacy guide feature.">
+  <message name="IDS_SETTINGS_PRIVACY_GUIDE_PROMO_HEADER" desc="Text of a button on a card in the settings page that starts the 'Privacy Guide'. 'Privacy Guide' is a term, see go/glossarymanager/termset?gid=27517723&amp;tsid=28b420b3.">
     Take the Privacy Guide
   </message>
-  <message name="IDS_SETTINGS_PRIVACY_GUIDE_PROMO_START_BUTTON" desc="Text of a button on a card in the settings page that starts the privacy guide feature.">
+  <message name="IDS_SETTINGS_PRIVACY_GUIDE_PROMO_START_BUTTON" desc="Text of a button on a card in the settings page that starts the 'Privacy Guide'.">
     Get started
   </message>
-  <message name="IDS_SETTINGS_PRIVACY_GUIDE_BACK_BUTTON" desc="Text of a button in the privacy guide that reverses to the previous step in the privacy guide.">
+  <message name="IDS_SETTINGS_PRIVACY_GUIDE_BACK_BUTTON" desc="Text of a button in the privacy guide that reverses to the previous step in the 'Privacy Guide'.">
     Back
   </message>
   <message name="IDS_SETTINGS_PRIVACY_GUIDE_STEPS" is_accessibility_with_no_ui="true" desc="Spoken by screenreaders indicating what step the user is in and how many steps there are in total.">
     Step <ph name="CURRENT_STEP">$1<ex>1</ex></ph> of <ph name="TOTAL_STEPS">$2<ex>2</ex></ph>
   </message>
-  <message name="IDS_SETTINGS_PRIVACY_GUIDE_NEXT_BUTTON" desc="Text of a button in the privacy guide that advances to the next step in the privacy guide.">
+  <message name="IDS_SETTINGS_PRIVACY_GUIDE_NEXT_BUTTON" desc="Text of a button in the 'Privacy Guide' that advances to the next step in the privacy guide.">
     Next
   </message>
-  <message name="IDS_SETTINGS_PRIVACY_GUIDE_FEATURE_DESCRIPTION_HEADER" desc="Header of a description text that describes the behavior of a particular feature. This description is shown to users alongside the feature itself in the privacy guide.">
+  <message name="IDS_SETTINGS_PRIVACY_GUIDE_FEATURE_DESCRIPTION_HEADER" desc="Header of a description text that describes the behavior of a particular feature. This description is shown to users alongside the feature itself in the 'Privacy Guide'.">
     When on
   </message>
-  <message name="IDS_SETTINGS_PRIVACY_GUIDE_THINGS_TO_CONSIDER" desc="Header of a text column that describes things users should consider about a feature. This description is shown to users alongside the feature itself in the privacy guide.">
+  <message name="IDS_SETTINGS_PRIVACY_GUIDE_THINGS_TO_CONSIDER" desc="Header of a text column that describes things users should consider about a feature. This description is shown to users alongside the feature itself in the 'Privacy Guide'.">
     Things to consider
   </message>
-  <message name="IDS_SETTINGS_PRIVACY_GUIDE_WELCOME_CARD_HEADER" desc="A header shown at the top of the welcome card in the privacy guide, explaining to users what privacy guide is.">
+  <message name="IDS_SETTINGS_PRIVACY_GUIDE_WELCOME_CARD_HEADER" desc="A header shown at the top of the welcome card in the 'Privacy Guide', explaining to users what the 'Privacy Guide' is.">
     A guide of your privacy choices
   </message>
-  <message name="IDS_SETTINGS_PRIVACY_GUIDE_WELCOME_CARD_SUB_HEADER" desc="A subheader shown at on the welcome card in the privacy guide, explaining to users what privacy guide is.">
+  <message name="IDS_SETTINGS_PRIVACY_GUIDE_WELCOME_CARD_SUB_HEADER" desc="A subheader shown at on the welcome card in the 'Privacy Guide', explaining to users what the 'Privacy Guide' is.">
     Take a guided tour of key privacy and security controls. For more options, go to individual settings.
   </message>
-  <message name="IDS_SETTINGS_PRIVACY_GUIDE_COMPLETION_CARD_HEADER" desc="A header shown at the top of the completion card in the privacy guide that informs users that they completed the privacy guide.">
+  <message name="IDS_SETTINGS_PRIVACY_GUIDE_COMPLETION_CARD_HEADER" desc="A header shown at the top of the completion card in the 'Privacy Guide' that informs users that they completed the 'Privacy Guide'.">
     Review complete
   </message>
-  <message name="IDS_SETTINGS_PRIVACY_GUIDE_COMPLETION_CARD_SUB_HEADER" desc="A subheader shown at the top of the completion card in the privacy guide that informs users that they can explore more settings or complete the privacy guide.">
+  <message name="IDS_SETTINGS_PRIVACY_GUIDE_COMPLETION_CARD_SUB_HEADER" desc="A subheader shown at the top of the completion card in the 'Privacy Guide' that informs users that they can explore more settings or complete the 'Privacy Guide'.">
     Explore more settings below or finish now
   </message>
-  <message name="IDS_SETTINGS_PRIVACY_GUIDE_COMPLETION_CARD_LEAVE_BUTTON" desc="Text of a button in the completion card in the privacy guide that leads the user back to Chrome settings.">
+  <message name="IDS_SETTINGS_PRIVACY_GUIDE_COMPLETION_CARD_LEAVE_BUTTON" desc="Text of a button in the completion card in the 'Privacy Guide' that leads the user back to Chrome settings.">
     Done
   </message>
-  <message name="IDS_SETTINGS_PRIVACY_GUIDE_COMPLETION_CARD_PRIVACY_SANDBOX_LABEL" desc="Text of the Privacy Sandbox label in the completion card of the privacy guide.">
+  <message name="IDS_SETTINGS_PRIVACY_GUIDE_COMPLETION_CARD_PRIVACY_SANDBOX_LABEL" desc="Text of the Privacy Sandbox label in the completion card of the 'Privacy Guide'.">
     Privacy Sandbox trial
   </message>
-  <message name="IDS_SETTINGS_PRIVACY_GUIDE_COMPLETION_CARD_WAA_LABEL" desc="Text of the Web and App Activity label in the completion card of the privacy guide.">
+  <message name="IDS_SETTINGS_PRIVACY_GUIDE_COMPLETION_CARD_WAA_LABEL" desc="Text of the Web and App Activity label in the completion card of the 'Privacy Guide'.">
     Web &#38; App Activity
   </message>
-  <message name="IDS_SETTINGS_PRIVACY_GUIDE_MSBB_CARD_HEADER" desc="Header of the 'make searches and browsing better' card in the privacy guide.">
+  <message name="IDS_SETTINGS_PRIVACY_GUIDE_MSBB_CARD_HEADER" desc="Header of the 'make searches and browsing better' card in the 'Privacy Guide'.">
     Choose your search and browsing quality
   </message>
-  <message name="IDS_SETTINGS_PRIVACY_GUIDE_MSBB_FEATURE_DESCRIPTION1" desc="A part of the feature description of 'make searches and browsing better' in the privacy guide.">
+  <message name="IDS_SETTINGS_PRIVACY_GUIDE_MSBB_FEATURE_DESCRIPTION1" desc="A part of the feature description of 'make searches and browsing better' in the 'Privacy Guide'.">
     You'll browse faster because content is proactively loaded based on your current webpage visit
   </message>
-  <message name="IDS_SETTINGS_PRIVACY_GUIDE_MSBB_FEATURE_DESCRIPTION2" desc="A part of the feature description of 'make searches and browsing better' in the privacy guide.">
+  <message name="IDS_SETTINGS_PRIVACY_GUIDE_MSBB_FEATURE_DESCRIPTION2" desc="A part of the feature description of 'make searches and browsing better' in the 'Privacy Guide'.">
     You'll get improved suggestions in the address bar
   </message>
-  <message name="IDS_SETTINGS_PRIVACY_GUIDE_MSBB_PRIVACY_DESCRIPTION1" desc="A part of the privacy description of 'make searches and browsing better' in the privacy guide.">
+  <message name="IDS_SETTINGS_PRIVACY_GUIDE_MSBB_PRIVACY_DESCRIPTION1" desc="A part of the privacy description of 'make searches and browsing better' in the 'Privacy Guide'.">
     URLs you visit are sent to Google to predict what sites you might visit next
   </message>
-  <message name="IDS_SETTINGS_PRIVACY_GUIDE_CLEAR_ON_EXIT_CARD_HEADER" desc="Header of the 'clear cookies on exit' card in the privacy guide.">
+  <message name="IDS_SETTINGS_PRIVACY_GUIDE_CLEAR_ON_EXIT_CARD_HEADER" desc="Header of the 'clear cookies on exit' card in the 'Privacy Guide'.">
     Review automatic data clearing
   </message>
-  <message name="IDS_SETTINGS_PRIVACY_GUIDE_HISTORY_SYNC_CARD_HEADER" desc="Header of the history sync card in the privacy guide.">
+  <message name="IDS_SETTINGS_PRIVACY_GUIDE_HISTORY_SYNC_CARD_HEADER" desc="Header of the history sync card in the 'Privacy Guide'.">
     Choose whether to sync history
   </message>
-  <message name="IDS_SETTINGS_PRIVACY_GUIDE_HISTORY_SYNC_SETTING_LABEL" desc="Label of the history sync settings toggle in the privacy guide.">
+  <message name="IDS_SETTINGS_PRIVACY_GUIDE_HISTORY_SYNC_SETTING_LABEL" desc="Label of the history sync settings toggle in the 'Privacy Guide'.">
     History sync
   </message>
-  <message name="IDS_SETTINGS_PRIVACY_GUIDE_HISTORY_SYNC_FEATURE_DESCRIPTION1" desc="A part of the feature description of history sync in the privacy guide.">
+  <message name="IDS_SETTINGS_PRIVACY_GUIDE_HISTORY_SYNC_FEATURE_DESCRIPTION1" desc="A part of the feature description of history sync in the 'Privacy guide'.">
     You'll have your history on all your synced devices so you can continue what you were doing
   </message>
-  <message name="IDS_SETTINGS_PRIVACY_GUIDE_HISTORY_SYNC_FEATURE_DESCRIPTION2" desc="A part of the feature description of history sync in the privacy guide.">
+  <message name="IDS_SETTINGS_PRIVACY_GUIDE_HISTORY_SYNC_FEATURE_DESCRIPTION2" desc="A part of the feature description of history sync in the 'Privacy guide'.">
     If Google is also your default search engine, you'll see better, contextually relevant suggestions
   </message>
-  <message name="IDS_SETTINGS_PRIVACY_GUIDE_HISTORY_SYNC_PRIVACY_DESCRIPTION1" desc="A part of the privacy description of history sync in the privacy guide.">
+  <message name="IDS_SETTINGS_PRIVACY_GUIDE_HISTORY_SYNC_PRIVACY_DESCRIPTION1" desc="A part of the privacy description of history sync in the 'Privacy guide'.">
     The URLs you visit are saved to your Google Account
   </message>
-  <message name="IDS_SETTINGS_PRIVACY_GUIDE_COOKIES_CARD_HEADER" desc="Header of the cookies card in the privacy guide.">
+  <message name="IDS_SETTINGS_PRIVACY_GUIDE_COOKIES_CARD_HEADER" desc="Header of the cookies card in the 'Privacy guide'.">
     Choose your third-party cookie preferences
   </message>
-  <message name="IDS_SETTINGS_PRIVACY_GUIDE_COOKIES_CARD_BLOCK_TPC_INCOGNITO_SUBHEADER" desc="A subheader for the cookies card of the privacy guide, for blocking third party cookies in incognito.">
+  <message name="IDS_SETTINGS_PRIVACY_GUIDE_COOKIES_CARD_BLOCK_TPC_INCOGNITO_SUBHEADER" desc="A subheader for the cookies card of the 'Privacy guide', for blocking third party cookies in incognito.">
     Block third-party cookies in Incognito
   </message>
-  <message name="IDS_SETTINGS_PRIVACY_GUIDE_COOKIES_CARD_BLOCK_TPC_INCOGNITO_FEATURE_DESCRIPTION1" desc="A part of the feature description of the third party cookie blocking in incognito section of the cookies card in the privacy guide.">
+  <message name="IDS_SETTINGS_PRIVACY_GUIDE_COOKIES_CARD_BLOCK_TPC_INCOGNITO_FEATURE_DESCRIPTION1" desc="A part of the feature description of the third party cookie blocking in incognito section of the cookies card in the 'Privacy guide'.">
     Sites can use cookies to improve your browsing experience, for example, to keep you signed in or to remember items in your shopping cart
   </message>
-  <message name="IDS_SETTINGS_PRIVACY_GUIDE_COOKIES_CARD_BLOCK_TPC_INCOGNITO_FEATURE_DESCRIPTION2" desc="A part of the feature description of the third party cookie blocking in incognito section of the cookies card in the privacy guide.">
+  <message name="IDS_SETTINGS_PRIVACY_GUIDE_COOKIES_CARD_BLOCK_TPC_INCOGNITO_FEATURE_DESCRIPTION2" desc="A part of the feature description of the third party cookie blocking in incognito section of the cookies card in the 'Privacy guide'.">
     Features on some sites may not work in Incognito
   </message>
-  <message name="IDS_SETTINGS_PRIVACY_GUIDE_COOKIES_CARD_BLOCK_TPC_INCOGNITO_PRIVACY_DESCRIPTION1" desc="A part of the privacy description of the third party cookie blocking in incognito section of the cookies card in the privacy guide.">
+  <message name="IDS_SETTINGS_PRIVACY_GUIDE_COOKIES_CARD_BLOCK_TPC_INCOGNITO_PRIVACY_DESCRIPTION1" desc="A part of the privacy description of the third party cookie blocking in incognito section of the cookies card in the 'Privacy guide'.">
     Sites can use cookies to see your browsing activity across different sites, for example, to personalize ads
   </message>
-  <message name="IDS_SETTINGS_PRIVACY_GUIDE_COOKIES_CARD_BLOCK_TPC_INCOGNITO_PRIVACY_DESCRIPTION2" desc="A part of the privacy description of the third party cookie blocking in incognito section of the cookies card in the privacy guide.">
+  <message name="IDS_SETTINGS_PRIVACY_GUIDE_COOKIES_CARD_BLOCK_TPC_INCOGNITO_PRIVACY_DESCRIPTION2" desc="A part of the privacy description of the third party cookie blocking in incognito section of the cookies card in the 'Privacy guide'.">
     When you’re in Incognito mode, sites can only use cookies to see your browsing activity on their own site. Cookies are deleted at the end of the Incognito session.
   </message>
-  <message name="IDS_SETTINGS_PRIVACY_GUIDE_COOKIES_CARD_BLOCK_TPC_SUBHEADER" desc="A subheader for the cookies card of the privacy guide, for blocking third party cookies.">
+  <message name="IDS_SETTINGS_PRIVACY_GUIDE_COOKIES_CARD_BLOCK_TPC_SUBHEADER" desc="A subheader for the cookies card of the 'Privacy guide', for blocking third party cookies.">
     Block all third-party cookies
   </message>
-  <message name="IDS_SETTINGS_PRIVACY_GUIDE_COOKIES_CARD_BLOCK_TPC_FEATURE_DESCRIPTION1" desc="A part of the feature description of the third party cookie blocking section of the cookies card in the privacy guide.">
+  <message name="IDS_SETTINGS_PRIVACY_GUIDE_COOKIES_CARD_BLOCK_TPC_FEATURE_DESCRIPTION1" desc="A part of the feature description of the third party cookie blocking section of the cookies card in the 'Privacy guide'.">
     Sites can use cookies to improve your browsing experience, for example, to keep you signed in or to remember items in your shopping cart
   </message>
-  <message name="IDS_SETTINGS_PRIVACY_GUIDE_COOKIES_CARD_BLOCK_TPC_FEATURE_DESCRIPTION2" desc="A part of the feature description of the third party cookie blocking section of the cookies card in the privacy guide.">
+  <message name="IDS_SETTINGS_PRIVACY_GUIDE_COOKIES_CARD_BLOCK_TPC_FEATURE_DESCRIPTION2" desc="A part of the feature description of the third party cookie blocking section of the cookies card in the 'Privacy guide'.">
     Features on some sites may not work
   </message>
-  <message name="IDS_SETTINGS_PRIVACY_GUIDE_COOKIES_CARD_BLOCK_TPC_PRIVACY_DESCRIPTION1" desc="A part of the privacy description of the third party cookie blocking section of the cookies card in the privacy guide.">
+  <message name="IDS_SETTINGS_PRIVACY_GUIDE_COOKIES_CARD_BLOCK_TPC_PRIVACY_DESCRIPTION1" desc="A part of the privacy description of the third party cookie blocking section of the cookies card in the 'Privacy guide'.">
     Sites can only use cookies to see your browsing activity on their own site
   </message>
-  <message name="IDS_SETTINGS_PRIVACY_GUIDE_SAFE_BROWSING_CARD_HEADER" desc="Header of the safe browsing card in the privacy guide.">
+  <message name="IDS_SETTINGS_PRIVACY_GUIDE_SAFE_BROWSING_CARD_HEADER" desc="Header of the safe browsing card in the 'Privacy guide'.">
     Choose your Safe Browsing protection
   </message>
-  <message name="IDS_SETTINGS_PRIVACY_GUIDE_SAFE_BROWSING_CARD_ENHANCED_PROTECTION_PRIVACY_DESCRIPTION1" desc="A part of the privacy description of the enhanced protection section of the safe browsing card in the privacy guide.">
+  <message name="IDS_SETTINGS_PRIVACY_GUIDE_SAFE_BROWSING_CARD_ENHANCED_PROTECTION_PRIVACY_DESCRIPTION1" desc="A part of the privacy description of the enhanced protection section of the safe browsing card in the 'Privacy guide'.">
     Sends URLs to Safe Browsing to check them
   </message>
-  <message name="IDS_SETTINGS_PRIVACY_GUIDE_SAFE_BROWSING_CARD_ENHANCED_PROTECTION_PRIVACY_DESCRIPTION2" desc="A part of the privacy description of the enhanced protection section of the safe browsing card in the privacy guide.">
+  <message name="IDS_SETTINGS_PRIVACY_GUIDE_SAFE_BROWSING_CARD_ENHANCED_PROTECTION_PRIVACY_DESCRIPTION2" desc="A part of the privacy description of the enhanced protection section of the safe browsing card in the 'Privacy guide'.">
     Also sends a small sample of pages, downloads, extension activity, and system information to help discover new threats
   </message>
-  <message name="IDS_SETTINGS_PRIVACY_GUIDE_SAFE_BROWSING_CARD_ENHANCED_PROTECTION_PRIVACY_DESCRIPTION3" desc="A part of the privacy description of the enhanced protection section of the safe browsing card in the privacy guide.">
+  <message name="IDS_SETTINGS_PRIVACY_GUIDE_SAFE_BROWSING_CARD_ENHANCED_PROTECTION_PRIVACY_DESCRIPTION3" desc="A part of the privacy description of the enhanced protection section of the safe browsing card in the 'Privacy guide'.">
     Temporarily links this data to your Google Account when you're signed in, to protect you across Google apps
   </message>
-  <message name="IDS_SETTINGS_PRIVACY_GUIDE_SAFE_BROWSING_CARD_STANDARD_PROTECTION_FEATURE_DESCRIPTION1" desc="A part of the feature description of the standard protection section of the safe browsing card in the privacy guide.">
+  <message name="IDS_SETTINGS_PRIVACY_GUIDE_SAFE_BROWSING_CARD_STANDARD_PROTECTION_FEATURE_DESCRIPTION1" desc="A part of the feature description of the standard protection section of the safe browsing card in the 'Privacy guide'.">
     Detects and warns you about dangerous events when they happen
   </message>
 
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 82112c1..35b3cc1 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -2520,8 +2520,12 @@
     ]
   }
   if (is_linux || is_chromeos) {
-    deps += [ "//chrome/browser/error_reporting" ]
+    deps += [
+      "//chrome/browser/error_reporting",
+      "//components/services/screen_ai/public/cpp:screen_ai_service_router_factory",
+    ]
   }
+
   if (use_ozone) {
     deps += [
       "//ui/events/ozone",
diff --git a/chrome/browser/DEPS b/chrome/browser/DEPS
index d2b1ef8..5b1482c 100644
--- a/chrome/browser/DEPS
+++ b/chrome/browser/DEPS
@@ -310,6 +310,7 @@
   "+components/session_manager",
   "+components/sessions/content",
   "+components/sessions/core",
+  "+components/services/screen_ai/public/cpp",
   "+components/signin/core/browser",
   "+components/signin/public",
   "+components/site_engagement",
diff --git a/chrome/browser/android/autofill_assistant/ui_controller_android.cc b/chrome/browser/android/autofill_assistant/ui_controller_android.cc
index 2b2f3b66..075975d9 100644
--- a/chrome/browser/android/autofill_assistant/ui_controller_android.cc
+++ b/chrome/browser/android/autofill_assistant/ui_controller_android.cc
@@ -58,6 +58,7 @@
     ConvertNativeOptionalStringToJava;
 using ::autofill_assistant::ui_controller_android_utils::CreateJavaInfoPopup;
 using ::base::android::AttachCurrentThread;
+using ::base::android::ConvertUTF16ToJavaString;
 using ::base::android::ConvertUTF8ToJavaString;
 using ::base::android::JavaParamRef;
 using ::base::android::JavaRef;
@@ -249,6 +250,43 @@
   return input_result.selection().selected(selection_index);
 }
 
+// Analog to
+// PersonalDataManagerAndroid::GetShippingAddressLabelForPaymentRequest.
+base::android::ScopedJavaLocalRef<jstring> GetShippingAddressLabel(
+    JNIEnv* env,
+    const autofill::AutofillProfile* profile,
+    bool with_country) {
+  if (!profile) {
+    return ConvertUTF8ToJavaString(env, std::string());
+  }
+
+  // The full name is not included in the label for shipping address. It is
+  // added separately instead.
+  static constexpr autofill::ServerFieldType kLabelFields[] = {
+      autofill::ServerFieldType::COMPANY_NAME,
+      autofill::ServerFieldType::ADDRESS_HOME_LINE1,
+      autofill::ServerFieldType::ADDRESS_HOME_LINE2,
+      autofill::ServerFieldType::ADDRESS_HOME_DEPENDENT_LOCALITY,
+      autofill::ServerFieldType::ADDRESS_HOME_CITY,
+      autofill::ServerFieldType::ADDRESS_HOME_STATE,
+      autofill::ServerFieldType::ADDRESS_HOME_ZIP,
+      autofill::ServerFieldType::ADDRESS_HOME_SORTING_CODE,
+      autofill::ServerFieldType::ADDRESS_HOME_COUNTRY,
+  };
+  size_t fields_size = base::size(kLabelFields);
+
+  // For the case where country is omitted, do not use the last field.
+  if (!with_country) {
+    --fields_size;
+  }
+
+  return ConvertUTF16ToJavaString(
+      env, profile->ConstructInferredLabel(
+               kLabelFields, /* label_fields_size= */ fields_size,
+               /* num_fields_to_include= */ fields_size,
+               base::android::GetDefaultLocaleString()));
+}
+
 }  // namespace
 
 // static
@@ -1445,12 +1483,22 @@
           ui_controller_android_utils::CreateAssistantAutofillProfile(
               env, *user_data.available_addresses_[index]->profile,
               base::android::GetDefaultLocaleString()),
+          GetShippingAddressLabel(
+              env, user_data.available_addresses_[index]->profile.get(),
+              /* with_country= */ true),
+          GetShippingAddressLabel(
+              env, user_data.available_addresses_[index]->profile.get(),
+              /* with_country= */ false),
           base::android::ToJavaArrayOfStrings(env, errors));
     }
     Java_AssistantCollectUserDataModel_setAvailableShippingAddresses(
         env, jmodel, jshippinglist);
     Java_AssistantCollectUserDataModel_setSelectedShippingAddress(
         env, jmodel, jselected_shipping_address,
+        GetShippingAddressLabel(env, selected_shipping_address,
+                                /* with_country= */ true),
+        GetShippingAddressLabel(env, selected_shipping_address,
+                                /* with_country= */ false),
         base::android::ToJavaArrayOfStrings(env,
                                             selected_shipping_address_errors));
   }
@@ -1475,6 +1523,10 @@
     // off case does not set updated shipping addresses.
     Java_AssistantCollectUserDataModel_setSelectedShippingAddress(
         env, jmodel, jselected_shipping_address,
+        GetShippingAddressLabel(env, selected_shipping_address,
+                                /* with_country= */ true),
+        GetShippingAddressLabel(env, selected_shipping_address,
+                                /* with_country= */ false),
         base::android::ToJavaArrayOfStrings(env,
                                             selected_shipping_address_errors));
   }
diff --git a/chrome/browser/android/preferences/autofill/autofill_payment_methods_delegate.cc b/chrome/browser/android/preferences/autofill/autofill_payment_methods_delegate.cc
index 4bb176c..7c0d2cd 100644
--- a/chrome/browser/android/preferences/autofill/autofill_payment_methods_delegate.cc
+++ b/chrome/browser/android/preferences/autofill/autofill_payment_methods_delegate.cc
@@ -6,6 +6,7 @@
 
 #include <memory>
 
+#include "base/android/callback_android.h"
 #include "base/android/jni_string.h"
 #include "chrome/android/chrome_jni_headers/AutofillPaymentMethodsDelegate_jni.h"
 #include "chrome/android/chrome_jni_headers/VirtualCardEnrollmentFields_jni.h"
@@ -30,6 +31,8 @@
 using base::android::AttachCurrentThread;
 using base::android::ConvertUTF16ToJavaString;
 using base::android::ConvertUTF8ToJavaString;
+using base::android::JavaRef;
+using base::android::ScopedJavaGlobalRef;
 using base::android::ScopedJavaLocalRef;
 
 namespace autofill {
@@ -75,6 +78,14 @@
   return java_object;
 }
 
+// static
+void RunVirtualCardEnrollmentFieldsLoadedCallback(
+    const JavaRef<jobject>& j_callback,
+    VirtualCardEnrollmentFields* virtual_card_enrollment_fields) {
+  RunObjectCallbackAndroid(j_callback, GetVirtualCardEnrollmentFieldsJavaObject(
+                                           virtual_card_enrollment_fields));
+}
+
 AutofillPaymentMethodsDelegate::AutofillPaymentMethodsDelegate(Profile* profile)
     : profile_(profile) {
   personal_data_manager_ = PersonalDataManagerFactory::GetForProfile(profile);
@@ -110,11 +121,13 @@
       personal_data_manager_->GetCreditCardByInstrumentId(instrument_id);
   virtual_card_enrollment_manager_->OfferVirtualCardEnroll(
       *credit_card, VirtualCardEnrollmentSource::kSettingsPage,
-      profile_->GetPrefs(), base::BindOnce(&risk_util::LoadRiskDataHelper));
+      profile_->GetPrefs(), base::BindOnce(&risk_util::LoadRiskDataHelper),
+      base::BindOnce(&RunVirtualCardEnrollmentFieldsLoadedCallback,
+                     ScopedJavaGlobalRef<jobject>(jcallback)));
 }
 
 void AutofillPaymentMethodsDelegate::EnrollOfferedVirtualCard(JNIEnv* env) {
-  // TODO (crbug/1281695) Implement call to enroll Virtual Cards.
+  virtual_card_enrollment_manager_->Enroll();
 }
 
 void AutofillPaymentMethodsDelegate::UnenrollVirtualCard(
diff --git a/chrome/browser/apps/app_service/app_service_proxy_ash.cc b/chrome/browser/apps/app_service/app_service_proxy_ash.cc
index ef240fc..7b0a685 100644
--- a/chrome/browser/apps/app_service/app_service_proxy_ash.cc
+++ b/chrome/browser/apps/app_service/app_service_proxy_ash.cc
@@ -279,7 +279,12 @@
     return;
   }
 
-  if (uninstall_dialogs_.find(app_id) != uninstall_dialogs_.end()) {
+  // If the dialog exists for the app id, we bring the dialog to the front
+  auto it = uninstall_dialogs_.find(app_id);
+  if (it != uninstall_dialogs_.end()) {
+    if (it->second->GetWidget()) {
+      it->second->GetWidget()->Show();
+    }
     if (!callback.is_null()) {
       std::move(callback).Run(false);
     }
diff --git a/chrome/browser/apps/app_service/uninstall_dialog.cc b/chrome/browser/apps/app_service/uninstall_dialog.cc
index 37bfee43..5312617 100644
--- a/chrome/browser/apps/app_service/uninstall_dialog.cc
+++ b/chrome/browser/apps/app_service/uninstall_dialog.cc
@@ -85,6 +85,10 @@
   }
 }
 
+views::Widget* UninstallDialog::GetWidget() {
+  return widget_;
+}
+
 void UninstallDialog::OnDialogClosed(bool uninstall,
                                      bool clear_site_data,
                                      bool report_abuse) {
@@ -109,8 +113,8 @@
     return;
   }
 
-  UiBase::Create(profile_, app_type_, app_id_, app_name_,
-                 icon_value->uncompressed, parent_window_, this);
+  widget_ = UiBase::Create(profile_, app_type_, app_id_, app_name_,
+                           icon_value->uncompressed, parent_window_, this);
 
   // For browser tests, if the callback is set, run the callback to stop the run
   // loop.
diff --git a/chrome/browser/apps/app_service/uninstall_dialog.h b/chrome/browser/apps/app_service/uninstall_dialog.h
index 2e50ff6..c944b441 100644
--- a/chrome/browser/apps/app_service/uninstall_dialog.h
+++ b/chrome/browser/apps/app_service/uninstall_dialog.h
@@ -14,6 +14,7 @@
 #include "components/services/app_service/public/cpp/icon_types.h"
 #include "components/services/app_service/public/mojom/types.mojom.h"
 #include "ui/gfx/native_widget_types.h"
+#include "ui/views/widget/widget.h"
 
 class NativeWindowTracker;
 class Profile;
@@ -56,13 +57,13 @@
     UiBase& operator=(const UiBase&) = delete;
     virtual ~UiBase() = default;
 
-    static void Create(Profile* profile,
-                       apps::mojom::AppType app_type,
-                       const std::string& app_id,
-                       const std::string& app_name,
-                       gfx::ImageSkia image,
-                       gfx::NativeWindow parent_window,
-                       UninstallDialog* uninstall_dialog);
+    static views::Widget* Create(Profile* profile,
+                                 apps::mojom::AppType app_type,
+                                 const std::string& app_id,
+                                 const std::string& app_name,
+                                 gfx::ImageSkia image,
+                                 gfx::NativeWindow parent_window,
+                                 UninstallDialog* uninstall_dialog);
 
     UninstallDialog* uninstall_dialog() const { return uninstall_dialog_; }
 
@@ -98,6 +99,8 @@
   void PrepareToShow(apps::mojom::IconKeyPtr mojom_icon_key,
                      apps::IconLoader* icon_loader);
 
+  views::Widget* GetWidget();
+
   // Called when the uninstall dialog is closing to process uninstall or cancel
   // the uninstall.
   void OnDialogClosed(bool uninstall, bool clear_site_data, bool report_abuse);
@@ -118,6 +121,8 @@
 
   OnUninstallForTestingCallback uninstall_dialog_created_callback_;
 
+  views::Widget* widget_ = nullptr;
+
   // Tracks whether |parent_window_| got destroyed.
   std::unique_ptr<NativeWindowTracker> parent_window_tracker_;
 
diff --git a/chrome/browser/ash/input_method/text_utils.cc b/chrome/browser/ash/input_method/text_utils.cc
index 7dd0d69..8d76ca63 100644
--- a/chrome/browser/ash/input_method/text_utils.cc
+++ b/chrome/browser/ash/input_method/text_utils.cc
@@ -29,7 +29,7 @@
   while (idx <= pos && pos - idx <= kSpecialWordMaxLength &&
          text[idx] != u' ' && text[idx] != u'(')
     idx--;
-  if (pos - idx > kSpecialWordMaxLength)
+  if (idx > pos || pos - idx > kSpecialWordMaxLength)
     return false;
   std::u16string last_word = text.substr(idx + 1, pos - idx);
   return (last_word == u"c.f." || last_word == u"cf." || last_word == u"e.g." ||
@@ -131,6 +131,8 @@
 }
 
 Sentence FindLastSentence(const std::u16string& text, uint32_t pos) {
+  if (pos > text.size())
+    return Sentence();
   if (pos > 0 &&
       (pos == text.size() || text[pos] == '\n' || text[pos] == '\r')) {
     pos--;
@@ -153,6 +155,8 @@
 }
 
 Sentence FindCurrentSentence(const std::u16string& text, uint32_t pos) {
+  if (pos > text.size())
+    return Sentence();
   if (pos > 0 &&
       (pos == text.size() || text[pos] == '\n' || text[pos] == '\r')) {
     pos--;
diff --git a/chrome/browser/ash/login/screens/gaia_screen.cc b/chrome/browser/ash/login/screens/gaia_screen.cc
index 3ff6793..6d7989f 100644
--- a/chrome/browser/ash/login/screens/gaia_screen.cc
+++ b/chrome/browser/ash/login/screens/gaia_screen.cc
@@ -20,6 +20,7 @@
 constexpr char kUserActionStartEnrollment[] = "startEnrollment";
 constexpr char kUserActionReloadDefault[] = "reloadDefault";
 constexpr char kUserActionSAMLVideoTimeout[] = "samlVideoTimeout";
+constexpr char kUserActionRetry[] = "retry";
 
 }  // namespace
 
@@ -109,6 +110,8 @@
   } else if (action_id == kUserActionReloadDefault) {
     DCHECK(features::IsRedirectToDefaultIdPEnabled());
     LoadOnline(EmptyAccountId());
+  } else if (action_id == kUserActionRetry) {
+    LoadOnline(EmptyAccountId());
   } else if (action_id == kUserActionSAMLVideoTimeout) {
     DCHECK(features::IsRedirectToDefaultIdPEnabled());
     exit_callback_.Run(Result::SAML_VIDEO_TIMEOUT);
diff --git a/chrome/browser/ash/login/ui/mock_login_display.h b/chrome/browser/ash/login/ui/mock_login_display.h
index 069601ca..a95f418 100644
--- a/chrome/browser/ash/login/ui/mock_login_display.h
+++ b/chrome/browser/ash/login/ui/mock_login_display.h
@@ -17,14 +17,13 @@
   MockLoginDisplay(const MockLoginDisplay&) = delete;
   MockLoginDisplay& operator=(const MockLoginDisplay&) = delete;
 
-  ~MockLoginDisplay();
+  ~MockLoginDisplay() override;
 
   MOCK_METHOD0(ClearAndEnablePassword, void(void));
   MOCK_METHOD4(Init, void(const user_manager::UserList&, bool, bool, bool));
   MOCK_METHOD0(OnPreferencesChanged, void(void));
   MOCK_METHOD1(OnUserImageChanged, void(const user_manager::User&));
   MOCK_METHOD1(SetUIEnabled, void(bool));
-  MOCK_METHOD1(ShowSigninUI, void(const std::string&));
 };
 
 }  // namespace ash
diff --git a/chrome/browser/ash/policy/enrollment/private_membership/psm_rlwe_id_provider_impl_unittest.cc b/chrome/browser/ash/policy/enrollment/private_membership/psm_rlwe_id_provider_impl_unittest.cc
new file mode 100644
index 0000000..635a2aad
--- /dev/null
+++ b/chrome/browser/ash/policy/enrollment/private_membership/psm_rlwe_id_provider_impl_unittest.cc
@@ -0,0 +1,51 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ash/policy/enrollment/private_membership/psm_rlwe_id_provider_impl.h"
+
+#include "chromeos/system/fake_statistics_provider.h"
+#include "chromeos/system/statistics_provider.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/private_membership/src/private_membership_rlwe.pb.h"
+
+namespace psm_rlwe = private_membership::rlwe;
+
+namespace policy {
+
+class PsmRlweIdProviderImplTest : public testing::Test {
+ protected:
+  PsmRlweIdProviderImplTest() = default;
+
+  PsmRlweIdProviderImplTest(const PsmRlweIdProviderImplTest&) = delete;
+  PsmRlweIdProviderImplTest& operator=(const PsmRlweIdProviderImplTest&) =
+      delete;
+  ~PsmRlweIdProviderImplTest() override = default;
+
+  chromeos::system::ScopedFakeStatisticsProvider fake_statistics_provider_;
+  PsmRlweIdProviderImpl psm_rlwe_impl;
+  const std::string kTestSerialNumber = "111111";
+  const std::string kTestBrandCode = "TEST";
+
+  // `kTestBrandCode` encoded in hex.
+  const std::string kTestBrandCodeHex = "54455354";
+};
+
+TEST_F(PsmRlweIdProviderImplTest, VerifyConstructedRlweId) {
+  // Sets the values for serial number and RLZ brand code as the values must be
+  // present to construct the RLWE ID without CHECK-failures.
+  fake_statistics_provider_.SetMachineStatistic(
+      chromeos::system::kSerialNumberKeyForTest, kTestSerialNumber);
+  fake_statistics_provider_.SetMachineStatistic(
+      chromeos::system::kRlzBrandCodeKey, kTestBrandCode);
+
+  // RLZ brand code "TEST" (as hex), "/" seperator, and serial number "111111".
+  const std::string kExpectedPsmRlweIdStr =
+      kTestBrandCodeHex + "/" + kTestSerialNumber;
+
+  // Construct the PSM RLWE ID, and verify its value.
+  psm_rlwe::RlwePlaintextId rlwe_id = psm_rlwe_impl.ConstructRlweId();
+  EXPECT_EQ(rlwe_id.sensitive_id(), kExpectedPsmRlweIdStr);
+}
+
+}  // namespace policy
diff --git a/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.cc b/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.cc
index 5df557b..4e47bb21 100644
--- a/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.cc
+++ b/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.cc
@@ -636,7 +636,7 @@
 
     if (filter_builder->GetMode() ==
         BrowsingDataFilterBuilder::Mode::kPreserve) {
-      PrivacySandboxSettings* privacy_sandbox_settings =
+      auto* privacy_sandbox_settings =
           PrivacySandboxSettingsFactory::GetForProfile(profile_);
       if (privacy_sandbox_settings)
         privacy_sandbox_settings->OnCookiesCleared();
diff --git a/chrome/browser/chrome_browser_interface_binders.cc b/chrome/browser/chrome_browser_interface_binders.cc
index 22d8c6d..b455296fc 100644
--- a/chrome/browser/chrome_browser_interface_binders.cc
+++ b/chrome/browser/chrome_browser_interface_binders.cc
@@ -264,6 +264,11 @@
 #include "ui/webui/resources/cr_components/app_management/app_management.mojom.h"
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
+#include "components/services/screen_ai/public/cpp/screen_ai_service_router.h"
+#include "components/services/screen_ai/public/cpp/screen_ai_service_router_factory.h"
+#endif
+
 #if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_MAC) || \
     BUILDFLAG(IS_ANDROID)
 #if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_MAC)
@@ -557,6 +562,16 @@
 }
 #endif
 
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
+void BindScreenAIAnnotator(
+    content::RenderFrameHost* frame_host,
+    mojo::PendingReceiver<screen_ai::mojom::ScreenAIAnnotator> receiver) {
+  ScreenAIServiceRouterFactory::GetForBrowserContext(
+      frame_host->GetProcess()->GetBrowserContext())
+      ->BindScreenAIAnnotator(std::move(receiver));
+}
+#endif
+
 void PopulateChromeFrameBinders(
     mojo::BinderMapWithContext<content::RenderFrameHost*>* map,
     content::RenderFrameHost* render_frame_host) {
@@ -687,6 +702,13 @@
         base::BindRepeating(&web_app::SubAppsServiceImpl::CreateIfAllowed));
   }
 #endif
+
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
+  if (base::FeatureList::IsEnabled(features::kScreenAI)) {
+    map->Add<screen_ai::mojom::ScreenAIAnnotator>(
+        base::BindRepeating(&BindScreenAIAnnotator));
+  }
+#endif
 }
 
 void PopulateChromeWebUIFrameBinders(
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index f7cd7ae..69a555f2 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -2774,7 +2774,7 @@
     const url::Origin& api_origin) {
   Profile* profile =
       Profile::FromBrowserContext(render_frame_host->GetBrowserContext());
-  PrivacySandboxSettings* privacy_sandbox_settings =
+  auto* privacy_sandbox_settings =
       PrivacySandboxSettingsFactory::GetForProfile(profile);
   DCHECK(privacy_sandbox_settings);
 
@@ -2804,7 +2804,7 @@
     const url::Origin* reporting_origin) {
   Profile* profile = Profile::FromBrowserContext(browser_context);
 
-  PrivacySandboxSettings* privacy_sandbox_settings =
+  auto* privacy_sandbox_settings =
       PrivacySandboxSettingsFactory::GetForProfile(profile);
   if (!privacy_sandbox_settings)
     return false;
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index 6d3fcaf..98fa00d 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -431,6 +431,8 @@
     "//components/services/app_service/public/cpp:instance_update",
     "//components/services/app_service/public/cpp:intents",
     "//components/services/app_service/public/cpp:types",
+    "//components/services/unzip/content:content",
+    "//components/services/unzip/public/cpp:cpp",
     "//components/session_manager/core",
     "//components/signin/public/identity_manager",
     "//components/signin/public/webdata",
@@ -509,6 +511,7 @@
     "//storage/common",
     "//third_party/blink/public/common",
     "//third_party/boringssl",
+    "//third_party/ced",
     "//third_party/icu",
     "//third_party/libaddressinput",
     "//third_party/libipp",
@@ -4467,6 +4470,7 @@
     "../ash/policy/enrollment/account_status_check_fetcher_unittest.cc",
     "../ash/policy/enrollment/auto_enrollment_client_impl_unittest.cc",
     "../ash/policy/enrollment/device_cloud_policy_initializer_unittest.cc",
+    "../ash/policy/enrollment/private_membership/psm_rlwe_id_provider_impl_unittest.cc",
     "../ash/policy/enrollment/private_membership/testing_private_membership_rlwe_client.cc",
     "../ash/policy/enrollment/private_membership/testing_private_membership_rlwe_client.h",
     "../ash/policy/enrollment/tpm_enrollment_key_signing_service_unittest.cc",
@@ -4780,6 +4784,7 @@
     "../ui/webui/settings/about_handler_unittest.cc",
     "../ui/webui/settings/ash/calculator/size_calculator_test_api.h",
     "../ui/webui/settings/ash/os_apps_page/app_notification_handler_unittest.cc",
+    "../ui/webui/settings/ash/os_apps_page/mojom/app_type_mojom_traits_unittest.cc",
     "../ui/webui/settings/chromeos/ambient_mode_handler_unittest.cc",
     "../ui/webui/settings/chromeos/bluetooth_handler_unittest.cc",
     "../ui/webui/settings/chromeos/change_picture_handler_unittest.cc",
diff --git a/chrome/browser/chromeos/DEPS b/chrome/browser/chromeos/DEPS
index 251c8bac..cd622ae 100644
--- a/chrome/browser/chromeos/DEPS
+++ b/chrome/browser/chromeos/DEPS
@@ -30,6 +30,7 @@
   "+services/network",
   "+services/tracing/public",
   "+services/viz/public/mojom",
+  "+third_party/ced",
 ]
 
 specific_include_rules = {
diff --git a/chrome/browser/chromeos/extensions/file_manager/private_api_mount.cc b/chrome/browser/chromeos/extensions/file_manager/private_api_mount.cc
index 7199354b..4907fb09a 100644
--- a/chrome/browser/chromeos/extensions/file_manager/private_api_mount.cc
+++ b/chrome/browser/chromeos/extensions/file_manager/private_api_mount.cc
@@ -5,7 +5,6 @@
 #include "chrome/browser/chromeos/extensions/file_manager/private_api_mount.h"
 
 #include <memory>
-#include <string>
 #include <utility>
 
 #include "ash/components/disks/disk_mount_manager.h"
@@ -27,6 +26,8 @@
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/common/extensions/api/file_manager_private.h"
 #include "components/drive/event_logger.h"
+#include "components/services/unzip/content/unzip_service.h"
+#include "components/services/unzip/public/cpp/unzip.h"
 #include "content/public/browser/browser_thread.h"
 #include "google_apis/common/task_util.h"
 #include "storage/browser/file_system/file_system_url.h"
@@ -41,6 +42,9 @@
 FileManagerPrivateAddMountFunction::FileManagerPrivateAddMountFunction() =
     default;
 
+FileManagerPrivateAddMountFunction::~FileManagerPrivateAddMountFunction() =
+    default;
+
 ExtensionFunction::ResponseAction FileManagerPrivateAddMountFunction::Run() {
   using file_manager_private::AddMount::Params;
   const std::unique_ptr<Params> params = Params::Create(args());
@@ -55,10 +59,10 @@
   }
   set_log_on_completion(true);
 
-  const base::FilePath path = file_manager::util::GetLocalPathFromURL(
-      render_frame_host(), profile, GURL(params->source));
+  path_ = file_manager::util::GetLocalPathFromURL(render_frame_host(), profile,
+                                                  GURL(params->source));
 
-  if (path.empty())
+  if (path_.empty())
     return RespondNow(Error("Invalid path"));
 
   if (auto* notifier =
@@ -77,19 +81,47 @@
 
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
-  std::vector<std::string> options;
   if (params->password)
-    options.push_back("password=" + *params->password);
+    options_.push_back("password=" + *params->password);
 
-  // MountPath() takes a std::string.
-  DiskMountManager* disk_mount_manager = DiskMountManager::GetInstance();
-  disk_mount_manager->MountPath(
-      path.AsUTF8Unsafe(), base::ToLowerASCII(path.Extension()),
-      path.BaseName().AsUTF8Unsafe(), options, chromeos::MOUNT_TYPE_ARCHIVE,
-      chromeos::MOUNT_ACCESS_MODE_READ_WRITE, base::DoNothing());
+  extension_ = base::ToLowerASCII(path_.Extension());
+
+  // Detect the file path encoding of ZIP archives.
+  if (extension_ == ".zip") {
+    unzip::DetectEncoding(
+        unzip::LaunchUnzipper(), path_,
+        base::BindOnce(&FileManagerPrivateAddMountFunction::OnEncodingDetected,
+                       this));
+  } else {
+    FinishMounting();
+  }
 
   // Pass back the actual source path of the mount point.
-  return RespondNow(OneArgument(base::Value(path.AsUTF8Unsafe())));
+  return RespondNow(OneArgument(base::Value(path_.AsUTF8Unsafe())));
+}
+
+void FileManagerPrivateAddMountFunction::OnEncodingDetected(
+    const Encoding encoding) {
+  // Pass the detected ZIP encoding as a mount option.
+  std::string& option = options_.emplace_back("encoding=");
+
+  if (IsShiftJisOrVariant(encoding) || encoding == RUSSIAN_CP866) {
+    option += MimeEncodingName(encoding);
+  } else {
+    option += "libzip";
+  }
+
+  FinishMounting();
+}
+
+void FileManagerPrivateAddMountFunction::FinishMounting() {
+  DiskMountManager* const disk_mount_manager = DiskMountManager::GetInstance();
+  DCHECK(disk_mount_manager);
+  disk_mount_manager->MountPath(
+      path_.AsUTF8Unsafe(), std::move(extension_),
+      path_.BaseName().AsUTF8Unsafe(), std::move(options_),
+      chromeos::MOUNT_TYPE_ARCHIVE, chromeos::MOUNT_ACCESS_MODE_READ_WRITE,
+      base::DoNothing());
 }
 
 ExtensionFunction::ResponseAction FileManagerPrivateRemoveMountFunction::Run() {
diff --git a/chrome/browser/chromeos/extensions/file_manager/private_api_mount.h b/chrome/browser/chromeos/extensions/file_manager/private_api_mount.h
index aaab168..8650fae5 100644
--- a/chrome/browser/chromeos/extensions/file_manager/private_api_mount.h
+++ b/chrome/browser/chromeos/extensions/file_manager/private_api_mount.h
@@ -7,8 +7,12 @@
 #ifndef CHROME_BROWSER_CHROMEOS_EXTENSIONS_FILE_MANAGER_PRIVATE_API_MOUNT_H_
 #define CHROME_BROWSER_CHROMEOS_EXTENSIONS_FILE_MANAGER_PRIVATE_API_MOUNT_H_
 
+#include <string>
+#include <vector>
+
 #include "chrome/browser/chromeos/extensions/file_manager/logged_extension_function.h"
 #include "components/drive/file_errors.h"
+#include "third_party/ced/src/util/encodings/encodings.h"
 
 namespace extensions {
 
@@ -21,11 +25,26 @@
   DECLARE_EXTENSION_FUNCTION("fileManagerPrivate.addMount",
                              FILEMANAGERPRIVATE_ADDMOUNT)
 
- protected:
-  ~FileManagerPrivateAddMountFunction() override = default;
+ private:
+  ~FileManagerPrivateAddMountFunction() override;
 
   // ExtensionFunction overrides.
   ResponseAction Run() override;
+
+  // Called when the encoding of a ZIP archive has been determined.
+  void OnEncodingDetected(Encoding encoding);
+
+  // Finishes mounting after encoding is detected.
+  void FinishMounting();
+
+  // Path of the device or archive to mount.
+  base::FilePath path_;
+
+  // Lowercase extension of the path to mount.
+  std::string extension_;
+
+  // Mount options.
+  std::vector<std::string> options_;
 };
 
 // Implements chrome.fileManagerPrivate.removeMount method.
diff --git a/chrome/browser/incognito/BUILD.gn b/chrome/browser/incognito/BUILD.gn
index 53bec49..4446fc7 100644
--- a/chrome/browser/incognito/BUILD.gn
+++ b/chrome/browser/incognito/BUILD.gn
@@ -10,6 +10,8 @@
     "android/java/src/org/chromium/chrome/browser/incognito/IncognitoStartup.java",
     "android/java/src/org/chromium/chrome/browser/incognito/IncognitoTabPersistence.java",
     "android/java/src/org/chromium/chrome/browser/incognito/IncognitoUtils.java",
+    "android/java/src/org/chromium/chrome/browser/incognito/reauth/IncognitoReauthController.java",
+    "android/java/src/org/chromium/chrome/browser/incognito/reauth/IncognitoReauthCoordinator.java",
     "android/java/src/org/chromium/chrome/browser/incognito/reauth/IncognitoReauthManager.java",
     "android/java/src/org/chromium/chrome/browser/incognito/reauth/IncognitoReauthSettingSwitchPreference.java",
     "android/java/src/org/chromium/chrome/browser/incognito/reauth/IncognitoReauthSettingUtils.java",
@@ -17,6 +19,7 @@
   deps = [
     ":java_resources",
     "//base:base_java",
+    "//chrome/browser/android/lifecycle:java",
     "//chrome/browser/dependency_injection:java",
     "//chrome/browser/device_reauth/android:java",
     "//chrome/browser/flags:java",
@@ -25,6 +28,7 @@
     "//chrome/browser/tab:java",
     "//chrome/browser/tabmodel:java",
     "//chrome/browser/tabpersistence:java",
+    "//chrome/browser/ui/android/layouts:java",
     "//chrome/browser/ui/android/strings:ui_strings_grd",
     "//chrome/browser/util:java",
     "//components/browser_ui/settings/android:java",
@@ -93,20 +97,31 @@
   # Platform checks are broken for Robolectric. See https://crbug.com/1071638.
   bypass_platform_checks = true
 
-  sources = [ "android/java/src/org/chromium/chrome/browser/incognito/reauth/IncognitoReauthManagerTest.java" ]
+  sources = [
+    "android/java/src/org/chromium/chrome/browser/incognito/reauth/IncognitoReauthControllerTest.java",
+    "android/java/src/org/chromium/chrome/browser/incognito/reauth/IncognitoReauthManagerTest.java",
+  ]
 
   deps = [
     ":java",
     "//base:base_java",
     "//base:base_java_test_support",
     "//base:base_junit_test_support",
+    "//chrome/android:chrome_java",
+    "//chrome/browser/android/lifecycle:java",
     "//chrome/browser/device_reauth/android:java",
     "//chrome/browser/flags:java",
+    "//chrome/browser/profiles/android:java",
+    "//chrome/browser/tab:java",
+    "//chrome/browser/tabmodel:java",
+    "//chrome/browser/ui/android/layouts:java",
     "//chrome/test/android:chrome_java_test_support",
     "//third_party/android_deps:robolectric_all_java",
+    "//third_party/android_support_test_runner:runner_java",
     "//third_party/androidx:androidx_test_runner_java",
     "//third_party/junit:junit",
     "//third_party/mockito:mockito_java",
     "//ui/android:ui_java_test_support",
+    "//ui/android:ui_no_recycler_view_java",
   ]
 }
diff --git a/chrome/browser/incognito/android/java/src/org/chromium/chrome/browser/incognito/reauth/IncognitoReauthController.java b/chrome/browser/incognito/android/java/src/org/chromium/chrome/browser/incognito/reauth/IncognitoReauthController.java
new file mode 100644
index 0000000..2560773
--- /dev/null
+++ b/chrome/browser/incognito/android/java/src/org/chromium/chrome/browser/incognito/reauth/IncognitoReauthController.java
@@ -0,0 +1,265 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.incognito.reauth;
+
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import org.chromium.base.Callback;
+import org.chromium.base.CallbackController;
+import org.chromium.base.supplier.ObservableSupplier;
+import org.chromium.base.supplier.OneshotSupplier;
+import org.chromium.chrome.browser.layouts.LayoutStateProvider;
+import org.chromium.chrome.browser.layouts.LayoutType;
+import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher;
+import org.chromium.chrome.browser.lifecycle.StartStopWithNativeObserver;
+import org.chromium.chrome.browser.profiles.Profile;
+import org.chromium.chrome.browser.tab.TabLaunchType;
+import org.chromium.chrome.browser.tab.state.CriticalPersistedTabData;
+import org.chromium.chrome.browser.tabmodel.IncognitoTabModelObserver;
+import org.chromium.chrome.browser.tabmodel.TabModel;
+import org.chromium.chrome.browser.tabmodel.TabModelSelector;
+import org.chromium.chrome.browser.tabmodel.TabModelSelectorObserver;
+import org.chromium.ui.modaldialog.DialogDismissalCause;
+import org.chromium.ui.modaldialog.ModalDialogManager;
+
+/**
+ * This is the access point for showing the Incognito re-auth dialog. It controls building the
+ * {@link IncognitoReauthCoordinator} and showing/hiding the re-auth dialog. The {@link
+ * IncognitoReauthCoordinator} is created and destroyed each time the dialog is shown and hidden.
+ */
+public class IncognitoReauthController
+        implements IncognitoTabModelObserver.IncognitoReauthDialogDelegate,
+                   StartStopWithNativeObserver {
+    // This callback is fired when the user clicks on "Unlock Incognito" option.
+    // This contains the logic to not require further re-authentication if the last one was a
+    // success. Please note, a re-authentication would be required again when Chrome is brought to
+    // foreground again.
+    // TODO(crbug.com/1227656): Connect this callback to "Unlock Incognito" option once the MVC is
+    // added.
+    private final IncognitoReauthManager.IncognitoReauthCallback mIncognitoReauthCallback =
+            new IncognitoReauthManager.IncognitoReauthCallback() {
+                @Override
+                public void onIncognitoReauthNotPossible() {
+                    // TODO(crbug.com/1227656): Add new dialog dismissal cause as the meaning can be
+                    // ambiguous.
+                    hideDialogIfShowing(DialogDismissalCause.UNKNOWN);
+                }
+
+                @Override
+                public void onIncognitoReauthSuccess() {
+                    mIncognitoReauthPending = false;
+                    // TODO(crbug.com/1227656): Add new dialog dismissal cause as the meaning can be
+                    // ambiguous.
+                    hideDialogIfShowing(DialogDismissalCause.ACTION_ON_CONTENT);
+                }
+
+                @Override
+                public void onIncognitoReauthFailure() {}
+            };
+
+    // If the user has closed all Incognito tabs, then they don't need to go through re-auth to open
+    // fresh Incognito tabs.
+    private final IncognitoTabModelObserver mIncognitoTabModelObserver =
+            new IncognitoTabModelObserver() {
+                @Override
+                public void didBecomeEmpty() {
+                    mIncognitoReauthPending = false;
+                }
+            };
+
+    // An observer to handle cases for Incognito tabs restore cases.
+    private final TabModelSelectorObserver mTabModelSelectorObserver =
+            new TabModelSelectorObserver() {
+                @Override
+                public void onTabStateInitialized() {
+                    onTabStateInitializedForReauth();
+                }
+            };
+
+    // The {@link TabModelSelectorProfileSupplier} passed to the constructor may not have a {@link
+    // Profile} set if the Tab state is not initialized yet. We make use of the {@link Profile} when
+    // accessing {@link UserPrefs} in showDialogIfRequired. A null {@link Profile} would result in a
+    // crash when accessing the pref. Therefore this callback is fired when the Profile is ready
+    // which sets the |mProfile| and shows the re-auth dialog if required.
+    private final Callback<Profile> mProfileSupplierCallback = new Callback<Profile>() {
+        @Override
+        public void onResult(@NonNull Profile profile) {
+            mProfile = profile;
+            showDialogIfRequired();
+        }
+    };
+
+    // The {@link CallbackController} to populate the |mLayoutStateProvider| when it becomes
+    // available.
+    private final CallbackController mLayoutStateProviderCallbackController =
+            new CallbackController();
+
+    private final @NonNull Context mContext;
+    private final @NonNull ActivityLifecycleDispatcher mActivityLifecycleDispatcher;
+    private final @NonNull TabModelSelector mTabModelSelector;
+    private final @NonNull ModalDialogManager mModalDialogManager;
+    private final @NonNull ObservableSupplier<Profile> mProfileObservableSupplier;
+
+    // No strong reference to this should be made outside of this class because
+    // we set this to null in hideDialogIfShowing for it to be garbage collected.
+    private @Nullable IncognitoReauthCoordinator mIncognitoReauthCoordinator;
+    private @Nullable LayoutStateProvider mLayoutStateProvider;
+
+    private @Nullable Profile mProfile;
+    private boolean mIncognitoReauthPending;
+
+    /**
+     * @param context The {@link Context} of the Activity where the re-auth dialog would be shown.
+     * @param tabModelSelector The {@link TabModelSelector} in order to interact with the
+     *         regular/Incognito {@link TabModel}.
+     * @param dispatcher The {@link ActivityLifecycleDispatcher} in order to register to
+     *         onStartWithNative event.
+     * @param modalDialogManager The {@link ModalDialogManager} of the underlying {@link
+     *         ChromeActivity} to handle dialogs.
+     * @param layoutStateProviderOneshotSupplier A supplier of {@link LayoutStateProvider} which is
+     *         used to determine the current {@link LayoutType} which is shown.
+     * @param profileSupplier A Observable Supplier of {@link Profile} which is used to query the
+     *         preference value of the Incognito lock setting.
+     */
+    public IncognitoReauthController(@NonNull Context context,
+            @NonNull TabModelSelector tabModelSelector,
+            @NonNull ActivityLifecycleDispatcher dispatcher,
+            @NonNull ModalDialogManager modalDialogManager,
+            @NonNull OneshotSupplier<LayoutStateProvider> layoutStateProviderOneshotSupplier,
+            @NonNull ObservableSupplier<Profile> profileSupplier) {
+        mContext = context;
+        mTabModelSelector = tabModelSelector;
+        mActivityLifecycleDispatcher = dispatcher;
+        mModalDialogManager = modalDialogManager;
+        mProfileObservableSupplier = profileSupplier;
+        mProfileObservableSupplier.addObserver(mProfileSupplierCallback);
+
+        layoutStateProviderOneshotSupplier.onAvailable(
+                mLayoutStateProviderCallbackController.makeCancelable(layoutStateProvider -> {
+                    mLayoutStateProvider = layoutStateProvider;
+                    showDialogIfRequired();
+                }));
+
+        mTabModelSelector.setIncognitoReauthDialogDelegate(this);
+        mTabModelSelector.addIncognitoTabModelObserver(mIncognitoTabModelObserver);
+        mTabModelSelector.addObserver(mTabModelSelectorObserver);
+
+        mActivityLifecycleDispatcher.register(this);
+
+        if (mTabModelSelector.isTabStateInitialized()) {
+            // It may happen that the tab state was initialized before the
+            // |mTabModelSelectorObserver| was added which explicitly takes care of restore case.
+            // Therefore, we need another restore check here for such a case.
+            onTabStateInitializedForReauth();
+        }
+    }
+
+    /**
+     * Should be called when the underlying {@link ChromeActivity} is destroyed.
+     */
+    public void destroy() {
+        mActivityLifecycleDispatcher.unregister(this);
+        mTabModelSelector.setIncognitoReauthDialogDelegate(null);
+        mTabModelSelector.removeIncognitoTabModelObserver(mIncognitoTabModelObserver);
+        mTabModelSelector.removeObserver(mTabModelSelectorObserver);
+        mProfileObservableSupplier.removeObserver(mProfileSupplierCallback);
+        mLayoutStateProviderCallbackController.destroy();
+        hideDialogIfShowing(DialogDismissalCause.ACTIVITY_DESTROYED);
+    }
+
+    /**
+     * Returns true if the re-auth page is showing, false otherwise.
+     */
+    public boolean isReauthPageShowing() {
+        return mIncognitoReauthCoordinator != null;
+    }
+
+    /**
+     * Override from {@link StartStopWithNativeObserver}. This relays the signal that Chrome was
+     * brought to foreground.
+     */
+    @Override
+    public void onStartWithNative() {
+        showDialogIfRequired();
+    }
+
+    /**
+     * Override from {@link StartStopWithNativeObserver}.
+     */
+    @Override
+    public void onStopWithNative() {
+        // |mIncognitoReauthPending| also gets set in
+        // IncognitoReauthController#onTabStateInitializedForReauth when the tab state is
+        // initialized for the first time which is needed to handle cases of restored Incognito tabs
+        // where a re-auth should be required. To tackle the more general case, we track the
+        // onStopWithNative to update the |mIncognitoReauthPending| which gets called each time when
+        // Chrome goes to background.
+        mIncognitoReauthPending = (mTabModelSelector.getModel(/*incognito=*/true).getCount() > 0);
+    }
+
+    /**
+     * Override from {@link IncognitoReauthDialogDelegate}. This relays the signal that the TabModal
+     * has changed. This is fired when all other observers of {@link onTabModelChanged} have been
+     * notified to bring determinism in the re-auth dialog.
+     */
+    @Override
+    public void onAfterTabModelSelected(TabModel newModel, TabModel oldModel) {
+        if ((newModel.isIncognito())) {
+            showDialogIfRequired();
+        } else {
+            // TODO(crbug.com/1227656): Add new dialog dismissal cause as the meaning can be
+            // ambiguous.
+            hideDialogIfShowing(DialogDismissalCause.NAVIGATE);
+        }
+    }
+
+    /**
+     * TODO(crbug.com/1227656): Add an extra check on IncognitoReauthManager#canAuthenticate method
+     * if needed here to tackle the case where a re-authentication might not be possible from the
+     * systems end in which case we should not show a re-auth dialog. The method currently doesn't
+     * exists and may need to be exposed.
+     */
+    private void showDialogIfRequired() {
+        if (mIncognitoReauthCoordinator != null) return;
+        if (mLayoutStateProvider == null) return;
+        if (!mIncognitoReauthPending) return;
+        if (!mTabModelSelector.isIncognitoSelected()) return;
+        if (mProfile == null) return;
+        if (!IncognitoReauthManager.isIncognitoReauthEnabled(mProfile)) return;
+
+        boolean showFullScreen = !mLayoutStateProvider.isLayoutVisible(LayoutType.TAB_SWITCHER);
+        mIncognitoReauthCoordinator = new IncognitoReauthCoordinator(mContext, mTabModelSelector,
+                mModalDialogManager, mIncognitoReauthCallback, showFullScreen);
+        mIncognitoReauthCoordinator.showDialog();
+    }
+
+    private void hideDialogIfShowing(@DialogDismissalCause int dismissalCause) {
+        if (mIncognitoReauthCoordinator != null) {
+            mIncognitoReauthCoordinator.hideDialogAndDestroy(dismissalCause);
+            mIncognitoReauthCoordinator = null;
+        }
+    }
+
+    // Tab state initialized is called when creating tabs from launcher shortcut or restore. Re-auth
+    // dialogs should be shown iff any Incognito tabs were restored.
+    private void onTabStateInitializedForReauth() {
+        boolean hasRestoredIncognitoTabs = false;
+        TabModel incognitoTabModel = mTabModelSelector.getModel(/*incognito=*/true);
+        for (int i = 0; i < incognitoTabModel.getCount(); ++i) {
+            @TabLaunchType
+            Integer tabLaunchType = CriticalPersistedTabData.from(incognitoTabModel.getTabAt(i))
+                                            .getTabLaunchTypeAtCreation();
+            if (tabLaunchType == TabLaunchType.FROM_RESTORE) {
+                hasRestoredIncognitoTabs = true;
+                break;
+            }
+        }
+        mIncognitoReauthPending = hasRestoredIncognitoTabs;
+        showDialogIfRequired();
+    }
+}
diff --git a/chrome/browser/incognito/android/java/src/org/chromium/chrome/browser/incognito/reauth/IncognitoReauthControllerTest.java b/chrome/browser/incognito/android/java/src/org/chromium/chrome/browser/incognito/reauth/IncognitoReauthControllerTest.java
new file mode 100644
index 0000000..0f62075
--- /dev/null
+++ b/chrome/browser/incognito/android/java/src/org/chromium/chrome/browser/incognito/reauth/IncognitoReauthControllerTest.java
@@ -0,0 +1,304 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.incognito.reauth;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+
+import androidx.test.filters.MediumTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import org.chromium.base.UserDataHost;
+import org.chromium.base.supplier.ObservableSupplierImpl;
+import org.chromium.base.supplier.OneshotSupplierImpl;
+import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.chrome.browser.layouts.LayoutStateProvider;
+import org.chromium.chrome.browser.layouts.LayoutType;
+import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher;
+import org.chromium.chrome.browser.profiles.Profile;
+import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.tab.TabImpl;
+import org.chromium.chrome.browser.tab.TabLaunchType;
+import org.chromium.chrome.browser.tab.state.CriticalPersistedTabData;
+import org.chromium.chrome.browser.tabmodel.IncognitoTabModelObserver;
+import org.chromium.chrome.browser.tabmodel.TabModel;
+import org.chromium.chrome.browser.tabmodel.TabModelSelector;
+import org.chromium.chrome.browser.tabmodel.TabModelSelectorObserver;
+import org.chromium.ui.modaldialog.ModalDialogManager;
+
+/**
+ * Unit tests for {@link IncognitoReauthController}.
+ */
+@RunWith(BaseRobolectricTestRunner.class)
+public class IncognitoReauthControllerTest {
+    @Mock
+    private Context mContextMock;
+    @Mock
+    private ActivityLifecycleDispatcher mActivityLifecycleDispatcherMock;
+    @Mock
+    private LayoutStateProvider mLayoutStateProviderMock;
+    @Mock
+    private TabModelSelector mTabModelSelectorMock;
+    @Mock
+    private ModalDialogManager mModalDialogManagerMock;
+    @Mock
+    private TabModel mIncognitoTabModelMock;
+    @Mock
+    private TabModel mRegularTabModelMock;
+    @Mock
+    private Profile mProfileMock;
+
+    @Captor
+    ArgumentCaptor<TabModelSelectorObserver> mTabModelSelectorObserverCaptor;
+    @Captor
+    ArgumentCaptor<IncognitoTabModelObserver> mIncognitoTabModelObserverCaptor;
+
+    private IncognitoReauthController mIncognitoReauthController;
+    private OneshotSupplierImpl<LayoutStateProvider> mLayoutStateProviderOneshotSupplier;
+    private ObservableSupplierImpl<Profile> mProfileObservableSupplier;
+
+    private void switchToIncognitoTabModel() {
+        doReturn(true).when(mTabModelSelectorMock).isIncognitoSelected();
+        mIncognitoReauthController.onAfterTabModelSelected(
+                /*newModel=*/mIncognitoTabModelMock, /*oldModel=*/mRegularTabModelMock);
+    }
+
+    private void switchToRegularTabModel() {
+        doReturn(false).when(mTabModelSelectorMock).isIncognitoSelected();
+        mIncognitoReauthController.onAfterTabModelSelected(
+                /*newModel=*/mRegularTabModelMock, /*oldModel=*/mIncognitoTabModelMock);
+    }
+
+    private Tab prepareTabForRestoreOrLauncherShortcut(boolean isRestore) {
+        TabImpl tab = mock(TabImpl.class);
+        doReturn(true).when(tab).isInitialized();
+        UserDataHost userDataHost = new UserDataHost();
+        CriticalPersistedTabData criticalPersistedTabData = mock(CriticalPersistedTabData.class);
+        userDataHost.setUserData(CriticalPersistedTabData.class, criticalPersistedTabData);
+        doReturn(userDataHost).when(tab).getUserDataHost();
+        doReturn(0).when(criticalPersistedTabData).getRootId();
+        @TabLaunchType
+        Integer launchType =
+                isRestore ? TabLaunchType.FROM_RESTORE : TabLaunchType.FROM_LAUNCHER_SHORTCUT;
+        doReturn(launchType).when(criticalPersistedTabData).getTabLaunchTypeAtCreation();
+        return tab;
+    }
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        doReturn(false).when(mTabModelSelectorMock).isTabStateInitialized();
+        doReturn(false).when(mTabModelSelectorMock).isIncognitoSelected();
+
+        doNothing()
+                .when(mTabModelSelectorMock)
+                .addObserver(mTabModelSelectorObserverCaptor.capture());
+        doNothing()
+                .when(mTabModelSelectorMock)
+                .addIncognitoTabModelObserver(mIncognitoTabModelObserverCaptor.capture());
+        doNothing().when(mTabModelSelectorMock).setIncognitoReauthDialogDelegate(any());
+        doNothing().when(mActivityLifecycleDispatcherMock).register(any());
+
+        doReturn(mIncognitoTabModelMock).when(mTabModelSelectorMock).getModel(/*incognito=*/true);
+        doReturn(0).when(mIncognitoTabModelMock).getCount();
+        doReturn(true).when(mIncognitoTabModelMock).isIncognito();
+        doReturn(false).when(mRegularTabModelMock).isIncognito();
+        doReturn(false).when(mLayoutStateProviderMock).isLayoutVisible(LayoutType.TAB_SWITCHER);
+
+        mLayoutStateProviderOneshotSupplier = new OneshotSupplierImpl<>();
+        mLayoutStateProviderOneshotSupplier.set(mLayoutStateProviderMock);
+        mProfileObservableSupplier = new ObservableSupplierImpl<>();
+        IncognitoReauthManager.setIsIncognitoReauthFeatureAvailableForTesting(true);
+
+        mIncognitoReauthController = new IncognitoReauthController(mContextMock,
+                mTabModelSelectorMock, mActivityLifecycleDispatcherMock, mModalDialogManagerMock,
+                mLayoutStateProviderOneshotSupplier, mProfileObservableSupplier);
+        mProfileObservableSupplier.set(mProfileMock);
+    }
+
+    @After
+    public void tearDown() {
+        doNothing().when(mActivityLifecycleDispatcherMock).unregister(any());
+        doNothing()
+                .when(mTabModelSelectorMock)
+                .removeObserver(mTabModelSelectorObserverCaptor.capture());
+        doNothing()
+                .when(mTabModelSelectorMock)
+                .removeIncognitoTabModelObserver(mIncognitoTabModelObserverCaptor.capture());
+        mIncognitoReauthController.destroy();
+
+        verify(mActivityLifecycleDispatcherMock, times(1)).unregister(any());
+        verify(mTabModelSelectorMock, times(1))
+                .removeObserver(mTabModelSelectorObserverCaptor.capture());
+        verify(mTabModelSelectorMock, times(1))
+                .removeIncognitoTabModelObserver(mIncognitoTabModelObserverCaptor.capture());
+    }
+
+    /**
+     * This tests that we don't show a re-auth for freshly created Incognito tabs where Chrome has
+     * not been backgrounded yet.
+     */
+    @Test
+    @MediumTest
+    public void testIncognitoTabsCreated_BeforeBackground_DoesNotShowReauth() {
+        // Pretend there's one incognito tab.
+        doReturn(1).when(mIncognitoTabModelMock).getCount();
+        switchToIncognitoTabModel();
+
+        assertFalse("IncognitoReauthCoordinator should not be created for fresh Incognito"
+                        + " session when Chrome has not been backgrounded yet.",
+                mIncognitoReauthController.isReauthPageShowing());
+    }
+
+    /**
+     *  This tests that we do show a re-auth when Incognito tabs already exists after Chrome comes
+     * to foreground.
+     */
+    @Test
+    @MediumTest
+    public void testIncognitoTabsCreated_BeforeForeground_ShowsReauth() {
+        // Pretend there's one incognito tab.
+        doReturn(1).when(mIncognitoTabModelMock).getCount();
+        switchToIncognitoTabModel();
+
+        mIncognitoReauthController.onStopWithNative();
+        mIncognitoReauthController.onStartWithNative();
+
+        assertTrue("IncognitoReauthCoordinator should be created when Incognito tabs"
+                        + " exists already after coming to foreground.",
+                mIncognitoReauthController.isReauthPageShowing());
+    }
+
+    @Test
+    @MediumTest
+    public void testRegularTabModel_DoesNotShowReauth() {
+        switchToRegularTabModel();
+        mIncognitoReauthController.onStopWithNative();
+        mIncognitoReauthController.onStartWithNative();
+
+        assertFalse("IncognitoReauthCoordinator should not be created on regular"
+                        + " TabModel.",
+                mIncognitoReauthController.isReauthPageShowing());
+    }
+
+    @Test
+    @MediumTest
+    public void testIncognitoTabsExisting_AndChromeForegroundedWithRegularTabs_DoesNotShowReauth() {
+        doReturn(1).when(mIncognitoTabModelMock).getCount();
+        doReturn(false).when(mTabModelSelectorMock).isIncognitoSelected();
+        mIncognitoReauthController.onStopWithNative();
+        mIncognitoReauthController.onStartWithNative();
+
+        assertFalse("IncognitoReauthCoordinator should not be created on regular"
+                        + " TabModel.",
+                mIncognitoReauthController.isReauthPageShowing());
+    }
+
+    @Test
+    @MediumTest
+    public void testWhenTabModelChangesToRegularFromIncognito_HidesReauth() {
+        // Pretend there's one incognito tab.
+        doReturn(1).when(mIncognitoTabModelMock).getCount();
+        switchToIncognitoTabModel();
+        assertFalse("IncognitoReauthCoordinator should not be created if Chrome has not"
+                        + " been to background.",
+                mIncognitoReauthController.isReauthPageShowing());
+
+        // Chrome went to background.
+        mIncognitoReauthController.onStopWithNative();
+        // Chrome coming to foregrounded. Re-auth would now be required since there are existing
+        // Incognito tabs.
+        mIncognitoReauthController.onStartWithNative();
+        assertTrue("IncognitoReauthCoordinator should have been created.",
+                mIncognitoReauthController.isReauthPageShowing());
+
+        switchToRegularTabModel();
+        assertFalse("IncognitoReauthCoordinator should have been destroyed"
+                        + "when a user switches to regular TabModel.",
+                mIncognitoReauthController.isReauthPageShowing());
+    }
+
+    @Test
+    @MediumTest
+    public void testIncognitoTabsRestore_ShowsReauth() {
+        // Pretend there's one incognito tab.
+        doReturn(1).when(mIncognitoTabModelMock).getCount();
+        switchToIncognitoTabModel();
+        Tab incognitoTab = prepareTabForRestoreOrLauncherShortcut(/*isRestore=*/true);
+        doReturn(true).when(incognitoTab).isIncognito();
+        doReturn(incognitoTab).when(mIncognitoTabModelMock).getTabAt(0);
+
+        mTabModelSelectorObserverCaptor.getValue().onTabStateInitialized();
+        assertTrue("IncognitoReauthCoordinator should be created for restored"
+                        + " Incognito tabs.",
+                mIncognitoReauthController.isReauthPageShowing());
+    }
+
+    @Test
+    @MediumTest
+    public void testIncognitoTabsFromLauncherShortcut_DoesNotShowReauth() {
+        // Pretend there's one incognito tab.
+        doReturn(1).when(mIncognitoTabModelMock).getCount();
+        switchToIncognitoTabModel();
+        Tab incognitoTab = prepareTabForRestoreOrLauncherShortcut(/*isRestore=*/false);
+        doReturn(true).when(incognitoTab).isIncognito();
+        doReturn(incognitoTab).when(mIncognitoTabModelMock).getTabAt(0);
+
+        mTabModelSelectorObserverCaptor.getValue().onTabStateInitialized();
+        assertFalse("IncognitoReauthCoordinator should not be created for Incognito tabs"
+                        + " opened from launcher.",
+                mIncognitoReauthController.isReauthPageShowing());
+    }
+
+    @Test
+    @MediumTest
+    public void testNewIncognitoSession_AfterClosingIncognitoTabs_DoesNotShowReauth() {
+        // Pretend there's one incognito tab.
+        doReturn(1).when(mIncognitoTabModelMock).getCount();
+        switchToIncognitoTabModel();
+        assertFalse("IncognitoReauthCoordinator should not be created if Chrome has not"
+                        + " been to background.",
+                mIncognitoReauthController.isReauthPageShowing());
+
+        // Chrome went to background.
+        mIncognitoReauthController.onStopWithNative();
+        // Chrome coming to foregrounded. Re-auth would now be required since there are existing
+        // Incognito tabs.
+        doReturn(true).when(mTabModelSelectorMock).isIncognitoSelected();
+        mIncognitoReauthController.onStartWithNative();
+        switchToIncognitoTabModel();
+        assertTrue("IncognitoReauthCoordinator should be created when all conditions are"
+                        + " met.",
+                mIncognitoReauthController.isReauthPageShowing());
+
+        // Move to regular mode.
+        switchToRegularTabModel();
+        // close all Incognito tabs.
+        mIncognitoTabModelObserverCaptor.getValue().didBecomeEmpty();
+        // Open an Incognito tab.
+        doReturn(1).when(mIncognitoTabModelMock).getCount();
+        switchToIncognitoTabModel();
+        assertFalse("IncognitoReauthCoordinator should not be created when starting a"
+                        + " fresh Incognito session.",
+                mIncognitoReauthController.isReauthPageShowing());
+    }
+}
\ No newline at end of file
diff --git a/chrome/browser/incognito/android/java/src/org/chromium/chrome/browser/incognito/reauth/IncognitoReauthCoordinator.java b/chrome/browser/incognito/android/java/src/org/chromium/chrome/browser/incognito/reauth/IncognitoReauthCoordinator.java
new file mode 100644
index 0000000..b05818e
--- /dev/null
+++ b/chrome/browser/incognito/android/java/src/org/chromium/chrome/browser/incognito/reauth/IncognitoReauthCoordinator.java
@@ -0,0 +1,54 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.incognito.reauth;
+
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+
+import org.chromium.chrome.browser.tabmodel.TabModelSelector;
+import org.chromium.ui.modaldialog.DialogDismissalCause;
+import org.chromium.ui.modaldialog.ModalDialogManager;
+
+/**
+ * The coordinator which is responsible for showing the Incognito re-authentication page.
+ *
+ * TODO(crbug.com/1227656):  Add View inflation and set up the re-auth dialog.
+ */
+class IncognitoReauthCoordinator {
+    private final @NonNull Context mContext;
+    private final @NonNull ModalDialogManager mModalDialogManager;
+    private final @NonNull IncognitoReauthManager.IncognitoReauthCallback mIncognitoReauthCallback;
+    private final boolean mShowFullScreen;
+
+    /**
+     * @param context The {@link Context} to use for inflating the Incognito re-auth view.
+     * @param tabModelSelector The {@link TabModelSelector} which will be passed to the mediator in
+     *         order to switch {@link TabModel} when the user clicks on "See other tabs" button.
+     * @param modalDialogManager The {@link ModalDialogManager} which is used to fire the dialog
+     *         containing the Incognito re-auth view.
+     * @param incognitoReauthCallback The {@link IncognitoReauthCallback} which would be executed
+     *         after an authentication attempt.
+     * @param showFullScreen Whether to show a fullscreen / tab based re-auth dialog.
+     */
+    public IncognitoReauthCoordinator(@NonNull Context context,
+            @NonNull TabModelSelector tabModelSelector,
+            @NonNull ModalDialogManager modalDialogManager,
+            @NonNull IncognitoReauthManager.IncognitoReauthCallback incognitoReauthCallback,
+            boolean showFullScreen) {
+        mContext = context;
+        mModalDialogManager = modalDialogManager;
+        mIncognitoReauthCallback = incognitoReauthCallback;
+        mShowFullScreen = showFullScreen;
+    }
+
+    void showDialog() {}
+
+    void hideDialogAndDestroy(@DialogDismissalCause int dismissalCause) {
+        destroy();
+    }
+
+    private void destroy() {}
+}
diff --git a/chrome/browser/net/profile_network_context_service.h b/chrome/browser/net/profile_network_context_service.h
index a789338..6657018 100644
--- a/chrome/browser/net/profile_network_context_service.h
+++ b/chrome/browser/net/profile_network_context_service.h
@@ -58,7 +58,7 @@
     : public KeyedService,
       public content_settings::Observer,
       public content_settings::CookieSettings::Observer,
-      public PrivacySandboxSettings::Observer {
+      public privacy_sandbox::PrivacySandboxSettings::Observer {
  public:
   explicit ProfileNetworkContextService(Profile* profile);
 
@@ -191,8 +191,8 @@
   base::ScopedObservation<content_settings::CookieSettings,
                           content_settings::CookieSettings::Observer>
       cookie_settings_observation_{this};
-  base::ScopedObservation<PrivacySandboxSettings,
-                          PrivacySandboxSettings::Observer>
+  base::ScopedObservation<privacy_sandbox::PrivacySandboxSettings,
+                          privacy_sandbox::PrivacySandboxSettings::Observer>
       privacy_sandbox_settings_observer_{this};
 
   // Used to post schedule CT policy updates
diff --git a/chrome/browser/privacy_sandbox/privacy_sandbox_service.cc b/chrome/browser/privacy_sandbox/privacy_sandbox_service.cc
index 0a770cf..6441b30e 100644
--- a/chrome/browser/privacy_sandbox/privacy_sandbox_service.cc
+++ b/chrome/browser/privacy_sandbox/privacy_sandbox_service.cc
@@ -111,7 +111,7 @@
 PrivacySandboxService::PrivacySandboxService() = default;
 
 PrivacySandboxService::PrivacySandboxService(
-    PrivacySandboxSettings* privacy_sandbox_settings,
+    privacy_sandbox::PrivacySandboxSettings* privacy_sandbox_settings,
     content_settings::CookieSettings* cookie_settings,
     PrefService* pref_service,
     policy::PolicyService* policy_service,
@@ -261,6 +261,10 @@
       prefs::kPrivacySandboxApisEnabledV2);
 }
 
+bool PrivacySandboxService::IsPrivacySandboxRestricted() {
+  return privacy_sandbox_settings_->IsPrivacySandboxRestricted();
+}
+
 void PrivacySandboxService::SetPrivacySandboxEnabled(bool enabled) {
   privacy_sandbox_settings_->SetPrivacySandboxEnabled(enabled);
 }
diff --git a/chrome/browser/privacy_sandbox/privacy_sandbox_service.h b/chrome/browser/privacy_sandbox/privacy_sandbox_service.h
index 0fc09ab..3077522 100644
--- a/chrome/browser/privacy_sandbox/privacy_sandbox_service.h
+++ b/chrome/browser/privacy_sandbox/privacy_sandbox_service.h
@@ -80,14 +80,15 @@
     kMaxValue = kConsentClosedNoDecision,
   };
 
-  PrivacySandboxService(PrivacySandboxSettings* privacy_sandbox_settings,
-                        content_settings::CookieSettings* cookie_settings,
-                        PrefService* pref_service,
-                        policy::PolicyService* policy_service,
-                        syncer::SyncService* sync_service,
-                        signin::IdentityManager* identity_manager,
-                        content::InterestGroupManager* interest_group_manager,
-                        profile_metrics::BrowserProfileType profile_type);
+  PrivacySandboxService(
+      privacy_sandbox::PrivacySandboxSettings* privacy_sandbox_settings,
+      content_settings::CookieSettings* cookie_settings,
+      PrefService* pref_service,
+      policy::PolicyService* policy_service,
+      syncer::SyncService* sync_service,
+      signin::IdentityManager* identity_manager,
+      content::InterestGroupManager* interest_group_manager,
+      profile_metrics::BrowserProfileType profile_type);
   ~PrivacySandboxService() override;
 
   // Returns the dialog type that should be shown to the user. This consults
@@ -159,6 +160,11 @@
   // Returns whether the state of the API is managed.
   bool IsPrivacySandboxManaged();
 
+  // Returns whether the Privacy Sandbox is currently restricted for the
+  // profile. UI code should consult this to ensure that when restricted,
+  // Privacy Sandbox related UI is updated appropriately.
+  bool IsPrivacySandboxRestricted();
+
   // Called when a preference relevant to the the Privacy Sandbox is changed.
   void OnPrivacySandboxPrefChanged();
 
@@ -310,7 +316,7 @@
       profile_metrics::BrowserProfileType profile_type);
 
  private:
-  raw_ptr<PrivacySandboxSettings> privacy_sandbox_settings_;
+  raw_ptr<privacy_sandbox::PrivacySandboxSettings> privacy_sandbox_settings_;
   raw_ptr<content_settings::CookieSettings> cookie_settings_;
   raw_ptr<PrefService> pref_service_;
   raw_ptr<policy::PolicyService> policy_service_;
diff --git a/chrome/browser/privacy_sandbox/privacy_sandbox_service_browsertest.cc b/chrome/browser/privacy_sandbox/privacy_sandbox_service_browsertest.cc
index 1def27e..ecec501 100644
--- a/chrome/browser/privacy_sandbox/privacy_sandbox_service_browsertest.cc
+++ b/chrome/browser/privacy_sandbox/privacy_sandbox_service_browsertest.cc
@@ -36,7 +36,7 @@
     policy_provider()->UpdateChromePolicy(third_party_cookies_blocked_policy);
   }
 
-  PrivacySandboxSettings* privacy_sandbox_settings() {
+  privacy_sandbox::PrivacySandboxSettings* privacy_sandbox_settings() {
     return PrivacySandboxSettingsFactory::GetForProfile(browser()->profile());
   }
 
diff --git a/chrome/browser/privacy_sandbox/privacy_sandbox_service_unittest.cc b/chrome/browser/privacy_sandbox/privacy_sandbox_service_unittest.cc
index a240c57..b23e939 100644
--- a/chrome/browser/privacy_sandbox/privacy_sandbox_service_unittest.cc
+++ b/chrome/browser/privacy_sandbox/privacy_sandbox_service_unittest.cc
@@ -661,7 +661,7 @@
   PrivacySandboxService* privacy_sandbox_service() {
     return privacy_sandbox_service_.get();
   }
-  PrivacySandboxSettings* privacy_sandbox_settings() {
+  privacy_sandbox::PrivacySandboxSettings* privacy_sandbox_settings() {
     return PrivacySandboxSettingsFactory::GetForProfile(profile());
   }
   base::test::ScopedFeatureList* feature_list() { return &feature_list_; }
diff --git a/chrome/browser/privacy_sandbox/privacy_sandbox_settings_browsertest.cc b/chrome/browser/privacy_sandbox/privacy_sandbox_settings_browsertest.cc
index cf71a972..6b4ac92 100644
--- a/chrome/browser/privacy_sandbox/privacy_sandbox_settings_browsertest.cc
+++ b/chrome/browser/privacy_sandbox/privacy_sandbox_settings_browsertest.cc
@@ -64,7 +64,7 @@
     observer.BlockUntilCompletion();
   }
 
-  PrivacySandboxSettings* privacy_sandbox_settings() {
+  privacy_sandbox::PrivacySandboxSettings* privacy_sandbox_settings() {
     return PrivacySandboxSettingsFactory::GetForProfile(browser()->profile());
   }
 
diff --git a/chrome/browser/privacy_sandbox/privacy_sandbox_settings_delegate.h b/chrome/browser/privacy_sandbox/privacy_sandbox_settings_delegate.h
index bd70af8..b9a9e460 100644
--- a/chrome/browser/privacy_sandbox/privacy_sandbox_settings_delegate.h
+++ b/chrome/browser/privacy_sandbox/privacy_sandbox_settings_delegate.h
@@ -9,7 +9,8 @@
 
 class Profile;
 
-class PrivacySandboxSettingsDelegate : public PrivacySandboxSettings::Delegate {
+class PrivacySandboxSettingsDelegate
+    : public privacy_sandbox::PrivacySandboxSettings::Delegate {
  public:
   explicit PrivacySandboxSettingsDelegate(Profile* profile);
   ~PrivacySandboxSettingsDelegate() override;
diff --git a/chrome/browser/privacy_sandbox/privacy_sandbox_settings_factory.cc b/chrome/browser/privacy_sandbox/privacy_sandbox_settings_factory.cc
index 9c3dea5..91e083c 100644
--- a/chrome/browser/privacy_sandbox/privacy_sandbox_settings_factory.cc
+++ b/chrome/browser/privacy_sandbox/privacy_sandbox_settings_factory.cc
@@ -19,9 +19,9 @@
   return base::Singleton<PrivacySandboxSettingsFactory>::get();
 }
 
-PrivacySandboxSettings* PrivacySandboxSettingsFactory::GetForProfile(
-    Profile* profile) {
-  return static_cast<PrivacySandboxSettings*>(
+privacy_sandbox::PrivacySandboxSettings*
+PrivacySandboxSettingsFactory::GetForProfile(Profile* profile) {
+  return static_cast<privacy_sandbox::PrivacySandboxSettings*>(
       GetInstance()->GetServiceForBrowserContext(profile, true));
 }
 
@@ -34,7 +34,7 @@
     content::BrowserContext* context) const {
   Profile* profile = Profile::FromBrowserContext(context);
 
-  return new PrivacySandboxSettings(
+  return new privacy_sandbox::PrivacySandboxSettings(
       std::make_unique<PrivacySandboxSettingsDelegate>(profile),
       HostContentSettingsMapFactory::GetForProfile(profile),
       CookieSettingsFactory::GetForProfile(profile).get(), profile->GetPrefs(),
diff --git a/chrome/browser/privacy_sandbox/privacy_sandbox_settings_factory.h b/chrome/browser/privacy_sandbox/privacy_sandbox_settings_factory.h
index 0fbf226..ab31353 100644
--- a/chrome/browser/privacy_sandbox/privacy_sandbox_settings_factory.h
+++ b/chrome/browser/privacy_sandbox/privacy_sandbox_settings_factory.h
@@ -8,13 +8,17 @@
 #include "base/memory/singleton.h"
 #include "components/keyed_service/content/browser_context_keyed_service_factory.h"
 
-class PrivacySandboxSettings;
 class Profile;
 
+namespace privacy_sandbox {
+class PrivacySandboxSettings;
+}
+
 class PrivacySandboxSettingsFactory : public BrowserContextKeyedServiceFactory {
  public:
   static PrivacySandboxSettingsFactory* GetInstance();
-  static PrivacySandboxSettings* GetForProfile(Profile* profile);
+  static privacy_sandbox::PrivacySandboxSettings* GetForProfile(
+      Profile* profile);
 
  private:
   friend struct base::DefaultSingletonTraits<PrivacySandboxSettingsFactory>;
diff --git a/chrome/browser/resources/chromeos/login/components/notification_card.js b/chrome/browser/resources/chromeos/login/components/notification_card.js
index 430e61f..b82ca1a 100644
--- a/chrome/browser/resources/chromeos/login/components/notification_card.js
+++ b/chrome/browser/resources/chromeos/login/components/notification_card.js
@@ -63,4 +63,4 @@
   }
 }
 
-customElements.define(NotificationCard.is, NotificationCard);
\ No newline at end of file
+customElements.define(NotificationCard.is, NotificationCard);
diff --git a/chrome/browser/resources/chromeos/login/cr_ui.js b/chrome/browser/resources/chromeos/login/cr_ui.js
index 62489b65..bc029a4d 100644
--- a/chrome/browser/resources/chromeos/login/cr_ui.js
+++ b/chrome/browser/resources/chromeos/login/cr_ui.js
@@ -123,14 +123,6 @@
     }
 
     /**
-     * Shows signin UI.
-     * @param {string} opt_email An optional email for signin UI.
-     */
-    static showSigninUI(opt_email) {
-      Oobe.getInstance().showSigninUI(opt_email);
-    }
-
-    /**
      * Sets the current height of the shelf area.
      * @param {number} height current shelf height
      */
diff --git a/chrome/browser/resources/chromeos/login/debug/debug.js b/chrome/browser/resources/chromeos/login/debug/debug.js
index 78d3e89..045d420 100644
--- a/chrome/browser/resources/chromeos/login/debug/debug.js
+++ b/chrome/browser/resources/chromeos/login/debug/debug.js
@@ -600,7 +600,7 @@
         {
           id: 'allowlist-customer',
           trigger: (screen) => {
-            screen.showAllowlistCheckFailedError(true, {
+            screen.showAllowlistCheckFailedError({
               enterpriseManaged: false,
             });
           },
diff --git a/chrome/browser/resources/chromeos/login/display_manager.js b/chrome/browser/resources/chromeos/login/display_manager.js
index af24585..52d5e599 100644
--- a/chrome/browser/resources/chromeos/login/display_manager.js
+++ b/chrome/browser/resources/chromeos/login/display_manager.js
@@ -554,16 +554,6 @@
       this.initializeDemoModeMultiTapListener();
     }
 
-    /**
-     * Shows signin UI.
-     * @param {string} opt_email An optional email for signin UI.
-     */
-    showSigninUI(opt_email) {
-      if (this.currentScreen.id == SCREEN_GAIA_SIGNIN) {
-        this.setOobeUIState(OOBE_UI_STATE.GAIA_SIGNIN);
-      }
-      chrome.send('showAddUser', [opt_email]);
-    }
 
     /**
      * Sets text content for a div with |labelId|.
diff --git a/chrome/browser/resources/chromeos/login/screens/common/gaia_signin.html b/chrome/browser/resources/chromeos/login/screens/common/gaia_signin.html
index e24d284..b57a9a1 100644
--- a/chrome/browser/resources/chromeos/login/screens/common/gaia_signin.html
+++ b/chrome/browser/resources/chromeos/login/screens/common/gaia_signin.html
@@ -124,8 +124,11 @@
     </div>
     <notification-card id="gaia-allowlist-error" type="fail" class="fit"
         for-step="allowlist-error"
+        on-buttonclick="onAllowlistErrorTryAgainClick_"
+        on-linkclick="onAllowlistErrorLinkClick_"
         button-label="[[i18nDynamic(locale, 'tryAgainButton')]]"
         link-label="[[i18nDynamic(locale, 'learnMoreButton')]]">
+      [[i18nDynamic(locale, allowListError_)]]
     </notification-card>
   </template>
   <script src="gaia_signin.js"></script>
diff --git a/chrome/browser/resources/chromeos/login/screens/common/gaia_signin.js b/chrome/browser/resources/chromeos/login/screens/common/gaia_signin.js
index 8b53b35b..ee0f492 100644
--- a/chrome/browser/resources/chromeos/login/screens/common/gaia_signin.js
+++ b/chrome/browser/resources/chromeos/login/screens/common/gaia_signin.js
@@ -269,6 +269,14 @@
         type: Boolean,
         value: loadTimeData.getBoolean('isRedirectToDefaultIdPEnabled'),
       },
+
+      /**
+       * @private {string}
+       */
+      allowlistError_: {
+        type: String,
+        value: 'allowlistErrorConsumer',
+      },
     };
   }
 
@@ -385,14 +393,6 @@
     this.authenticator_.getIsSamlUserPasswordlessCallback =
         this.getIsSamlUserPasswordless_.bind(this);
 
-    this.$['gaia-allowlist-error'].addEventListener('buttonclick', function() {
-      this.showAllowlistCheckFailedError(false);
-    }.bind(this));
-
-    this.$['gaia-allowlist-error'].addEventListener('linkclick', function() {
-      chrome.send('launchHelpApp', [HELP_CANT_ACCESS_ACCOUNT]);
-    });
-
     this.initializeLoginScreen('GaiaSigninScreen', {
       resetAllowed: true,
     });
@@ -445,6 +445,7 @@
    */
   loadAuthenticator_(doSamlRedirect) {
     this.loadingFrameContents_ = true;
+    this.isAllowlistErrorShown_ = false;
     this.isDefaultSsoProvider_ = doSamlRedirect;
     this.startLoadingTimer_();
 
@@ -536,8 +537,6 @@
    * @param {Object} data Screen init payload
    */
   onBeforeShow(data) {
-    // Show spinner early.
-    // this.loadingFrameContents_ = true;
     chrome.send('loginUIStateChanged', ['gaia-signin', true]);
 
     // Ensure that GAIA signin (or loading UI) is actually visible.
@@ -1005,6 +1004,7 @@
       return;
     this.authenticator_.reload();
     this.loadingFrameContents_ = true;
+    this.isAllowlistErrorShown_ = false;
     this.startLoadingTimer_();
     this.authCompleted_ = false;
   }
@@ -1073,39 +1073,27 @@
   /**
    * Show/Hide error when user is not in allowlist. When UI is hidden GAIA is
    * reloaded.
-   * @param {boolean} show Show/hide error UI.
    * @param {!Object=} opt_data Optional additional information.
    */
-  showAllowlistCheckFailedError(show, opt_data) {
-    if (show) {
-      const isManaged = opt_data && opt_data.enterpriseManaged;
-      const isFamilyLinkAllowed = opt_data && opt_data.familyLinkAllowed;
-      let errorMessage = '';
-      if (isManaged && isFamilyLinkAllowed) {
-        errorMessage = 'allowlistErrorEnterpriseAndFamilyLink';
-      } else if (isManaged) {
-        errorMessage = 'allowlistErrorEnterprise';
-      } else {
-        errorMessage = 'allowlistErrorConsumer';
-      }
-
-      this.$['gaia-allowlist-error'].textContent =
-          loadTimeData.getValue(errorMessage);
-      // To make animations correct, we need to make sure Gaia is completely
-      // reloaded. Otherwise ChromeOS overlays hide and Gaia page is shown
-      // somewhere in the middle of animations.
-      if (this.screenMode_ == ScreenAuthMode.DEFAULT)
-        this.authenticator_.resetWebview();
-
-      // It might show the OOBE screen id=SCREEN_CONFIRM_PASSWORD.
-      // Ensures showing the screen id=SCREEN_GAIA_SIGNIN.
-      Oobe.showScreen({id: SCREEN_GAIA_SIGNIN});
-      this.$['gaia-allowlist-error'].submitButton.focus();
+  showAllowlistCheckFailedError(opt_data) {
+    const isManaged = opt_data && opt_data.enterpriseManaged;
+    const isFamilyLinkAllowed = opt_data && opt_data.familyLinkAllowed;
+    if (isManaged && isFamilyLinkAllowed) {
+      this.allowlistError_ = 'allowlistErrorEnterpriseAndFamilyLink';
+    } else if (isManaged) {
+      this.allowlistError_ = 'allowlistErrorEnterprise';
     } else {
-      Oobe.showSigninUI('');
+      this.allowlistError_ = 'allowlistErrorConsumer';
     }
 
-    this.isAllowlistErrorShown_ = show;
+    // To make animations correct, we need to make sure Gaia is completely
+    // reloaded. Otherwise ChromeOS overlays hide and Gaia page is shown
+    // somewhere in the middle of animations.
+    if (this.screenMode_ == ScreenAuthMode.DEFAULT)
+      this.authenticator_.resetWebview();
+
+    this.$['gaia-allowlist-error'].submitButton.focus();
+    this.isAllowlistErrorShown_ = true;
   }
 
   /**
@@ -1293,6 +1281,14 @@
     }
     return '';
   }
+
+  onAllowlistErrorTryAgainClick_() {
+    this.userActed('retry');
+  }
+
+  onAllowlistErrorLinkClick_() {
+    chrome.send('launchHelpApp', [HELP_CANT_ACCESS_ACCOUNT]);
+  }
 }
 
 customElements.define(GaiaSigninElement.is, GaiaSigninElement);
diff --git a/chrome/browser/resources/history/history.ts b/chrome/browser/resources/history/history.ts
index 6e920d3f..bdada40 100644
--- a/chrome/browser/resources/history/history.ts
+++ b/chrome/browser/resources/history/history.ts
@@ -19,3 +19,5 @@
 export {HistoryToolbarElement} from './history_toolbar.js';
 export {HistorySearchedLabelElement} from './searched_label.js';
 export {HistorySideBarElement} from './side_bar.js';
+export {HistorySyncedDeviceCardElement} from './synced_device_card.js';
+export {HistorySyncedDeviceManagerElement} from './synced_device_manager.js';
diff --git a/chrome/browser/resources/history/synced_device_manager.ts b/chrome/browser/resources/history/synced_device_manager.ts
index cf631f9..6c2d125 100644
--- a/chrome/browser/resources/history/synced_device_manager.ts
+++ b/chrome/browser/resources/history/synced_device_manager.ts
@@ -46,6 +46,8 @@
 export interface HistorySyncedDeviceManagerElement {
   $: {
     'menu': CrLazyRenderElement<CrActionMenuElement>,
+    'no-synced-tabs': HTMLElement,
+    'sign-in-guide': HTMLElement,
   };
 }
 
@@ -129,6 +131,16 @@
     this.focusGrid_!.destroy();
   }
 
+  configureSignInForTest(data: {
+    signInState: boolean,
+    signInAllowed: boolean,
+    guestSession: boolean
+  }) {
+    this.signInState = data.signInState;
+    this.signInAllowed_ = data.signInAllowed;
+    this.guestSession_ = data.guestSession;
+  }
+
   /** @return {HTMLElement} */
   getContentScrollTarget() {
     return this;
@@ -239,6 +251,10 @@
     menu.close();
   }
 
+  clearSyncedDevicesForTest() {
+    this.clearDisplayedSyncedDevices_();
+  }
+
   private clearDisplayedSyncedDevices_() {
     this.syncedDevices_ = [];
   }
diff --git a/chrome/browser/resources/tab_search/app.ts b/chrome/browser/resources/tab_search/app.ts
index 8217c25..b40424d 100644
--- a/chrome/browser/resources/tab_search/app.ts
+++ b/chrome/browser/resources/tab_search/app.ts
@@ -502,6 +502,15 @@
     this.apiProxy_.saveRecentlyClosedExpandedPref(expanded);
 
     this.updateFilteredTabs_();
+
+    // If a section's title item is the last visible element in the list and the
+    // list's height is at its maximum, it will not be evident to the user that
+    // on expanding the section there are now section tab items available. By
+    // ensuring the first element of the section is visible, we can avoid this
+    // confusion.
+    if (expanded) {
+      this.$.tabsList.scrollIndexIntoView(this.filteredOpenTabsCount_);
+    }
     e.stopPropagation();
   }
 
diff --git a/chrome/browser/resources/tab_search/infinite_list.ts b/chrome/browser/resources/tab_search/infinite_list.ts
index 34a18f4..b10d7b38 100644
--- a/chrome/browser/resources/tab_search/infinite_list.ts
+++ b/chrome/browser/resources/tab_search/infinite_list.ts
@@ -128,6 +128,15 @@
     }
   }
 
+  scrollIndexIntoView(index: number) {
+    assert(
+        index >= 0 && index < this.selectableIndexToItemIndex_!.size(),
+        'Index is out of range.');
+    this.ensureSelectableDomItemAvailable_(index);
+    this.getSelectableDomItem_(index)!.scrollIntoView(
+        {behavior: 'smooth', block: 'nearest'});
+  }
+
   /**
    * @param key Keyboard event key value.
    * @param focusItem Whether to focus the selected item.
@@ -252,15 +261,7 @@
 
   private getDomItem_(index: number): HTMLElement|undefined {
     const instance = this.instances_[index];
-    if (instance === undefined) {
-      // TODO(crbug.com/1225247): Remove this after we root cause the issue.
-      console.error(`Unexpected call to non-existing instance index: ${
-          index}. Instance count: ${this.instances_.length}. Item count: ${
-          this.items.length}`);
-      return undefined;
-    }
-
-    return instance.children[0] as HTMLElement;
+    return instance!.children[0] as HTMLElement;
   }
 
   private getSelectableDomItem_(selectableItemIndex: number): HTMLElement
diff --git a/chrome/browser/resources/tab_search/tab_search_item.html b/chrome/browser/resources/tab_search/tab_search_item.html
index 7a794112..37fd113 100644
--- a/chrome/browser/resources/tab_search/tab_search_item.html
+++ b/chrome/browser/resources/tab_search/tab_search_item.html
@@ -1,16 +1,15 @@
 <style include="mwb-element-shared-style">
-   :host {
-      --audio-icon-color: var(--google-grey-700);
-      --media-recording-icon-color:  var(--google-red-600);
-    }
+  :host {
+     --audio-icon-color: var(--google-grey-700);
+     --media-recording-icon-color:  var(--google-red-600);
+   }
 
-    @media (prefers-color-scheme: dark) {
-      :host {
-        --audio-icon-color: var(--google-grey-300);
-        --media-recording-icon-color:  var(--google-red-300);
-      }
+  @media (prefers-color-scheme: dark) {
+    :host {
+      --audio-icon-color: var(--google-grey-300);
+      --media-recording-icon-color:  var(--google-red-300);
     }
-
+  }
 
   :host(:focus) {
     outline: none;
@@ -141,7 +140,6 @@
   -webkit-mask-image: url(alert_indicators/tab_audio_muting_rounded.svg);
     background-color:  var(--audio-icon-color);
   }
-
 </style>
 
 <div class="favicon" style="background-image:[[faviconUrl_(data.tab)]]"></div>
diff --git a/chrome/browser/safe_browsing/cloud_content_scanning/multipart_data_pipe_getter_unittest.cc b/chrome/browser/safe_browsing/cloud_content_scanning/multipart_data_pipe_getter_unittest.cc
index 1436661..916f621 100644
--- a/chrome/browser/safe_browsing/cloud_content_scanning/multipart_data_pipe_getter_unittest.cc
+++ b/chrome/browser/safe_browsing/cloud_content_scanning/multipart_data_pipe_getter_unittest.cc
@@ -17,12 +17,17 @@
 
 class MultipartDataPipeGetterTest : public testing::Test {
  public:
-  base::File CreateFile(const std::string& content) {
+  absl::optional<base::File> CreateFile(const std::string& content) {
     EXPECT_TRUE(temp_dir_.CreateUniqueTempDir());
     base::FilePath path = temp_dir_.GetPath().AppendASCII("test.txt");
     base::File file(path, base::File::FLAG_CREATE | base::File::FLAG_READ |
                               base::File::FLAG_WRITE);
-    file.WriteAtCurrentPos(content.data(), content.size());
+    if (!file.IsValid())
+      return absl::nullopt;
+
+    if (file.WriteAtCurrentPos(content.data(), content.size()) < 0)
+      return absl::nullopt;
+
     return file;
   }
 
@@ -106,9 +111,12 @@
   std::unique_ptr<MultipartDataPipeGetter> CreateDataPipeGetter(
       const std::string& content) {
     if (is_file_data_pipe()) {
-      base::File file = CreateFile(content);
+      absl::optional<base::File> file = CreateFile(content);
+      if (!file)
+        return nullptr;
+
       return MultipartDataPipeGetter::Create("boundary", metadata_,
-                                             std::move(file));
+                                             std::move(*file));
     } else {
       base::ReadOnlySharedMemoryRegion page = CreatePage(content);
       return MultipartDataPipeGetter::Create("boundary", metadata_,
@@ -140,6 +148,7 @@
 
   std::unique_ptr<MultipartDataPipeGetter> data_pipe_getter =
       CreateDataPipeGetter("small file content");
+  EXPECT_TRUE(data_pipe_getter);
   EXPECT_EQ(data_pipe_getter->is_page_data_pipe(), is_page_data_pipe());
   EXPECT_EQ(data_pipe_getter->is_file_data_pipe(), is_file_data_pipe());
 
@@ -163,6 +172,11 @@
 
   std::unique_ptr<MultipartDataPipeGetter> data_pipe_getter =
       CreateDataPipeGetter(large_file_content);
+  // It's possible the large file couldn't be created due to a lack of space on
+  // the device, in this case stop the test early.
+  if (!data_pipe_getter)
+    return;
+
   EXPECT_EQ(data_pipe_getter->is_page_data_pipe(), is_page_data_pipe());
   EXPECT_EQ(data_pipe_getter->is_file_data_pipe(), is_file_data_pipe());
 
@@ -188,6 +202,11 @@
   set_metadata(large_data);
   std::unique_ptr<MultipartDataPipeGetter> data_pipe_getter =
       CreateDataPipeGetter(large_data);
+  // It's possible the large file couldn't be created due to a lack of space on
+  // the device, in this case stop the test early.
+  if (!data_pipe_getter)
+    return;
+
   EXPECT_EQ(data_pipe_getter->is_page_data_pipe(), is_page_data_pipe());
   EXPECT_EQ(data_pipe_getter->is_file_data_pipe(), is_file_data_pipe());
 
@@ -209,6 +228,7 @@
 
   std::unique_ptr<MultipartDataPipeGetter> data_pipe_getter =
       CreateDataPipeGetter("small file content");
+  EXPECT_TRUE(data_pipe_getter);
   EXPECT_EQ(data_pipe_getter->is_page_data_pipe(), is_page_data_pipe());
   EXPECT_EQ(data_pipe_getter->is_file_data_pipe(), is_file_data_pipe());
 
@@ -234,6 +254,11 @@
 
   std::unique_ptr<MultipartDataPipeGetter> data_pipe_getter =
       CreateDataPipeGetter(large_file_content);
+  // It's possible the large file couldn't be created due to a lack of space on
+  // the device, in this case stop the test early.
+  if (!data_pipe_getter)
+    return;
+
   EXPECT_EQ(data_pipe_getter->is_page_data_pipe(), is_page_data_pipe());
   EXPECT_EQ(data_pipe_getter->is_file_data_pipe(), is_file_data_pipe());
 
diff --git a/chrome/browser/safe_browsing/safe_browsing_navigation_observer_browsertest.cc b/chrome/browser/safe_browsing/safe_browsing_navigation_observer_browsertest.cc
index a3e71aac..2be08ce 100644
--- a/chrome/browser/safe_browsing/safe_browsing_navigation_observer_browsertest.cc
+++ b/chrome/browser/safe_browsing/safe_browsing_navigation_observer_browsertest.cc
@@ -3166,7 +3166,7 @@
                         false,        // has_server_redirect
                         nav_list->GetNavigationEvent(1));
   VerifyNavigationEvent(GURL(),       // source_url
-                        GURL(),       // source_main_frame_url
+                        initial_url,  // source_main_frame_url
                         landing_url,  // original_request_url
                         landing_url,  // destination_url
                         false,        // is_user_initiated,
@@ -3185,7 +3185,7 @@
 
   ReferrerChain referrer_chain;
   IdentifyReferrerChainForDownload(GetDownload(), &referrer_chain);
-  EXPECT_EQ(3, referrer_chain.size());
+  EXPECT_EQ(2, referrer_chain.size());
   VerifyReferrerChainEntry(
       download_url,                   // url
       GURL(),                         // main_frame_url
@@ -3202,23 +3202,12 @@
       GURL(),                            // main_frame_url
       ReferrerChainEntry::LANDING_PAGE,  // type
       test_server_ip,                    // ip_address
-      initial_url,                       // referrer_url
-      GURL(),                            // referrer_main_frame_url
-      true,                              // is_retargeting
+      GURL(),                            // referrer_url
+      initial_url,                       // referrer_main_frame_url
+      false,                             // is_retargeting
       std::vector<GURL>(),               // server redirects
-      ReferrerChainEntry::RENDERER_INITIATED_WITH_USER_GESTURE,
+      ReferrerChainEntry::RENDERER_INITIATED_WITHOUT_USER_GESTURE,
       referrer_chain.Get(1));
-  VerifyReferrerChainEntry(
-      initial_url,                           // url
-      GURL(),                                // main_frame_url
-      ReferrerChainEntry::LANDING_REFERRER,  // type
-      test_server_ip,                        // ip_address
-      GURL(),                                // referrer_url
-      GURL(),                                // referrer_main_frame_url
-      false,                                 // is_retargeting
-      std::vector<GURL>(),                   // server redirects
-      ReferrerChainEntry::BROWSER_INITIATED,
-      referrer_chain.Get(2));
 }
 
 // Click a link which creates a portal which redirects to the landing page and
@@ -3288,7 +3277,7 @@
                         false,                    // has_server_redirect
                         nav_list->GetNavigationEvent(1));
   VerifyNavigationEvent(GURL(),                   // source_url
-                        GURL(),                   // source_main_frame_url
+                        initial_url,              // source_main_frame_url
                         redirect_to_landing_url,  // original_request_url
                         redirect_to_landing_url,  // destination_url
                         false,                    // is_user_initiated,
@@ -3296,7 +3285,7 @@
                         false,                    // has_server_redirect
                         nav_list->GetNavigationEvent(2));
   VerifyNavigationEvent(redirect_to_landing_url,  // source_url
-                        redirect_to_landing_url,  // source_main_frame_url
+                        initial_url,              // source_main_frame_url
                         landing_url,              // original_request_url
                         landing_url,              // destination_url
                         false,                    // is_user_initiated,
@@ -3315,7 +3304,7 @@
 
   ReferrerChain referrer_chain;
   IdentifyReferrerChainForDownload(GetDownload(), &referrer_chain);
-  EXPECT_EQ(4, referrer_chain.size());
+  EXPECT_EQ(3, referrer_chain.size());
   VerifyReferrerChainEntry(
       download_url,                   // url
       GURL(),                         // main_frame_url
@@ -3333,32 +3322,22 @@
       ReferrerChainEntry::LANDING_PAGE,  // type
       test_server_ip,                    // ip_address
       redirect_to_landing_url,           // referrer_url
-      GURL(),                            // referrer_main_frame_url
+      initial_url,                       // referrer_main_frame_url
       false,                             // is_retargeting
       std::vector<GURL>(),               // server redirects
       ReferrerChainEntry::RENDERER_INITIATED_WITHOUT_USER_GESTURE,
       referrer_chain.Get(1));
   VerifyReferrerChainEntry(
       redirect_to_landing_url,              // url
-      GURL(),                               // main_frame_url
+      initial_url,                          // main_frame_url
       ReferrerChainEntry::CLIENT_REDIRECT,  // type
       test_server_ip,                       // ip_address
-      initial_url,                          // referrer_url
-      GURL(),                               // referrer_main_frame_url
-      true,                                 // is_retargeting
+      GURL(),                               // referrer_url
+      initial_url,                          // referrer_main_frame_url
+      false,                                // is_retargeting
       std::vector<GURL>(),                  // server redirects
-      ReferrerChainEntry::RENDERER_INITIATED_WITH_USER_GESTURE,
+      ReferrerChainEntry::RENDERER_INITIATED_WITHOUT_USER_GESTURE,
       referrer_chain.Get(2));
-  VerifyReferrerChainEntry(initial_url,  // url
-                           GURL(),       // main_frame_url
-                           ReferrerChainEntry::LANDING_REFERRER,  // type
-                           test_server_ip,                        // ip_address
-                           GURL(),               // referrer_url
-                           GURL(),               // referrer_main_frame_url
-                           false,                // is_retargeting
-                           std::vector<GURL>(),  // server redirects
-                           ReferrerChainEntry::BROWSER_INITIATED,
-                           referrer_chain.Get(3));
 }
 
 class SBNavigationObserverOmitNonUserGesturesBrowserTest
diff --git a/chrome/browser/safe_browsing/threat_details_unittest.cc b/chrome/browser/safe_browsing/threat_details_unittest.cc
index 9ccc3f7..22fd3f1 100644
--- a/chrome/browser/safe_browsing/threat_details_unittest.cc
+++ b/chrome/browser/safe_browsing/threat_details_unittest.cc
@@ -151,6 +151,13 @@
     run_loop_ = nullptr;
   }
 
+  void OnRedirectionCollectionReady() override {
+    ThreatDetails::OnRedirectionCollectionReady();
+    if (should_stop_after_redirect_collection_ && run_loop_) {
+      run_loop_->Quit();
+    }
+  }
+
   // Used to synchronize ThreatDetailsDone() with WaitForThreatDetailsDone().
   // RunLoop::RunUntilIdle() is not sufficient because the MessageLoop task
   // queue completely drains at some point between the send and the wait.
@@ -163,9 +170,14 @@
 
   void StartCollection() { ThreatDetails::StartCollection(); }
 
+  void SetShouldStopAfterRedirectCollection(bool should_stop) {
+    should_stop_after_redirect_collection_ = should_stop;
+  }
+
  private:
   raw_ptr<base::RunLoop> run_loop_;
   size_t done_callback_count_;
+  bool should_stop_after_redirect_collection_ = false;
 };
 
 class MockSafeBrowsingUIManager : public SafeBrowsingUIManager {
@@ -1977,4 +1989,45 @@
   VerifyResults(actual, expected);
 }
 
+TEST_F(ThreatDetailsTest, CanCancelDuringCollection) {
+  content::WebContentsTester::For(web_contents())
+      ->NavigateAndCommit(GURL(kLandingURL));
+
+  UnsafeResource resource;
+  InitResource(SB_THREAT_TYPE_URL_CLIENT_SIDE_PHISHING,
+               ThreatSource::CLIENT_SIDE_DETECTION, true /* is_subresource */,
+               GURL(kThreatURL), &resource);
+
+  auto report = std::make_unique<ThreatDetailsWrap>(
+      ui_manager_.get(), web_contents(), resource, test_shared_loader_factory_,
+      history_service(), user_population_callback(),
+      referrer_chain_provider_.get());
+  report->StartCollection();
+
+  SimulateFillCache(kThreatURL);
+
+  // The cache collection starts after the IPC from the DOM is fired.
+  std::vector<mojom::ThreatDOMDetailsNodePtr> params;
+  report->OnReceivedThreatDOMDetails(mojo::Remote<mojom::ThreatReporter>(),
+                                     main_rfh()->GetGlobalId(),
+                                     std::move(params));
+
+  // Let the cache callbacks complete.
+  base::RunLoop().RunUntilIdle();
+
+  // Let the cache collection start
+  {
+    base::RunLoop run_loop;
+    report->SetShouldStopAfterRedirectCollection(true);
+    report->SetRunLoopToQuit(&run_loop);
+    report->FinishCollection(/*did_proceed=*/true, /*num_visits=*/-1);
+    run_loop.Run();
+  }
+
+  // Cancel the collection
+  report.reset();
+
+  base::RunLoop().RunUntilIdle();
+}
+
 }  // namespace safe_browsing
diff --git a/chrome/browser/ui/ash/device_scheduled_reboot/reboot_notification_controller.cc b/chrome/browser/ui/ash/device_scheduled_reboot/reboot_notification_controller.cc
index 910c82c9..9daf07c 100644
--- a/chrome/browser/ui/ash/device_scheduled_reboot/reboot_notification_controller.cc
+++ b/chrome/browser/ui/ash/device_scheduled_reboot/reboot_notification_controller.cc
@@ -36,10 +36,10 @@
 
 void RebootNotificationController::MaybeShowPendingRebootNotification(
     const base::Time& reboot_time,
-    ButtonClickCallback reboot_callback) const {
+    base::RepeatingClosure reboot_callback) {
   if (!ShouldNotifyUser())
     return;
-
+  notification_callback_ = std::move(reboot_callback);
   std::u16string reboot_title =
       l10n_util::GetStringUTF16(IDS_POLICY_DEVICE_SCHEDULED_REBOOT_TITLE);
   std::u16string reboot_message =
@@ -53,7 +53,9 @@
       l10n_util::GetStringUTF16(IDS_POLICY_REBOOT_BUTTON));
   scoped_refptr<message_center::NotificationDelegate> delegate =
       base::MakeRefCounted<message_center::HandleNotificationClickDelegate>(
-          std::move(reboot_callback));
+          base::BindRepeating(
+              &RebootNotificationController::HandleNotificationClick,
+              weak_ptr_factory_.GetWeakPtr()));
 
   ShowNotification(kPendingRebootNotificationId, reboot_title, reboot_message,
                    notification_data, delegate);
@@ -74,6 +76,22 @@
       reboot_time, parent, std::move(reboot_callback));
 }
 
+void RebootNotificationController::CloseRebootNotification() const {
+  if (!ShouldNotifyUser())
+    return;
+  NotificationDisplayService* notification_display_service =
+      NotificationDisplayService::GetForProfile(
+          ProfileManager::GetActiveUserProfile());
+  notification_display_service->Close(NotificationHandler::Type::TRANSIENT,
+                                      kPendingRebootNotificationId);
+}
+
+void RebootNotificationController::CloseRebootDialog() {
+  if (scheduled_reboot_dialog_) {
+    scheduled_reboot_dialog_.reset();
+  }
+}
+
 void RebootNotificationController::ShowNotification(
     const std::string& id,
     const std::u16string& title,
@@ -103,4 +121,13 @@
   return (user_manager::UserManager::IsInitialized() &&
           user_manager::UserManager::Get()->IsUserLoggedIn() &&
           !user_manager::UserManager::Get()->IsLoggedInAsAnyKioskApp());
+}
+
+void RebootNotificationController::HandleNotificationClick(
+    absl::optional<int> button_index) const {
+  // Only request restart when the button is clicked, i.e. ignore the clicks
+  // on the body of the notification.
+  if (!button_index)
+    return;
+  notification_callback_.Run();
 }
\ No newline at end of file
diff --git a/chrome/browser/ui/ash/device_scheduled_reboot/reboot_notification_controller.h b/chrome/browser/ui/ash/device_scheduled_reboot/reboot_notification_controller.h
index 4292b16..7afcf26 100644
--- a/chrome/browser/ui/ash/device_scheduled_reboot/reboot_notification_controller.h
+++ b/chrome/browser/ui/ash/device_scheduled_reboot/reboot_notification_controller.h
@@ -8,8 +8,11 @@
 #include <memory>
 #include <string>
 
+#include "base/callback.h"
 #include "base/memory/scoped_refptr.h"
+#include "base/memory/weak_ptr.h"
 #include "chrome/browser/ui/ash/device_scheduled_reboot/scheduled_reboot_dialog.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 #include "ui/message_center/public/cpp/notification.h"
 #include "ui/message_center/public/cpp/notification_delegate.h"
 
@@ -17,9 +20,6 @@
 class Time;
 }
 
-using ButtonClickCallback =
-    message_center::HandleNotificationClickDelegate::ButtonClickCallback;
-
 // This class is responsible for creating and managing notifications about the
 // reboot when DeviceScheduledRebootPolicy is set.
 class RebootNotificationController {
@@ -34,13 +34,17 @@
   // in progress.
   void MaybeShowPendingRebootNotification(
       const base::Time& reboot_time,
-      ButtonClickCallback reboot_callback) const;
+      base::RepeatingClosure reboot_callback);
 
   // Only show dialog if the user is in session and kiosk session is not
   // in progress.
   void MaybeShowPendingRebootDialog(const base::Time& reboot_time,
                                     base::OnceClosure reboot_callback);
 
+  void CloseRebootNotification() const;
+
+  void CloseRebootDialog();
+
  protected:
   // Only notify in-session users that are not running in kiosk mode.
   virtual bool ShouldNotifyUser() const;
@@ -53,8 +57,15 @@
       const message_center::RichNotificationData& data,
       scoped_refptr<message_center::NotificationDelegate> delegate) const;
 
+  // Button click callback.
+  void HandleNotificationClick(absl::optional<int> button_index) const;
+
   // Dialog notifying the user about the pending reboot.
   std::unique_ptr<ScheduledRebootDialog> scheduled_reboot_dialog_;
+
+  // Callback to run on notification button click.
+  base::RepeatingClosure notification_callback_;
+  base::WeakPtrFactory<RebootNotificationController> weak_ptr_factory_{this};
 };
 
 #endif  // CHROME_BROWSER_UI_ASH_DEVICE_SCHEDULED_REBOOT_REBOOT_NOTIFICATION_CONTROLLER_H_
diff --git a/chrome/browser/ui/ash/device_scheduled_reboot/reboot_notification_controller_browsertest.cc b/chrome/browser/ui/ash/device_scheduled_reboot/reboot_notification_controller_browsertest.cc
index 02c91066..3c7259f 100644
--- a/chrome/browser/ui/ash/device_scheduled_reboot/reboot_notification_controller_browsertest.cc
+++ b/chrome/browser/ui/ash/device_scheduled_reboot/reboot_notification_controller_browsertest.cc
@@ -37,6 +37,8 @@
     notification_controller_.MaybeShowPendingRebootDialog(deadline,
                                                           base::DoNothing());
   }
+  // DialogBrowserTest:
+  void DismissUi() override { notification_controller_.CloseRebootDialog(); }
 
  private:
   RebootNotificationControllerForTest notification_controller_;
diff --git a/chrome/browser/ui/ash/device_scheduled_reboot/reboot_notification_controller_unittest.cc b/chrome/browser/ui/ash/device_scheduled_reboot/reboot_notification_controller_unittest.cc
index e4d3783a..afa35bd 100644
--- a/chrome/browser/ui/ash/device_scheduled_reboot/reboot_notification_controller_unittest.cc
+++ b/chrome/browser/ui/ash/device_scheduled_reboot/reboot_notification_controller_unittest.cc
@@ -47,6 +47,13 @@
     0,     // second
     0      // millisecond
 };
+
+class ClickCounter {
+ public:
+  void ButtonClickCallback() { ++clicks; }
+  int clicks = 0;
+  base::WeakPtrFactory<ClickCounter> weak_ptr_factory_{this};
+};
 }  // namespace
 
 class RebootNotificationControllerTest : public testing::Test {
@@ -107,6 +114,7 @@
   ash::FakeChromeUserManager* fake_user_manager_ = nullptr;
   std::unique_ptr<user_manager::ScopedUserManager> scoped_user_manager_;
   std::unique_ptr<NotificationDisplayServiceTester> display_service_tester_;
+  RebootNotificationController notification_controller_;
 };
 
 TEST_F(RebootNotificationControllerTest, UserSessionShowsNotification) {
@@ -117,14 +125,13 @@
       base::Time::FromLocalExploded(kRebootTime2022Feb2At1520, &reboot_time));
 
   // User is not logged in. Don't show notification.
-  RebootNotificationController notification_controller;
-  notification_controller.MaybeShowPendingRebootNotification(
+  notification_controller_.MaybeShowPendingRebootNotification(
       reboot_time, base::NullCallback());
   EXPECT_EQ(absl::nullopt, GetNotification());
 
   // Log in user and show notification.
   LoginFakeUser(account_id);
-  notification_controller.MaybeShowPendingRebootNotification(
+  notification_controller_.MaybeShowPendingRebootNotification(
       reboot_time, base::NullCallback());
   EXPECT_NE(absl::nullopt, GetNotification());
   EXPECT_EQ(
@@ -142,15 +149,14 @@
       base::Time::FromLocalExploded(kRebootTime2023May15At1115, &reboot_time2));
 
   // User is not logged in. Don't show notification.
-  RebootNotificationController notification_controller;
-  notification_controller.MaybeShowPendingRebootNotification(
+  notification_controller_.MaybeShowPendingRebootNotification(
       reboot_time1, base::NullCallback());
   EXPECT_EQ(absl::nullopt, GetNotification());
   EXPECT_EQ(GetTransientNotificationCount(), 0);
 
   // Log in user and show notification.
   LoginFakeUser(account_id);
-  notification_controller.MaybeShowPendingRebootNotification(
+  notification_controller_.MaybeShowPendingRebootNotification(
       reboot_time1, base::NullCallback());
   EXPECT_NE(absl::nullopt, GetNotification());
   EXPECT_EQ(
@@ -159,7 +165,7 @@
   EXPECT_EQ(GetTransientNotificationCount(), 1);
 
   // Change reboot time. Close old notification and show new one.
-  notification_controller.MaybeShowPendingRebootNotification(
+  notification_controller_.MaybeShowPendingRebootNotification(
       reboot_time2, base::NullCallback());
   EXPECT_NE(absl::nullopt, GetNotification());
   EXPECT_EQ(GetNotification()->message(),
@@ -176,14 +182,13 @@
       base::Time::FromLocalExploded(kRebootTime2022Feb2At1520, &reboot_time));
 
   // User is not logged in. Don't show notification.
-  RebootNotificationController notification_controller;
-  notification_controller.MaybeShowPendingRebootNotification(
+  notification_controller_.MaybeShowPendingRebootNotification(
       reboot_time, base::NullCallback());
   EXPECT_EQ(absl::nullopt, GetNotification());
 
   // Log in user and show notification.
   LoginFakeUser(account_id);
-  notification_controller.MaybeShowPendingRebootNotification(
+  notification_controller_.MaybeShowPendingRebootNotification(
       reboot_time, base::NullCallback());
   EXPECT_NE(absl::nullopt, GetNotification());
   EXPECT_EQ(
@@ -200,14 +205,55 @@
       base::Time::FromUTCExploded(kRebootTime2022Feb2At1520, &reboot_time));
 
   // User is not logged in. Don't show notification.
-  RebootNotificationController notification_controller;
-  notification_controller.MaybeShowPendingRebootNotification(
+  notification_controller_.MaybeShowPendingRebootNotification(
       reboot_time, base::NullCallback());
   EXPECT_EQ(absl::nullopt, GetNotification());
 
   // Start kiosk session. Don't show notification.
   LoginFakeUser(account_id);
-  notification_controller.MaybeShowPendingRebootNotification(
+  notification_controller_.MaybeShowPendingRebootNotification(
       reboot_time, base::NullCallback());
   EXPECT_EQ(absl::nullopt, GetNotification());
 }
+
+TEST_F(RebootNotificationControllerTest, CloseNotification) {
+  AccountId account_id = AccountId::FromUserEmailGaiaId(kEmailId, kGaiaId);
+  CreateFakeUser(account_id);
+  base::Time reboot_time;
+  ASSERT_TRUE(
+      base::Time::FromLocalExploded(kRebootTime2022Feb2At1520, &reboot_time));
+
+  // Log in user and show notification.
+  LoginFakeUser(account_id);
+  notification_controller_.MaybeShowPendingRebootNotification(
+      reboot_time, base::NullCallback());
+  EXPECT_NE(absl::nullopt, GetNotification());
+  EXPECT_EQ(GetTransientNotificationCount(), 1);
+
+  // Explicitly close notification.
+  notification_controller_.CloseRebootNotification();
+  EXPECT_EQ(absl::nullopt, GetNotification());
+  EXPECT_EQ(GetTransientNotificationCount(), 0);
+}
+
+TEST_F(RebootNotificationControllerTest, HandleNotificationClick) {
+  AccountId account_id = AccountId::FromUserEmailGaiaId(kEmailId, kGaiaId);
+  CreateFakeUser(account_id);
+  base::Time reboot_time;
+  ASSERT_TRUE(
+      base::Time::FromLocalExploded(kRebootTime2022Feb2At1520, &reboot_time));
+
+  // Log in user and show notification.
+  LoginFakeUser(account_id);
+  ClickCounter counter;
+  notification_controller_.MaybeShowPendingRebootNotification(
+      reboot_time, base::BindRepeating(&ClickCounter::ButtonClickCallback,
+                                       counter.weak_ptr_factory_.GetWeakPtr()));
+  auto notification = GetNotification().value();
+  // Click on notification and do nothing.
+  notification.delegate()->Click(absl::nullopt, absl::nullopt);
+  EXPECT_EQ(counter.clicks, 0);
+  // Click on notification button and run callback.
+  notification.delegate()->Click(0, absl::nullopt);
+  EXPECT_EQ(counter.clicks, 1);
+}
\ No newline at end of file
diff --git a/chrome/browser/ui/views/apps/app_dialog/app_uninstall_dialog_view.cc b/chrome/browser/ui/views/apps/app_dialog/app_uninstall_dialog_view.cc
index db43d8c..49c24295 100644
--- a/chrome/browser/ui/views/apps/app_dialog/app_uninstall_dialog_view.cc
+++ b/chrome/browser/ui/views/apps/app_dialog/app_uninstall_dialog_view.cc
@@ -81,7 +81,7 @@
 }  // namespace
 
 // static
-void apps::UninstallDialog::UiBase::Create(
+views::Widget* apps::UninstallDialog::UiBase::Create(
     Profile* profile,
     apps::mojom::AppType app_type,
     const std::string& app_id,
@@ -89,11 +89,12 @@
     gfx::ImageSkia image,
     gfx::NativeWindow parent_window,
     apps::UninstallDialog* uninstall_dialog) {
-  constrained_window::CreateBrowserModalDialogViews(
+  views::Widget* widget = constrained_window::CreateBrowserModalDialogViews(
       (new AppUninstallDialogView(profile, app_type, app_id, app_name, image,
                                   uninstall_dialog)),
-      parent_window)
-      ->Show();
+      parent_window);
+  widget->Show();
+  return widget;
 }
 
 AppUninstallDialogView::AppUninstallDialogView(
diff --git a/chrome/browser/ui/views/apps/app_dialog/app_uninstall_dialog_view_browsertest.cc b/chrome/browser/ui/views/apps/app_dialog/app_uninstall_dialog_view_browsertest.cc
index bbfbf74..254434e5 100644
--- a/chrome/browser/ui/views/apps/app_dialog/app_uninstall_dialog_view_browsertest.cc
+++ b/chrome/browser/ui/views/apps/app_dialog/app_uninstall_dialog_view_browsertest.cc
@@ -33,6 +33,7 @@
 #include "content/public/test/test_navigation_observer.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/base/l10n/l10n_util.h"
+#include "ui/views/widget/any_widget_observer.h"
 
 class AppUninstallDialogViewBrowserTest : public DialogBrowserTest {
  public:
@@ -207,6 +208,50 @@
 }
 
 IN_PROC_BROWSER_TEST_F(WebAppsUninstallDialogViewBrowserTest,
+                       ExistingDialogFocus) {
+  CreateApp();
+
+  auto* app_service_proxy =
+      apps::AppServiceProxyFactory::GetForProfile(browser()->profile());
+  ASSERT_TRUE(app_service_proxy);
+
+  // First call to uninstall should return true in callback for successful.
+  {
+    base::RunLoop run_loop;
+    app_service_proxy->UninstallForTesting(
+        app_id_, nullptr, base::BindLambdaForTesting([&](bool dialog_opened) {
+          EXPECT_TRUE(dialog_opened);
+          run_loop.Quit();
+        }));
+    run_loop.Run();
+  }
+
+  views::Widget* first_widget = ActiveView()->GetWidget();
+  first_widget->Hide();
+  EXPECT_FALSE(first_widget->IsVisible());
+
+  // Second call should be unsuccessful.
+  {
+    base::RunLoop run_loop;
+
+    // The shown widget should be the one opened in the first call to uninstall.
+    views::AnyWidgetObserver observer(views::test::AnyWidgetTestPasskey{});
+    observer.set_shown_callback(
+        base::BindLambdaForTesting([&](views::Widget* widget) {
+          EXPECT_EQ(first_widget, widget);
+          EXPECT_TRUE(first_widget->IsVisible());
+        }));
+    app_service_proxy->UninstallForTesting(
+        app_id_, nullptr, base::BindLambdaForTesting([&](bool dialog_opened) {
+          EXPECT_FALSE(dialog_opened);
+          run_loop.Quit();
+        }));
+
+    run_loop.Run();
+  }
+}
+
+IN_PROC_BROWSER_TEST_F(WebAppsUninstallDialogViewBrowserTest,
                        PreventDuplicateUninstallDialogs) {
   CreateApp();
 
diff --git a/chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.cc b/chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.cc
index bf5f279..1da07dc8 100644
--- a/chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.cc
+++ b/chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.cc
@@ -1362,7 +1362,7 @@
                                   &family_link_allowed);
   params.SetBoolKey("familyLinkAllowed", family_link_allowed);
 
-  CallJS("login.GaiaSigninScreen.showAllowlistCheckFailedError", true, params);
+  CallJS("login.GaiaSigninScreen.showAllowlistCheckFailedError", params);
 }
 
 void GaiaScreenHandler::LoadAuthExtension(bool force) {
diff --git a/chrome/browser/ui/webui/settings/ash/os_apps_page/app_notification_handler.cc b/chrome/browser/ui/webui/settings/ash/os_apps_page/app_notification_handler.cc
index 2afa80c..5db32715 100644
--- a/chrome/browser/ui/webui/settings/ash/os_apps_page/app_notification_handler.cc
+++ b/chrome/browser/ui/webui/settings/ash/os_apps_page/app_notification_handler.cc
@@ -11,19 +11,21 @@
 #include "chrome/browser/apps/app_service/app_service_proxy.h"
 #include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
 #include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/webui/settings/ash/os_apps_page/mojom/app_type_mojom_traits.h"
+#include "components/services/app_service/public/cpp/app_types.h"
+#include "components/services/app_service/public/cpp/permission.h"
 #include "components/services/app_service/public/cpp/types_util.h"
-#include "components/services/app_service/public/mojom/types.mojom.h"
 
 namespace chromeos {
 namespace settings {
 
 namespace {
 app_notification::mojom::AppPtr CreateAppPtr(const apps::AppUpdate& update) {
-  apps::mojom::PermissionPtr permission_copy;
+  apps::PermissionPtr permission_copy;
   for (const auto& permission : update.Permissions()) {
     if (permission->permission_type ==
         apps::mojom::PermissionType::kNotifications) {
-      permission_copy = permission->Clone();
+      permission_copy = apps::ConvertMojomPermissionToPermission(permission);
       break;
     }
   }
@@ -32,7 +34,9 @@
   app->id = update.AppId();
   app->title = update.Name();
   app->notification_permission = std::move(permission_copy);
-  app->readiness = update.Readiness();
+  app->readiness =
+      mojo::EnumTraits<app_notification::mojom::Readiness, apps::Readiness>::
+          ToMojom(apps::ConvertMojomReadinessToReadiness(update.Readiness()));
 
   return app;
 }
@@ -103,8 +107,9 @@
 
 void AppNotificationHandler::SetNotificationPermission(
     const std::string& app_id,
-    apps::mojom::PermissionPtr permission) {
-  app_service_proxy_->SetPermission(app_id, std::move(permission));
+    apps::PermissionPtr permission) {
+  app_service_proxy_->SetPermission(
+      app_id, apps::ConvertPermissionToMojomPermission(permission));
 }
 
 void AppNotificationHandler::GetApps(GetAppsCallback callback) {
diff --git a/chrome/browser/ui/webui/settings/ash/os_apps_page/app_notification_handler.h b/chrome/browser/ui/webui/settings/ash/os_apps_page/app_notification_handler.h
index c2632e6..ab6f4840 100644
--- a/chrome/browser/ui/webui/settings/ash/os_apps_page/app_notification_handler.h
+++ b/chrome/browser/ui/webui/settings/ash/os_apps_page/app_notification_handler.h
@@ -41,9 +41,8 @@
 
   // settings::mojom::AppNotificationHandler:
   void SetQuietMode(bool in_quiet_mode) override;
-  void SetNotificationPermission(
-      const std::string& app_id,
-      apps::mojom::PermissionPtr permission) override;
+  void SetNotificationPermission(const std::string& app_id,
+                                 apps::PermissionPtr permission) override;
   void GetApps(GetAppsCallback callback) override;
   void GetQuietMode(GetQuietModeCallback callback) override;
 
diff --git a/chrome/browser/ui/webui/settings/ash/os_apps_page/app_notification_handler_unittest.cc b/chrome/browser/ui/webui/settings/ash/os_apps_page/app_notification_handler_unittest.cc
index 6337c09..4a48dce 100644
--- a/chrome/browser/ui/webui/settings/ash/os_apps_page/app_notification_handler_unittest.cc
+++ b/chrome/browser/ui/webui/settings/ash/os_apps_page/app_notification_handler_unittest.cc
@@ -15,7 +15,8 @@
 #include "chrome/browser/ui/webui/settings/ash/os_apps_page/mojom/app_notification_handler.mojom.h"
 #include "chrome/test/base/testing_profile.h"
 #include "components/services/app_service/public/cpp/app_registry_cache.h"
-#include "components/services/app_service/public/mojom/types.mojom.h"
+#include "components/services/app_service/public/cpp/app_types.h"
+#include "components/services/app_service/public/cpp/permission.h"
 #include "content/public/test/browser_task_environment.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -209,7 +210,7 @@
   EXPECT_EQ("arcAppWithNotifications", observer()->recently_updated_app()->id);
   EXPECT_TRUE(observer()
                   ->recently_updated_app()
-                  ->notification_permission->value->get_bool_value());
+                  ->notification_permission->value->bool_value.value());
 
   CreateAndStoreFakeApp("webAppWithNotifications", apps::mojom::AppType::kWeb,
                         apps::mojom::PermissionType::kNotifications,
@@ -220,7 +221,7 @@
   EXPECT_EQ("webAppWithNotifications", observer()->recently_updated_app()->id);
   EXPECT_TRUE(observer()
                   ->recently_updated_app()
-                  ->notification_permission->value->get_bool_value());
+                  ->notification_permission->value->bool_value.value());
 
   CreateAndStoreFakeApp("arcAppWithCamera", apps::mojom::AppType::kArc,
                         apps::mojom::PermissionType::kCamera);
@@ -250,7 +251,7 @@
   EXPECT_EQ("arcAppWithNotifications", observer()->recently_updated_app()->id);
   EXPECT_FALSE(observer()
                    ->recently_updated_app()
-                   ->notification_permission->value->get_bool_value());
+                   ->notification_permission->value->bool_value.value());
 
   CreateAndStoreFakeApp("webAppWithNotifications", apps::mojom::AppType::kWeb,
                         apps::mojom::PermissionType::kNotifications,
@@ -261,7 +262,7 @@
   EXPECT_EQ("webAppWithNotifications", observer()->recently_updated_app()->id);
   EXPECT_FALSE(observer()
                    ->recently_updated_app()
-                   ->notification_permission->value->get_bool_value());
+                   ->notification_permission->value->bool_value.value());
 }
 
 }  // namespace settings
diff --git a/chrome/browser/ui/webui/settings/ash/os_apps_page/mojom/BUILD.gn b/chrome/browser/ui/webui/settings/ash/os_apps_page/mojom/BUILD.gn
index d58f83e1..c7bacea 100644
--- a/chrome/browser/ui/webui/settings/ash/os_apps_page/mojom/BUILD.gn
+++ b/chrome/browser/ui/webui/settings/ash/os_apps_page/mojom/BUILD.gn
@@ -14,4 +14,36 @@
     "//components/services/app_service/public/mojom",
     "//mojo/public/mojom/base",
   ]
+
+  cpp_typemaps = [
+    {
+      types = [
+        {
+          mojom = "chromeos.settings.app_notification.mojom.Permission"
+          cpp = "::apps::PermissionPtr"
+          move_only = true
+        },
+      ]
+      traits_headers = [
+        "//chrome/browser/ui/webui/settings/ash/os_apps_page/mojom/app_type_mojom_traits.h",
+        "//components/services/app_service/public/cpp/app_types.h",
+        "//components/services/app_service/public/cpp/permission.h",
+      ]
+      traits_sources = [ "//chrome/browser/ui/webui/settings/ash/os_apps_page/mojom/app_type_mojom_traits.cc" ]
+      traits_public_deps =
+          [ "//components/services/app_service/public/cpp:app_types" ]
+    },
+  ]
+}
+
+source_set("test_support") {
+  testonly = true
+
+  deps = [
+    ":mojom",
+    "//base/test:test_support",
+    "//chrome/test:test_support",
+    "//mojo/public/cpp/test_support:test_utils",
+    "//testing/gtest",
+  ]
 }
diff --git a/chrome/browser/ui/webui/settings/ash/os_apps_page/mojom/OWNERS b/chrome/browser/ui/webui/settings/ash/os_apps_page/mojom/OWNERS
index 20352ec..11550d5 100644
--- a/chrome/browser/ui/webui/settings/ash/os_apps_page/mojom/OWNERS
+++ b/chrome/browser/ui/webui/settings/ash/os_apps_page/mojom/OWNERS
@@ -3,4 +3,7 @@
 hsuregan@chromium.org
 
 per-file *.mojom=set noparent
-per-file *.mojom=file://ipc/SECURITY_OWNERS
\ No newline at end of file
+per-file *.mojom=file://ipc/SECURITY_OWNERS
+
+per-file *_mojom_traits*.*=set noparent
+per-file *_mojom_traits*.*=file://ipc/SECURITY_OWNERS
diff --git a/chrome/browser/ui/webui/settings/ash/os_apps_page/mojom/app_notification_handler.mojom b/chrome/browser/ui/webui/settings/ash/os_apps_page/mojom/app_notification_handler.mojom
index 1646039..29345ec 100644
--- a/chrome/browser/ui/webui/settings/ash/os_apps_page/mojom/app_notification_handler.mojom
+++ b/chrome/browser/ui/webui/settings/ash/os_apps_page/mojom/app_notification_handler.mojom
@@ -4,7 +4,48 @@
 
 module chromeos.settings.app_notification.mojom;
 
-import "components/services/app_service/public/mojom/types.mojom";
+// Whether an app is ready to launch, i.e. installed.
+enum Readiness {
+  kUnknown = 0,
+  kReady,                // Installed and launchable.
+  kDisabledByBlocklist,  // Disabled by SafeBrowsing.
+  kDisabledByPolicy,     // Disabled by admin policy.
+  kDisabledByUser,       // Disabled by explicit user action.
+  kTerminated,           // Renderer process crashed.
+  kUninstalledByUser,
+  kRemoved,
+  kUninstalledByMigration,
+};
+
+// The types of permission.
+enum PermissionType {
+  kUnknown         = 0,
+  kCamera          = 1,
+  kLocation        = 2,
+  kMicrophone      = 3,
+  kNotifications   = 4,
+  kContacts        = 5,
+  kStorage         = 6,
+  kPrinting        = 7,
+};
+
+enum TriState {
+  kAllow,
+  kBlock,
+  kAsk,
+};
+
+union PermissionValue {
+  bool bool_value;
+  TriState tristate_value;
+};
+
+struct Permission {
+  PermissionType permission_type;
+  PermissionValue value;
+  // If the permission is managed by an enterprise policy.
+  bool is_managed;
+};
 
 // Implementation of App
 // Contains the app's id, title, and only the notification permission, as this
@@ -19,10 +60,10 @@
   string? title;
 
   // Whether an app is ready to launch, i.e. installed.
-  apps.mojom.Readiness readiness;
+  Readiness readiness;
 
   // Contains the current permission state of the App's notification.
-  apps.mojom.Permission notification_permission;
+  Permission notification_permission;
 };
 
 // Browser interface.
@@ -36,7 +77,7 @@
   AddObserver(pending_remote<AppNotificationsObserver> observer);
 
   // Updates the permission of the specified app (via app_id).
-  SetNotificationPermission(string app_id, apps.mojom.Permission permission);
+  SetNotificationPermission(string app_id, Permission permission);
 
   // Get the list of installed apps.
   GetApps() => (array<App> apps);
diff --git a/chrome/browser/ui/webui/settings/ash/os_apps_page/mojom/app_type_mojom_traits.cc b/chrome/browser/ui/webui/settings/ash/os_apps_page/mojom/app_type_mojom_traits.cc
new file mode 100644
index 0000000..8a4fecad
--- /dev/null
+++ b/chrome/browser/ui/webui/settings/ash/os_apps_page/mojom/app_type_mojom_traits.cc
@@ -0,0 +1,196 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/webui/settings/ash/os_apps_page/mojom/app_type_mojom_traits.h"
+
+#include <utility>
+
+namespace mojo {
+
+Readiness EnumTraits<Readiness, apps::Readiness>::ToMojom(
+    apps::Readiness input) {
+  switch (input) {
+    case apps::Readiness::kUnknown:
+      return Readiness::kUnknown;
+    case apps::Readiness::kReady:
+      return Readiness::kReady;
+    case apps::Readiness::kDisabledByBlocklist:
+      return Readiness::kDisabledByBlocklist;
+    case apps::Readiness::kDisabledByPolicy:
+      return Readiness::kDisabledByPolicy;
+    case apps::Readiness::kDisabledByUser:
+      return Readiness::kDisabledByUser;
+    case apps::Readiness::kTerminated:
+      return Readiness::kTerminated;
+    case apps::Readiness::kUninstalledByUser:
+      return Readiness::kUninstalledByUser;
+    case apps::Readiness::kRemoved:
+      return Readiness::kRemoved;
+    case apps::Readiness::kUninstalledByMigration:
+      return Readiness::kUninstalledByMigration;
+  }
+}
+
+bool EnumTraits<Readiness, apps::Readiness>::FromMojom(
+    Readiness input,
+    apps::Readiness* output) {
+  switch (input) {
+    case Readiness::kUnknown:
+      *output = apps::Readiness::kUnknown;
+      return true;
+    case Readiness::kReady:
+      *output = apps::Readiness::kReady;
+      return true;
+    case Readiness::kDisabledByBlocklist:
+      *output = apps::Readiness::kDisabledByBlocklist;
+      return true;
+    case Readiness::kDisabledByPolicy:
+      *output = apps::Readiness::kDisabledByPolicy;
+      return true;
+    case Readiness::kDisabledByUser:
+      *output = apps::Readiness::kDisabledByUser;
+      return true;
+    case Readiness::kTerminated:
+      *output = apps::Readiness::kTerminated;
+      return true;
+    case Readiness::kUninstalledByUser:
+      *output = apps::Readiness::kUninstalledByUser;
+      return true;
+    case Readiness::kRemoved:
+      *output = apps::Readiness::kRemoved;
+      return true;
+    case Readiness::kUninstalledByMigration:
+      *output = apps::Readiness::kUninstalledByMigration;
+      return true;
+  }
+}
+
+bool StructTraits<PermissionDataView, apps::PermissionPtr>::Read(
+    PermissionDataView data,
+    apps::PermissionPtr* out) {
+  apps::PermissionType permission_type;
+  if (!data.ReadPermissionType(&permission_type))
+    return false;
+
+  apps::PermissionValuePtr value;
+  if (!data.ReadValue(&value))
+    return false;
+
+  *out = std::make_unique<apps::Permission>(permission_type, std::move(value),
+                                            data.is_managed());
+  return true;
+}
+
+PermissionType EnumTraits<PermissionType, apps::PermissionType>::ToMojom(
+    apps::PermissionType input) {
+  switch (input) {
+    case apps::PermissionType::kUnknown:
+      return PermissionType::kUnknown;
+    case apps::PermissionType::kCamera:
+      return PermissionType::kCamera;
+    case apps::PermissionType::kLocation:
+      return PermissionType::kLocation;
+    case apps::PermissionType::kMicrophone:
+      return PermissionType::kMicrophone;
+    case apps::PermissionType::kNotifications:
+      return PermissionType::kNotifications;
+    case apps::PermissionType::kContacts:
+      return PermissionType::kContacts;
+    case apps::PermissionType::kStorage:
+      return PermissionType::kStorage;
+    case apps::PermissionType::kPrinting:
+      return PermissionType::kPrinting;
+  }
+}
+
+bool EnumTraits<PermissionType, apps::PermissionType>::FromMojom(
+    PermissionType input,
+    apps::PermissionType* output) {
+  switch (input) {
+    case PermissionType::kUnknown:
+      *output = apps::PermissionType::kUnknown;
+      return true;
+    case PermissionType::kCamera:
+      *output = apps::PermissionType::kCamera;
+      return true;
+    case PermissionType::kLocation:
+      *output = apps::PermissionType::kLocation;
+      return true;
+    case PermissionType::kMicrophone:
+      *output = apps::PermissionType::kMicrophone;
+      return true;
+    case PermissionType::kNotifications:
+      *output = apps::PermissionType::kNotifications;
+      return true;
+    case PermissionType::kContacts:
+      *output = apps::PermissionType::kContacts;
+      return true;
+    case PermissionType::kStorage:
+      *output = apps::PermissionType::kStorage;
+      return true;
+    case PermissionType::kPrinting:
+      *output = apps::PermissionType::kPrinting;
+      return true;
+  }
+}
+
+TriState EnumTraits<TriState, apps::TriState>::ToMojom(apps::TriState input) {
+  switch (input) {
+    case apps::TriState::kAllow:
+      return TriState::kAllow;
+    case apps::TriState::kBlock:
+      return TriState::kBlock;
+    case apps::TriState::kAsk:
+      return TriState::kAsk;
+  }
+}
+
+bool EnumTraits<TriState, apps::TriState>::FromMojom(TriState input,
+                                                     apps::TriState* output) {
+  switch (input) {
+    case TriState::kAllow:
+      *output = apps::TriState::kAllow;
+      return true;
+    case TriState::kBlock:
+      *output = apps::TriState::kBlock;
+      return true;
+    case TriState::kAsk:
+      *output = apps::TriState::kAsk;
+      return true;
+  }
+}
+
+PermissionValueDataView::Tag
+UnionTraits<PermissionValueDataView, apps::PermissionValuePtr>::GetTag(
+    const apps::PermissionValuePtr& r) {
+  if (r->bool_value.has_value()) {
+    return PermissionValueDataView::Tag::BOOL_VALUE;
+  } else if (r->tristate_value.has_value()) {
+    return PermissionValueDataView::Tag::TRISTATE_VALUE;
+  }
+  NOTREACHED();
+  return PermissionValueDataView::Tag::BOOL_VALUE;
+}
+
+bool UnionTraits<PermissionValueDataView, apps::PermissionValuePtr>::Read(
+    PermissionValueDataView data,
+    apps::PermissionValuePtr* out) {
+  switch (data.tag()) {
+    case PermissionValueDataView::Tag::BOOL_VALUE: {
+      *out = std::make_unique<apps::PermissionValue>(data.bool_value());
+      return true;
+    }
+    case PermissionValueDataView::Tag::TRISTATE_VALUE: {
+      apps::TriState tristate_value;
+      if (!data.ReadTristateValue(&tristate_value))
+        return false;
+      *out = std::make_unique<apps::PermissionValue>(tristate_value);
+      return true;
+    }
+  }
+  NOTREACHED();
+  return false;
+}
+
+}  // namespace mojo
diff --git a/chrome/browser/ui/webui/settings/ash/os_apps_page/mojom/app_type_mojom_traits.h b/chrome/browser/ui/webui/settings/ash/os_apps_page/mojom/app_type_mojom_traits.h
new file mode 100644
index 0000000..24714f7
--- /dev/null
+++ b/chrome/browser/ui/webui/settings/ash/os_apps_page/mojom/app_type_mojom_traits.h
@@ -0,0 +1,91 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_WEBUI_SETTINGS_ASH_OS_APPS_PAGE_MOJOM_APP_TYPE_MOJOM_TRAITS_H_
+#define CHROME_BROWSER_UI_WEBUI_SETTINGS_ASH_OS_APPS_PAGE_MOJOM_APP_TYPE_MOJOM_TRAITS_H_
+
+#include "chrome/browser/ui/webui/settings/ash/os_apps_page/mojom/app_notification_handler.mojom.h"
+#include "components/services/app_service/public/cpp/app_types.h"
+#include "components/services/app_service/public/cpp/permission.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace mojo {
+
+namespace {
+
+using Readiness = chromeos::settings::app_notification::mojom::Readiness;
+using PermissionDataView =
+    chromeos::settings::app_notification::mojom::PermissionDataView;
+using PermissionType =
+    chromeos::settings::app_notification::mojom::PermissionType;
+using TriState = chromeos::settings::app_notification::mojom::TriState;
+using PermissionValueDataView =
+    chromeos::settings::app_notification::mojom::PermissionValueDataView;
+
+}  // namespace
+
+template <>
+struct EnumTraits<Readiness, apps::Readiness> {
+  static Readiness ToMojom(apps::Readiness input);
+  static bool FromMojom(Readiness input, apps::Readiness* output);
+};
+
+template <>
+struct StructTraits<PermissionDataView, apps::PermissionPtr> {
+  static apps::PermissionType permission_type(const apps::PermissionPtr& r) {
+    return r->permission_type;
+  }
+
+  static const apps::PermissionValuePtr& value(const apps::PermissionPtr& r) {
+    return r->value;
+  }
+
+  static bool is_managed(const apps::PermissionPtr& r) { return r->is_managed; }
+
+  static bool Read(PermissionDataView, apps::PermissionPtr* out);
+};
+
+template <>
+struct CloneTraits<apps::PermissionPtr, false> {
+  static apps::PermissionPtr Clone(const apps::PermissionPtr& input) {
+    return input->Clone();
+  }
+};
+
+template <>
+struct EnumTraits<PermissionType, apps::PermissionType> {
+  static PermissionType ToMojom(apps::PermissionType input);
+  static bool FromMojom(PermissionType input, apps::PermissionType* output);
+};
+
+template <>
+struct EnumTraits<TriState, apps::TriState> {
+  static TriState ToMojom(apps::TriState input);
+  static bool FromMojom(TriState input, apps::TriState* output);
+};
+
+template <>
+struct UnionTraits<PermissionValueDataView, apps::PermissionValuePtr> {
+  static PermissionValueDataView::Tag GetTag(const apps::PermissionValuePtr& r);
+
+  static bool IsNull(const apps::PermissionValuePtr& r) {
+    return !r->bool_value.has_value() && !r->tristate_value.has_value();
+  }
+
+  static void SetToNull(apps::PermissionValuePtr* out) { out->reset(); }
+
+  static bool bool_value(const apps::PermissionValuePtr& r) {
+    return r->bool_value.value();
+  }
+
+  static apps::TriState tristate_value(const apps::PermissionValuePtr& r) {
+    return r->tristate_value.value();
+  }
+
+  static bool Read(PermissionValueDataView data, apps::PermissionValuePtr* out);
+};
+
+}  // namespace mojo
+
+#endif  // CHROME_BROWSER_UI_WEBUI_SETTINGS_ASH_OS_APPS_PAGE_MOJOM_APP_TYPE_MOJOM_TRAITS_H_
diff --git a/chrome/browser/ui/webui/settings/ash/os_apps_page/mojom/app_type_mojom_traits_unittest.cc b/chrome/browser/ui/webui/settings/ash/os_apps_page/mojom/app_type_mojom_traits_unittest.cc
new file mode 100644
index 0000000..63dddef
--- /dev/null
+++ b/chrome/browser/ui/webui/settings/ash/os_apps_page/mojom/app_type_mojom_traits_unittest.cc
@@ -0,0 +1,131 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <utility>
+
+#include "chrome/browser/ui/webui/settings/ash/os_apps_page/mojom/app_notification_handler.mojom.h"
+#include "chrome/browser/ui/webui/settings/ash/os_apps_page/mojom/app_type_mojom_traits.h"
+#include "components/services/app_service/public/cpp/app_types.h"
+#include "components/services/app_service/public/cpp/permission.h"
+#include "mojo/public/cpp/test_support/test_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+// Test that serialization and deserialization works with updating readiness.
+TEST(AppTypeMojomTraitsTest, RoundTripReadiness) {
+  static constexpr apps::Readiness kTestReadiness[] = {
+      apps::Readiness::kUnknown,
+      apps::Readiness::kReady,
+      apps::Readiness::kDisabledByBlocklist,
+      apps::Readiness::kDisabledByPolicy,
+      apps::Readiness::kDisabledByUser,
+      apps::Readiness::kTerminated,
+      apps::Readiness::kUninstalledByUser,
+      apps::Readiness::kRemoved,
+      apps::Readiness::kUninstalledByMigration};
+
+  for (auto readiness_in : kTestReadiness) {
+    apps::Readiness readiness_out;
+
+    chromeos::settings::app_notification::mojom::Readiness
+        serialized_readiness = mojo::EnumTraits<
+            chromeos::settings::app_notification::mojom::Readiness,
+            apps::Readiness>::ToMojom(readiness_in);
+    ASSERT_TRUE((
+        mojo::EnumTraits<chromeos::settings::app_notification::mojom::Readiness,
+                         apps::Readiness>::FromMojom(serialized_readiness,
+                                                     &readiness_out)));
+    EXPECT_EQ(readiness_in, readiness_out);
+  }
+}
+
+TEST(AppTypeMojomTraitsTest, RoundTripPermissions) {
+  {
+    auto permission = std::make_unique<apps::Permission>(
+        apps::PermissionType::kUnknown,
+        std::make_unique<apps::PermissionValue>(true),
+        /*is_managed=*/false);
+    apps::PermissionPtr output;
+    ASSERT_TRUE(mojo::test::SerializeAndDeserialize<
+                chromeos::settings::app_notification::mojom::Permission>(
+        permission, output));
+    EXPECT_EQ(*permission, *output);
+  }
+  {
+    auto permission = std::make_unique<apps::Permission>(
+        apps::PermissionType::kCamera,
+        std::make_unique<apps::PermissionValue>(true),
+        /*is_managed=*/true);
+    apps::PermissionPtr output;
+    ASSERT_TRUE(mojo::test::SerializeAndDeserialize<
+                chromeos::settings::app_notification::mojom::Permission>(
+        permission, output));
+    EXPECT_EQ(*permission, *output);
+  }
+  {
+    auto permission = std::make_unique<apps::Permission>(
+        apps::PermissionType::kLocation,
+        std::make_unique<apps::PermissionValue>(apps::TriState::kAllow),
+        /*is_managed=*/false);
+    apps::PermissionPtr output;
+    ASSERT_TRUE(mojo::test::SerializeAndDeserialize<
+                chromeos::settings::app_notification::mojom::Permission>(
+        permission, output));
+    EXPECT_EQ(*permission, *output);
+  }
+  {
+    auto permission = std::make_unique<apps::Permission>(
+        apps::PermissionType::kMicrophone,
+        std::make_unique<apps::PermissionValue>(apps::TriState::kBlock),
+        /*is_managed=*/true);
+    apps::PermissionPtr output;
+    ASSERT_TRUE(mojo::test::SerializeAndDeserialize<
+                chromeos::settings::app_notification::mojom::Permission>(
+        permission, output));
+    EXPECT_EQ(*permission, *output);
+  }
+  {
+    auto permission = std::make_unique<apps::Permission>(
+        apps::PermissionType::kNotifications,
+        std::make_unique<apps::PermissionValue>(apps::TriState::kAsk),
+        /*is_managed=*/false);
+    apps::PermissionPtr output;
+    ASSERT_TRUE(mojo::test::SerializeAndDeserialize<
+                chromeos::settings::app_notification::mojom::Permission>(
+        permission, output));
+    EXPECT_EQ(*permission, *output);
+  }
+  {
+    auto permission = std::make_unique<apps::Permission>(
+        apps::PermissionType::kContacts,
+        std::make_unique<apps::PermissionValue>(apps::TriState::kAllow),
+        /*is_managed=*/true);
+    apps::PermissionPtr output;
+    ASSERT_TRUE(mojo::test::SerializeAndDeserialize<
+                chromeos::settings::app_notification::mojom::Permission>(
+        permission, output));
+    EXPECT_EQ(*permission, *output);
+  }
+  {
+    auto permission = std::make_unique<apps::Permission>(
+        apps::PermissionType::kStorage,
+        std::make_unique<apps::PermissionValue>(apps::TriState::kBlock),
+        /*is_managed=*/false);
+    apps::PermissionPtr output;
+    ASSERT_TRUE(mojo::test::SerializeAndDeserialize<
+                chromeos::settings::app_notification::mojom::Permission>(
+        permission, output));
+    EXPECT_EQ(*permission, *output);
+  }
+  {
+    auto permission = std::make_unique<apps::Permission>(
+        apps::PermissionType::kPrinting,
+        std::make_unique<apps::PermissionValue>(apps::TriState::kBlock),
+        /*is_managed=*/false);
+    apps::PermissionPtr output;
+    ASSERT_TRUE(mojo::test::SerializeAndDeserialize<
+                chromeos::settings::app_notification::mojom::Permission>(
+        permission, output));
+    EXPECT_EQ(*permission, *output);
+  }
+}
diff --git a/chrome/browser/ui/webui/settings/privacy_sandbox_handler_unittest.cc b/chrome/browser/ui/webui/settings/privacy_sandbox_handler_unittest.cc
index 12d3071..c7e8cf753 100644
--- a/chrome/browser/ui/webui/settings/privacy_sandbox_handler_unittest.cc
+++ b/chrome/browser/ui/webui/settings/privacy_sandbox_handler_unittest.cc
@@ -150,7 +150,7 @@
   content::TestWebUI* web_ui() { return web_ui_.get(); }
   PrivacySandboxHandler* handler() { return handler_.get(); }
   TestingProfile* profile() { return &profile_; }
-  PrivacySandboxSettings* privacy_sandbox_settings() {
+  privacy_sandbox::PrivacySandboxSettings* privacy_sandbox_settings() {
     return PrivacySandboxSettingsFactory::GetForProfile(profile());
   }
 
diff --git a/chrome/browser/ui/webui/settings/settings_ui.cc b/chrome/browser/ui/webui/settings/settings_ui.cc
index 36bb5bd4..6e65ed4 100644
--- a/chrome/browser/ui/webui/settings/settings_ui.cc
+++ b/chrome/browser/ui/webui/settings/settings_ui.cc
@@ -275,6 +275,10 @@
       "enablePasswordNotes",
       base::FeatureList::IsEnabled(password_manager::features::kPasswordNotes));
 
+  html_source->AddBoolean(
+      "enableSendPasswords",
+      base::FeatureList::IsEnabled(password_manager::features::kSendPasswords));
+
 #if !BUILDFLAG(IS_CHROMEOS_ASH) && !BUILDFLAG(IS_CHROMEOS_LACROS)
   html_source->AddBoolean("enableDesktopRestructuredLanguageSettings",
                           base::FeatureList::IsEnabled(
diff --git a/chrome/build/linux.pgo.txt b/chrome/build/linux.pgo.txt
index 50c452d2..46bc525 100644
--- a/chrome/build/linux.pgo.txt
+++ b/chrome/build/linux.pgo.txt
@@ -1 +1 @@
-chrome-linux-main-1645574159-4f68b0ac4c0ad4f9971762f66286704eaa122288.profdata
+chrome-linux-main-1645617234-edce5eead33c3da0cd365f8afd5609c9c0a116a4.profdata
diff --git a/chrome/build/mac-arm.pgo.txt b/chrome/build/mac-arm.pgo.txt
index b3b0e11b..f5c5422 100644
--- a/chrome/build/mac-arm.pgo.txt
+++ b/chrome/build/mac-arm.pgo.txt
@@ -1 +1 @@
-chrome-mac-arm-main-1645574159-9b0952a0e6f8425b04314d4ce625a5b4c219dcfc.profdata
+chrome-mac-arm-main-1645617234-f1d8894b1e23955fd37ea585b43a081ff82a5c0f.profdata
diff --git a/chrome/build/mac.pgo.txt b/chrome/build/mac.pgo.txt
index dabfd4c5..29e17c1 100644
--- a/chrome/build/mac.pgo.txt
+++ b/chrome/build/mac.pgo.txt
@@ -1 +1 @@
-chrome-mac-main-1645574159-84ac20ae2b3921896fbf677a762cbe6c60e5d3d8.profdata
+chrome-mac-main-1645595566-c3d1942eba7792b1af68e744864b2f418a87b3b5.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index 139f86d2..3cd3b3b47 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-main-1645574159-895357cd8bedc9dc31ba86d30478d3731468f622.profdata
+chrome-win32-main-1645606721-838006e01d1447b87930ec35d524bd3f5806c21b.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index 7bb2d0a6..f7b24dec 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-main-1645574159-8ba46068808193f24883e0c5d0b484d8961428e9.profdata
+chrome-win64-main-1645606721-f0295952acd787de1e9d9ee8ddbc3c1c957cec11.profdata
diff --git a/chrome/test/data/webui/chromeos/os_feedback_ui/fake_help_content_provider_test.js b/chrome/test/data/webui/chromeos/os_feedback_ui/fake_help_content_provider_test.js
index 1d1db393..9b94ef7e 100644
--- a/chrome/test/data/webui/chromeos/os_feedback_ui/fake_help_content_provider_test.js
+++ b/chrome/test/data/webui/chromeos/os_feedback_ui/fake_help_content_provider_test.js
@@ -25,21 +25,21 @@
    * Test that the fake help content provider returns the non-empty list which
    * was set explicitly.
    */
-  test('getHelpContents', () => {
+  test('getHelpContents', async () => {
     provider.setFakeSearchResponse(fakeSearchResponse);
 
-    return provider.getHelpContents(fakeSearchRequest).then((response) => {
-      assertDeepEquals(fakeHelpContentList, response.response.results);
-      assertEquals(
-          fakeSearchResponse.totalResults, response.response.totalResults);
-    });
+    const response = await provider.getHelpContents(fakeSearchRequest);
+
+    assertDeepEquals(fakeHelpContentList, response.response.results);
+    assertEquals(
+        fakeSearchResponse.totalResults, response.response.totalResults);
   });
 
   /**
    * Test that the fake help content provider returns the empty list which was
    * set explicitly.
    */
-  test('getHelpContentsEmpty', () => {
+  test('getHelpContentsEmpty', async () => {
     /** @type {!HelpContentList} */
     const expectedList = [];
 
@@ -48,13 +48,13 @@
       results: expectedList,
       totalResults: 0,
     };
-
     provider.setFakeSearchResponse(emptyResponse);
-    return provider.getHelpContents(fakeSearchRequest).then((response) => {
-      assertDeepEquals(expectedList, response.response.results);
-      assertEquals(emptyResponse.totalResults, response.response.totalResults);
-      assertEquals(
-          mojoString16ToString(fakeSearchRequest.query), provider.lastQuery);
-    });
+
+    const response = await provider.getHelpContents(fakeSearchRequest);
+
+    assertDeepEquals(expectedList, response.response.results);
+    assertEquals(emptyResponse.totalResults, response.response.totalResults);
+    assertEquals(
+        mojoString16ToString(fakeSearchRequest.query), provider.lastQuery);
   });
 }
diff --git a/chrome/test/data/webui/chromeos/os_feedback_ui/help_content_test.js b/chrome/test/data/webui/chromeos/os_feedback_ui/help_content_test.js
index a9e94b34..0935859c 100644
--- a/chrome/test/data/webui/chromeos/os_feedback_ui/help_content_test.js
+++ b/chrome/test/data/webui/chromeos/os_feedback_ui/help_content_test.js
@@ -46,49 +46,47 @@
   }
 
   /** Test that expected html elements are in the element. */
-  test('HelpContentLoaded', () => {
-    return initializeHelpContentElement(fakeHelpContentList).then(() => {
-      // Verify the title is in the helpContentElement.
-      const title = getElement('#helpContentLabel');
-      assertTrue(!!title);
-      assertEquals('Suggested help content:', title.textContent);
+  test('HelpContentLoaded', async () => {
+    await initializeHelpContentElement(fakeHelpContentList);
 
-      // Verify the help content is populated with correct number of items.
-      assertEquals(5, getElement('dom-repeat').items.length);
-      const helpLinks =
-          helpContentElement.shadowRoot.querySelectorAll('.help-item a');
-      assertEquals(5, helpLinks.length);
+    // Verify the title is in the helpContentElement.
+    const title = getElement('#helpContentLabel');
+    assertTrue(!!title);
+    assertEquals('Suggested help content:', title.textContent);
 
-      // Verify the help links are displayed in order with correct title and
-      // url.
-      assertEquals('Fix connection problems', helpLinks[0].innerText);
-      assertEquals(
-          'https://support.google.com/chromebook/?q=6318213',
-          helpLinks[0].href);
+    // Verify the help content is populated with correct number of items.
+    assertEquals(5, getElement('dom-repeat').items.length);
+    const helpLinks =
+        helpContentElement.shadowRoot.querySelectorAll('.help-item a');
+    assertEquals(5, helpLinks.length);
 
-      assertEquals(
-          'Why won\'t my wireless mouse with a USB piece wor...?',
-          helpLinks[1].innerText);
-      assertEquals(
-          'https://support.google.com/chromebook/?q=123920509',
-          helpLinks[1].href);
+    // Verify the help links are displayed in order with correct title and
+    // url.
+    assertEquals('Fix connection problems', helpLinks[0].innerText);
+    assertEquals(
+        'https://support.google.com/chromebook/?q=6318213', helpLinks[0].href);
 
-      assertEquals('Wifi Issues - only on Chromebooks', helpLinks[2].innerText);
-      assertEquals(
-          'https://support.google.com/chromebook/?q=114174470',
-          helpLinks[2].href);
+    assertEquals(
+        'Why won\'t my wireless mouse with a USB piece wor...?',
+        helpLinks[1].innerText);
+    assertEquals(
+        'https://support.google.com/chromebook/?q=123920509',
+        helpLinks[1].href);
 
-      assertEquals('Network Connectivity Fault', helpLinks[3].innerText);
-      assertEquals(
-          'https://support.google.com/chromebook/?q=131459420',
-          helpLinks[3].href);
+    assertEquals('Wifi Issues - only on Chromebooks', helpLinks[2].innerText);
+    assertEquals(
+        'https://support.google.com/chromebook/?q=114174470',
+        helpLinks[2].href);
 
-      assertEquals(
-          'Connected to WiFi but can\'t connect to the internet',
-          helpLinks[4].innerText);
-      assertEquals(
-          'https://support.google.com/chromebook/?q=22864239',
-          helpLinks[4].href);
-    });
+    assertEquals('Network Connectivity Fault', helpLinks[3].innerText);
+    assertEquals(
+        'https://support.google.com/chromebook/?q=131459420',
+        helpLinks[3].href);
+
+    assertEquals(
+        'Connected to WiFi but can\'t connect to the internet',
+        helpLinks[4].innerText);
+    assertEquals(
+        'https://support.google.com/chromebook/?q=22864239', helpLinks[4].href);
   });
-}
\ No newline at end of file
+}
diff --git a/chrome/test/data/webui/chromeos/shimless_rma/onboarding_choose_wipe_device_page_test.js b/chrome/test/data/webui/chromeos/shimless_rma/onboarding_choose_wipe_device_page_test.js
index 7edc04c84..ebe8576 100644
--- a/chrome/test/data/webui/chromeos/shimless_rma/onboarding_choose_wipe_device_page_test.js
+++ b/chrome/test/data/webui/chromeos/shimless_rma/onboarding_choose_wipe_device_page_test.js
@@ -2,7 +2,11 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import {PromiseResolver} from 'chrome://resources/js/promise_resolver.m.js';
+import {FakeShimlessRmaService} from 'chrome://shimless-rma/fake_shimless_rma_service.js';
+import {setShimlessRmaServiceForTesting} from 'chrome://shimless-rma/mojo_interface_provider.js';
 import {OnboardingChooseWipeDevicePage} from 'chrome://shimless-rma/onboarding_choose_wipe_device_page.js';
+import {ShimlessRma} from 'chrome://shimless-rma/shimless_rma.js';
 
 import {assertFalse, assertTrue} from '../../chai_assert.js';
 import {flushTasks} from '../../test_util.js';
@@ -11,6 +15,14 @@
   /** @type {?OnboardingChooseWipeDevicePage} */
   let component = null;
 
+  /** @type {?FakeShimlessRmaService} */
+  let service = null;
+
+  suiteSetup(() => {
+    service = new FakeShimlessRmaService();
+    setShimlessRmaServiceForTesting(service);
+  });
+
   setup(() => {
     document.body.innerHTML = '';
   });
@@ -18,6 +30,7 @@
   teardown(() => {
     component.remove();
     component = null;
+    service.reset();
   });
 
   /**
@@ -39,4 +52,44 @@
 
     assertTrue(!!component);
   });
+
+  test('ChooseWipeDevicePageSelectWipeDevice', async () => {
+    const resolver = new PromiseResolver();
+    await initializeChooseWipeDevicePage();
+
+    let shouldWipeDevice = false;
+    service.setWipeDevice = (wipeDevice) => {
+      shouldWipeDevice = wipeDevice;
+      return resolver.promise;
+    };
+
+    const wipeDeviceOption =
+        component.shadowRoot.querySelector('cr-radio-button[name=wipeDevice]');
+    wipeDeviceOption.click();
+    assertTrue(wipeDeviceOption.checked);
+
+    component.onNextButtonClick();
+    await resolver;
+    assertTrue(shouldWipeDevice);
+  });
+
+  test('ChooseWipeDevicePageSelectPreserveData', async () => {
+    const resolver = new PromiseResolver();
+    await initializeChooseWipeDevicePage();
+
+    let shouldWipeDevice = true;
+    service.setWipeDevice = (wipeDevice) => {
+      shouldWipeDevice = wipeDevice;
+      return resolver.promise;
+    };
+
+    const wipeDeviceOption = component.shadowRoot.querySelector(
+        'cr-radio-button[name=preserveData]');
+    wipeDeviceOption.click();
+    assertTrue(wipeDeviceOption.checked);
+
+    component.onNextButtonClick();
+    await resolver;
+    assertFalse(shouldWipeDevice);
+  });
 }
diff --git a/chrome/test/data/webui/history/BUILD.gn b/chrome/test/data/webui/history/BUILD.gn
index 776fca3..6e0ea7a 100644
--- a/chrome/test/data/webui/history/BUILD.gn
+++ b/chrome/test/data/webui/history/BUILD.gn
@@ -17,9 +17,9 @@
   "history_overflow_menu_test.ts",
   "history_routing_test.ts",
   "history_routing_with_query_param_test.ts",
-  "history_supervised_user_test.js",
+  "history_supervised_user_test.ts",
   "history_synced_device_manager_focus_test.js",
-  "history_synced_tabs_test.js",
+  "history_synced_tabs_test.ts",
   "history_toolbar_focus_test.ts",
   "history_toolbar_test.ts",
   "link_click_test.ts",
diff --git a/chrome/test/data/webui/history/history_supervised_user_test.js b/chrome/test/data/webui/history/history_supervised_user_test.js
deleted file mode 100644
index c0900ed..0000000
--- a/chrome/test/data/webui/history/history_supervised_user_test.js
+++ /dev/null
@@ -1,62 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import {BrowserServiceImpl, ensureLazyLoaded} from 'chrome://history/history.js';
-import {flushTasks} from 'chrome://webui-test/test_util.js';
-
-import {TestBrowserService} from './test_browser_service.js';
-import {createHistoryEntry, createHistoryInfo} from './test_util.js';
-
-suite('history-list supervised-user', function() {
-  let app;
-  let historyList;
-  let toolbar;
-  let testService;
-  const TEST_HISTORY_RESULTS =
-      [createHistoryEntry('2016-03-15', 'https://www.google.com')];
-
-  setup(function() {
-    document.body.innerHTML = '';
-    testService = new TestBrowserService();
-    BrowserServiceImpl.setInstance(testService);
-
-    testService.setQueryResult({
-      info: createHistoryInfo(),
-      value: TEST_HISTORY_RESULTS,
-    });
-    app = document.createElement('history-app');
-    document.body.appendChild(app);
-
-    historyList = app.$.history;
-    toolbar = app.$.toolbar;
-    return Promise.all([
-      testService.whenCalled('queryHistory'),
-      ensureLazyLoaded(),
-    ]);
-  });
-
-  test('checkboxes disabled for supervised user', function() {
-    return flushTasks().then(function() {
-      const items = historyList.shadowRoot.querySelectorAll('history-item');
-
-      items[0].$['checkbox'].click();
-
-      assertFalse(items[0].selected);
-    });
-  });
-
-  test('deletion disabled for supervised user', function() {
-    // Make sure that removeVisits is not being called.
-    historyList.historyData_[0].selected = true;
-    toolbar.deleteSelectedItems();
-    assertEquals(0, testService.getCallCount('removeVisits'));
-  });
-
-  test('remove history menu button disabled', function() {
-    const listContainer = app.$['history'];
-    listContainer.$.sharedMenu.get();
-    assertTrue(
-        listContainer.shadowRoot.querySelector('#menuRemoveButton').hidden);
-  });
-});
diff --git a/chrome/test/data/webui/history/history_supervised_user_test.ts b/chrome/test/data/webui/history/history_supervised_user_test.ts
new file mode 100644
index 0000000..cf5f1db
--- /dev/null
+++ b/chrome/test/data/webui/history/history_supervised_user_test.ts
@@ -0,0 +1,78 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import {BrowserServiceImpl, ensureLazyLoaded, HistoryAppElement, HistoryEntry, HistoryListElement, HistoryToolbarElement} from 'chrome://history/history.js';
+import {assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
+import {eventToPromise, flushTasks} from 'chrome://webui-test/test_util.js';
+
+import {TestBrowserService} from './test_browser_service.js';
+import {createHistoryEntry, createHistoryInfo} from './test_util.js';
+
+suite('history-list supervised-user', function() {
+  let app: HistoryAppElement;
+  let historyList: HistoryListElement;
+  let toolbar: HistoryToolbarElement;
+  let testService: TestBrowserService;
+  const TEST_HISTORY_RESULTS: HistoryEntry[] =
+      [createHistoryEntry('2016-03-15', 'https://www.google.com')];
+
+  setup(function() {
+    document.body.innerHTML = '';
+    testService = new TestBrowserService();
+    BrowserServiceImpl.setInstance(testService);
+
+    testService.setQueryResult({
+      info: createHistoryInfo(),
+      value: TEST_HISTORY_RESULTS,
+    });
+    app = document.createElement('history-app');
+    document.body.appendChild(app);
+
+    historyList = app.$.history;
+    toolbar = app.$.toolbar;
+    return Promise.all([
+      testService.whenCalled('queryHistory'),
+      ensureLazyLoaded(),
+    ]);
+  });
+
+  test('checkboxes disabled for supervised user', function() {
+    return flushTasks().then(function() {
+      const items = historyList.shadowRoot!.querySelectorAll('history-item');
+
+      items[0]!.$.checkbox.click();
+
+      assertFalse(items[0]!.selected);
+    });
+  });
+
+  test('deletion disabled for supervised user', function() {
+    return flushTasks()
+        .then(function() {
+          const whenChecked =
+              eventToPromise('history-checkbox-select', historyList);
+          // Manually dispatch the event since the checkboxes are disabled due
+          // to the test configuration.
+          historyList.shadowRoot!.querySelector('history-item')!.dispatchEvent(
+              new CustomEvent('history-checkbox-select', {
+                bubbles: true,
+                composed: true,
+                detail: {index: 0, shiftKey: false}
+              }));
+          return whenChecked;
+        })
+        .then(() => {
+          toolbar.deleteSelectedItems();
+          // Make sure that removeVisits is not being called.
+          assertEquals(0, testService.getCallCount('removeVisits'));
+        });
+  });
+
+  test('remove history menu button disabled', function() {
+    historyList.$.sharedMenu.get();
+    assertTrue(
+        historyList.shadowRoot!.querySelector<HTMLElement>(
+                                   '#menuRemoveButton')!.hidden);
+  });
+});
diff --git a/chrome/test/data/webui/history/history_synced_tabs_test.js b/chrome/test/data/webui/history/history_synced_tabs_test.ts
similarity index 68%
rename from chrome/test/data/webui/history/history_synced_tabs_test.js
rename to chrome/test/data/webui/history/history_synced_tabs_test.ts
index 4c0748a..cab0207 100644
--- a/chrome/test/data/webui/history/history_synced_tabs_test.js
+++ b/chrome/test/data/webui/history/history_synced_tabs_test.ts
@@ -2,34 +2,39 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import {BrowserServiceImpl, ensureLazyLoaded} from 'chrome://history/history.js';
+import {BrowserServiceImpl, ensureLazyLoaded, ForeignSession, HistorySyncedDeviceCardElement, HistorySyncedDeviceManagerElement} from 'chrome://history/history.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
+import {assertEquals, assertFalse, assertNotEquals, assertTrue} from 'chrome://webui-test/chai_assert.js';
 import {flushTasks, waitBeforeNextRender} from 'chrome://webui-test/test_util.js';
 
 import {TestBrowserService} from './test_browser_service.js';
-import {createSession, createWindow, polymerSelectAll} from './test_util.js';
+import {createSession, createWindow} from './test_util.js';
 
-function getCards(manager) {
-  return polymerSelectAll(manager, 'history-synced-device-card');
+function getCards(manager: HistorySyncedDeviceManagerElement):
+    NodeListOf<HistorySyncedDeviceCardElement> {
+  return manager.shadowRoot!.querySelectorAll('history-synced-device-card');
 }
 
-function numWindowSeparators(card) {
-  return polymerSelectAll(card, ':not([hidden]).window-separator').length;
+function numWindowSeparators(card: HistorySyncedDeviceCardElement): number {
+  return card.shadowRoot!.querySelectorAll(':not([hidden]).window-separator')
+      .length;
 }
 
-function assertNoSyncedTabsMessageShown(manager, stringID) {
+function assertNoSyncedTabsMessageShown(
+    manager: HistorySyncedDeviceManagerElement, stringID: string) {
   assertFalse(manager.$['no-synced-tabs'].hidden);
   const message = loadTimeData.getString(stringID);
-  assertNotEquals(-1, manager.$['no-synced-tabs'].textContent.indexOf(message));
+  assertNotEquals(
+      -1, manager.$['no-synced-tabs'].textContent!.indexOf(message));
 }
 
 suite('<history-synced-device-manager>', function() {
-  let element;
-  let testService;
+  let element: HistorySyncedDeviceManagerElement;
+  let testService: TestBrowserService;
 
-  const setForeignSessions = function(sessions) {
+  function setForeignSessions(sessions: Array<ForeignSession>) {
     element.sessionList = sessions;
-  };
+  }
 
   setup(function() {
     document.body.innerHTML = '';
@@ -45,30 +50,31 @@
       // the same order in tests, in order to catch regressions like
       // https://crbug.com/915641.
       element.searchTerm = '';
-      element.signInState = true;
+      element.configureSignInForTest(
+          {signInState: true, signInAllowed: true, guestSession: false});
       document.body.appendChild(element);
     });
   });
 
   test('single card, single window', function() {
-    const sessionList = [createSession(
+    const sessionList: Array<ForeignSession> = [createSession(
         'Nexus 5',
         [createWindow(['http://www.google.com', 'http://example.com'])])];
     setForeignSessions(sessionList);
 
     return flushTasks().then(function() {
       const card =
-          element.shadowRoot.querySelector('history-synced-device-card');
+          element.shadowRoot!.querySelector('history-synced-device-card')!;
       assertEquals(
           'http://www.google.com',
-          card.shadowRoot.querySelectorAll('.website-title')[0]
-              .textContent.trim());
+          card.shadowRoot!.querySelectorAll<HTMLElement>(
+                              '.website-title')[0]!.textContent!.trim());
       assertEquals(2, card.tabs.length);
     });
   });
 
   test('two cards, multiple windows', function() {
-    const sessionList = [
+    const sessionList: Array<ForeignSession> = [
       createSession(
           'Nexus 5',
           [createWindow(['http://www.google.com', 'http://example.com'])]),
@@ -86,8 +92,8 @@
       assertEquals(2, cards.length);
 
       // Ensure separators between windows are added appropriately.
-      assertEquals(0, numWindowSeparators(cards[0]));
-      assertEquals(1, numWindowSeparators(cards[1]));
+      assertEquals(0, numWindowSeparators(cards[0]!));
+      assertEquals(1, numWindowSeparators(cards[1]!));
     });
   });
 
@@ -120,19 +126,19 @@
           assertEquals(2, cards.length);
 
           // There are now 2 windows in the first device.
-          assertEquals(1, numWindowSeparators(cards[0]));
+          assertEquals(1, numWindowSeparators(cards[0]!));
 
           // Check that the actual link changes.
           assertEquals(
               'http://crbug.com/new',
-              cards[0]
-                  .shadowRoot.querySelectorAll('.website-title')[1]
-                  .textContent.trim());
+              cards[0]!.shadowRoot!
+                  .querySelectorAll<HTMLElement>(
+                      '.website-title')[1]!.textContent!.trim());
         });
   });
 
   test('two cards, multiple windows, search', function() {
-    const sessionList = [
+    const sessionList: Array<ForeignSession> = [
       createSession(
           'Nexus 5',
           [createWindow(['http://www.google.com', 'http://example.com'])]),
@@ -152,8 +158,8 @@
           assertEquals(2, cards.length);
 
           // Ensure separators between windows are added appropriately.
-          assertEquals(0, numWindowSeparators(cards[0]));
-          assertEquals(2, numWindowSeparators(cards[1]));
+          assertEquals(0, numWindowSeparators(cards[0]!));
+          assertEquals(2, numWindowSeparators(cards[1]!));
           element.searchTerm = 'g';
 
           return flushTasks();
@@ -161,21 +167,21 @@
         .then(function() {
           const cards = getCards(element);
 
-          assertEquals(0, numWindowSeparators(cards[0]));
-          assertEquals(1, cards[0].tabs.length);
-          assertEquals('http://www.google.com', cards[0].tabs[0].title);
-          assertEquals(1, numWindowSeparators(cards[1]));
-          assertEquals(3, cards[1].tabs.length);
-          assertEquals('http://www.gmail.com', cards[1].tabs[0].title);
-          assertEquals('http://www.gmail.com', cards[1].tabs[1].title);
-          assertEquals('http://bagssl.com', cards[1].tabs[2].title);
+          assertEquals(0, numWindowSeparators(cards[0]!));
+          assertEquals(1, cards[0]!.tabs.length);
+          assertEquals('http://www.google.com', cards[0]!.tabs[0]!.title);
+          assertEquals(1, numWindowSeparators(cards[1]!));
+          assertEquals(3, cards[1]!.tabs.length);
+          assertEquals('http://www.gmail.com', cards[1]!.tabs[0]!.title);
+          assertEquals('http://www.gmail.com', cards[1]!.tabs[1]!.title);
+          assertEquals('http://bagssl.com', cards[1]!.tabs[2]!.title);
 
           // Ensure the title text is rendered during searches.
           assertEquals(
               'http://www.google.com',
-              cards[0]
-                  .shadowRoot.querySelectorAll('.website-title')[0]
-                  .textContent.trim());
+              cards[0]!.shadowRoot!
+                  .querySelectorAll<HTMLElement>(
+                      '.website-title')[0]!.textContent!.trim());
 
           element.searchTerm = 'Sans';
           return flushTasks();
@@ -188,7 +194,7 @@
   });
 
   test('delete a session', function() {
-    const sessionList = [
+    const sessionList: Array<ForeignSession> = [
       createSession('Nexus 5', [createWindow(['http://www.example.com'])]),
       createSession('Pixel C', [createWindow(['http://www.badssl.com'])]),
     ];
@@ -200,30 +206,31 @@
           const cards = getCards(element);
           assertEquals(2, cards.length);
 
-          cards[0].$['menu-button'].click();
+          cards[0]!.$['menu-button'].click();
           return flushTasks();
         })
         .then(function() {
-          element.shadowRoot.querySelector('#menuDeleteButton').click();
+          element.shadowRoot!.querySelector<HTMLElement>(
+                                 '#menuDeleteButton')!.click();
           return testService.whenCalled('deleteForeignSession');
         })
         .then(args => {
           assertEquals('Nexus 5', args);
 
           // Simulate deleting the first device.
-          setForeignSessions([sessionList[1]]);
+          setForeignSessions([sessionList[1]!]);
 
           return flushTasks();
         })
         .then(function() {
           const cards = getCards(element);
           assertEquals(1, cards.length);
-          assertEquals('http://www.badssl.com', cards[0].tabs[0].title);
+          assertEquals('http://www.badssl.com', cards[0]!.tabs[0]!.title);
         });
   });
 
   test('delete a collapsed session', function() {
-    const sessionList = [
+    const sessionList: Array<ForeignSession> = [
       createSession('Nexus 5', [createWindow(['http://www.example.com'])]),
       createSession('Pixel C', [createWindow(['http://www.badssl.com'])]),
     ];
@@ -232,16 +239,16 @@
     return flushTasks()
         .then(function() {
           const cards = getCards(element);
-          cards[0].$['card-heading'].click();
-          assertFalse(cards[0].opened);
+          cards[0]!.$['card-heading'].click();
+          assertFalse(cards[0]!.opened);
 
           // Simulate deleting the first device.
-          setForeignSessions([sessionList[1]]);
+          setForeignSessions([sessionList[1]!]);
           return flushTasks();
         })
         .then(function() {
           const cards = getCards(element);
-          assertTrue(cards[0].opened);
+          assertTrue(cards[0]!.opened);
         });
   });
 
@@ -251,7 +258,7 @@
     return flushTasks()
         .then(function() {
           const cards = getCards(element);
-          const anchor = cards[0].root.querySelector('a');
+          const anchor = cards[0]!.shadowRoot!.querySelector('a')!;
           anchor.click();
           return testService.whenCalled('openForeignSessionTab');
         })
@@ -272,17 +279,19 @@
 
     return flushTasks().then(function() {
       const cards = getCards(element);
-      cards[0].$['menu-button'].click();
-      assertTrue(element.$.menu.getIfExists().open);
+      cards[0]!.$['menu-button'].click();
+      assertTrue(element.$.menu.getIfExists()!.open);
     });
   });
 
   test('show sign in promo', function() {
-    element.signInState = false;
+    element.configureSignInForTest(
+        {signInState: false, signInAllowed: true, guestSession: false});
     return flushTasks()
         .then(function() {
           assertFalse(element.$['sign-in-guide'].hidden);
-          element.signInState = true;
+          element.configureSignInForTest(
+              {signInState: true, signInAllowed: true, guestSession: false});
           return flushTasks();
         })
         .then(function() {
@@ -292,8 +301,9 @@
 
   test('no synced tabs message', function() {
     // When user is not logged in, there is no synced tabs.
-    element.signInState = false;
-    element.syncedDevices_ = [];
+    element.configureSignInForTest(
+        {signInState: false, signInAllowed: true, guestSession: false});
+    element.clearSyncedDevicesForTest();
     return flushTasks()
         .then(function() {
           assertTrue(element.$['no-synced-tabs'].hidden);
@@ -301,7 +311,8 @@
           const cards = getCards(element);
           assertEquals(0, cards.length);
 
-          element.signInState = true;
+          element.configureSignInForTest(
+              {signInState: true, signInAllowed: true, guestSession: false});
 
           return flushTasks();
         })
@@ -309,7 +320,7 @@
           // When user signs in, first show loading message.
           assertNoSyncedTabsMessageShown(element, 'loading');
 
-          const sessionList = [];
+          const sessionList: Array<ForeignSession> = [];
           setForeignSessions(sessionList);
           return flushTasks();
         })
@@ -319,7 +330,7 @@
           // If no synced tabs are fetched, show 'no synced tabs'.
           assertNoSyncedTabsMessageShown(element, 'noSyncedResults');
 
-          const sessionList = [createSession(
+          const sessionList: Array<ForeignSession> = [createSession(
               'Nexus 5',
               [createWindow(['http://www.google.com', 'http://example.com'])])];
           setForeignSessions(sessionList);
@@ -332,7 +343,8 @@
           // If there are any synced tabs, hide the 'no synced tabs' message.
           assertTrue(element.$['no-synced-tabs'].hidden);
 
-          element.signInState = false;
+          element.configureSignInForTest(
+              {signInState: false, signInAllowed: true, guestSession: false});
           return flushTasks();
         })
         .then(function() {
@@ -342,22 +354,22 @@
   });
 
   test('hide sign in promo in guest mode', function() {
-    element.signInState = false;
-    element.guestSession_ = true;
+    element.configureSignInForTest(
+        {signInState: false, signInAllowed: true, guestSession: true});
     return flushTasks().then(function() {
       assertTrue(element.$['sign-in-guide'].hidden);
     });
   });
 
   test('hide sign-in promo if sign-in is disabled', async function() {
-    element.signInState = false;
-    element.signInAllowed_ = false;
+    element.configureSignInForTest(
+        {signInState: false, signInAllowed: false, guestSession: false});
     await flushTasks();
     assertTrue(element.$['sign-in-guide'].hidden);
   });
 
   test('no synced tabs message displays on load', function() {
-    element.syncedDevices_ = [];
+    element.clearSyncedDevicesForTest();
     // Should show no synced tabs message on initial load. Regression test for
     // https://crbug.com/915641.
     return Promise.all([flushTasks(), waitBeforeNextRender(element)])
diff --git a/chrome/test/data/webui/tab_search/tab_search_app_focus_test.ts b/chrome/test/data/webui/tab_search/tab_search_app_focus_test.ts
index 8a6fc3fa..3de837d 100644
--- a/chrome/test/data/webui/tab_search/tab_search_app_focus_test.ts
+++ b/chrome/test/data/webui/tab_search/tab_search_app_focus_test.ts
@@ -5,11 +5,11 @@
 import {getDeepActiveElement} from 'chrome://resources/js/util.m.js';
 import {keyDownOn} from 'chrome://resources/polymer/v3_0/iron-test-helpers/mock-interactions.js';
 import {InfiniteList, ProfileData, TabSearchApiProxyImpl, TabSearchAppElement, TabSearchItem} from 'chrome://tab-search.top-chrome/tab_search.js';
-import {assertEquals, assertGT, assertNotEquals} from 'chrome://webui-test/chai_assert.js';
+import {assertEquals, assertGT, assertNotEquals, assertTrue} from 'chrome://webui-test/chai_assert.js';
 import {flushTasks, waitAfterNextRender} from 'chrome://webui-test/test_util.js';
 
-import {createProfileData, generateSampleDataFromSiteNames, sampleSiteNames} from './tab_search_test_data.js';
-import {assertTabItemAndNeighborsInViewBounds, disableAnimationBehavior, initLoadTimeDataWithDefaults} from './tab_search_test_helper.js';
+import {createProfileData, generateSampleDataFromSiteNames, generateSampleRecentlyClosedTabs, generateSampleTabsFromSiteNames, sampleSiteNames, sampleToken} from './tab_search_test_data.js';
+import {assertTabItemAndNeighborsInViewBounds, assertTabItemInViewBounds, disableAnimationBehavior, getStylePropertyPixelValue, initLoadTimeDataWithDefaults} from './tab_search_test_helper.js';
 import {TestTabSearchApiProxy} from './test_tab_search_api_proxy.js';
 
 suite('TabSearchAppFocusTest', () => {
@@ -39,6 +39,10 @@
     return tabSearchApp.$.tabsList.querySelectorAll('tab-search-item');
   }
 
+  function queryListTitle(): NodeListOf<HTMLElement> {
+    return tabSearchApp.$.tabsList.querySelectorAll('.list-section-title');
+  }
+
   test('KeyNavigation', async () => {
     await setupTest(createProfileData());
 
@@ -158,4 +162,46 @@
     await flushTasks();
     assertEquals(searchInput, getDeepActiveElement());
   });
+
+  test('Section item visible on recently closed section expand', async () => {
+    // A list height that encompasses two title items, and four open tab items,
+    // thus ensuring that on adding a recently closed item to the list, it will
+    // be outside the visible boundaries.
+    const tabItemHeight =
+        getStylePropertyPixelValue(tabSearchApp, '--mwb-item-height');
+    const titleItemHeight = getStylePropertyPixelValue(
+        tabSearchApp, '--mwb-list-section-title-height');
+    const windowHeight = (titleItemHeight * 2) + (4 * tabItemHeight);
+
+    await setupTest(createProfileData({
+      windows: [{
+        active: true,
+        height: windowHeight,
+        tabs: generateSampleTabsFromSiteNames(sampleSiteNames(4))
+      }],
+      recentlyClosedTabs: generateSampleRecentlyClosedTabs(
+          'Sample Tab', 1, sampleToken(0n, 1n)),
+      recentlyClosedSectionExpanded: false,
+    }));
+
+    const recentlyClosedTitleItem = queryListTitle()[1];
+    assertTrue(!!recentlyClosedTitleItem);
+
+    const recentlyClosedTitleExpandButton =
+        recentlyClosedTitleItem!.querySelector('cr-expand-button');
+    assertTrue(!!recentlyClosedTitleExpandButton);
+
+    // Expand the `Recently Closed` section.
+    recentlyClosedTitleExpandButton!.click();
+
+    await waitAfterNextRender(tabSearchApp);
+    const tabsDiv = tabSearchApp.$.tabsList;
+    // Assert that the tabs are in a overflowing state.
+    assertGT(tabsDiv.scrollHeight, tabsDiv.clientHeight);
+
+    // Assert the first recently closed item is in view bounds.
+    const tabItems =
+        tabSearchApp.$.tabsList.querySelectorAll('tab-search-item');
+    assertTabItemInViewBounds(tabsDiv, tabItems[4]!);
+  });
 });
diff --git a/chrome/test/data/webui/tab_search/tab_search_test_helper.ts b/chrome/test/data/webui/tab_search/tab_search_test_helper.ts
index f650e3d..ffd3448 100644
--- a/chrome/test/data/webui/tab_search/tab_search_test_helper.ts
+++ b/chrome/test/data/webui/tab_search/tab_search_test_helper.ts
@@ -66,3 +66,13 @@
       },
       loadTimeOverriddenData));
 }
+
+/**
+ * Returns a style property number value that needs to be determined from the
+ * computed style of an HTML element.
+ */
+export function getStylePropertyPixelValue(
+    element: HTMLElement, name: string): number {
+  const pxValue = getComputedStyle(element).getPropertyValue(name);
+  return Number.parseInt(pxValue.trim().slice(0, -2), 10);
+}
diff --git a/chrome/utility/BUILD.gn b/chrome/utility/BUILD.gn
index 66972c7..b5d18dd 100644
--- a/chrome/utility/BUILD.gn
+++ b/chrome/utility/BUILD.gn
@@ -134,6 +134,10 @@
     ]
   }
 
+  if (is_linux || is_chromeos) {
+    deps += [ "//components/services/screen_ai" ]
+  }
+
   if (enable_extensions) {
     deps += [
       "//chrome/common/extensions/api",
diff --git a/chrome/utility/DEPS b/chrome/utility/DEPS
index c425b93..0ba4d051 100644
--- a/chrome/utility/DEPS
+++ b/chrome/utility/DEPS
@@ -44,6 +44,7 @@
   "+components/services/patch",
   "+components/services/print_compositor",
   "+components/services/quarantine",
+  "+components/services/screen_ai",
   "+components/services/unzip",
   "+components/webapps/services/web_app_origin_association",
   "+content/public/child",
diff --git a/chrome/utility/services.cc b/chrome/utility/services.cc
index 1cd1291..22828b8 100644
--- a/chrome/utility/services.cc
+++ b/chrome/utility/services.cc
@@ -28,6 +28,10 @@
 #include "mojo/public/cpp/bindings/service_factory.h"
 #include "printing/buildflags/buildflags.h"
 
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
+#include "components/services/screen_ai/screen_ai_service_impl.h"
+#endif
+
 #if BUILDFLAG(IS_WIN)
 #include "chrome/services/util_win/processor_metrics.h"
 #include "chrome/services/util_win/public/mojom/util_read_icon.mojom.h"
@@ -209,6 +213,13 @@
 }
 #endif  // !BUILDFLAG(IS_ANDROID)
 
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
+auto RunScreenAIService(
+    mojo::PendingReceiver<screen_ai::mojom::ScreenAIService> receiver) {
+  return std::make_unique<screen_ai::ScreenAIService>(std::move(receiver));
+}
+#endif
+
 #if BUILDFLAG(ENABLE_PRINTING) && BUILDFLAG(IS_CHROMEOS_ASH)
 auto RunCupsIppParser(
     mojo::PendingReceiver<ipp_parser::mojom::IppParser> receiver) {
@@ -363,6 +374,11 @@
   services.Add(RunSpeechRecognitionService);
 #endif
 
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
+  if (base::FeatureList::IsEnabled(features::kScreenAI))
+    services.Add(RunScreenAIService);
+#endif
+
 #if BUILDFLAG(IS_WIN)
   services.Add(RunProcessorMetrics);
   services.Add(RunQuarantineService);
diff --git a/components/autofill/core/browser/payments/payments_client.cc b/components/autofill/core/browser/payments/payments_client.cc
index ad8da5b..d8735a9 100644
--- a/components/autofill/core/browser/payments/payments_client.cc
+++ b/components/autofill/core/browser/payments/payments_client.cc
@@ -1145,7 +1145,7 @@
         callback) {
   IssueRequest(std::make_unique<GetDetailsForEnrollmentRequest>(
                    request_details, std::move(callback)),
-               /*authenticate=*/false);
+               /*authenticate=*/true);
 }
 
 void PaymentsClient::UpdateVirtualCardEnrollment(
diff --git a/components/autofill/core/browser/payments/payments_client_unittest.cc b/components/autofill/core/browser/payments/payments_client_unittest.cc
index 6ffca778..2816353c 100644
--- a/components/autofill/core/browser/payments/payments_client_unittest.cc
+++ b/components/autofill/core/browser/payments/payments_client_unittest.cc
@@ -1980,7 +1980,7 @@
       request_details,
       base::BindOnce(&PaymentsClientTest::OnDidGetVirtualCardEnrollmentDetails,
                      weak_ptr_factory_.GetWeakPtr()));
-
+  IssueOAuthToken();
   // Ensures the request contains the correct fields.
   EXPECT_TRUE(!GetUploadData().empty());
   EXPECT_TRUE(GetUploadData().find("language_code") != std::string::npos);
diff --git a/components/autofill/core/browser/payments/test_virtual_card_enrollment_manager.h b/components/autofill/core/browser/payments/test_virtual_card_enrollment_manager.h
index 789bef10..652d080 100644
--- a/components/autofill/core/browser/payments/test_virtual_card_enrollment_manager.h
+++ b/components/autofill/core/browser/payments/test_virtual_card_enrollment_manager.h
@@ -52,6 +52,13 @@
     autofill_client_ = autofill_client;
   }
 
+  void SetVirtualCardEnrollmentFieldsLoadedCallback(
+      VirtualCardEnrollmentFieldsLoadedCallback
+          virtual_card_enrollment_fields_loaded_callback) {
+    virtual_card_enrollment_fields_loaded_callback_ =
+        std::move(virtual_card_enrollment_fields_loaded_callback);
+  }
+
   bool AutofillClientIsPresent() { return autofill_client_ != nullptr; }
 
   // VirtualCardEnrollmentManager:
diff --git a/components/autofill/core/browser/payments/virtual_card_enrollment_manager.cc b/components/autofill/core/browser/payments/virtual_card_enrollment_manager.cc
index 45af9762..ec19952 100644
--- a/components/autofill/core/browser/payments/virtual_card_enrollment_manager.cc
+++ b/components/autofill/core/browser/payments/virtual_card_enrollment_manager.cc
@@ -53,12 +53,15 @@
     const CreditCard& credit_card,
     VirtualCardEnrollmentSource virtual_card_enrollment_source,
     const raw_ptr<PrefService> user_prefs,
-    RiskAssessmentFunction risk_assessment_function) {
+    RiskAssessmentFunction risk_assessment_function,
+    VirtualCardEnrollmentFieldsLoadedCallback
+        virtual_card_enrollment_fields_loaded_callback) {
   Reset();
   DCHECK_NE(virtual_card_enrollment_source, VirtualCardEnrollmentSource::kNone);
   state_.virtual_card_enrollment_fields.credit_card = credit_card;
   risk_assessment_function_ = std::move(risk_assessment_function);
-
+  virtual_card_enrollment_fields_loaded_callback_ =
+      std::move(virtual_card_enrollment_fields_loaded_callback);
   // The |card_art_image| might not be synced yet from the sync server which
   // will result in a nullptr. This situation can occur in the upstream flow. If
   // it is not synced, GetCreditCardArtImageForUrl() will send a fetch request
@@ -89,6 +92,28 @@
   }
 }
 
+void VirtualCardEnrollmentManager::Enroll() {
+  LogUpdateVirtualCardEnrollmentRequestAttempt(
+      state_.virtual_card_enrollment_fields.virtual_card_enrollment_source,
+      VirtualCardEnrollmentRequestType::kEnroll);
+  payments::PaymentsClient::UpdateVirtualCardEnrollmentRequestDetails
+      request_details;
+  request_details.virtual_card_enrollment_source =
+      state_.virtual_card_enrollment_fields.virtual_card_enrollment_source;
+  request_details.virtual_card_enrollment_request_type =
+      VirtualCardEnrollmentRequestType::kEnroll;
+  request_details.billing_customer_number =
+      payments::GetBillingCustomerId(personal_data_manager_);
+  request_details.vcn_context_token = state_.vcn_context_token;
+
+  payments_client_->UpdateVirtualCardEnrollment(
+      request_details,
+      base::BindOnce(&VirtualCardEnrollmentManager::
+                         OnDidGetUpdateVirtualCardEnrollmentResponse,
+                     weak_ptr_factory_.GetWeakPtr(),
+                     VirtualCardEnrollmentRequestType::kEnroll));
+}
+
 void VirtualCardEnrollmentManager::Unenroll(int64_t instrument_id) {
   LogUpdateVirtualCardEnrollmentRequestAttempt(
       VirtualCardEnrollmentSource::kSettingsPage,
@@ -271,33 +296,15 @@
   if (autofill_client_) {
     ShowVirtualCardEnrollBubble();
   } else {
-    // TODO(crbug.com/1281695): Run the callback that is passed from the Clank
-    // settings page here to display the Virtual Card Enroll Dialog.
+    // If the `autofill_client_` is not present, it means that the request is
+    // from Android settings page, thus run the callback with the
+    // `virtual_card_enrollment_fields_`, which would show the enrollment
+    // dialog.
+    std::move(virtual_card_enrollment_fields_loaded_callback_)
+        .Run(&state_.virtual_card_enrollment_fields);
   }
 }
 
-void VirtualCardEnrollmentManager::Enroll() {
-  LogUpdateVirtualCardEnrollmentRequestAttempt(
-      state_.virtual_card_enrollment_fields.virtual_card_enrollment_source,
-      VirtualCardEnrollmentRequestType::kEnroll);
-  payments::PaymentsClient::UpdateVirtualCardEnrollmentRequestDetails
-      request_details;
-  request_details.virtual_card_enrollment_source =
-      state_.virtual_card_enrollment_fields.virtual_card_enrollment_source;
-  request_details.virtual_card_enrollment_request_type =
-      VirtualCardEnrollmentRequestType::kEnroll;
-  request_details.billing_customer_number =
-      payments::GetBillingCustomerId(personal_data_manager_);
-  request_details.vcn_context_token = state_.vcn_context_token;
-
-  payments_client_->UpdateVirtualCardEnrollment(
-      request_details,
-      base::BindOnce(&VirtualCardEnrollmentManager::
-                         OnDidGetUpdateVirtualCardEnrollmentResponse,
-                     weak_ptr_factory_.GetWeakPtr(),
-                     VirtualCardEnrollmentRequestType::kEnroll));
-}
-
 void VirtualCardEnrollmentManager::OnVirtualCardEnrollmentBubbleCancelled() {
   Reset();
 }
diff --git a/components/autofill/core/browser/payments/virtual_card_enrollment_manager.h b/components/autofill/core/browser/payments/virtual_card_enrollment_manager.h
index 829104350..7c4635e2 100644
--- a/components/autofill/core/browser/payments/virtual_card_enrollment_manager.h
+++ b/components/autofill/core/browser/payments/virtual_card_enrollment_manager.h
@@ -99,6 +99,9 @@
       const raw_ptr<content::WebContents> web_contents,
       gfx::Rect window_bounds)>;
 
+  using VirtualCardEnrollmentFieldsLoadedCallback = base::OnceCallback<void(
+      VirtualCardEnrollmentFields* virtual_card_enrollment_fields)>;
+
   // Starting point for the VCN enroll flow. The fields in |credit_card| will
   // be used throughout the flow, such as for request fields as well as credit
   // card specific fields for the bubble to display.
@@ -108,22 +111,32 @@
   void OfferVirtualCardEnroll(
       const CreditCard& credit_card,
       VirtualCardEnrollmentSource virtual_card_enrollment_source,
-      // |user_prefs| will be populated if we are in the clank settings page,
+      // |user_prefs| will be populated if we are in the Android settings page,
       // to then be used for loading risk data. Otherwise it will always be
       // nullptr, and we should load risk data through |autofill_client_| as we
       // have access to web contents.
       const raw_ptr<PrefService> user_prefs = nullptr,
-      // Callback that will be run in the Clank settings page use cases. It will
-      // take in a |callback|, |obfuscated_gaia_id|, and |user_prefs| that will
-      // end up being passed into the overloaded risk_util::LoadRiskData() call
-      // that does not require web contents.
-      RiskAssessmentFunction risk_assessment_function = base::DoNothing());
+      // Callback that will be run in the Android settings page use cases. It
+      // will take in a |callback|, |obfuscated_gaia_id|, and |user_prefs| that
+      // will end up being passed into the overloaded risk_util::LoadRiskData()
+      // call that does not require web contents.
+      RiskAssessmentFunction risk_assessment_function = base::DoNothing(),
+      // Callback that be run once the `state_.virtual_card_enrollment_fields_`
+      // is loaded from the server response. The callback would trigger the
+      // enrollment dialog in the Settings page on Android.
+      VirtualCardEnrollmentFieldsLoadedCallback = base::DoNothing());
 
   // Updates |avatar_animation_complete| to true if the user is beginning the
   // upstream enrollment flow. This is a prerequisite to showing the enrollment
   // bubble.
   void OnCardSavedAnimationComplete();
 
+  // Uses |payments_client_| to send the enroll request. |state_|'s
+  // |vcn_context_token_|, which should be set when we receive the
+  // GetDetailsForEnrollResponse, is used in the
+  // UpdateVirtualCardEnrollmentRequest to enroll the correct card.
+  void Enroll();
+
   // Unenrolls the card mapped to the given |instrument_id|.
   void Unenroll(int64_t instrument_id);
 
@@ -191,6 +204,12 @@
   // VirtualCardEnrollmentBubbleController needs to display the correct bubble.
   virtual void ShowVirtualCardEnrollBubble();
 
+  // Callback triggered after the VirtualCardEnrollmentFields are loaded from
+  // the server response. Note: This is only called when the `autofill_client_`
+  // is not available.
+  VirtualCardEnrollmentFieldsLoadedCallback
+      virtual_card_enrollment_fields_loaded_callback_;
+
  private:
   // Called once the risk data is loaded. The |risk_data| will be used with
   // |state_|'s |virtual_card_enrollment_fields|'s |credit_card|'s
@@ -217,12 +236,6 @@
       const payments::PaymentsClient::GetDetailsForEnrollmentResponseDetails&
           response);
 
-  // Uses |payments_client_| to send the enroll request. |state_|'s
-  // |vcn_context_token_|, which should be set when we receive the
-  // GetDetailsForEnrollResponse, is used in the
-  // UpdateVirtualCardEnrollmentRequest to enroll the correct card.
-  void Enroll();
-
   // Cancels the entire Virtual Card Enrollment process.
   void OnVirtualCardEnrollmentBubbleCancelled();
 
diff --git a/components/autofill/core/browser/payments/virtual_card_enrollment_manager_unittest.cc b/components/autofill/core/browser/payments/virtual_card_enrollment_manager_unittest.cc
index 4c84252..cc20c808 100644
--- a/components/autofill/core/browser/payments/virtual_card_enrollment_manager_unittest.cc
+++ b/components/autofill/core/browser/payments/virtual_card_enrollment_manager_unittest.cc
@@ -4,7 +4,9 @@
 
 #include <string>
 
+#include "base/callback.h"
 #include "base/test/metrics/histogram_tester.h"
+#include "base/test/mock_callback.h"
 #include "base/test/task_environment.h"
 #include "components/autofill/core/browser/autofill_test_utils.h"
 #include "components/autofill/core/browser/data_model/credit_card.h"
@@ -22,6 +24,8 @@
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/gfx/image/image_unittest_util.h"
 
+using testing::_;
+
 namespace autofill {
 
 namespace {
@@ -238,7 +242,13 @@
       VirtualCardEnrollmentSource::kSettingsPage;
 
   virtual_card_enrollment_manager_->SetAutofillClient(nullptr);
-
+  base::MockCallback<TestVirtualCardEnrollmentManager::
+                         VirtualCardEnrollmentFieldsLoadedCallback>
+      virtual_card_enrollment_fields_loadaed_callback;
+  virtual_card_enrollment_manager_
+      ->SetVirtualCardEnrollmentFieldsLoadedCallback(
+          virtual_card_enrollment_fields_loadaed_callback.Get());
+  EXPECT_CALL(virtual_card_enrollment_fields_loadaed_callback, Run(_));
   virtual_card_enrollment_manager_->OnDidGetDetailsForEnrollResponse(
       AutofillClient::PaymentsRpcResult::kSuccess, response);
 
diff --git a/components/autofill_assistant/browser/actions/collect_user_data_action.cc b/components/autofill_assistant/browser/actions/collect_user_data_action.cc
index 245aaf8..ca614c8 100644
--- a/components/autofill_assistant/browser/actions/collect_user_data_action.cc
+++ b/components/autofill_assistant/browser/actions/collect_user_data_action.cc
@@ -363,7 +363,7 @@
     UserData* user_data,
     UserModel* user_model,
     const CollectUserDataOptions& options,
-    const CollectUserDataProto::UserDataProto& proto_data) {
+    const GetUserDataResponseProto& proto_data) {
   if (!user_data->selected_phone_number()) {
     return;
   }
@@ -1331,7 +1331,7 @@
 }
 
 void CollectUserDataAction::UpdateUserDataFromProto(
-    const CollectUserDataProto::UserDataProto& proto_data,
+    const GetUserDataResponseProto& proto_data,
     UserData* user_data) {
   DCHECK(user_data != nullptr);
 
diff --git a/components/autofill_assistant/browser/actions/collect_user_data_action.h b/components/autofill_assistant/browser/actions/collect_user_data_action.h
index 5979d90..eddfd0d 100644
--- a/components/autofill_assistant/browser/actions/collect_user_data_action.h
+++ b/components/autofill_assistant/browser/actions/collect_user_data_action.h
@@ -103,9 +103,8 @@
   void WriteProcessedAction(UserData* user_data, const UserModel* user_model);
   void UpdateProfileAndCardUse(UserData* user_data);
 
-  void UpdateUserDataFromProto(
-      const CollectUserDataProto::UserDataProto& proto_data,
-      UserData* user_data);
+  void UpdateUserDataFromProto(const GetUserDataResponseProto& proto_data,
+                               UserData* user_data);
   // Update user data with the new state from personal data manager.
   void UpdatePersonalDataManagerProfiles(
       UserData* user_data,
diff --git a/components/autofill_assistant/browser/protocol_utils.cc b/components/autofill_assistant/browser/protocol_utils.cc
index 3abc98e..c387bc1 100644
--- a/components/autofill_assistant/browser/protocol_utils.cc
+++ b/components/autofill_assistant/browser/protocol_utils.cc
@@ -911,4 +911,20 @@
   }
 }
 
+// static
+std::string ProtocolUtils::CreateGetUserDataRequest(
+    const CollectUserDataOptions& options) {
+  GetUserDataRequestProto request_proto;
+  request_proto.set_request_name(options.request_payer_name);
+  request_proto.set_request_email(options.request_payer_email);
+  request_proto.set_request_phone(options.request_phone_number_separately);
+  request_proto.set_request_addresses(options.request_shipping);
+  request_proto.set_request_payment_methods(options.request_payment_method);
+
+  std::string serialized_request_proto;
+  bool success = request_proto.SerializeToString(&serialized_request_proto);
+  DCHECK(success);
+  return serialized_request_proto;
+}
+
 }  // namespace autofill_assistant
diff --git a/components/autofill_assistant/browser/protocol_utils.h b/components/autofill_assistant/browser/protocol_utils.h
index f0c043d..51d737a 100644
--- a/components/autofill_assistant/browser/protocol_utils.h
+++ b/components/autofill_assistant/browser/protocol_utils.h
@@ -16,6 +16,7 @@
 #include "components/autofill_assistant/browser/script_parameters.h"
 #include "components/autofill_assistant/browser/service.pb.h"
 #include "components/autofill_assistant/browser/trigger_scripts/trigger_script.h"
+#include "components/autofill_assistant/browser/user_data.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
 class GURL;
@@ -72,6 +73,10 @@
       const ClientContextProto& client_context,
       const ScriptParameters& script_parameters);
 
+  // Create request to get user data.
+  static std::string CreateGetUserDataRequest(
+      const CollectUserDataOptions& options);
+
   // Create an action from the |action|.
   static std::unique_ptr<Action> CreateAction(ActionDelegate* delegate,
                                               const ActionProto& action);
diff --git a/components/autofill_assistant/browser/protocol_utils_unittest.cc b/components/autofill_assistant/browser/protocol_utils_unittest.cc
index 4c708edf..c8e75746 100644
--- a/components/autofill_assistant/browser/protocol_utils_unittest.cc
+++ b/components/autofill_assistant/browser/protocol_utils_unittest.cc
@@ -584,4 +584,22 @@
   ASSERT_FALSE(ProtocolUtils::ParseFromString(11, "\xff\xff\xff", nullptr));
 }
 
+TEST_F(ProtocolUtilsTest, CreateGetUserDataRequest) {
+  CollectUserDataOptions options;
+  options.request_payer_name = true;
+  options.request_payer_email = true;
+  options.request_phone_number_separately = true;
+  options.request_shipping = true;
+  options.request_payment_method = true;
+
+  GetUserDataRequestProto request;
+  EXPECT_TRUE(request.ParseFromString(
+      ProtocolUtils::CreateGetUserDataRequest(options)));
+  EXPECT_TRUE(request.request_name());
+  EXPECT_TRUE(request.request_email());
+  EXPECT_TRUE(request.request_phone());
+  EXPECT_TRUE(request.request_addresses());
+  EXPECT_TRUE(request.request_payment_methods());
+}
+
 }  // namespace autofill_assistant
diff --git a/components/autofill_assistant/browser/service.proto b/components/autofill_assistant/browser/service.proto
index 313e612..fafb430 100644
--- a/components/autofill_assistant/browser/service.proto
+++ b/components/autofill_assistant/browser/service.proto
@@ -220,6 +220,56 @@
   repeated MatchInfoProto match_info = 1;
 }
 
+// Request to get user data.
+message GetUserDataRequestProto {
+  // For logging, to know which run this request has originated from.
+  optional uint64 run_id = 1;
+
+  optional bool request_name = 2;
+  optional bool request_email = 3;
+  optional bool request_phone = 4;
+  optional bool request_addresses = 5;
+  optional bool request_payment_methods = 6;
+}
+
+// Response with user data.
+message GetUserDataResponseProto {
+  // The locale to be used when creating this data. This can have influence
+  // on the potential processing by Autofill.
+  optional string locale = 3;
+
+  repeated ProfileProto available_contacts = 1;
+  // The identifier of the contact to select. If not specified, the client
+  // will use its own heuristics to select a default contact.
+  optional string selected_contact_identifier = 5;
+
+  // Available phone numbers to select.
+  repeated PhoneNumberProto available_phone_numbers = 10;
+  // The identifier of the phone number to select. If not specified,
+  // the client will use its own heuristics to select a default payment
+  // instrument.
+  optional string selected_phone_number_identifier = 11;
+
+  // Available (shipping) addresses to select.
+  repeated ProfileProto available_addresses = 4;
+  // The identifier of the shipping address to select. If not specified, the
+  // client will use its own heuristics to select a default shipping address.
+  optional string selected_shipping_address_identifier = 6;
+  // An opaque token to launch the GMS address editor for creating a new
+  // address.
+  optional bytes add_address_token = 8;
+
+  // Available payment instruments to select.
+  repeated PaymentInstrumentProto available_payment_instruments = 2;
+  // The identifier of the payment instrument to select. If not specified,
+  // the client will use its own heuristics to select a default payment
+  // instrument.
+  optional string selected_payment_instrument_identifier = 7;
+  // An opaque token returned by the |GetInstrumentListResponse| to launch
+  // the GMS card editor to create a new credit card.
+  optional bytes add_payment_instrument_token = 9;
+}
+
 // Overlay image to be drawn on top of full overlays.
 message OverlayImageProto {
   // TODO(b/170202574): Remove legacy |image_url|.
@@ -2594,38 +2644,9 @@
   repeated RequiredDataPiece required_credit_card_data_piece = 35;
   repeated RequiredDataPiece required_billing_address_data_piece = 36;
 
-  message UserDataProto {
-    // Available contacts to select.
-    repeated ProfileProto available_contacts = 1;
-    // The identifier of the contact to select. If not specified, the client
-    // will use its own heuristics to select a default contact.
-    optional string selected_contact_identifier = 5;
-
-    // Available (shipping) addresses to select.
-    repeated ProfileProto available_addresses = 4;
-    // The identifier of the shipping address to select. If not specified, the
-    // client will use its own heuristics to select a default shipping address.
-    optional string selected_shipping_address_identifier = 6;
-
-    // Available payment instruments to select.
-    repeated PaymentInstrumentProto available_payment_instruments = 2;
-    // The identifier of the payment instrument to select. If not specified,
-    // the client will use its own heuristics to select a default payment
-    // instrument.
-    optional string selected_payment_instrument_identifier = 7;
-
-    // Available phone numbers to select.
-    repeated PhoneNumberProto available_phone_numbers = 10;
-    // The identifier of the phone number to select. If not specified,
-    // the client will use its own heuristics to select a default payment
-    // instrument.
-    optional string selected_phone_number_identifier = 11;
-
-    // The locale to be used when creating this data. This can have influence
-    // on the potential processing by Autofill.
-    optional string locale = 3;
-  }
-  optional UserDataProto user_data = 37;
+  // TODO(b/218838411): This way of getting data is about to be deprecated. It
+  // is left in place until the backend has been adapted. Reserve after.
+  optional GetUserDataResponseProto user_data = 37;
 
   reserved 7, 10, 14, 15, 17, 26;
 }
diff --git a/components/autofill_assistant/browser/service/java_service.cc b/components/autofill_assistant/browser/service/java_service.cc
index 11376a8e..650b72ce 100644
--- a/components/autofill_assistant/browser/service/java_service.cc
+++ b/components/autofill_assistant/browser/service/java_service.cc
@@ -87,4 +87,13 @@
   std::move(callback).Run(net::HTTP_OK, response);
 }
 
+void JavaService::GetUserData(const CollectUserDataOptions& options,
+                              ResponseCallback callback) {
+  // TODO(b/218838411): Mock.
+  GetUserDataResponseProto response;
+  std::string serialized_response;
+  response.SerializeToString(&serialized_response);
+  std::move(callback).Run(net::HTTP_OK, serialized_response);
+}
+
 }  // namespace autofill_assistant
diff --git a/components/autofill_assistant/browser/service/java_service.h b/components/autofill_assistant/browser/service/java_service.h
index eb1b4b0c..1d7e101 100644
--- a/components/autofill_assistant/browser/service/java_service.h
+++ b/components/autofill_assistant/browser/service/java_service.h
@@ -12,6 +12,7 @@
 #include "base/android/scoped_java_ref.h"
 #include "base/memory/weak_ptr.h"
 #include "components/autofill_assistant/browser/service/service.h"
+#include "components/autofill_assistant/browser/user_data.h"
 #include "url/gurl.h"
 
 namespace autofill_assistant {
@@ -54,6 +55,10 @@
       const RoundtripTimingStats& timing_stats,
       ResponseCallback callback) override;
 
+  // Get user data.
+  void GetUserData(const CollectUserDataOptions& options,
+                   ResponseCallback callback) override;
+
  private:
   base::android::ScopedJavaGlobalRef<jobject> java_service_;
 };
diff --git a/components/autofill_assistant/browser/service/java_test_endpoint_service.cc b/components/autofill_assistant/browser/service/java_test_endpoint_service.cc
index e0ad586..d0aab73 100644
--- a/components/autofill_assistant/browser/service/java_test_endpoint_service.cc
+++ b/components/autofill_assistant/browser/service/java_test_endpoint_service.cc
@@ -118,4 +118,9 @@
                                 timing_stats, std::move(callback));
 }
 
+void JavaTestEndpointService::GetUserData(const CollectUserDataOptions& options,
+                                          ResponseCallback callback) {
+  service_impl_->GetUserData(options, std::move(callback));
+}
+
 }  // namespace autofill_assistant
diff --git a/components/autofill_assistant/browser/service/java_test_endpoint_service.h b/components/autofill_assistant/browser/service/java_test_endpoint_service.h
index b6bf24d..e29fdedd 100644
--- a/components/autofill_assistant/browser/service/java_test_endpoint_service.h
+++ b/components/autofill_assistant/browser/service/java_test_endpoint_service.h
@@ -9,6 +9,7 @@
 
 #include "base/memory/weak_ptr.h"
 #include "components/autofill_assistant/browser/service/service.h"
+#include "components/autofill_assistant/browser/user_data.h"
 
 namespace autofill_assistant {
 
@@ -44,6 +45,9 @@
       const RoundtripTimingStats& timing_stats,
       ResponseCallback callback) override;
 
+  void GetUserData(const CollectUserDataOptions& options,
+                   ResponseCallback callback) override;
+
  private:
   void OnGetScriptsForUrl(ResponseCallback callback,
                           int http_status,
diff --git a/components/autofill_assistant/browser/service/mock_service.cc b/components/autofill_assistant/browser/service/mock_service.cc
index a0bb4ba3..92da091 100644
--- a/components/autofill_assistant/browser/service/mock_service.cc
+++ b/components/autofill_assistant/browser/service/mock_service.cc
@@ -15,6 +15,7 @@
                   /* request_sender = */ nullptr,
                   /* script_server_url = */ GURL("http://fake"),
                   /* action_server_url = */ GURL("http://fake"),
+                  /* user_data_url = */ GURL("http://fake"),
                   /* client_context = */ nullptr) {}
 MockService::~MockService() {}
 
diff --git a/components/autofill_assistant/browser/service/rpc_type.h b/components/autofill_assistant/browser/service/rpc_type.h
index 17ad3fd..d23428a 100644
--- a/components/autofill_assistant/browser/service/rpc_type.h
+++ b/components/autofill_assistant/browser/service/rpc_type.h
@@ -13,6 +13,7 @@
   GET_TRIGGER_SCRIPTS,
   SUPPORTS_SCRIPT,
   GET_CAPABILITIES_BY_HASH_PREFIX,
+  GET_USER_DATA,
 };
 }
 
diff --git a/components/autofill_assistant/browser/service/server_url_fetcher.cc b/components/autofill_assistant/browser/service/server_url_fetcher.cc
index 0793a4ff..703be675a 100644
--- a/components/autofill_assistant/browser/service/server_url_fetcher.cc
+++ b/components/autofill_assistant/browser/service/server_url_fetcher.cc
@@ -17,6 +17,7 @@
 const char kActionEndpoint[] = "/v1/actions2";
 const char kTriggersEndpoint[] = "/v1/triggers";
 const char kCapabilitiesByHashEndpoint[] = "/v1/capabilitiesByHashPrefix2";
+const char kUserDataEndpoint[] = "/v1/userData";
 }  // namespace
 
 namespace autofill_assistant {
@@ -59,9 +60,15 @@
 }
 
 GURL ServerUrlFetcher::GetCapabilitiesByHashEndpoint() const {
-  GURL::Replacements trigger_replacements;
-  trigger_replacements.SetPathStr(kCapabilitiesByHashEndpoint);
-  return server_url_.ReplaceComponents(trigger_replacements);
+  GURL::Replacements capabilities_replacements;
+  capabilities_replacements.SetPathStr(kCapabilitiesByHashEndpoint);
+  return server_url_.ReplaceComponents(capabilities_replacements);
+}
+
+GURL ServerUrlFetcher::GetUserDataEndpoint() const {
+  GURL::Replacements user_data_replacements;
+  user_data_replacements.SetPathStr(kUserDataEndpoint);
+  return server_url_.ReplaceComponents(user_data_replacements);
 }
 
 }  // namespace autofill_assistant
diff --git a/components/autofill_assistant/browser/service/server_url_fetcher.h b/components/autofill_assistant/browser/service/server_url_fetcher.h
index b0aee70..5af784cd 100644
--- a/components/autofill_assistant/browser/service/server_url_fetcher.h
+++ b/components/autofill_assistant/browser/service/server_url_fetcher.h
@@ -29,6 +29,8 @@
   virtual GURL GetTriggerScriptsEndpoint() const;
   // Returns the endpoint to send the GetCapabilitiesByHashPrefix RPC to.
   virtual GURL GetCapabilitiesByHashEndpoint() const;
+  // Returns the endpoint to send the GetUserData RPC to.
+  virtual GURL GetUserDataEndpoint() const;
 
  private:
   GURL server_url_;
diff --git a/components/autofill_assistant/browser/service/server_url_fetcher_unittest.cc b/components/autofill_assistant/browser/service/server_url_fetcher_unittest.cc
index ce78aeda..9443fe0 100644
--- a/components/autofill_assistant/browser/service/server_url_fetcher_unittest.cc
+++ b/components/autofill_assistant/browser/service/server_url_fetcher_unittest.cc
@@ -60,5 +60,11 @@
               Eq(GURL("https://www.example.com/v1/capabilitiesByHashPrefix2")));
 }
 
+TEST(ServerUrlFetcherTest, GetUserDataEndpoint) {
+  EXPECT_THAT(
+      ServerUrlFetcher(GURL("https://www.example.com")).GetUserDataEndpoint(),
+      Eq(GURL("https://www.example.com/v1/userData")));
+}
+
 }  // namespace
 }  // namespace autofill_assistant
diff --git a/components/autofill_assistant/browser/service/service.h b/components/autofill_assistant/browser/service/service.h
index 13773272..e3421214 100644
--- a/components/autofill_assistant/browser/service/service.h
+++ b/components/autofill_assistant/browser/service/service.h
@@ -10,6 +10,7 @@
 
 #include "base/callback.h"
 #include "components/autofill_assistant/browser/service.pb.h"
+#include "components/autofill_assistant/browser/user_data.h"
 #include "url/gurl.h"
 
 namespace autofill_assistant {
@@ -50,6 +51,10 @@
   virtual void SetScriptStoreConfig(
       const ScriptStoreConfig& script_store_config) {}
 
+  // Get user data.
+  virtual void GetUserData(const CollectUserDataOptions& options,
+                           ResponseCallback callback) = 0;
+
  protected:
   Service() = default;
 };
diff --git a/components/autofill_assistant/browser/service/service_impl.cc b/components/autofill_assistant/browser/service/service_impl.cc
index 633a953..11337e82 100644
--- a/components/autofill_assistant/browser/service/service_impl.cc
+++ b/components/autofill_assistant/browser/service/service_impl.cc
@@ -56,7 +56,7 @@
   return std::make_unique<ServiceImpl>(
       client, std::move(request_sender),
       url_fetcher.GetSupportsScriptEndpoint(),
-      url_fetcher.GetNextActionsEndpoint(),
+      url_fetcher.GetNextActionsEndpoint(), url_fetcher.GetUserDataEndpoint(),
       std::make_unique<ClientContextImpl>(client));
 }
 
@@ -64,14 +64,17 @@
                          std::unique_ptr<ServiceRequestSender> request_sender,
                          const GURL& script_server_url,
                          const GURL& action_server_url,
+                         const GURL& user_data_url,
                          std::unique_ptr<ClientContext> client_context)
     : client_(client),
       request_sender_(std::move(request_sender)),
       script_server_url_(script_server_url),
       script_action_server_url_(action_server_url),
+      user_data_url_(user_data_url),
       client_context_(std::move(client_context)) {
   DCHECK(script_server_url.is_valid());
   DCHECK(action_server_url.is_valid());
+  DCHECK(user_data_url_.is_valid());
 }
 
 ServiceImpl::~ServiceImpl() {}
@@ -160,4 +163,11 @@
       std::move(callback), RpcType::GET_ACTIONS);
 }
 
+void ServiceImpl::GetUserData(const CollectUserDataOptions& options,
+                              ResponseCallback callback) {
+  request_sender_->SendRequest(user_data_url_,
+                               ProtocolUtils::CreateGetUserDataRequest(options),
+                               std::move(callback), RpcType::GET_USER_DATA);
+}
+
 }  // namespace autofill_assistant
diff --git a/components/autofill_assistant/browser/service/service_impl.h b/components/autofill_assistant/browser/service/service_impl.h
index 69a2c5b1..dd7116b 100644
--- a/components/autofill_assistant/browser/service/service_impl.h
+++ b/components/autofill_assistant/browser/service/service_impl.h
@@ -51,6 +51,7 @@
               std::unique_ptr<ServiceRequestSender> request_sender,
               const GURL& script_server_url,
               const GURL& action_server_url,
+              const GURL& user_data_url,
               std::unique_ptr<ClientContext> client_context);
   ServiceImpl(const ServiceImpl&) = delete;
   ServiceImpl& operator=(const ServiceImpl&) = delete;
@@ -82,6 +83,9 @@
   void SetScriptStoreConfig(
       const ScriptStoreConfig& script_store_config) override;
 
+  void GetUserData(const CollectUserDataOptions& options,
+                   ResponseCallback callback) override;
+
  private:
   void OnFetchPaymentsClientToken(
       const std::string& script_path,
@@ -106,6 +110,7 @@
   // The RPC endpoints to send requests to.
   GURL script_server_url_;
   GURL script_action_server_url_;
+  GURL user_data_url_;
 
   // The client context to send to the backend.
   std::unique_ptr<ClientContext> client_context_;
diff --git a/components/autofill_assistant/browser/service/service_impl_unittest.cc b/components/autofill_assistant/browser/service/service_impl_unittest.cc
index 314d6ce..b21bf64 100644
--- a/components/autofill_assistant/browser/service/service_impl_unittest.cc
+++ b/components/autofill_assistant/browser/service/service_impl_unittest.cc
@@ -29,6 +29,8 @@
 
 const char kScriptServerUrl[] = "https://www.fake.backend.com/script_server";
 const char kActionServerUrl[] = "https://www.fake.backend.com/action_server";
+const char kUserDataServerUrl[] =
+    "https://www.fake.backend.com/user_data_server";
 
 // TODO(b/207744539): In all tests, check that protocol utils is called with
 // the correct parameters.
@@ -44,7 +46,8 @@
 
     service_ = std::make_unique<ServiceImpl>(
         &mock_client_, std::move(mock_request_sender), GURL(kScriptServerUrl),
-        GURL(kActionServerUrl), std::move(mock_client_context));
+        GURL(kActionServerUrl), GURL(kUserDataServerUrl),
+        std::move(mock_client_context));
   }
   ~ServiceImplTest() override = default;
 
@@ -178,5 +181,16 @@
       mock_response_callback_.Get());
 }
 
+TEST_F(ServiceImplTest, GetUserData) {
+  EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kUserDataServerUrl), _,
+                                                   _, RpcType::GET_USER_DATA))
+      .WillOnce(RunOnceCallback<2>(net::HTTP_OK, std::string("response")));
+  EXPECT_CALL(mock_response_callback_,
+              Run(net::HTTP_OK, std::string("response")));
+
+  service_->GetUserData(CollectUserDataOptions(),
+                        mock_response_callback_.Get());
+}
+
 }  // namespace
 }  // namespace autofill_assistant
diff --git a/components/privacy_sandbox/privacy_sandbox_settings.cc b/components/privacy_sandbox/privacy_sandbox_settings.cc
index 4fce80b0..958d4c7 100644
--- a/components/privacy_sandbox/privacy_sandbox_settings.cc
+++ b/components/privacy_sandbox/privacy_sandbox_settings.cc
@@ -20,6 +20,8 @@
 #include "url/gurl.h"
 #include "url/origin.h"
 
+namespace privacy_sandbox {
+
 namespace {
 
 bool IsCookiesClearOnExitEnabled(HostContentSettingsMap* map) {
@@ -333,6 +335,10 @@
   return IsPrivacySandboxEnabled();
 }
 
+bool PrivacySandboxSettings::IsPrivacySandboxRestricted() {
+  return delegate_->IsPrivacySandboxRestricted();
+}
+
 void PrivacySandboxSettings::OnCookiesCleared() {
   SetFlocDataAccessibleFromNow(/*reset_calculate_timer=*/false);
 }
@@ -369,3 +375,5 @@
       cookie_settings, url,
       top_frame_origin ? top_frame_origin->GetURL() : GURL());
 }
+
+}  // namespace privacy_sandbox
diff --git a/components/privacy_sandbox/privacy_sandbox_settings.h b/components/privacy_sandbox/privacy_sandbox_settings.h
index 4d5e893..a0174ad 100644
--- a/components/privacy_sandbox/privacy_sandbox_settings.h
+++ b/components/privacy_sandbox/privacy_sandbox_settings.h
@@ -25,6 +25,8 @@
 class Origin;
 }
 
+namespace privacy_sandbox {
+
 // A service which acts as a intermediary between Privacy Sandbox APIs and the
 // preferences and content settings which define when they are allowed to be
 // accessed. Privacy Sandbox APIs, regardless of where they live (renderer,
@@ -146,6 +148,10 @@
   // appropriate context specific check.
   bool IsTrustTokensAllowed();
 
+  // Returns whether the Privacy Sandbox is being restricted by the associated
+  // delegate. Forwards directly to the corresponding delegate function.
+  bool IsPrivacySandboxRestricted();
+
   // Called when there's a broad cookies clearing action. For example, this
   // should be called on "Clear browsing data", but shouldn't be called on the
   // Clear-Site-Data header, as it's restricted to a specific site.
@@ -179,4 +185,6 @@
   bool incognito_profile_;
 };
 
+}  // namespace privacy_sandbox
+
 #endif  // COMPONENTS_PRIVACY_SANDBOX_PRIVACY_SANDBOX_SETTINGS_H_
diff --git a/components/privacy_sandbox/privacy_sandbox_settings_unittest.cc b/components/privacy_sandbox/privacy_sandbox_settings_unittest.cc
index d88ea9f..f191a53d 100644
--- a/components/privacy_sandbox/privacy_sandbox_settings_unittest.cc
+++ b/components/privacy_sandbox/privacy_sandbox_settings_unittest.cc
@@ -20,6 +20,8 @@
 #include "testing/gtest/include/gtest/gtest.h"
 #include "url/origin.h"
 
+namespace privacy_sandbox {
+
 class MockPrivacySandboxDelegate : public PrivacySandboxSettings::Delegate {
  public:
   void SetupDefaultResponse() {
@@ -833,3 +835,5 @@
 INSTANTIATE_TEST_SUITE_P(PrivacySandboxSettingsMockDelegateTestInstance,
                          PrivacySandboxSettingsMockDelegateTest,
                          testing::Bool());
+
+}  // namespace privacy_sandbox
diff --git a/components/privacy_sandbox/privacy_sandbox_test_util.h b/components/privacy_sandbox/privacy_sandbox_test_util.h
index 1b5c692..6c32f8c 100644
--- a/components/privacy_sandbox/privacy_sandbox_test_util.h
+++ b/components/privacy_sandbox/privacy_sandbox_test_util.h
@@ -19,7 +19,8 @@
 
 namespace privacy_sandbox_test_util {
 
-class MockPrivacySandboxObserver : public PrivacySandboxSettings::Observer {
+class MockPrivacySandboxObserver
+    : public privacy_sandbox::PrivacySandboxSettings::Observer {
  public:
   MockPrivacySandboxObserver();
   ~MockPrivacySandboxObserver();
diff --git a/components/safe_browsing/content/browser/safe_browsing_navigation_observer.cc b/components/safe_browsing/content/browser/safe_browsing_navigation_observer.cc
index 82c56c2..f01b6ca 100644
--- a/components/safe_browsing/content/browser/safe_browsing_navigation_observer.cc
+++ b/components/safe_browsing/content/browser/safe_browsing_navigation_observer.cc
@@ -326,13 +326,13 @@
 void SafeBrowsingNavigationObserver::SetNavigationSourceMainFrameUrl(
     content::NavigationHandle* navigation_handle,
     NavigationEvent* nav_event) {
-  if (navigation_handle->IsInMainFrame()) {
+  if (!navigation_handle->GetParentFrameOrOuterDocument()) {
     nav_event->source_main_frame_url = nav_event->source_url;
   } else {
     nav_event->source_main_frame_url =
         SafeBrowsingNavigationObserverManager::ClearURLRef(
-            navigation_handle->GetParentFrame()
-                ->GetMainFrame()
+            navigation_handle->GetParentFrameOrOuterDocument()
+                ->GetOutermostMainFrame()
                 ->GetLastCommittedURL());
   }
 }
diff --git a/components/safe_browsing/content/browser/safe_browsing_navigation_observer_manager.cc b/components/safe_browsing/content/browser/safe_browsing_navigation_observer_manager.cc
index 07fc208..f864e1ad 100644
--- a/components/safe_browsing/content/browser/safe_browsing_navigation_observer_manager.cc
+++ b/components/safe_browsing/content/browser/safe_browsing_navigation_observer_manager.cc
@@ -626,7 +626,8 @@
         source_render_frame_host->GetLastCommittedURL());
     nav_event->source_main_frame_url =
         SafeBrowsingNavigationObserverManager::ClearURLRef(
-            source_render_frame_host->GetMainFrame()->GetLastCommittedURL());
+            source_render_frame_host->GetOutermostMainFrame()
+                ->GetLastCommittedURL());
   }
   nav_event->source_tab_id =
       sessions::SessionTabHelper::IdForTab(source_web_contents);
diff --git a/components/safe_browsing/content/browser/threat_details.cc b/components/safe_browsing/content/browser/threat_details.cc
index 3444ebd2..e252991 100644
--- a/components/safe_browsing/content/browser/threat_details.cc
+++ b/components/safe_browsing/content/browser/threat_details.cc
@@ -401,11 +401,11 @@
       did_proceed_(false),
       num_visits_(0),
       trim_to_ad_tags_(trim_to_ad_tags),
-      cache_collector_(new ThreatDetailsCacheCollector),
+      cache_collector_(std::make_unique<ThreatDetailsCacheCollector>()),
       done_callback_(std::move(done_callback)),
       all_done_expected_(false),
       is_all_done_(false) {
-  redirects_collector_ = new ThreatDetailsRedirectsCollector(
+  redirects_collector_ = std::make_unique<ThreatDetailsRedirectsCollector>(
       history_service ? history_service->AsWeakPtr()
                       : base::WeakPtr<history::HistoryService>());
 }
@@ -420,9 +420,7 @@
       all_done_expected_(false),
       is_all_done_(false) {}
 
-ThreatDetails::~ThreatDetails() {
-  DCHECK_EQ(all_done_expected_, is_all_done_);
-}
+ThreatDetails::~ThreatDetails() = default;
 
 bool ThreatDetails::IsReportableUrl(const GURL& url) const {
   // TODO(panayiotis): also skip internal urls.
diff --git a/components/safe_browsing/content/browser/threat_details.h b/components/safe_browsing/content/browser/threat_details.h
index f1f9b25..29932811d 100644
--- a/components/safe_browsing/content/browser/threat_details.h
+++ b/components/safe_browsing/content/browser/threat_details.h
@@ -109,7 +109,8 @@
 
   void OnCacheCollectionReady();
 
-  void OnRedirectionCollectionReady();
+  // Overridden during tests
+  virtual void OnRedirectionCollectionReady();
 
   // WebContentsObserver implementation:
   void RenderFrameDeleted(content::RenderFrameHost* render_frame_host) override;
@@ -262,10 +263,10 @@
   static ThreatDetailsFactory* factory_;
 
   // Used to collect details from the HTTP Cache.
-  scoped_refptr<ThreatDetailsCacheCollector> cache_collector_;
+  std::unique_ptr<ThreatDetailsCacheCollector> cache_collector_;
 
   // Used to collect redirect urls from the history service
-  scoped_refptr<ThreatDetailsRedirectsCollector> redirects_collector_;
+  std::unique_ptr<ThreatDetailsRedirectsCollector> redirects_collector_;
 
   // Callback to run when the report is finished.
   ThreatDetailsDoneCallback done_callback_;
@@ -294,6 +295,7 @@
   FRIEND_TEST_ALL_PREFIXES(ThreatDetailsTest, ThreatDOMDetails_MultipleFrames);
   FRIEND_TEST_ALL_PREFIXES(ThreatDetailsTest, ThreatDOMDetails_TrimToAdTags);
   FRIEND_TEST_ALL_PREFIXES(ThreatDetailsTest, ThreatDOMDetails);
+  FRIEND_TEST_ALL_PREFIXES(ThreatDetailsTest, CanCancelDuringCollection);
 };
 
 // Factory for creating ThreatDetails.  Useful for tests.
diff --git a/components/safe_browsing/content/browser/threat_details_cache.cc b/components/safe_browsing/content/browser/threat_details_cache.cc
index 8219b0be..1f0a5b14 100644
--- a/components/safe_browsing/content/browser/threat_details_cache.cc
+++ b/components/safe_browsing/content/browser/threat_details_cache.cc
@@ -54,7 +54,8 @@
   // Post a task in the message loop, so the callers don't need to
   // check if we call their callback immediately.
   content::GetUIThreadTaskRunner({})->PostTask(
-      FROM_HERE, base::BindOnce(&ThreatDetailsCacheCollector::OpenEntry, this));
+      FROM_HERE, base::BindOnce(&ThreatDetailsCacheCollector::OpenEntry,
+                                weak_factory_.GetWeakPtr()));
 }
 
 bool ThreatDetailsCacheCollector::HasStarted() {
@@ -120,6 +121,7 @@
   current_load_->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
       url_loader_factory_.get(),
       base::BindOnce(&ThreatDetailsCacheCollector::OnURLLoaderComplete,
+                     // This is safe because `current_load_` is owned by `this`.
                      base::Unretained(this)));
 }
 
@@ -229,7 +231,8 @@
 
   // Create a task so we don't take over the UI thread for too long.
   content::GetUIThreadTaskRunner({})->PostTask(
-      FROM_HERE, base::BindOnce(&ThreatDetailsCacheCollector::OpenEntry, this));
+      FROM_HERE, base::BindOnce(&ThreatDetailsCacheCollector::OpenEntry,
+                                weak_factory_.GetWeakPtr()));
 }
 
 void ThreatDetailsCacheCollector::AllDone(bool success) {
diff --git a/components/safe_browsing/content/browser/threat_details_cache.h b/components/safe_browsing/content/browser/threat_details_cache.h
index 51cb5c7..b2ea679 100644
--- a/components/safe_browsing/content/browser/threat_details_cache.h
+++ b/components/safe_browsing/content/browser/threat_details_cache.h
@@ -31,10 +31,10 @@
     std::unique_ptr<ClientSafeBrowsingReportRequest::Resource>>
     ResourceMap;
 
-class ThreatDetailsCacheCollector
-    : public base::RefCounted<ThreatDetailsCacheCollector> {
+class ThreatDetailsCacheCollector {
  public:
   ThreatDetailsCacheCollector();
+  ~ThreatDetailsCacheCollector();
 
   // We use |request_context_getter|, we modify |resources| and
   // |result|, and we call |callback|, so they must all remain alive
@@ -55,8 +55,6 @@
  private:
   friend class base::RefCounted<ThreatDetailsCacheCollector>;
 
-  ~ThreatDetailsCacheCollector();
-
   // Points to the url for which we are fetching the HTTP cache entry or
   // redirect chain.
   ResourceMap::iterator resources_it_;
@@ -80,6 +78,8 @@
   // The current SimpleURLLoader.
   std::unique_ptr<network::SimpleURLLoader> current_load_;
 
+  base::WeakPtrFactory<ThreatDetailsCacheCollector> weak_factory_{this};
+
   // Returns the resource from resources_ that corresponds to |url|
   ClientSafeBrowsingReportRequest::Resource* GetResource(const GURL& url);
 
diff --git a/components/safe_browsing/content/browser/threat_details_history.cc b/components/safe_browsing/content/browser/threat_details_history.cc
index 3a36327..c71d155 100644
--- a/components/safe_browsing/content/browser/threat_details_history.cc
+++ b/components/safe_browsing/content/browser/threat_details_history.cc
@@ -42,8 +42,8 @@
 
   content::GetUIThreadTaskRunner({})->PostTask(
       FROM_HERE,
-      base::BindOnce(&ThreatDetailsRedirectsCollector::StartGetRedirects, this,
-                     urls));
+      base::BindOnce(&ThreatDetailsRedirectsCollector::StartGetRedirects,
+                     weak_factory_.GetWeakPtr(), urls));
 }
 
 bool ThreatDetailsRedirectsCollector::HasStarted() const {
@@ -79,7 +79,7 @@
   history_service_->QueryRedirectsTo(
       url,
       base::BindOnce(&ThreatDetailsRedirectsCollector::OnGotQueryRedirectsTo,
-                     base::Unretained(this), url),
+                     weak_factory_.GetWeakPtr(), url),
       &request_tracker_);
 }
 
diff --git a/components/safe_browsing/content/browser/threat_details_history.h b/components/safe_browsing/content/browser/threat_details_history.h
index bddeee8..670d1b5 100644
--- a/components/safe_browsing/content/browser/threat_details_history.h
+++ b/components/safe_browsing/content/browser/threat_details_history.h
@@ -22,12 +22,11 @@
 
 typedef std::vector<GURL> RedirectChain;
 
-class ThreatDetailsRedirectsCollector
-    : public base::RefCounted<ThreatDetailsRedirectsCollector>,
-      public history::HistoryServiceObserver {
+class ThreatDetailsRedirectsCollector : public history::HistoryServiceObserver {
  public:
   explicit ThreatDetailsRedirectsCollector(
       const base::WeakPtr<history::HistoryService>& history_service);
+  ~ThreatDetailsRedirectsCollector() override;
 
   ThreatDetailsRedirectsCollector(const ThreatDetailsRedirectsCollector&) =
       delete;
@@ -52,8 +51,6 @@
  private:
   friend class base::RefCounted<ThreatDetailsRedirectsCollector>;
 
-  ~ThreatDetailsRedirectsCollector() override;
-
   void StartGetRedirects(const std::vector<GURL>& urls);
   void GetRedirects(const GURL& url);
   void OnGotQueryRedirectsTo(const GURL& url,
@@ -82,6 +79,8 @@
   base::ScopedObservation<history::HistoryService,
                           history::HistoryServiceObserver>
       history_service_observation_{this};
+
+  base::WeakPtrFactory<ThreatDetailsRedirectsCollector> weak_factory_{this};
 };
 
 }  // namespace safe_browsing
diff --git a/components/security_interstitials/BUILD.gn b/components/security_interstitials/BUILD.gn
new file mode 100644
index 0000000..05c1389
--- /dev/null
+++ b/components/security_interstitials/BUILD.gn
@@ -0,0 +1,14 @@
+# Copyright 2022 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//third_party/closure_compiler/compile_js.gni")
+
+assert(enable_js_type_check)
+
+group("closure_compile") {
+  deps = [
+    "content/resources:closure_compile",
+    "core/common/resources:closure_compile",
+  ]
+}
diff --git a/components/security_interstitials/content/resources/BUILD.gn b/components/security_interstitials/content/resources/BUILD.gn
new file mode 100644
index 0000000..a812d35
--- /dev/null
+++ b/components/security_interstitials/content/resources/BUILD.gn
@@ -0,0 +1,18 @@
+# Copyright 2022 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//third_party/closure_compiler/compile_js.gni")
+
+assert(enable_js_type_check)
+
+js_type_check("closure_compile") {
+  deps = [ ":connection_help" ]
+}
+
+js_library("connection_help") {
+  deps = [
+    "//ui/webui/resources/js:load_time_data.m",
+    "//ui/webui/resources/js:util.m",
+  ]
+}
diff --git a/components/security_interstitials/content/resources/connection_help.html b/components/security_interstitials/content/resources/connection_help.html
index e431a7c6..5bb01aa1 100644
--- a/components/security_interstitials/content/resources/connection_help.html
+++ b/components/security_interstitials/content/resources/connection_help.html
@@ -8,12 +8,7 @@
     <link rel="stylesheet" href="interstitial_core.css">
     <link rel="stylesheet" href="interstitial_common.css">
     <link rel="stylesheet" href="connection_help.css">
-    <script src="chrome://resources/js/cr.js"></script>
-    <script src="chrome://resources/js/load_time_data.js"></script>
-    <script src="chrome://resources/js/assert.js"></script>
-    <script src="chrome://resources/js/util.js"></script>
-    <script src="strings.js"></script>
-    <script src="connection_help.js"></script>
+    <script type="module" src="connection_help.js"></script>
   </head>
   <body>
     <div class="interstitial-wrapper">
diff --git a/components/security_interstitials/content/resources/connection_help.js b/components/security_interstitials/content/resources/connection_help.js
index fe42ea6a..8a6eba1 100644
--- a/components/security_interstitials/content/resources/connection_help.js
+++ b/components/security_interstitials/content/resources/connection_help.js
@@ -2,6 +2,11 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import './strings.m.js';
+
+import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
+import {$} from 'chrome://resources/js/util.m.js';
+
 const HIDDEN_CLASS = 'hidden';
 
 function setupEvents() {
diff --git a/components/services/screen_ai/BUILD.gn b/components/services/screen_ai/BUILD.gn
new file mode 100644
index 0000000..b5fb37e
--- /dev/null
+++ b/components/services/screen_ai/BUILD.gn
@@ -0,0 +1,30 @@
+# Copyright 2022 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+source_set("screen_ai") {
+  sources = [
+    "screen_ai_service_impl.cc",
+    "screen_ai_service_impl.h",
+  ]
+
+  public_deps = [
+    "//base",
+    "//components/services/screen_ai/public/mojom",
+    "//mojo/public/cpp/bindings",
+  ]
+}
+
+source_set("screen_ai_sandbox_hook") {
+  sources = [
+    "sandbox/screen_ai_sandbox_hook_linux.cc",
+    "sandbox/screen_ai_sandbox_hook_linux.h",
+  ]
+
+  deps = [
+    "//base",
+    "//sandbox/linux:sandbox_services",
+  ]
+
+  public_deps = [ "//sandbox/policy" ]
+}
diff --git a/components/services/screen_ai/OWNERS b/components/services/screen_ai/OWNERS
new file mode 100644
index 0000000..976b955
--- /dev/null
+++ b/components/services/screen_ai/OWNERS
@@ -0,0 +1 @@
+file://ui/accessibility/OWNERS
diff --git a/components/services/screen_ai/README.md b/components/services/screen_ai/README.md
new file mode 100644
index 0000000..a6ed810
--- /dev/null
+++ b/components/services/screen_ai/README.md
@@ -0,0 +1,2 @@
+ScreenAIService provides an API to extract metadata from snapshots using a local
+machine intelligence based module.
\ No newline at end of file
diff --git a/components/services/screen_ai/public/cpp/BUILD.gn b/components/services/screen_ai/public/cpp/BUILD.gn
new file mode 100644
index 0000000..fc9084b
--- /dev/null
+++ b/components/services/screen_ai/public/cpp/BUILD.gn
@@ -0,0 +1,19 @@
+# Copyright 2022 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+source_set("screen_ai_service_router_factory") {
+  sources = [
+    "screen_ai_service_router.cc",
+    "screen_ai_service_router.h",
+    "screen_ai_service_router_factory.cc",
+    "screen_ai_service_router_factory.h",
+  ]
+
+  deps = [
+    "//components/keyed_service/content",
+    "//components/keyed_service/core",
+    "//components/services/screen_ai/public/mojom",
+    "//content/public/browser",
+  ]
+}
diff --git a/components/services/screen_ai/public/cpp/DEPS b/components/services/screen_ai/public/cpp/DEPS
new file mode 100644
index 0000000..794f22c
--- /dev/null
+++ b/components/services/screen_ai/public/cpp/DEPS
@@ -0,0 +1,5 @@
+include_rules = [
+  "+content/public/browser",
+  "+components/keyed_service/core",
+  "+components/keyed_service/content"
+]
diff --git a/components/services/screen_ai/public/cpp/screen_ai_service_router.cc b/components/services/screen_ai/public/cpp/screen_ai_service_router.cc
new file mode 100644
index 0000000..7fa986b3
--- /dev/null
+++ b/components/services/screen_ai/public/cpp/screen_ai_service_router.cc
@@ -0,0 +1,33 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/services/screen_ai/public/cpp/screen_ai_service_router.h"
+
+#include "content/public/browser/service_process_host.h"
+
+namespace screen_ai {
+
+ScreenAIServiceRouter::ScreenAIServiceRouter() = default;
+ScreenAIServiceRouter::~ScreenAIServiceRouter() = default;
+
+void ScreenAIServiceRouter::BindScreenAIAnnotator(
+    mojo::PendingReceiver<screen_ai::mojom::ScreenAIAnnotator> receiver) {
+  LaunchIfNotRunning();
+
+  if (screen_ai_service_.is_bound())
+    screen_ai_service_->BindAnnotator(std::move(receiver));
+}
+
+void ScreenAIServiceRouter::LaunchIfNotRunning() {
+  if (screen_ai_service_.is_bound())
+    return;
+
+  content::ServiceProcessHost::Launch(
+      screen_ai_service_.BindNewPipeAndPassReceiver(),
+      content::ServiceProcessHost::Options()
+          .WithDisplayName("Screen AI Service")
+          .Pass());
+}
+
+}  // namespace screen_ai
diff --git a/components/services/screen_ai/public/cpp/screen_ai_service_router.h b/components/services/screen_ai/public/cpp/screen_ai_service_router.h
new file mode 100644
index 0000000..fa2ce427d
--- /dev/null
+++ b/components/services/screen_ai/public/cpp/screen_ai_service_router.h
@@ -0,0 +1,33 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_SERVICES_SCREEN_AI_PUBLIC_CPP_SCREEN_AI_SERVICE_ROUTER_H_
+#define COMPONENTS_SERVICES_SCREEN_AI_PUBLIC_CPP_SCREEN_AI_SERVICE_ROUTER_H_
+
+#include "components/keyed_service/core/keyed_service.h"
+#include "components/services/screen_ai/public/mojom/screen_ai_service.mojom.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/remote.h"
+
+namespace screen_ai {
+
+class ScreenAIServiceRouter : public KeyedService {
+ public:
+  ScreenAIServiceRouter();
+  ScreenAIServiceRouter(const ScreenAIServiceRouter&) = delete;
+  ScreenAIServiceRouter& operator=(const ScreenAIServiceRouter&) = delete;
+  ~ScreenAIServiceRouter() override;
+
+  void BindScreenAIAnnotator(
+      mojo::PendingReceiver<screen_ai::mojom::ScreenAIAnnotator> receiver);
+
+  void LaunchIfNotRunning();
+
+ private:
+  mojo::Remote<mojom::ScreenAIService> screen_ai_service_;
+};
+
+}  // namespace screen_ai
+
+#endif  // COMPONENTS_SERVICES_SCREEN_AI_PUBLIC_CPP_SCREEN_AI_SERVICE_ROUTER_H_
diff --git a/components/services/screen_ai/public/cpp/screen_ai_service_router_factory.cc b/components/services/screen_ai/public/cpp/screen_ai_service_router_factory.cc
new file mode 100644
index 0000000..cb51e699
--- /dev/null
+++ b/components/services/screen_ai/public/cpp/screen_ai_service_router_factory.cc
@@ -0,0 +1,41 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/services/screen_ai/public/cpp/screen_ai_service_router_factory.h"
+
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
+#include "components/services/screen_ai/public/cpp/screen_ai_service_router.h"
+#include "content/public/browser/browser_context.h"
+
+// static
+screen_ai::ScreenAIServiceRouter*
+ScreenAIServiceRouterFactory::GetForBrowserContext(
+    content::BrowserContext* context) {
+  return static_cast<screen_ai::ScreenAIServiceRouter*>(
+      GetInstance()->GetServiceForBrowserContext(context, true));
+}
+
+// static
+ScreenAIServiceRouterFactory* ScreenAIServiceRouterFactory::GetInstance() {
+  static base::NoDestructor<ScreenAIServiceRouterFactory> instance;
+  return instance.get();
+}
+
+ScreenAIServiceRouterFactory::ScreenAIServiceRouterFactory()
+    : BrowserContextKeyedServiceFactory(
+          "ScreenAIService",
+          BrowserContextDependencyManager::GetInstance()) {}
+
+ScreenAIServiceRouterFactory::~ScreenAIServiceRouterFactory() = default;
+
+KeyedService* ScreenAIServiceRouterFactory::BuildServiceInstanceFor(
+    content::BrowserContext* /*context*/) const {
+  return new screen_ai::ScreenAIServiceRouter();
+}
+
+// Incognito profiles should use their own instance.
+content::BrowserContext* ScreenAIServiceRouterFactory::GetBrowserContextToUse(
+    content::BrowserContext* context) const {
+  return context;
+}
diff --git a/components/services/screen_ai/public/cpp/screen_ai_service_router_factory.h b/components/services/screen_ai/public/cpp/screen_ai_service_router_factory.h
new file mode 100644
index 0000000..e6a1d65
--- /dev/null
+++ b/components/services/screen_ai/public/cpp/screen_ai_service_router_factory.h
@@ -0,0 +1,39 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_SERVICES_SCREEN_AI_PUBLIC_CPP_SCREEN_AI_SERVICE_ROUTER_FACTORY_H_
+#define COMPONENTS_SERVICES_SCREEN_AI_PUBLIC_CPP_SCREEN_AI_SERVICE_ROUTER_FACTORY_H_
+
+#include "base/no_destructor.h"
+#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
+
+namespace content {
+class BrowserContext;
+}
+
+namespace screen_ai {
+class ScreenAIServiceRouter;
+}
+
+// Factory to get or create an instance of ScreenAIServiceRouter for a Profile.
+class ScreenAIServiceRouterFactory : public BrowserContextKeyedServiceFactory {
+ public:
+  static screen_ai::ScreenAIServiceRouter* GetForBrowserContext(
+      content::BrowserContext* context);
+
+ private:
+  friend class base::NoDestructor<ScreenAIServiceRouterFactory>;
+  static ScreenAIServiceRouterFactory* GetInstance();
+
+  ScreenAIServiceRouterFactory();
+  ~ScreenAIServiceRouterFactory() override;
+
+  // BrowserContextKeyedServiceFactory:
+  KeyedService* BuildServiceInstanceFor(
+      content::BrowserContext* context) const override;
+  content::BrowserContext* GetBrowserContextToUse(
+      content::BrowserContext* context) const override;
+};
+
+#endif  // COMPONENTS_SERVICES_SCREEN_AI_PUBLIC_CPP_SCREEN_AI_SERVICE_ROUTER_FACTORY_H_
diff --git a/components/services/screen_ai/public/mojom/BUILD.gn b/components/services/screen_ai/public/mojom/BUILD.gn
new file mode 100644
index 0000000..38b74f03
--- /dev/null
+++ b/components/services/screen_ai/public/mojom/BUILD.gn
@@ -0,0 +1,14 @@
+# Copyright 2022 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//mojo/public/tools/bindings/mojom.gni")
+
+mojom("mojom") {
+  sources = [ "screen_ai_service.mojom" ]
+
+  public_deps = [
+    "//media/mojo/mojom",
+    "//sandbox/policy/mojom",
+  ]
+}
diff --git a/components/services/screen_ai/public/mojom/OWNERS b/components/services/screen_ai/public/mojom/OWNERS
new file mode 100644
index 0000000..61b5e28
--- /dev/null
+++ b/components/services/screen_ai/public/mojom/OWNERS
@@ -0,0 +1,2 @@
+per-file *.mojom=set noparent
+per-file *.mojom=file://ipc/SECURITY_OWNERS
\ No newline at end of file
diff --git a/components/services/screen_ai/public/mojom/screen_ai_service.mojom b/components/services/screen_ai/public/mojom/screen_ai_service.mojom
new file mode 100644
index 0000000..8c5c846
--- /dev/null
+++ b/components/services/screen_ai/public/mojom/screen_ai_service.mojom
@@ -0,0 +1,86 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+module screen_ai.mojom;
+
+import "sandbox/policy/mojom/sandbox.mojom";
+import "skia/public/mojom/bitmap.mojom";
+import "ui/gfx/geometry/mojom/geometry.mojom";
+
+// The type of object that the Screen AI module has detected.
+// This list is in sync with the following proto:
+// google3/research/socrates/multimodal/visual_annotation/config/
+// labelmap_layout_extractor_v1.pbtxt
+enum NodeType {
+  kImage = 1,
+  kPictogram = 2,
+  kButton = 3,
+  kText = 4,
+  kTextInput = 5,
+  kLabel = 6,
+  kMap = 7,
+  kCheckBox = 8,
+  kSwitch = 9,
+  kPageIndicator = 10,
+  kRadioButton = 11,
+  kSlider = 12,
+  kSpinner = 13,
+  kProgressBar = 14,
+  kAdvertisement = 15,
+  kDrawer = 16,
+  kNavigationBar = 17,
+  kToolbar = 18,
+  kListItem = 19,
+  kCardView = 20,
+  kContainer = 21,
+  kDatePicker = 22,
+  kNumberStepper = 23,
+  kKeyboard = 24
+};
+
+// An object detected by the Screen AI library.
+struct Node {
+  // Rectangle covering the detected object, with respect to the top-left of
+  // the image.
+  gfx.mojom.Rect rect;
+
+  // Detected type of the object.
+  NodeType type;
+
+  // Confidence level of detection, [0..1].
+  float confidence;
+};
+
+// Indicates the result of image processing service.
+enum ErrorType {
+  // No error.
+  kOK = 0,
+
+  // Could not find the library.
+  kFailedLibraryNotFound = 1,
+
+  // Library could not process image.
+  kFailedProcessingImage = 2,
+};
+
+// Main interface a client uses for Screen AI services. Each renderer has its
+// own ScreenAIAnnotator and all ScreenAIAnnotators of one profile use one
+// ScreenAIService.
+// Requests are sent from renderers in AxScreenAIAnnotator class.
+interface ScreenAIAnnotator {
+  // Receives a snapshot, schedules image processing, and returns the error
+  // type (if any) and an array of detected nodes.
+  Annotate(skia.mojom.BitmapN32 image) =>
+    (ErrorType error, array<Node> nodes);
+};
+
+
+// The service runs in a sandboxed process to run Screen AI service library. The
+// library provides an image processing module to analyze snapshots of the
+// browser and add more details to the accessibility tree.
+[ServiceSandbox=sandbox.mojom.Sandbox.kScreenAI]
+interface ScreenAIService {
+  // Binds a new annotator to the service.
+  BindAnnotator(pending_receiver<ScreenAIAnnotator> annotator);
+};
diff --git a/components/services/screen_ai/sandbox/DEPS b/components/services/screen_ai/sandbox/DEPS
new file mode 100644
index 0000000..e194a2d
--- /dev/null
+++ b/components/services/screen_ai/sandbox/DEPS
@@ -0,0 +1 @@
+include_rules = [ "+sandbox/linux", "+sandbox/policy/linux" ]
diff --git a/components/services/screen_ai/sandbox/OWNERS b/components/services/screen_ai/sandbox/OWNERS
new file mode 100644
index 0000000..56bafd3e
--- /dev/null
+++ b/components/services/screen_ai/sandbox/OWNERS
@@ -0,0 +1,2 @@
+set noparent
+file://sandbox/linux/OWNERS
\ No newline at end of file
diff --git a/components/services/screen_ai/sandbox/screen_ai_sandbox_hook_linux.cc b/components/services/screen_ai/sandbox/screen_ai_sandbox_hook_linux.cc
new file mode 100644
index 0000000..8bd1ee6
--- /dev/null
+++ b/components/services/screen_ai/sandbox/screen_ai_sandbox_hook_linux.cc
@@ -0,0 +1,48 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/services/screen_ai/sandbox/screen_ai_sandbox_hook_linux.h"
+
+#include <dlfcn.h>
+
+#include "base/files/file_util.h"
+#include "sandbox/linux/syscall_broker/broker_command.h"
+#include "sandbox/linux/syscall_broker/broker_file_permission.h"
+
+using sandbox::syscall_broker::BrokerFilePermission;
+using sandbox::syscall_broker::MakeBrokerCommandSet;
+
+namespace screen_ai {
+
+bool ScreenAIPreSandboxHook(sandbox::policy::SandboxLinux::Options options) {
+  // TODO(https://crbug.com/1278249): Add a common getter function for the
+  // library file path.
+  const base::FilePath library_path =
+      base::FilePath(FILE_PATH_LITERAL("/"))
+          .Append(FILE_PATH_LITERAL("lib"))
+          .Append(FILE_PATH_LITERAL("libchrome_screen_ai.so"));
+
+  void* screen_ai_library = dlopen(library_path.value().c_str(),
+                                   RTLD_LAZY | RTLD_GLOBAL | RTLD_NODELETE);
+  // TODO(https://crbug.com/1278249): Consider handling differently when library
+  // is downloaded using component updater and feature is enabled by default.
+  if (!screen_ai_library)
+    VLOG(1) << dlerror();
+
+  auto* instance = sandbox::policy::SandboxLinux::GetInstance();
+
+  std::vector<BrokerFilePermission> permissions{
+      BrokerFilePermission::ReadOnly("/dev/urandom"),
+      BrokerFilePermission::ReadOnly("/proc/meminfo")};
+
+  instance->StartBrokerProcess(
+      MakeBrokerCommandSet({sandbox::syscall_broker::COMMAND_ACCESS,
+                            sandbox::syscall_broker::COMMAND_OPEN}),
+      permissions, sandbox::policy::SandboxLinux::PreSandboxHook(), options);
+  instance->EngageNamespaceSandboxIfPossible();
+
+  return true;
+}
+
+}  // namespace screen_ai
diff --git a/components/services/screen_ai/sandbox/screen_ai_sandbox_hook_linux.h b/components/services/screen_ai/sandbox/screen_ai_sandbox_hook_linux.h
new file mode 100644
index 0000000..fbe695a
--- /dev/null
+++ b/components/services/screen_ai/sandbox/screen_ai_sandbox_hook_linux.h
@@ -0,0 +1,18 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_SERVICES_SCREEN_AI_SANDBOX_SCREEN_AI_SANDBOX_HOOK_LINUX_H_
+#define COMPONENTS_SERVICES_SCREEN_AI_SANDBOX_SCREEN_AI_SANDBOX_HOOK_LINUX_H_
+
+#include "sandbox/policy/linux/sandbox_linux.h"
+
+namespace screen_ai {
+
+// Opens the chrome_screen_ai.lib binary and grants broker file permissions to
+// the necessary files required by the binary.
+bool ScreenAIPreSandboxHook(sandbox::policy::SandboxLinux::Options options);
+
+}  // namespace screen_ai
+
+#endif  // COMPONENTS_SERVICES_SCREEN_AI_SANDBOX_SCREEN_AI_SANDBOX_HOOK_LINUX_H_
diff --git a/components/services/screen_ai/screen_ai_service_impl.cc b/components/services/screen_ai/screen_ai_service_impl.cc
new file mode 100644
index 0000000..fc105c1
--- /dev/null
+++ b/components/services/screen_ai/screen_ai_service_impl.cc
@@ -0,0 +1,73 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/services/screen_ai/screen_ai_service_impl.h"
+
+namespace screen_ai {
+
+// static
+base::FilePath ScreenAIService::GetLibraryPath() {
+  return base::FilePath(FILE_PATH_LITERAL("/"))
+      .Append(FILE_PATH_LITERAL("lib"))
+      .Append(FILE_PATH_LITERAL("libchrome_screen_ai.so"));
+}
+
+ScreenAIService::ScreenAIService(
+    mojo::PendingReceiver<mojom::ScreenAIService> receiver)
+    : library_(GetLibraryPath()),
+      init_function_(reinterpret_cast<ScreenAIInitFunction>(
+          library_.GetFunctionPointer("Init"))),
+      annotator_function_(reinterpret_cast<ScreenAIAnnotateFunction>(
+          library_.GetFunctionPointer("Annotate"))),
+      receiver_(this, std::move(receiver)) {
+  if (!init_function_ || !init_function_()) {
+    VLOG(1) << "Screen AI library initialization failed.";
+    annotator_function_ = nullptr;
+  }
+}
+
+ScreenAIService::~ScreenAIService() = default;
+
+void ScreenAIService::BindAnnotator(
+    mojo::PendingReceiver<mojom::ScreenAIAnnotator> annotator) {
+  screen_ai_annotators_.Add(this, std::move(annotator));
+}
+
+void ScreenAIService::Annotate(const SkBitmap& image,
+                               AnnotationCallback callback) {
+  std::vector<mojom::NodePtr> nodes;
+  mojom::ErrorType error = mojom::ErrorType::kOK;
+
+  if (annotator_function_) {
+    VLOG(2) << "Screen AI library starting to process " << image.width() << "x"
+            << image.height() << " snapshot.";
+
+    std::string annotation_text;
+    // TODO(https://crbug.com/1278249): Consider adding a signature that
+    // verifies the data integrity and source.
+    // TODO(https://crbug.com/1278249): Consider replacing the input with a data
+    // item that includes data size.
+    if (annotator_function_(
+            static_cast<const unsigned char*>(image.getPixels()), image.width(),
+            image.height(), annotation_text)) {
+      VLOG(2) << "Screen AI library returned: " << annotation_text;
+      DecodeProto(annotation_text, nodes);
+    } else {
+      VLOG(1) << "Screen AI library could not process snapshot.";
+      error = mojom::ErrorType::kFailedProcessingImage;
+    }
+  } else {
+    error = mojom::ErrorType::kFailedLibraryNotFound;
+  }
+
+  std::move(callback).Run(error, std::move(nodes));
+}
+
+// TODO(https://crbug.com/1278249): Implement!
+bool ScreenAIService::DecodeProto(const std::string& proto_text,
+                                  std::vector<mojom::NodePtr>& annotations) {
+  return false;
+}
+
+}  // namespace screen_ai
diff --git a/components/services/screen_ai/screen_ai_service_impl.h b/components/services/screen_ai/screen_ai_service_impl.h
new file mode 100644
index 0000000..382ff3d
--- /dev/null
+++ b/components/services/screen_ai/screen_ai_service_impl.h
@@ -0,0 +1,67 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_SERVICES_SCREEN_AI_SCREEN_AI_SERVICE_IMPL_H_
+#define COMPONENTS_SERVICES_SCREEN_AI_SCREEN_AI_SERVICE_IMPL_H_
+
+#include "base/callback.h"
+#include "base/files/file_path.h"
+#include "base/scoped_native_library.h"
+#include "components/services/screen_ai/public/mojom/screen_ai_service.mojom.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+#include "mojo/public/cpp/bindings/receiver_set.h"
+
+namespace screen_ai {
+
+using AnnotationCallback =
+    base::OnceCallback<void(mojom::ErrorType, std::vector<mojom::NodePtr>)>;
+
+// Sends the snapshot to a local machine learning library to get annotations
+// that can help in updating the accessibility tree. See more in:
+// google3/chrome/chromeos/accessibility/machine_intelligence/
+// chrome_screen_ai/README.md
+class ScreenAIService : public mojom::ScreenAIService,
+                        public mojom::ScreenAIAnnotator {
+ public:
+  explicit ScreenAIService(
+      mojo::PendingReceiver<mojom::ScreenAIService> receiver);
+  ScreenAIService(const ScreenAIService&) = delete;
+  ScreenAIService& operator=(const ScreenAIService&) = delete;
+  ~ScreenAIService() override;
+
+  static base::FilePath GetLibraryPath();
+
+ private:
+  base::ScopedNativeLibrary library_;
+
+  // mojom::ScreenAIAnnotator
+  void Annotate(const SkBitmap& image, AnnotationCallback callback) override;
+
+  // mojom::ScreenAIService
+  void BindAnnotator(
+      mojo::PendingReceiver<mojom::ScreenAIAnnotator> annotator) override;
+
+  // Translates proto output form screen-ai library to vector of
+  // screen_ai::mojom::Node.
+  bool DecodeProto(const std::string& proto_text,
+                   std::vector<mojom::NodePtr>& annotations);
+
+  typedef bool (*ScreenAIInitFunction)();
+  ScreenAIInitFunction init_function_;
+
+  typedef bool (*ScreenAIAnnotateFunction)(const unsigned char* /*png_pixels*/,
+                                           int /*image_width*/,
+                                           int /*image_height*/,
+                                           std::string& /*annotation_text*/);
+  ScreenAIAnnotateFunction annotator_function_;
+
+  mojo::Receiver<mojom::ScreenAIService> receiver_;
+
+  // The set of receivers used to receive messages from the renderer clients.
+  mojo::ReceiverSet<mojom::ScreenAIAnnotator> screen_ai_annotators_;
+};
+
+}  // namespace screen_ai
+
+#endif  // COMPONENTS_SERVICES_SCREEN_AI_SCREEN_AI_SERVICE_IMPL_H_
diff --git a/components/signin/internal/identity_manager/oauth_multilogin_token_fetcher.cc b/components/signin/internal/identity_manager/oauth_multilogin_token_fetcher.cc
index 7ee1cec..310f0a3 100644
--- a/components/signin/internal/identity_manager/oauth_multilogin_token_fetcher.cc
+++ b/components/signin/internal/identity_manager/oauth_multilogin_token_fetcher.cc
@@ -103,8 +103,6 @@
   if (error.IsTransientError() &&
       retried_requests_.find(account_id) == retried_requests_.end()) {
     retried_requests_.insert(account_id);
-    UMA_HISTOGRAM_ENUMERATION("Signin.GetAccessTokenRetry", error.state(),
-                              GoogleServiceAuthError::NUM_STATES);
     EraseRequest(request);
     // Fetching fresh access tokens requires network.
     signin_client_->DelayNetworkCall(
diff --git a/components/sync/model/processor_entity.cc b/components/sync/model/processor_entity.cc
index 1d0bb3a..9bcc573 100644
--- a/components/sync/model/processor_entity.cc
+++ b/components/sync/model/processor_entity.cc
@@ -90,12 +90,6 @@
   data->creation_time = ProtoTimeToTime(metadata_.creation_time());
   data->modification_time = ProtoTimeToTime(metadata_.modification_time());
 
-  commit_data_.reset();
-  CacheCommitData(std::move(data));
-}
-
-void ProcessorEntity::CacheCommitData(std::unique_ptr<EntityData> data) {
-  DCHECK(RequiresCommitData());
   commit_data_ = std::move(data);
   DCHECK(HasCommitData());
 }
diff --git a/components/sync/model/processor_entity.h b/components/sync/model/processor_entity.h
index 3de0375..94952ad 100644
--- a/components/sync/model/processor_entity.h
+++ b/components/sync/model/processor_entity.h
@@ -122,8 +122,6 @@
   // and caches it in the instance.
   void SetCommitData(std::unique_ptr<EntityData> data);
 
-  void CacheCommitData(std::unique_ptr<EntityData> data);
-
   // Check if the instance has cached commit data.
   bool HasCommitData() const;
 
diff --git a/content/browser/utility_process_sandbox_browsertest.cc b/content/browser/utility_process_sandbox_browsertest.cc
index 59166a6..6107192 100644
--- a/content/browser/utility_process_sandbox_browsertest.cc
+++ b/content/browser/utility_process_sandbox_browsertest.cc
@@ -141,6 +141,11 @@
         EXPECT_EQ(sandbox_status, kExpectedPartialSandboxFlags);
         break;
       }
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
+      case Sandbox::kScreenAI:
+        // TODO(https://crbug.com/1278249): Add test.
+        break;
+#endif
 
       case Sandbox::kGpu:
       case Sandbox::kRenderer:
diff --git a/content/browser/utility_sandbox_delegate.cc b/content/browser/utility_sandbox_delegate.cc
index 7c0e15ae..d5703cf 100644
--- a/content/browser/utility_sandbox_delegate.cc
+++ b/content/browser/utility_sandbox_delegate.cc
@@ -73,6 +73,9 @@
       sandbox_type_ == sandbox::mojom::Sandbox::kLibassistant ||
 #endif  // BUILDFLAG(ENABLE_CROS_LIBASSISTANT)
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
+      sandbox_type_ == sandbox::mojom::Sandbox::kScreenAI ||
+#endif
       sandbox_type_ == sandbox::mojom::Sandbox::kAudio ||
       sandbox_type_ == sandbox::mojom::Sandbox::kSpeechRecognition;
   DCHECK(supported_sandbox_type);
@@ -115,6 +118,9 @@
 #if BUILDFLAG(ENABLE_OOP_PRINTING)
       sandbox_type_ == sandbox::mojom::Sandbox::kPrintBackend ||
 #endif
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
+      sandbox_type_ == sandbox::mojom::Sandbox::kScreenAI ||
+#endif
       sandbox_type_ == sandbox::mojom::Sandbox::kSpeechRecognition) {
     return GetUnsandboxedZygote();
   }
diff --git a/content/browser/xr/service/browser_xr_runtime_impl.h b/content/browser/xr/service/browser_xr_runtime_impl.h
index 5982ad2..69a44f0 100644
--- a/content/browser/xr/service/browser_xr_runtime_impl.h
+++ b/content/browser/xr/service/browser_xr_runtime_impl.h
@@ -121,7 +121,7 @@
   mojo::Remote<device::mojom::XRRuntime> runtime_;
   mojo::Remote<device::mojom::XRSessionController>
       immersive_session_controller_;
-  bool immersive_session_has_camera_access_;
+  bool immersive_session_has_camera_access_ = false;
 
   std::set<VRServiceImpl*> services_;
   device::mojom::VRDisplayInfoPtr display_info_;
diff --git a/content/renderer/BUILD.gn b/content/renderer/BUILD.gn
index 52babf62..f20d944 100644
--- a/content/renderer/BUILD.gn
+++ b/content/renderer/BUILD.gn
@@ -203,7 +203,11 @@
   }
 
   if (is_linux || is_chromeos) {
-    sources += [ "renderer_main_platform_delegate_linux.cc" ]
+    sources += [
+      "accessibility/ax_screen_ai_annotator.cc",
+      "accessibility/ax_screen_ai_annotator.h",
+      "renderer_main_platform_delegate_linux.cc",
+    ]
   }
 
   if (is_mac) {
@@ -345,7 +349,11 @@
   }
 
   if (is_linux || is_chromeos) {
-    deps += [ "//components/services/font/public/cpp" ]
+    deps += [
+      "//components/services/font/public/cpp",
+      "//components/services/screen_ai",
+      "//ui/gfx/codec",
+    ]
   }
 
   if (is_fuchsia) {
diff --git a/content/renderer/DEPS b/content/renderer/DEPS
index 87e50ab..798358f 100644
--- a/content/renderer/DEPS
+++ b/content/renderer/DEPS
@@ -7,6 +7,7 @@
   "+components/discardable_memory/client",
   "+components/metrics",
   "+components/metrics:single_sample_metrics",
+  "+components/services/screen_ai",
   "+components/url_formatter",
   "+components/viz/client",
   "+components/viz/common",
diff --git a/content/renderer/accessibility/ax_screen_ai_annotator.cc b/content/renderer/accessibility/ax_screen_ai_annotator.cc
new file mode 100644
index 0000000..c4b7c824
--- /dev/null
+++ b/content/renderer/accessibility/ax_screen_ai_annotator.cc
@@ -0,0 +1,108 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "content/renderer/accessibility/ax_screen_ai_annotator.h"
+
+#include "net/base/data_url.h"
+#include "third_party/blink/public/platform/web_string.h"
+#include "third_party/blink/public/web/web_ax_object.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "ui/accessibility/ax_node_data.h"
+#include "ui/gfx/codec/png_codec.h"
+#include "ui/gfx/geometry/size.h"
+
+namespace {
+bool GetImageFromWebAXObject(const blink::WebAXObject& object,
+                             SkBitmap& bitmap) {
+  const std::string image_data_url = object.ImageDataUrl(gfx::Size()).Utf8();
+  if (image_data_url.empty()) {
+    VLOG(1) << "Screen AI could not get image for " << object.ToString().Utf8();
+    return false;
+  }
+
+  std::string mimetype;
+  std::string charset;
+  std::string png_data;
+
+  if (!net::DataURL::Parse(GURL(image_data_url), &mimetype, &charset,
+                           &png_data)) {
+    VLOG(1) << "Screen AI could not parse image.";
+    return false;
+  }
+
+  if (!gfx::PNGCodec::Decode(
+          reinterpret_cast<const unsigned char*>(png_data.data()),
+          png_data.size(), &bitmap)) {
+    VLOG(2) << "Screen AI could not decode image.";
+    return false;
+  }
+
+  return true;
+}
+}  // namespace
+
+namespace content {
+
+AXScreenAIAnnotator::AXScreenAIAnnotator(
+    RenderAccessibilityImpl* const render_accessibility,
+    mojo::PendingRemote<screen_ai::mojom::ScreenAIAnnotator>
+        screen_ai_annotator)
+    : render_accessibility_(render_accessibility),
+      screen_ai_annotator_(std::move(screen_ai_annotator)) {}
+
+AXScreenAIAnnotator::~AXScreenAIAnnotator() = default;
+
+bool AXScreenAIAnnotator::ShouldAnnotateObject(
+    const blink::WebAXObject& object) {
+  // TODO(https://crbug.com/1278249): Add snapshot archiving and comparison to
+  // prevent re-annotating an object unless its image is modified.
+
+  // TODO(https://crbug.com/1278249): Update heuristic.
+  return object.Role() == ax::mojom::Role::kRootWebArea;
+}
+
+bool AXScreenAIAnnotator::ApplyAnnotationsIfAvailable(
+    const blink::WebAXObject& src,
+    ui::AXNodeData& dst) {
+  const auto lookup = annotations_.find(src.AxID());
+  if (lookup == annotations_.end())
+    return false;
+
+  // TODO(https://crbug.com/1278249): Apply annotations.
+  return true;
+}
+
+void AXScreenAIAnnotator::MaybeRunScreenAI(const blink::WebAXObject& object) {
+  if (ShouldAnnotateObject(object)) {
+    SkBitmap bitmap;
+    if (!GetImageFromWebAXObject(object, bitmap))
+      return;
+
+    annotations_[object.AxID()] = std::vector<screen_ai::mojom::Node>();
+    screen_ai_annotator_->Annotate(
+        bitmap, base::BindOnce(&AXScreenAIAnnotator::OnAnnotationReceived,
+                               weak_ptr_factory_.GetWeakPtr(), object.AxID()));
+  }
+}
+
+void AXScreenAIAnnotator::OnAnnotationReceived(
+    ui::AXNodeID ax_id,
+    screen_ai::mojom::ErrorType error_type,
+    std::vector<screen_ai::mojom::NodePtr> annotation) {
+  if (error_type != screen_ai::mojom::ErrorType::kOK)
+    return;
+
+  // TODO(https://crbug.com/1278249): Perform required conversions on members of
+  // |annotation| and store them in |annotations_|
+
+  // TODO(https://crbug.com/1278249): Apply received annotation on |object|.
+  // Depending on the data received from Screen AI library, we may decide to
+  // update the node here, or just add the data during serialization.
+  blink::WebAXObject object = blink::WebAXObject::FromWebDocumentByID(
+      render_accessibility_->GetMainDocument(), ax_id);
+
+  render_accessibility_->MarkWebAXObjectDirty(object, true /* subtree */);
+}
+
+}  // namespace content
\ No newline at end of file
diff --git a/content/renderer/accessibility/ax_screen_ai_annotator.h b/content/renderer/accessibility/ax_screen_ai_annotator.h
new file mode 100644
index 0000000..6572a4d
--- /dev/null
+++ b/content/renderer/accessibility/ax_screen_ai_annotator.h
@@ -0,0 +1,63 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CONTENT_RENDERER_ACCESSIBILITY_AX_SCREEN_AI_ANNOTATOR_H_
+#define CONTENT_RENDERER_ACCESSIBILITY_AX_SCREEN_AI_ANNOTATOR_H_
+
+#include "base/memory/weak_ptr.h"
+#include "components/services/screen_ai/public/mojom/screen_ai_service.mojom.h"
+#include "content/renderer/accessibility/render_accessibility_impl.h"
+#include "mojo/public/cpp/bindings/remote.h"
+
+namespace blink {
+class WebAXObject;
+}
+
+namespace ui {
+struct AXNodeData;
+}
+
+namespace content {
+
+class AXScreenAIAnnotator {
+ public:
+  AXScreenAIAnnotator(RenderAccessibilityImpl* const render_accessibility,
+                      mojo::PendingRemote<screen_ai::mojom::ScreenAIAnnotator>
+                          screen_ai_annotator);
+  ~AXScreenAIAnnotator();
+  AXScreenAIAnnotator(const AXScreenAIAnnotator&) = delete;
+  AXScreenAIAnnotator& operator=(const AXScreenAIAnnotator&) = delete;
+
+  // If |src| is already annotated, updates |dst| and returns true. Otherwise
+  // returns false.
+  bool ApplyAnnotationsIfAvailable(const blink::WebAXObject& src,
+                                   ui::AXNodeData& dst);
+
+  // If validated by heuristics, |object|'s image is sent to ScreenAI service.
+  void MaybeRunScreenAI(const blink::WebAXObject& object);
+
+ private:
+  // Heuristic based assessment if an object needs annotation.
+  bool ShouldAnnotateObject(const blink::WebAXObject& object);
+
+  // Receives the annotation from ScreenAI service, stores them in
+  // |annotations_|, and marks the respective object dirty.
+  void OnAnnotationReceived(ui::AXNodeID ax_id,
+                            screen_ai::mojom::ErrorType error_type,
+                            std::vector<screen_ai::mojom::NodePtr> annotation);
+
+  std::unordered_map<ui::AXNodeID, std::vector<screen_ai::mojom::Node>>
+      annotations_;
+
+  // Weak, owns us.
+  RenderAccessibilityImpl* const render_accessibility_;
+
+  mojo::Remote<screen_ai::mojom::ScreenAIAnnotator> screen_ai_annotator_;
+
+  base::WeakPtrFactory<AXScreenAIAnnotator> weak_ptr_factory_{this};
+};
+
+}  // namespace content
+
+#endif  // CONTENT_RENDERER_ACCESSIBILITY_AX_SCREEN_AI_ANNOTATOR_H_
diff --git a/content/renderer/accessibility/blink_ax_tree_source.cc b/content/renderer/accessibility/blink_ax_tree_source.cc
index e693e63..7218fb79 100644
--- a/content/renderer/accessibility/blink_ax_tree_source.cc
+++ b/content/renderer/accessibility/blink_ax_tree_source.cc
@@ -49,6 +49,10 @@
 #include "url/gurl.h"
 #include "url/url_constants.h"
 
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
+#include "content/renderer/accessibility/ax_screen_ai_annotator.h"
+#endif
+
 using base::ASCIIToUTF16;
 using base::UTF16ToUTF8;
 using blink::WebAXObject;
@@ -587,6 +591,13 @@
                                     element.GetAttribute("type").Utf8());
     }
   }
+
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
+  if (screen_ai_annotator_ &&
+      !screen_ai_annotator_->ApplyAnnotationsIfAvailable(src, *dst)) {
+    screen_ai_annotator_->MaybeRunScreenAI(src);
+  }
+#endif
 }
 
 blink::WebDocument BlinkAXTreeSource::GetMainDocument() const {
diff --git a/content/renderer/accessibility/blink_ax_tree_source.h b/content/renderer/accessibility/blink_ax_tree_source.h
index 82a6cb4..cee34de 100644
--- a/content/renderer/accessibility/blink_ax_tree_source.h
+++ b/content/renderer/accessibility/blink_ax_tree_source.h
@@ -24,6 +24,7 @@
 namespace content {
 
 class AXImageAnnotator;
+class AXScreenAIAnnotator;
 class BlinkAXTreeSource;
 class RenderFrameImpl;
 
@@ -95,6 +96,12 @@
     first_unlabeled_image_id_ = absl::nullopt;
   }
 
+  // The following method add or remove Screen AI annotator that is used to
+  // analyze the image of the nodes and provide additional metadata.
+  void set_screen_ai_annotator(AXScreenAIAnnotator* annotator) {
+    screen_ai_annotator_ = annotator;
+  }
+
   // Query or update a set of IDs for which we should load inline text boxes.
   bool ShouldLoadInlineTextBoxes(const blink::WebAXObject& obj) const;
   void SetLoadInlineTextBoxesForId(int32_t id);
@@ -190,6 +197,11 @@
   // The class instance that retrieves and manages automatic labels for images.
   AXImageAnnotator* image_annotator_ = nullptr;
 
+  // The class instance that uses Screen AI library to analyze snapshots.
+  // |screen_ai_annotator_| belongs to RenderAccessibilityImpl which owns
+  // |BlinkAXTreeSource|.
+  AXScreenAIAnnotator* screen_ai_annotator_ = nullptr;
+
   // Whether we should highlight annotation results visually on the page
   // for debugging.
   bool image_annotation_debugging_ = false;
diff --git a/content/renderer/accessibility/render_accessibility_impl.cc b/content/renderer/accessibility/render_accessibility_impl.cc
index 7f8b7fe1..13e9647 100644
--- a/content/renderer/accessibility/render_accessibility_impl.cc
+++ b/content/renderer/accessibility/render_accessibility_impl.cc
@@ -26,6 +26,7 @@
 #include "base/timer/elapsed_timer.h"
 #include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
+#include "content/public/common/content_features.h"
 #include "content/public/renderer/render_thread.h"
 #include "content/renderer/accessibility/ax_action_target_factory.h"
 #include "content/renderer/accessibility/ax_image_annotator.h"
@@ -52,6 +53,10 @@
 #include "ui/accessibility/ax_role_properties.h"
 #include "ui/accessibility/ax_tree_id.h"
 
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
+#include "content/renderer/accessibility/ax_screen_ai_annotator.h"
+#endif
+
 using blink::WebAXContext;
 using blink::WebAXObject;
 using blink::WebDocument;
@@ -168,6 +173,10 @@
   image_annotation_debugging_ =
       base::CommandLine::ForCurrentProcess()->HasSwitch(
           ::switches::kEnableExperimentalAccessibilityLabelsDebugging);
+
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
+  StartOrStopScreenAIAnnotator(mode);
+#endif
 }
 
 RenderAccessibilityImpl::~RenderAccessibilityImpl() = default;
@@ -240,6 +249,9 @@
     event_schedule_mode_ = EventScheduleMode::kProcessEventsImmediately;
     ScheduleSendPendingAccessibilityEvents();
   }
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
+  StartOrStopScreenAIAnnotator(mode);
+#endif
 }
 
 void RenderAccessibilityImpl::HitTest(
@@ -1245,6 +1257,33 @@
   }
 }
 
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
+void RenderAccessibilityImpl::StartOrStopScreenAIAnnotator(
+    ui::AXMode new_mode) {
+  if (!base::FeatureList::IsEnabled(features::kScreenAI))
+    return;
+
+  if (new_mode.has_mode(ui::AXMode::kScreenReader) &&
+      !ax_screen_ai_annotator_.get()) {
+    mojo::PendingRemote<screen_ai::mojom::ScreenAIAnnotator>
+        screen_ai_annotator;
+    render_frame_->GetBrowserInterfaceBroker()->GetInterface(
+        screen_ai_annotator.InitWithNewPipeAndPassReceiver());
+
+    ax_screen_ai_annotator_ = std::make_unique<AXScreenAIAnnotator>(
+        this, std::move(screen_ai_annotator));
+    tree_source_->set_screen_ai_annotator(ax_screen_ai_annotator_.get());
+    return;
+  }
+
+  if (ax_screen_ai_annotator_.get() &&
+      !new_mode.has_mode(ui::AXMode::kScreenReader)) {
+    tree_source_->set_screen_ai_annotator(nullptr);
+    ax_screen_ai_annotator_.reset();
+  }
+}
+#endif  // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
+
 void RenderAccessibilityImpl::MarkAllAXObjectsDirty(
     ax::mojom::Role role,
     ax::mojom::Action event_from_action) {
diff --git a/content/renderer/accessibility/render_accessibility_impl.h b/content/renderer/accessibility/render_accessibility_impl.h
index 61ce661..92cd85c 100644
--- a/content/renderer/accessibility/render_accessibility_impl.h
+++ b/content/renderer/accessibility/render_accessibility_impl.h
@@ -50,6 +50,7 @@
 namespace content {
 
 class AXImageAnnotator;
+class AXScreenAIAnnotator;
 class RenderFrameImpl;
 class RenderAccessibilityManager;
 
@@ -210,6 +211,14 @@
   // any automatic annotations that might have been added before.
   void StartOrStopLabelingImages(ui::AXMode old_mode, ui::AXMode new_mode);
 
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
+  // If new mode includes screen reader, initializes Screen AI service
+  // connection that processes snapshots to get more metadata.
+  // If new mode does not includes screen reader and Screen AI is enabled, turns
+  // it off.
+  void StartOrStopScreenAIAnnotator(ui::AXMode new_mode);
+#endif
+
   // Marks all AXObjects with the given role in the current tree dirty.
   void MarkAllAXObjectsDirty(ax::mojom::Role role,
                              ax::mojom::Action event_from_action);
@@ -270,6 +279,11 @@
   // Manages the automatic image annotations, if enabled.
   std::unique_ptr<AXImageAnnotator> ax_image_annotator_;
 
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
+  // Manages the snapshot processing, if enabled.
+  std::unique_ptr<AXScreenAIAnnotator> ax_screen_ai_annotator_;
+#endif
+
   // Events from Blink are collected until they are ready to be
   // sent to the browser.
   std::vector<ui::AXEvent> pending_events_;
diff --git a/content/test/data/gpu/webcodecs/copyTo.html b/content/test/data/gpu/webcodecs/copyTo.html
index 2a19e40..6b8af26 100644
--- a/content/test/data/gpu/webcodecs/copyTo.html
+++ b/content/test/data/gpu/webcodecs/copyTo.html
@@ -100,5 +100,6 @@
     }
 
     frame.close();
+    source.close();
   }
 </script>
\ No newline at end of file
diff --git a/content/test/data/gpu/webcodecs/draw-image.html b/content/test/data/gpu/webcodecs/draw-image.html
index eb56b00..77fb866 100644
--- a/content/test/data/gpu/webcodecs/draw-image.html
+++ b/content/test/data/gpu/webcodecs/draw-image.html
@@ -50,6 +50,7 @@
         frame.close();
         await waitForNextFrame();
       }
+      source.close();
     }
   </script>
 </head>
diff --git a/content/test/data/gpu/webcodecs/encode-decode.html b/content/test/data/gpu/webcodecs/encode-decode.html
index 5eb3354b..8ed6fb3 100644
--- a/content/test/data/gpu/webcodecs/encode-decode.html
+++ b/content/test/data/gpu/webcodecs/encode-decode.html
@@ -91,6 +91,7 @@
       await decoder.flush();
       encoder.close();
       decoder.close();
+      source.close();
 
       TEST.assert(
         frames_encoded == frames_to_encode,
diff --git a/content/test/data/gpu/webcodecs/encode.html b/content/test/data/gpu/webcodecs/encode.html
index b4faf94..a20f760f 100644
--- a/content/test/data/gpu/webcodecs/encode.html
+++ b/content/test/data/gpu/webcodecs/encode.html
@@ -18,12 +18,6 @@
       const width = 640;
       const height = 480;
       const frames_in_one_pass = 15;
-      let source = await createFrameSource(arg.source_type, width, height);
-      if (!source) {
-        TEST.skip('Unsupported source: ' + arg.source_type);
-        return;
-      }
-
       let errors = 0;
       let chunks = [];
       let decoder_configs = [];
@@ -43,6 +37,12 @@
         return;
       }
 
+      let source = await createFrameSource(arg.source_type, width, height);
+      if (!source) {
+        TEST.skip('Unsupported source: ' + arg.source_type);
+        return;
+      }
+
       const init = {
         output(chunk, metadata) {
           if (metadata.decoderConfig)
@@ -84,6 +84,7 @@
 
       await encoder.flush();
       encoder.close();
+      source.close();
 
       TEST.assert(
         decoder_configs.length >= 2,
diff --git a/content/test/data/gpu/webcodecs/encoding-modes.html b/content/test/data/gpu/webcodecs/encoding-modes.html
index f8deda84..5f4f03d8 100644
--- a/content/test/data/gpu/webcodecs/encoding-modes.html
+++ b/content/test/data/gpu/webcodecs/encoding-modes.html
@@ -13,11 +13,6 @@
       const width = 640;
       const height = 480;
       const frames_in_one_pass = 15;
-      let source = await createFrameSource(arg.source_type, width, height);
-      if (!source) {
-        TEST.skip('Unsupported source: ' + arg.source_type);
-        return;
-      }
 
       let errors = 0;
       let chunks = [];
@@ -40,6 +35,12 @@
         return;
       }
 
+      let source = await createFrameSource(arg.source_type, width, height);
+      if (!source) {
+        TEST.skip('Unsupported source: ' + arg.source_type);
+        return;
+      }
+
       const init = {
         output(chunk, metadata) {
           if (metadata.decoderConfig)
@@ -65,6 +66,7 @@
 
       await encoder.flush();
       encoder.close();
+      source.close();
 
       TEST.assert(
         decoder_configs.length >= 1,
diff --git a/content/test/data/gpu/webcodecs/svc.html b/content/test/data/gpu/webcodecs/svc.html
index 5d6c7ed..9508c5e 100644
--- a/content/test/data/gpu/webcodecs/svc.html
+++ b/content/test/data/gpu/webcodecs/svc.html
@@ -95,6 +95,7 @@
       await decoder.flush();
       encoder.close();
       decoder.close();
+      source.close();
 
       TEST.assert(
         frames_encoded == frames_to_encode,
diff --git a/content/test/data/gpu/webcodecs/tex-image-2d.html b/content/test/data/gpu/webcodecs/tex-image-2d.html
index 64c1e4d..699ccbe 100644
--- a/content/test/data/gpu/webcodecs/tex-image-2d.html
+++ b/content/test/data/gpu/webcodecs/tex-image-2d.html
@@ -112,6 +112,7 @@
         frame.close();
         await waitForNextFrame();
       }
+      source.close();
     }
   </script>
 </head>
diff --git a/content/test/data/gpu/webcodecs/webcodecs_common.js b/content/test/data/gpu/webcodecs/webcodecs_common.js
index 4a9c593..6096c19 100644
--- a/content/test/data/gpu/webcodecs/webcodecs_common.js
+++ b/content/test/data/gpu/webcodecs/webcodecs_common.js
@@ -175,6 +175,8 @@
   async getNextFrame() {
     return null;
   }
+
+  close() {}
 }
 
 // Source of video frames coming from taking snapshots of a canvas.
@@ -214,6 +216,11 @@
     const frame = result.value;
     return frame;
   }
+
+  close() {
+    if (this.reader)
+      this.reader.cancel();
+  }
 }
 
 class ArrayBufferSource extends FrameSource {
@@ -293,6 +300,11 @@
 
     return next.promise;
   }
+
+  close() {
+    if (this.decoder)
+      this.decoder.close();
+  }
 }
 
 function createCanvasCaptureSource(width, height) {
@@ -367,7 +379,15 @@
     encoder.encode(frame, {keyFrame: false});
     frame.close();
   }
-  await encoder.flush();
+  try {
+    await encoder.flush();
+    encoder.close();
+    canvasSource.close();
+  } catch (e) {
+    errors++;
+    TEST.log(e);
+  }
+
   if (errors > 0)
     return null;
 
diff --git a/content/test/gpu/gpu_tests/webcodecs_integration_test.py b/content/test/gpu/gpu_tests/webcodecs_integration_test.py
index 05469b6f..c44cd0f 100644
--- a/content/test/gpu/gpu_tests/webcodecs_integration_test.py
+++ b/content/test/gpu/gpu_tests/webcodecs_integration_test.py
@@ -68,7 +68,8 @@
       for acc in accelerations:
         for bitrate_mode in ['constant', 'variable']:
           for latency_mode in ['realtime', 'quality']:
-            args = ('offscreen', codec, acc, bitrate_mode, latency_mode)
+            source_type = 'offscreen'
+            args = (source_type, codec, acc, bitrate_mode, latency_mode)
             yield ('WebCodecs_EncodingModes_%s_%s_%s_%s_%s' % args,
                    'encoding-modes.html', ({
                        'source_type': source_type,
diff --git a/content/utility/BUILD.gn b/content/utility/BUILD.gn
index fe84145..3182033 100644
--- a/content/utility/BUILD.gn
+++ b/content/utility/BUILD.gn
@@ -112,6 +112,7 @@
 
   if (is_linux || is_chromeos) {
     deps += [
+      "//components/services/screen_ai:screen_ai_sandbox_hook",
       "//content/utility/speech:speech_recognition_sandbox_hook",
       "//services/network:network_sandbox_hook",
     ]
diff --git a/content/utility/DEPS b/content/utility/DEPS
index 81ecc04..0bde0a1 100644
--- a/content/utility/DEPS
+++ b/content/utility/DEPS
@@ -17,6 +17,7 @@
   "+services/service_manager",
   "+services/shape_detection",
   "+services/tracing",
+  "+components/services/screen_ai",
   "+services/video_capture",
   "+services/viz",
   "+sandbox/win/src",
diff --git a/content/utility/utility_main.cc b/content/utility/utility_main.cc
index dd6a232..57206009 100644
--- a/content/utility/utility_main.cc
+++ b/content/utility/utility_main.cc
@@ -33,6 +33,7 @@
 #include "third_party/icu/source/i18n/unicode/timezone.h"
 
 #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
+#include "components/services/screen_ai/sandbox/screen_ai_sandbox_hook_linux.h"
 #include "content/utility/speech/speech_recognition_sandbox_hook_linux.h"
 #if BUILDFLAG(ENABLE_PRINTING)
 #include "printing/sandbox/print_backend_sandbox_hook_linux.h"
@@ -158,6 +159,9 @@
       pre_sandbox_hook =
           base::BindOnce(&speech::SpeechRecognitionPreSandboxHook);
       break;
+    case sandbox::mojom::Sandbox::kScreenAI:
+      pre_sandbox_hook = base::BindOnce(&screen_ai::ScreenAIPreSandboxHook);
+      break;
 #if BUILDFLAG(IS_CHROMEOS_ASH)
     case sandbox::mojom::Sandbox::kHardwareVideoDecoding:
       pre_sandbox_hook =
diff --git a/google_apis/google_api_keys_unittest.cc b/google_apis/google_api_keys_unittest.cc
index 0b335aa5..fc92bc1 100644
--- a/google_apis/google_api_keys_unittest.cc
+++ b/google_apis/google_api_keys_unittest.cc
@@ -26,9 +26,10 @@
 // unit_tests, and the Android builders complain about multiply
 // defined symbols (likely they don't do name decoration as well as
 // the Mac and Linux linkers).  Therefore these tests are only built
-// and run on Mac and Linux, which should provide plenty of coverage
+// and run on Mac, Linux and Fuchsia, which should provide plenty of coverage
 // since there are no platform-specific bits in this code.
-#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_APPLE)
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_APPLE) || \
+    BUILDFLAG(IS_FUCHSIA)
 
 // We need to include everything included by google_api_keys.cc once
 // at global scope so that things like STL and classes from base don't
@@ -587,3 +588,4 @@
 }
 
 #endif  // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_APPLE)
+        // || BUILDFLAG(IS_FUCHSIA)
diff --git a/ios/chrome/app/BUILD.gn b/ios/chrome/app/BUILD.gn
index d221d1c..effb4eb1 100644
--- a/ios/chrome/app/BUILD.gn
+++ b/ios/chrome/app/BUILD.gn
@@ -202,6 +202,7 @@
     "//ios/chrome/app/application_delegate:app_state_header",
     "//ios/chrome/browser",
     "//ios/chrome/browser/policy",
+    "//ios/chrome/browser/ui/first_run:utils",
     "//ios/chrome/browser/ui/main:scene_state_header",
   ]
 }
diff --git a/ios/chrome/app/application_delegate/app_state.mm b/ios/chrome/app/application_delegate/app_state.mm
index 0a8d4a3..64d69ee 100644
--- a/ios/chrome/app/application_delegate/app_state.mm
+++ b/ios/chrome/app/application_delegate/app_state.mm
@@ -230,7 +230,8 @@
   }
 
   // Return YES if the First Run UI is showing.
-  return self.initStage == InitStageFirstRun &&
+  return (self.initStage == InitStageFirstRun ||
+          self.initStage == InitStageEnterprise) &&
          self.startupInformation.isFirstRun;
 }
 
diff --git a/ios/chrome/app/enterprise_app_agent.mm b/ios/chrome/app/enterprise_app_agent.mm
index 571a487..825d451 100644
--- a/ios/chrome/app/enterprise_app_agent.mm
+++ b/ios/chrome/app/enterprise_app_agent.mm
@@ -16,6 +16,7 @@
 #include "ios/chrome/browser/policy/chrome_browser_cloud_management_controller_ios.h"
 #import "ios/chrome/browser/policy/chrome_browser_cloud_management_controller_observer_bridge.h"
 #import "ios/chrome/browser/policy/cloud_policy_client_observer_bridge.h"
+#import "ios/chrome/browser/ui/first_run/first_run_util.h"
 #import "ios/chrome/browser/ui/main/scene_state.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
@@ -191,7 +192,7 @@
       machineLevelUserCloudPolicyManager =
           self.policyConnector->machine_level_user_cloud_policy_manager();
 
-  return !tests_hook::DisableFirstRun() &&
+  return ShouldPresentFirstRunExperience() &&
          self.policyConnector->chrome_browser_cloud_management_controller()
              ->IsEnabled() &&
          machineLevelUserCloudPolicyManager &&
diff --git a/ios/chrome/browser/web_state_list/BUILD.gn b/ios/chrome/browser/web_state_list/BUILD.gn
index cccf25c..481aa1d 100644
--- a/ios/chrome/browser/web_state_list/BUILD.gn
+++ b/ios/chrome/browser/web_state_list/BUILD.gn
@@ -53,6 +53,7 @@
     "//ios/web",
     "//ios/web/public/session",
   ]
+  public_deps = [ "//third_party/abseil-cpp:absl" ]
   frameworks = [ "Foundation.framework" ]
   configs += [ "//build/config/compiler:enable_arc" ]
 }
diff --git a/ios/chrome/browser/web_state_list/web_state_list_order_controller.mm b/ios/chrome/browser/web_state_list/web_state_list_order_controller.mm
index cf60802..a08af5b 100644
--- a/ios/chrome/browser/web_state_list/web_state_list_order_controller.mm
+++ b/ios/chrome/browser/web_state_list/web_state_list_order_controller.mm
@@ -5,6 +5,7 @@
 #import "ios/chrome/browser/web_state_list/web_state_list_order_controller.h"
 
 #include <cstdint>
+#include <set>
 
 #include "base/check_op.h"
 #import "ios/chrome/browser/web_state_list/web_state_list.h"
@@ -15,6 +16,45 @@
 #error "This file requires ARC support."
 #endif
 
+namespace {
+
+// Find the index of next non-removed WebState opened by |web_state|. It
+// may return WebStateList::kInvalidIndex if there is no such indexes.
+int FindIndexOfNextNonRemovedWebStateOpenedBy(
+    const WebStateListRemovingIndexes& removing_indexes,
+    const WebStateList& web_state_list,
+    const web::WebState* web_state,
+    int starting_index) {
+  std::set<int> children;
+  for (;;) {
+    const int child_index = web_state_list.GetIndexOfNextWebStateOpenedBy(
+        web_state, starting_index, false);
+
+    // The active WebState has no child, fall back to the next heuristic.
+    if (child_index == WebStateList::kInvalidIndex)
+      break;
+
+    // All children are going to be removed, fallback to the next heuristic.
+    if (children.find(child_index) != children.end())
+      break;
+
+    // Found a child that is not removed, select it as the next active
+    // WebState.
+    const int child_index_after_removal =
+        removing_indexes.IndexAfterRemoval(child_index);
+
+    if (child_index_after_removal != WebStateList::kInvalidIndex)
+      return child_index_after_removal;
+
+    children.insert(child_index);
+    starting_index = child_index;
+  }
+
+  return WebStateList::kInvalidIndex;
+}
+
+}  // anonymous namespace
+
 WebStateListOrderController::WebStateListOrderController(
     const WebStateList& web_state_list)
     : web_state_list_(web_state_list) {}
@@ -69,9 +109,9 @@
   // the new active element. Prefer childs located after the active element,
   // but this may end up selecting an element before it.
   const int child_index_after_removal =
-      removing_indexes.FindIndexOfNextNonRemovedWebStateOpenedBy(
-          web_state_list_, web_state_list_.GetWebStateAt(active_index),
-          active_index);
+      FindIndexOfNextNonRemovedWebStateOpenedBy(
+          removing_indexes, web_state_list_,
+          web_state_list_.GetWebStateAt(active_index), active_index);
   if (child_index_after_removal != WebStateList::kInvalidIndex)
     return child_index_after_removal;
 
@@ -82,8 +122,8 @@
     // to be the new active element. Prefer siblings located after the active
     // element, but this may end up selecting an element before it.
     const int sibling_index_after_removal =
-        removing_indexes.FindIndexOfNextNonRemovedWebStateOpenedBy(
-            web_state_list_, opener.opener, active_index);
+        FindIndexOfNextNonRemovedWebStateOpenedBy(
+            removing_indexes, web_state_list_, opener.opener, active_index);
     if (sibling_index_after_removal != WebStateList::kInvalidIndex)
       return sibling_index_after_removal;
 
diff --git a/ios/chrome/browser/web_state_list/web_state_list_removing_indexes.h b/ios/chrome/browser/web_state_list/web_state_list_removing_indexes.h
index 1819104..d80aaad 100644
--- a/ios/chrome/browser/web_state_list/web_state_list_removing_indexes.h
+++ b/ios/chrome/browser/web_state_list/web_state_list_removing_indexes.h
@@ -8,6 +8,8 @@
 #include <initializer_list>
 #include <vector>
 
+#include "third_party/abseil-cpp/absl/types/variant.h"
+
 class WebStateList;
 
 namespace web {
@@ -32,7 +34,7 @@
   ~WebStateListRemovingIndexes();
 
   // Returns the number of WebState that will be closed.
-  int count() const { return static_cast<int>(indexes_.size()); }
+  int count() const;
 
   // Returns whether index is present in the list of indexes to close.
   bool Contains(int index) const;
@@ -41,15 +43,15 @@
   // scheduled to be removed, will return WebStateList::kInvalidIndex.
   int IndexAfterRemoval(int index) const;
 
-  // Find the index of next non-removed WebState opened by |web_state|. It
-  // may return WebStateList::kInvalidIndex if there is no such indexes.
-  int FindIndexOfNextNonRemovedWebStateOpenedBy(
-      const WebStateList& web_state_list,
-      const web::WebState* web_state,
-      int starting_index);
+  // Represents an empty WebStateListRemovingIndexes.
+  struct Empty {};
+
+  // Alias for the variant storing the indexes to remove. Using a variant
+  // allow not allocating for the common case of removing one element.
+  using Storage = absl::variant<Empty, int, std::vector<int>>;
 
  private:
-  std::vector<int> indexes_;
+  Storage removing_;
 };
 
 #endif  // IOS_CHROME_BROWSER_WEB_STATE_LIST_WEB_STATE_LIST_REMOVING_INDEXES_H_
diff --git a/ios/chrome/browser/web_state_list/web_state_list_removing_indexes.mm b/ios/chrome/browser/web_state_list/web_state_list_removing_indexes.mm
index 4677871..d687958 100644
--- a/ios/chrome/browser/web_state_list/web_state_list_removing_indexes.mm
+++ b/ios/chrome/browser/web_state_list/web_state_list_removing_indexes.mm
@@ -5,25 +5,115 @@
 #import "ios/chrome/browser/web_state_list/web_state_list_removing_indexes.h"
 
 #include <algorithm>
-#include <set>
 
 #import "ios/chrome/browser/web_state_list/web_state_list.h"
-#import "ios/chrome/browser/web_state_list/web_state_opener.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
 #endif
 
+namespace {
+
+// Constructs a WebStateListRemovingIndexes::Storage from a
+// std::vector<int>, sorting it and removing duplicates.
+WebStateListRemovingIndexes::Storage StorageFromVector(
+    std::vector<int> indexes) {
+  std::sort(indexes.begin(), indexes.end());
+  indexes.erase(std::unique(indexes.begin(), indexes.end()), indexes.end());
+
+  if (indexes.empty())
+    return WebStateListRemovingIndexes::Empty{};
+
+  if (indexes.size() == 1)
+    return indexes[0];
+
+  return indexes;
+}
+
+// Constructs a WebStateListRemovingIndexes::Storage from a
+// std::initializer_list<int>.
+WebStateListRemovingIndexes::Storage StorageFromInitializerList(
+    std::initializer_list<int> indexes) {
+  if (indexes.size() == 0)
+    return WebStateListRemovingIndexes::Empty{};
+
+  if (indexes.size() == 1)
+    return *indexes.begin();
+
+  // Use the vector overload.
+  return StorageFromVector(std::vector<int>(std::move(indexes)));
+}
+
+// Visitor implementing WebStateListRemovingIndexes::count().
+struct CountVisitor {
+  using Empty = WebStateListRemovingIndexes::Empty;
+
+  int operator()(const Empty&) const { return 0; }
+
+  int operator()(const int& index) const { return 1; }
+
+  int operator()(const std::vector<int>& indexes) const {
+    return static_cast<int>(indexes.size());
+  }
+};
+
+// Visitor implementing WebStateListRemovingIndexes::Contains(int).
+struct ContainsVisitor {
+  using Empty = WebStateListRemovingIndexes::Empty;
+
+  explicit ContainsVisitor(int index) : index_(index) {}
+
+  bool operator()(const Empty&) const { return false; }
+
+  bool operator()(const int& index) const { return index_ == index; }
+
+  bool operator()(const std::vector<int>& indexes) const {
+    return std::binary_search(indexes.begin(), indexes.end(), index_);
+  }
+
+  const int index_;
+};
+
+// Visitor implementing WebStateListRemovingIndexes::IndexAfterRemoval(int).
+struct IndexAfterRemovalVisitor {
+  using Empty = WebStateListRemovingIndexes::Empty;
+
+  explicit IndexAfterRemovalVisitor(int index) : index_(index) {}
+
+  int operator()(const Empty&) const { return index_; }
+
+  int operator()(const int& index) const {
+    if (index_ == index)
+      return WebStateList::kInvalidIndex;
+
+    if (index_ > index)
+      return index_ - 1;
+
+    return index_;
+  }
+
+  int operator()(const std::vector<int>& indexes) const {
+    const auto lower_bound =
+        std::lower_bound(indexes.begin(), indexes.end(), index_);
+
+    if (lower_bound == indexes.end() || *lower_bound != index_)
+      return index_ - std::distance(indexes.begin(), lower_bound);
+
+    return WebStateList::kInvalidIndex;
+  }
+
+  const int index_;
+};
+
+}  // anonymous namespace
+
 WebStateListRemovingIndexes::WebStateListRemovingIndexes(
     std::vector<int> indexes)
-    : indexes_(std::move(indexes)) {
-  std::sort(indexes_.begin(), indexes_.end());
-  indexes_.erase(std::unique(indexes_.begin(), indexes_.end()), indexes_.end());
-}
+    : removing_(StorageFromVector(std::move(indexes))) {}
 
 WebStateListRemovingIndexes::WebStateListRemovingIndexes(
     std::initializer_list<int> indexes)
-    : WebStateListRemovingIndexes(std::vector<int>(indexes)) {}
+    : removing_(StorageFromInitializerList(std::move(indexes))) {}
 
 WebStateListRemovingIndexes::WebStateListRemovingIndexes(
     const WebStateListRemovingIndexes&) = default;
@@ -39,47 +129,14 @@
 
 WebStateListRemovingIndexes::~WebStateListRemovingIndexes() = default;
 
+int WebStateListRemovingIndexes::count() const {
+  return absl::visit(CountVisitor(), removing_);
+}
+
 bool WebStateListRemovingIndexes::Contains(int index) const {
-  return std::binary_search(indexes_.begin(), indexes_.end(), index);
+  return absl::visit(ContainsVisitor(index), removing_);
 }
 
 int WebStateListRemovingIndexes::IndexAfterRemoval(int index) const {
-  const auto lower_bound =
-      std::lower_bound(indexes_.begin(), indexes_.end(), index);
-
-  if (lower_bound == indexes_.end() || *lower_bound != index)
-    return index - std::distance(indexes_.begin(), lower_bound);
-
-  // The index is scheduled for removal.
-  return WebStateList::kInvalidIndex;
-}
-
-int WebStateListRemovingIndexes::FindIndexOfNextNonRemovedWebStateOpenedBy(
-    const WebStateList& web_state_list,
-    const web::WebState* web_state,
-    int starting_index) {
-  std::set<int> children;
-  for (;;) {
-    const int child_index = web_state_list.GetIndexOfNextWebStateOpenedBy(
-        web_state, starting_index, false);
-
-    // The active WebState has no child, fall back to the next heuristic.
-    if (child_index == WebStateList::kInvalidIndex)
-      break;
-
-    // All children are going to be removed, fallback to the next heuristic.
-    if (children.find(child_index) != children.end())
-      break;
-
-    // Found a child that is not removed, select it as the next active
-    // WebState.
-    const int child_index_after_removal = IndexAfterRemoval(child_index);
-    if (child_index_after_removal != WebStateList::kInvalidIndex)
-      return child_index_after_removal;
-
-    children.insert(child_index);
-    starting_index = child_index;
-  }
-
-  return WebStateList::kInvalidIndex;
+  return absl::visit(IndexAfterRemovalVisitor(index), removing_);
 }
diff --git a/ios/chrome/browser/web_state_list/web_state_list_removing_indexes_unittest.mm b/ios/chrome/browser/web_state_list/web_state_list_removing_indexes_unittest.mm
index 136d233..4dbe4f9 100644
--- a/ios/chrome/browser/web_state_list/web_state_list_removing_indexes_unittest.mm
+++ b/ios/chrome/browser/web_state_list/web_state_list_removing_indexes_unittest.mm
@@ -63,11 +63,43 @@
   EXPECT_EQ(WebStateListRemovingIndexes({}).count(), 0);
   EXPECT_EQ(WebStateListRemovingIndexes({1}).count(), 1);
   EXPECT_EQ(WebStateListRemovingIndexes({1, 1}).count(), 1);
+  EXPECT_EQ(WebStateListRemovingIndexes({1, 2}).count(), 2);
+  EXPECT_EQ(WebStateListRemovingIndexes({2, 1, 2, 1}).count(), 2);
 }
 
 // Tests that WebStateListRemovingIndexes correctly returns the correct
-// updated value when asked for index once tabs have been removed.
-TEST_F(WebStateListRemovingIndexesTest, IndexAfterRemoval) {
+// updated value when asked for index if no tabs are removed.
+TEST_F(WebStateListRemovingIndexesTest, IndexAfterRemovalEmpty) {
+  WebStateListRemovingIndexes removing_indexes({});
+  EXPECT_EQ(removing_indexes.IndexAfterRemoval(0), 0);  // no removal before
+  EXPECT_EQ(removing_indexes.IndexAfterRemoval(1), 1);  // no removal before
+  EXPECT_EQ(removing_indexes.IndexAfterRemoval(2), 2);  // no removal before
+  EXPECT_EQ(removing_indexes.IndexAfterRemoval(3), 3);  // no removal before
+  EXPECT_EQ(removing_indexes.IndexAfterRemoval(4), 4);  // no removal before
+  EXPECT_EQ(removing_indexes.IndexAfterRemoval(5), 5);  // no removal before
+  EXPECT_EQ(removing_indexes.IndexAfterRemoval(6), 6);  // no removal before
+  EXPECT_EQ(removing_indexes.IndexAfterRemoval(7), 7);  // no removal before
+  EXPECT_EQ(removing_indexes.IndexAfterRemoval(8), 8);  // no removal before
+}
+
+// Tests that WebStateListRemovingIndexes correctly returns the correct
+// updated value when asked for index if one tab is removed.
+TEST_F(WebStateListRemovingIndexesTest, IndexAfterRemovalOneTab) {
+  WebStateListRemovingIndexes removing_indexes({4});
+  EXPECT_EQ(removing_indexes.IndexAfterRemoval(0), 0);
+  EXPECT_EQ(removing_indexes.IndexAfterRemoval(1), 1);
+  EXPECT_EQ(removing_indexes.IndexAfterRemoval(2), 2);
+  EXPECT_EQ(removing_indexes.IndexAfterRemoval(3), 3);
+  EXPECT_EQ(removing_indexes.IndexAfterRemoval(4), WebStateList::kInvalidIndex);
+  EXPECT_EQ(removing_indexes.IndexAfterRemoval(5), 4);  // one removals before
+  EXPECT_EQ(removing_indexes.IndexAfterRemoval(6), 5);  // one removals before
+  EXPECT_EQ(removing_indexes.IndexAfterRemoval(7), 6);  // one removals before
+  EXPECT_EQ(removing_indexes.IndexAfterRemoval(8), 7);  // one removals before
+}
+
+// Tests that WebStateListRemovingIndexes correctly returns the correct
+// updated value when asked for index if multiple tabs have been removed.
+TEST_F(WebStateListRemovingIndexesTest, IndexAfterRemovalMultipleTabs) {
   WebStateListRemovingIndexes removing_indexes({1, 3, 7});
   EXPECT_EQ(removing_indexes.IndexAfterRemoval(0), 0);  // no removal before
   EXPECT_EQ(removing_indexes.IndexAfterRemoval(1), WebStateList::kInvalidIndex);
@@ -79,75 +111,3 @@
   EXPECT_EQ(removing_indexes.IndexAfterRemoval(7), WebStateList::kInvalidIndex);
   EXPECT_EQ(removing_indexes.IndexAfterRemoval(8), 5);  // three removals before
 }
-
-// Tests that WebStateListRemovingIndexes correctly find a child index after
-// removal.
-TEST_F(WebStateListRemovingIndexesTest,
-       FindIndexOfNextNonRemovedWebStateOpenedBy) {
-  // Create a WebStateList with 6 WebStates, 5 of them children of the
-  // WebState at index 2 (so the WebState at index 2 has two children
-  // before itself and three children after).
-  web::WebState* opener = InsertNewWebState(0, WebStateOpener());
-  InsertNewWebState(0, WebStateOpener(opener));
-  InsertNewWebState(0, WebStateOpener(opener));
-  InsertNewWebState(3, WebStateOpener(opener));
-  InsertNewWebState(4, WebStateOpener(opener));
-  InsertNewWebState(5, WebStateOpener(opener));
-
-  // If no indexes are removed, FindIndexOfNextNonRemovedWebStateOpenedBy()
-  // should behave as GetIndexOfNextWebStateOpenedBy().
-  WebStateListRemovingIndexes removing_no_children({});
-  EXPECT_EQ(removing_no_children.FindIndexOfNextNonRemovedWebStateOpenedBy(
-                web_state_list_, opener, 2),
-            3);
-  EXPECT_EQ(removing_no_children.FindIndexOfNextNonRemovedWebStateOpenedBy(
-                web_state_list_, opener, 3),
-            4);
-  EXPECT_EQ(removing_no_children.FindIndexOfNextNonRemovedWebStateOpenedBy(
-                web_state_list_, opener, 4),
-            5);
-  EXPECT_EQ(removing_no_children.FindIndexOfNextNonRemovedWebStateOpenedBy(
-                web_state_list_, opener, 5),
-            0);
-  EXPECT_EQ(removing_no_children.FindIndexOfNextNonRemovedWebStateOpenedBy(
-                web_state_list_, opener, 0),
-            1);
-  EXPECT_EQ(removing_no_children.FindIndexOfNextNonRemovedWebStateOpenedBy(
-                web_state_list_, opener, 1),
-            3);
-  EXPECT_EQ(removing_no_children.FindIndexOfNextNonRemovedWebStateOpenedBy(
-                web_state_list_, web_state_list_.GetWebStateAt(0), 0),
-            WebStateList::kInvalidIndex);
-
-  // If some child are removed, FindIndexOfNextNonRemovedWebStateOpenedBy()
-  // correctly skips them.
-  WebStateListRemovingIndexes removing_some_children({1, 3});
-  EXPECT_EQ(removing_some_children.FindIndexOfNextNonRemovedWebStateOpenedBy(
-                web_state_list_, opener, 2),
-            2);
-  EXPECT_EQ(removing_some_children.FindIndexOfNextNonRemovedWebStateOpenedBy(
-                web_state_list_, opener, 3),
-            2);
-  EXPECT_EQ(removing_some_children.FindIndexOfNextNonRemovedWebStateOpenedBy(
-                web_state_list_, opener, 4),
-            3);
-  EXPECT_EQ(removing_some_children.FindIndexOfNextNonRemovedWebStateOpenedBy(
-                web_state_list_, opener, 5),
-            0);
-  EXPECT_EQ(removing_some_children.FindIndexOfNextNonRemovedWebStateOpenedBy(
-                web_state_list_, opener, 0),
-            2);
-  EXPECT_EQ(removing_some_children.FindIndexOfNextNonRemovedWebStateOpenedBy(
-                web_state_list_, opener, 1),
-            2);
-  EXPECT_EQ(removing_some_children.FindIndexOfNextNonRemovedWebStateOpenedBy(
-                web_state_list_, web_state_list_.GetWebStateAt(0), 0),
-            WebStateList::kInvalidIndex);
-
-  // If some child are removed, FindIndexOfNextNonRemovedWebStateOpenedBy()
-  // correctly reports there is no possible index.
-  WebStateListRemovingIndexes removing_all_children({0, 1, 3, 4, 5});
-  EXPECT_EQ(removing_all_children.FindIndexOfNextNonRemovedWebStateOpenedBy(
-                web_state_list_, opener, 2),
-            WebStateList::kInvalidIndex);
-}
diff --git a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios.zip.sha1
index fb03eb0..41f4046 100644
--- a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-49ddad3a398cfc43d83250608d149e6dc3d59680
\ No newline at end of file
+2b8e506504799f30cc8f2d1711fd836332c93cc9
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator.zip.sha1
index 7e1d84a..c8ad181 100644
--- a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-d3352e27b5c67a0e5c04ba01001282d5581065dc
\ No newline at end of file
+21c3e988c0d2b80ee8ae6ed7df4a1b69300372c5
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios.zip.sha1
index dda4b3b..b38f0ab 100644
--- a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-e623d33bcc8a5dd1114675945f000c9e1510914e
\ No newline at end of file
+7feebe295ab656b03330cff8a9e3814d2c6daff6
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator.zip.sha1
index 0a6cb96..62c950b7 100644
--- a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-2d2489b63b5b7f88e7426e846a3b2dd00efd7853
\ No newline at end of file
+531d8eae58162f78991db60373da7b7d7d31f680
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.ios.zip.sha1
index d498de1..e232b20 100644
--- a/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-19ab26ff503f81c71cfb5fca8babaad044cea1fa
\ No newline at end of file
+3c1ba500ed12019a5cc362238449823d4a5e13f8
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.iossimulator.zip.sha1
index 6ff1621..e9e870af3 100644
--- a/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-6adc817dd79c2ceaa10b049d24379e98240b0693
\ No newline at end of file
+d53868e633dad1a790228a2efa4c61b0d7507398
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios.zip.sha1
index fac15f3..4f1515a 100644
--- a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-4bcd8f7e9bb9055936fb7e317983b4db20d0b9e2
\ No newline at end of file
+799e048bb2d0549f7c11015ff19db71fbfdde4f7
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator.zip.sha1
index 2a6c62b7..de4468b 100644
--- a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-adcea63ec37ade93a9e1fd87a8c296ab3f905ccb
\ No newline at end of file
+0d2884566b27fb8d7ba1b6376d05247f58f1819e
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios.zip.sha1
index d07ec66..fb27462 100644
--- a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-11bb5b5dc64bee4a8722a6ba68356898e71f04a1
\ No newline at end of file
+0d65a7e4c4d05bb98c37d5eae3c2f051534900d7
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator.zip.sha1
index 569c153c..00e1feac 100644
--- a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-63e99800a977f2445d3b08483c6025d6592adc5f
\ No newline at end of file
+79b1716dadb6802f0963892b4c74af4d61873eac
\ No newline at end of file
diff --git a/media/base/audio_processing.h b/media/base/audio_processing.h
index ca80199..95fda4b1 100644
--- a/media/base/audio_processing.h
+++ b/media/base/audio_processing.h
@@ -9,6 +9,7 @@
 
 #include "build/build_config.h"
 #include "media/base/media_export.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace media {
 
@@ -89,6 +90,13 @@
   std::string ToString() const;
 };
 
+// This struct contains audio processing metrics that are reported by the audio
+// service.
+struct MEDIA_EXPORT AudioProcessingStats {
+  absl::optional<double> echo_return_loss;
+  absl::optional<double> echo_return_loss_enhancement;
+};
+
 }  // namespace media
 
 #endif  // MEDIA_BASE_AUDIO_PROCESSING_H_
diff --git a/media/capture/video/chromeos/camera_device_delegate.cc b/media/capture/video/chromeos/camera_device_delegate.cc
index 8dc31033..6587b22 100644
--- a/media/capture/video/chromeos/camera_device_delegate.cc
+++ b/media/capture/video/chromeos/camera_device_delegate.cc
@@ -280,10 +280,10 @@
 
 CameraDeviceDelegate::CameraDeviceDelegate(
     VideoCaptureDeviceDescriptor device_descriptor,
-    scoped_refptr<CameraHalDelegate> camera_hal_delegate,
+    CameraHalDelegate* camera_hal_delegate,
     scoped_refptr<base::SingleThreadTaskRunner> ipc_task_runner)
     : device_descriptor_(device_descriptor),
-      camera_hal_delegate_(std::move(camera_hal_delegate)),
+      camera_hal_delegate_(camera_hal_delegate),
       ipc_task_runner_(std::move(ipc_task_runner)) {}
 
 CameraDeviceDelegate::~CameraDeviceDelegate() = default;
diff --git a/media/capture/video/chromeos/camera_device_delegate.h b/media/capture/video/chromeos/camera_device_delegate.h
index 4b1708d8..4d495ae3 100644
--- a/media/capture/video/chromeos/camera_device_delegate.h
+++ b/media/capture/video/chromeos/camera_device_delegate.h
@@ -119,7 +119,7 @@
 
   CameraDeviceDelegate(
       VideoCaptureDeviceDescriptor device_descriptor,
-      scoped_refptr<CameraHalDelegate> camera_hal_delegate,
+      CameraHalDelegate* camera_hal_delegate,
       scoped_refptr<base::SingleThreadTaskRunner> ipc_task_runner);
 
   CameraDeviceDelegate(const CameraDeviceDelegate&) = delete;
@@ -248,7 +248,7 @@
   // Current configured resolution of BLOB stream.
   gfx::Size current_blob_resolution_;
 
-  const scoped_refptr<CameraHalDelegate> camera_hal_delegate_;
+  CameraHalDelegate* camera_hal_delegate_;
 
   // Map client type to video capture parameter.
   base::flat_map<ClientType, VideoCaptureParams> chrome_capture_params_;
diff --git a/media/capture/video/chromeos/camera_device_delegate_unittest.cc b/media/capture/video/chromeos/camera_device_delegate_unittest.cc
index c481f80..4f8d6be 100644
--- a/media/capture/video/chromeos/camera_device_delegate_unittest.cc
+++ b/media/capture/video/chromeos/camera_device_delegate_unittest.cc
@@ -146,9 +146,10 @@
         &mock_gpu_memory_buffer_manager_);
     hal_delegate_thread_.Start();
     camera_hal_delegate_ =
-        new CameraHalDelegate(hal_delegate_thread_.task_runner());
-    auto get_camera_info = base::BindRepeating(
-        &CameraHalDelegate::GetCameraInfoFromDeviceId, camera_hal_delegate_);
+        std::make_unique<CameraHalDelegate>(hal_delegate_thread_.task_runner());
+    auto get_camera_info =
+        base::BindRepeating(&CameraHalDelegate::GetCameraInfoFromDeviceId,
+                            base::Unretained(camera_hal_delegate_.get()));
     camera_hal_delegate_->SetCameraModule(
         mock_camera_module_.GetPendingRemote());
   }
@@ -175,7 +176,7 @@
     device_delegate_thread_.Start();
 
     camera_device_delegate_ = std::make_unique<CameraDeviceDelegate>(
-        devices_info[0].descriptor, camera_hal_delegate_,
+        devices_info[0].descriptor, camera_hal_delegate_.get(),
         device_delegate_thread_.task_runner());
   }
 
@@ -516,7 +517,7 @@
 
  protected:
   base::test::TaskEnvironment task_environment_;
-  scoped_refptr<CameraHalDelegate> camera_hal_delegate_;
+  std::unique_ptr<CameraHalDelegate> camera_hal_delegate_;
   std::unique_ptr<CameraDeviceDelegate> camera_device_delegate_;
 
   testing::StrictMock<unittest_internal::MockCameraModule> mock_camera_module_;
diff --git a/media/capture/video/chromeos/camera_hal_delegate.cc b/media/capture/video/chromeos/camera_hal_delegate.cc
index ef67081e..50a22a94 100644
--- a/media/capture/video/chromeos/camera_hal_delegate.cc
+++ b/media/capture/video/chromeos/camera_hal_delegate.cc
@@ -42,12 +42,11 @@
  public:
   LocalCameraClientObserver() = delete;
 
-  explicit LocalCameraClientObserver(
-      scoped_refptr<CameraHalDelegate> camera_hal_delegate,
-      cros::mojom::CameraClientType type,
-      base::UnguessableToken auth_token)
+  explicit LocalCameraClientObserver(CameraHalDelegate* camera_hal_delegate,
+                                     cros::mojom::CameraClientType type,
+                                     base::UnguessableToken auth_token)
       : CameraClientObserver(type, std::move(auth_token)),
-        camera_hal_delegate_(std::move(camera_hal_delegate)) {}
+        camera_hal_delegate_(camera_hal_delegate) {}
 
   LocalCameraClientObserver(const LocalCameraClientObserver&) = delete;
   LocalCameraClientObserver& operator=(const LocalCameraClientObserver&) =
@@ -59,7 +58,7 @@
   }
 
  private:
-  scoped_refptr<CameraHalDelegate> camera_hal_delegate_;
+  CameraHalDelegate* camera_hal_delegate_;
 };
 
 // chromeos::system::StatisticsProvider::IsRunningOnVM() is not available in
@@ -148,17 +147,19 @@
   DETACH_FROM_SEQUENCE(sequence_checker_);
 }
 
-CameraHalDelegate::~CameraHalDelegate() = default;
+CameraHalDelegate::~CameraHalDelegate() {}
 
 bool CameraHalDelegate::RegisterCameraClient() {
   auto* dispatcher = CameraHalDispatcherImpl::GetInstance();
   auto type = cros::mojom::CameraClientType::CHROME;
+  auto client_observer = std::make_unique<LocalCameraClientObserver>(
+      this, type, dispatcher->GetTokenForTrustedClient(type));
   dispatcher->AddClientObserver(
-      std::make_unique<LocalCameraClientObserver>(
-          this, type, dispatcher->GetTokenForTrustedClient(type)),
+      client_observer.get(),
       base::BindOnce(&CameraHalDelegate::OnRegisteredCameraHalClient,
                      base::Unretained(this)));
   camera_hal_client_registered_.Wait();
+  local_client_observers_.emplace_back(std::move(client_observer));
   return authenticated_;
 }
 
@@ -176,14 +177,24 @@
 void CameraHalDelegate::SetCameraModule(
     mojo::PendingRemote<cros::mojom::CameraModule> camera_module) {
   ipc_task_runner_->PostTask(
-      FROM_HERE, base::BindOnce(&CameraHalDelegate::SetCameraModuleOnIpcThread,
-                                this, std::move(camera_module)));
+      FROM_HERE,
+      base::BindOnce(&CameraHalDelegate::SetCameraModuleOnIpcThread,
+                     base::Unretained(this), std::move(camera_module)));
 }
 
 void CameraHalDelegate::Reset() {
   ipc_task_runner_->PostTask(
       FROM_HERE,
-      base::BindOnce(&CameraHalDelegate::ResetMojoInterfaceOnIpcThread, this));
+      base::BindOnce(&CameraHalDelegate::ResetMojoInterfaceOnIpcThread,
+                     base::Unretained(this)));
+
+  std::vector<CameraClientObserver*> observers;
+  for (auto& client_observer : local_client_observers_) {
+    observers.emplace_back(client_observer.get());
+  }
+  auto* dispatcher = CameraHalDispatcherImpl::GetInstance();
+  dispatcher->RemoveClientObservers(observers);
+  local_client_observers_.clear();
 }
 
 std::unique_ptr<VideoCaptureDevice> CameraHalDelegate::CreateDevice(
@@ -490,7 +501,8 @@
   camera_module_has_been_set_.Wait();
   ipc_task_runner_->PostTask(
       FROM_HERE,
-      base::BindOnce(&CameraHalDelegate::OpenDeviceOnIpcThread, this, camera_id,
+      base::BindOnce(&CameraHalDelegate::OpenDeviceOnIpcThread,
+                     base::Unretained(this), camera_id,
                      std::move(device_ops_receiver), std::move(callback)));
 }
 
@@ -533,8 +545,9 @@
   }
   if (camera_module.is_valid()) {
     camera_module_.Bind(std::move(camera_module));
-    camera_module_.set_disconnect_handler(base::BindOnce(
-        &CameraHalDelegate::ResetMojoInterfaceOnIpcThread, this));
+    camera_module_.set_disconnect_handler(
+        base::BindOnce(&CameraHalDelegate::ResetMojoInterfaceOnIpcThread,
+                       base::Unretained(this)));
   }
   camera_module_has_been_set_.Signal();
 }
@@ -568,7 +581,7 @@
   ipc_task_runner_->PostTask(
       FROM_HERE,
       base::BindOnce(&CameraHalDelegate::UpdateBuiltInCameraInfoOnIpcThread,
-                     this));
+                     base::Unretained(this)));
   if (!builtin_camera_info_updated_.TimedWait(kEventWaitTimeoutSecs)) {
     LOG(ERROR) << "Timed out getting camera info";
     return false;
@@ -578,8 +591,9 @@
 
 void CameraHalDelegate::UpdateBuiltInCameraInfoOnIpcThread() {
   DCHECK(ipc_task_runner_->BelongsToCurrentThread());
-  camera_module_->GetNumberOfCameras(base::BindOnce(
-      &CameraHalDelegate::OnGotNumberOfCamerasOnIpcThread, this));
+  camera_module_->GetNumberOfCameras(
+      base::BindOnce(&CameraHalDelegate::OnGotNumberOfCamerasOnIpcThread,
+                     base::Unretained(this)));
 }
 
 void CameraHalDelegate::OnGotNumberOfCamerasOnIpcThread(int32_t num_cameras) {
@@ -598,11 +612,13 @@
   // functions are called.
   camera_module_->SetCallbacksAssociated(
       camera_module_callbacks_.BindNewEndpointAndPassRemote(),
-      base::BindOnce(&CameraHalDelegate::OnSetCallbacksOnIpcThread, this));
+      base::BindOnce(&CameraHalDelegate::OnSetCallbacksOnIpcThread,
+                     base::Unretained(this)));
 
   camera_module_->GetVendorTagOps(
       vendor_tag_ops_delegate_.MakeReceiver(),
-      base::BindOnce(&CameraHalDelegate::OnGotVendorTagOpsOnIpcThread, this));
+      base::BindOnce(&CameraHalDelegate::OnGotVendorTagOpsOnIpcThread,
+                     base::Unretained(this)));
 }
 
 void CameraHalDelegate::OnSetCallbacksOnIpcThread(int32_t result) {
@@ -625,8 +641,8 @@
   for (size_t camera_id = 0; camera_id < num_builtin_cameras_; ++camera_id) {
     GetCameraInfoOnIpcThread(
         camera_id,
-        base::BindOnce(&CameraHalDelegate::OnGotCameraInfoOnIpcThread, this,
-                       camera_id));
+        base::BindOnce(&CameraHalDelegate::OnGotCameraInfoOnIpcThread,
+                       base::Unretained(this), camera_id));
   }
 }
 
@@ -717,8 +733,8 @@
         }
         GetCameraInfoOnIpcThread(
             camera_id,
-            base::BindOnce(&CameraHalDelegate::OnGotCameraInfoOnIpcThread, this,
-                           camera_id));
+            base::BindOnce(&CameraHalDelegate::OnGotCameraInfoOnIpcThread,
+                           base::Unretained(this), camera_id));
       } else {
         LOG(WARNING) << "Ignore duplicated camera_id = " << camera_id;
       }
diff --git a/media/capture/video/chromeos/camera_hal_delegate.h b/media/capture/video/chromeos/camera_hal_delegate.h
index 58c7b0f..e0c5ef1a 100644
--- a/media/capture/video/chromeos/camera_hal_delegate.h
+++ b/media/capture/video/chromeos/camera_hal_delegate.h
@@ -15,6 +15,7 @@
 #include "base/synchronization/waitable_event.h"
 #include "base/task/single_thread_task_runner.h"
 #include "base/threading/thread.h"
+#include "media/capture/video/chromeos/camera_hal_dispatcher_impl.h"
 #include "media/capture/video/chromeos/mojom/camera3.mojom.h"
 #include "media/capture/video/chromeos/mojom/camera_common.mojom.h"
 #include "media/capture/video/chromeos/vendor_tag_ops_delegate.h"
@@ -35,18 +36,21 @@
 // process on Chrome OS to access the module-level camera functionalities such
 // as camera device info look-up and opening camera devices.
 //
-// CameraHalDelegate is refcounted because VideoCaptureDeviceFactoryChromeOS and
-// CameraDeviceDelegate both need to reference CameraHalDelegate, and
-// VideoCaptureDeviceFactoryChromeOS may be destroyed while CameraDeviceDelegate
-// is still alive.
+// CameraHalDelegate is owned by VideoCaptureDeviceFactoryChromeOS.
+// VideoCaptureDeviceChromeOSDelegate and CameraDeviceDelegate have
+// CameraHalDelegate's raw pointer.
+// When VideoCaptureDeviceFactoryChromeOS destroys,
+// CameraHalDelegate destroys VideoCaptureDeviceChromeOSDelegate and
+// VideoCaptureDeviceChromeOSDelegate destroys CameraDeviceDelegate.
 class CAPTURE_EXPORT CameraHalDelegate final
-    : public base::RefCountedThreadSafe<CameraHalDelegate>,
-      public cros::mojom::CameraModuleCallbacks {
+    : public cros::mojom::CameraModuleCallbacks {
  public:
   // All the Mojo IPC operations happen on |ipc_task_runner|.
   explicit CameraHalDelegate(
       scoped_refptr<base::SingleThreadTaskRunner> ipc_task_runner);
 
+  ~CameraHalDelegate() final;
+
   CameraHalDelegate(const CameraHalDelegate&) = delete;
   CameraHalDelegate& operator=(const CameraHalDelegate&) = delete;
 
@@ -98,10 +102,6 @@
   void DisableAllVirtualDevices();
 
  private:
-  friend class base::RefCountedThreadSafe<CameraHalDelegate>;
-
-  ~CameraHalDelegate() final;
-
   void OnRegisteredCameraHalClient(int32_t result);
 
   void GetSupportedFormats(const cros::mojom::CameraInfoPtr& camera_info,
@@ -224,6 +224,8 @@
   // A map from camera id to corresponding delegate instance.
   base::flat_map<int, std::unique_ptr<VideoCaptureDeviceChromeOSDelegate>>
       vcd_delegate_map_;
+
+  std::vector<std::unique_ptr<CameraClientObserver>> local_client_observers_;
 };
 
 }  // namespace media
diff --git a/media/capture/video/chromeos/camera_hal_delegate_unittest.cc b/media/capture/video/chromeos/camera_hal_delegate_unittest.cc
index e0a740f..f9efb92c 100644
--- a/media/capture/video/chromeos/camera_hal_delegate_unittest.cc
+++ b/media/capture/video/chromeos/camera_hal_delegate_unittest.cc
@@ -48,7 +48,7 @@
         &mock_gpu_memory_buffer_manager_);
     hal_delegate_thread_.Start();
     camera_hal_delegate_ =
-        new CameraHalDelegate(hal_delegate_thread_.task_runner());
+        std::make_unique<CameraHalDelegate>(hal_delegate_thread_.task_runner());
     camera_hal_delegate_->SetCameraModule(
         mock_camera_module_.GetPendingRemote());
   }
@@ -65,7 +65,7 @@
 
  protected:
   base::test::TaskEnvironment task_environment_;
-  scoped_refptr<CameraHalDelegate> camera_hal_delegate_;
+  std::unique_ptr<CameraHalDelegate> camera_hal_delegate_;
   testing::StrictMock<unittest_internal::MockCameraModule> mock_camera_module_;
   testing::StrictMock<unittest_internal::MockVendorTagOps> mock_vendor_tag_ops_;
   unittest_internal::MockGpuMemoryBufferManager mock_gpu_memory_buffer_manager_;
diff --git a/media/capture/video/chromeos/camera_hal_dispatcher_impl.cc b/media/capture/video/chromeos/camera_hal_dispatcher_impl.cc
index aef8354f..3a3ca47 100644
--- a/media/capture/video/chromeos/camera_hal_dispatcher_impl.cc
+++ b/media/capture/video/chromeos/camera_hal_dispatcher_impl.cc
@@ -265,8 +265,7 @@
 
   jda_factory_ = std::move(jda_factory);
   jea_factory_ = std::move(jea_factory);
-  base::WaitableEvent started(base::WaitableEvent::ResetPolicy::MANUAL,
-                              base::WaitableEvent::InitialState::NOT_SIGNALED);
+  base::WaitableEvent started;
   // It's important we generate tokens before creating the socket, because once
   // it is available, everyone connecting to socket would start fetching
   // tokens.
@@ -292,15 +291,15 @@
 }
 
 void CameraHalDispatcherImpl::AddClientObserver(
-    std::unique_ptr<CameraClientObserver> observer,
+    CameraClientObserver* observer,
     base::OnceCallback<void(int32_t)> result_callback) {
   // If |proxy_thread_| fails to start in Start() then CameraHalDelegate will
   // not be created, and this function will not be called.
   DCHECK(proxy_thread_.IsRunning());
-  proxy_thread_.task_runner()->PostTask(
+  proxy_task_runner_->PostTask(
       FROM_HERE,
       base::BindOnce(&CameraHalDispatcherImpl::AddClientObserverOnProxyThread,
-                     base::Unretained(this), std::move(observer),
+                     base::Unretained(this), observer,
                      std::move(result_callback)));
 }
 
@@ -366,7 +365,7 @@
 CameraHalDispatcherImpl::~CameraHalDispatcherImpl() {
   VLOG(1) << "Stopping CameraHalDispatcherImpl...";
   if (proxy_thread_.IsRunning()) {
-    proxy_thread_.task_runner()->PostTask(
+    proxy_task_runner_->PostTask(
         FROM_HERE, base::BindOnce(&CameraHalDispatcherImpl::StopOnProxyThread,
                                   base::Unretained(this)));
     proxy_thread_.Stop();
@@ -410,8 +409,8 @@
 
   // Set up the Mojo channels for clients which registered before the server
   // registers.
-  for (auto& client_observer : client_observers_) {
-    EstablishMojoChannel(client_observer.get());
+  for (auto* client_observer : client_observers_) {
+    EstablishMojoChannel(client_observer);
   }
 }
 
@@ -669,12 +668,12 @@
   client_observer->client().set_disconnect_handler(base::BindOnce(
       &CameraHalDispatcherImpl::OnCameraHalClientConnectionError,
       base::Unretained(this), base::Unretained(client_observer.get())));
-  AddClientObserverOnProxyThread(std::move(client_observer),
-                                 std::move(callback));
+  AddClientObserverOnProxyThread(client_observer.get(), std::move(callback));
+  mojo_client_observers_[client_observer.get()] = std::move(client_observer);
 }
 
 void CameraHalDispatcherImpl::AddClientObserverOnProxyThread(
-    std::unique_ptr<CameraClientObserver> observer,
+    CameraClientObserver* observer,
     base::OnceCallback<void(int32_t)> result_callback) {
   DCHECK(proxy_task_runner_->BelongsToCurrentThread());
   if (!observer->Authenticate(&token_manager_)) {
@@ -683,9 +682,9 @@
     return;
   }
   if (camera_hal_server_) {
-    EstablishMojoChannel(observer.get());
+    EstablishMojoChannel(observer);
   }
-  client_observers_.insert(std::move(observer));
+  client_observers_.insert(observer);
   std::move(result_callback).Run(0);
   CAMERA_LOG(EVENT) << "Camera HAL client registered";
 }
@@ -739,6 +738,17 @@
 void CameraHalDispatcherImpl::OnCameraHalClientConnectionError(
     CameraClientObserver* client_observer) {
   DCHECK(proxy_task_runner_->BelongsToCurrentThread());
+  CleanupClientOnProxyThread(client_observer);
+  if (mojo_client_observers_.find(client_observer) !=
+      mojo_client_observers_.end()) {
+    mojo_client_observers_[client_observer].reset();
+    mojo_client_observers_.erase(client_observer);
+  }
+}
+
+void CameraHalDispatcherImpl::CleanupClientOnProxyThread(
+    CameraClientObserver* client_observer) {
+  DCHECK(proxy_task_runner_->BelongsToCurrentThread());
   base::AutoLock lock(opened_camera_id_map_lock_);
   auto camera_client_type = client_observer->GetType();
   auto opened_it = opened_camera_id_map_.find(camera_client_type);
@@ -761,6 +771,31 @@
   }
 }
 
+void CameraHalDispatcherImpl::RemoveClientObserversOnProxyThread(
+    std::vector<CameraClientObserver*> client_observers,
+    base::WaitableEvent* removed) {
+  DCHECK(proxy_task_runner_->BelongsToCurrentThread());
+  for (auto* client_observer : client_observers) {
+    CleanupClientOnProxyThread(client_observer);
+  }
+  removed->Signal();
+}
+
+void CameraHalDispatcherImpl::RemoveClientObservers(
+    std::vector<CameraClientObserver*> client_observers) {
+  if (client_observers.empty())
+    return;
+  DCHECK(proxy_thread_.IsRunning());
+  base::WaitableEvent removed;
+  proxy_task_runner_->PostTask(
+      FROM_HERE,
+      base::BindOnce(
+          &CameraHalDispatcherImpl::RemoveClientObserversOnProxyThread,
+          base::Unretained(this), client_observers,
+          base::Unretained(&removed)));
+  removed.Wait();
+}
+
 void CameraHalDispatcherImpl::RegisterSensorClientWithTokenOnUIThread(
     mojo::PendingRemote<chromeos::sensors::mojom::SensorHalClient> client,
     const base::UnguessableToken& auth_token,
@@ -801,6 +836,7 @@
   }
   // Close |cancel_pipe_| to quit the loop in WaitForIncomingConnection.
   cancel_pipe_.reset();
+  mojo_client_observers_.clear();
   client_observers_.clear();
   camera_hal_server_callbacks_.reset();
   camera_hal_server_.reset();
diff --git a/media/capture/video/chromeos/camera_hal_dispatcher_impl.h b/media/capture/video/chromeos/camera_hal_dispatcher_impl.h
index f86e523..1dc7339c 100644
--- a/media/capture/video/chromeos/camera_hal_dispatcher_impl.h
+++ b/media/capture/video/chromeos/camera_hal_dispatcher_impl.h
@@ -27,7 +27,6 @@
 #include "media/capture/capture_export.h"
 #include "media/capture/video/chromeos/mojom/cros_camera_service.mojom.h"
 #include "media/capture/video/chromeos/token_manager.h"
-#include "media/capture/video/chromeos/video_capture_device_factory_chromeos.h"
 #include "media/capture/video/video_capture_device_factory.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
@@ -46,6 +45,8 @@
 
 using MojoJpegEncodeAcceleratorFactoryCB = base::RepeatingCallback<void(
     mojo::PendingReceiver<chromeos_camera::mojom::JpegEncodeAccelerator>)>;
+using MojoMjpegDecodeAcceleratorFactoryCB = base::RepeatingCallback<void(
+    mojo::PendingReceiver<chromeos_camera::mojom::MjpegDecodeAccelerator>)>;
 
 class CAPTURE_EXPORT CameraClientObserver {
  public:
@@ -118,8 +119,8 @@
 // establish direct Mojo connections between the CameraHalServer and the
 // CameraHalClients.
 //
-// For general documentation about the CameraHalDispater Mojo interface see the
-// comments in mojo/cros_camera_service.mojom.
+// For general documentation about the CameraHalDispatcher Mojo interface see
+// the comments in mojo/cros_camera_service.mojom.
 //
 // On ChromeOS the video capture service must run in the browser process,
 // because parts of the code depend on global objects that are only available in
@@ -139,7 +140,7 @@
   bool Start(MojoMjpegDecodeAcceleratorFactoryCB jda_factory,
              MojoJpegEncodeAcceleratorFactoryCB jea_factory);
 
-  void AddClientObserver(std::unique_ptr<CameraClientObserver> observer,
+  void AddClientObserver(CameraClientObserver* observer,
                          base::OnceCallback<void(int32_t)> result_callback);
 
   bool IsStarted();
@@ -152,6 +153,11 @@
   // being destroyed.
   void RemoveActiveClientObserver(CameraActiveClientObserver* observer);
 
+  // Removes the observers after a call by the subject and returns after
+  // the observers are removed.
+  void RemoveClientObservers(
+      std::vector<CameraClientObserver*> client_observers);
+
   // Adds an observer to get notified when the camera privacy switch status
   // changed. Please note that for some devices, the signal will only be
   // detectable when the camera is currently on due to hardware limitations.
@@ -231,7 +237,7 @@
       RegisterClientWithTokenCallback callback);
 
   void AddClientObserverOnProxyThread(
-      std::unique_ptr<CameraClientObserver> observer,
+      CameraClientObserver* observer,
       base::OnceCallback<void(int32_t)> result_callback);
 
   void EstablishMojoChannel(CameraClientObserver* client_observer);
@@ -243,6 +249,12 @@
   void OnCameraHalServerConnectionError();
   void OnCameraHalClientConnectionError(CameraClientObserver* client);
 
+  // Cleans up everything about the observer
+  void CleanupClientOnProxyThread(CameraClientObserver* client_observer);
+  void RemoveClientObserversOnProxyThread(
+      std::vector<CameraClientObserver*> client_observers,
+      base::WaitableEvent* removed);
+
   void RegisterSensorClientWithTokenOnUIThread(
       mojo::PendingRemote<chromeos::sensors::mojom::SensorHalClient> client,
       const base::UnguessableToken& auth_token,
@@ -272,8 +284,7 @@
       camera_hal_server_callbacks_;
   FailedCameraHalServerCallbacks failed_camera_hal_server_callbacks_;
 
-  std::set<std::unique_ptr<CameraClientObserver>, base::UniquePtrComparator>
-      client_observers_;
+  std::set<CameraClientObserver*> client_observers_;
 
   MojoMjpegDecodeAcceleratorFactoryCB jda_factory_;
 
@@ -295,6 +306,9 @@
   scoped_refptr<base::ObserverListThreadSafe<CameraPrivacySwitchObserver>>
       privacy_switch_observers_;
 
+  std::map<CameraClientObserver*, std::unique_ptr<CameraClientObserver>>
+      mojo_client_observers_;
+
   base::WeakPtrFactory<CameraHalDispatcherImpl> weak_factory_{this};
 };
 
diff --git a/media/capture/video/chromeos/video_capture_device_chromeos_delegate.cc b/media/capture/video/chromeos/video_capture_device_chromeos_delegate.cc
index 137afb9f..6ae67e43 100644
--- a/media/capture/video/chromeos/video_capture_device_chromeos_delegate.cc
+++ b/media/capture/video/chromeos/video_capture_device_chromeos_delegate.cc
@@ -106,10 +106,10 @@
 VideoCaptureDeviceChromeOSDelegate::VideoCaptureDeviceChromeOSDelegate(
     scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner,
     const VideoCaptureDeviceDescriptor& device_descriptor,
-    scoped_refptr<CameraHalDelegate> camera_hal_delegate,
+    CameraHalDelegate* camera_hal_delegate,
     base::OnceClosure cleanup_callback)
     : device_descriptor_(device_descriptor),
-      camera_hal_delegate_(std::move(camera_hal_delegate)),
+      camera_hal_delegate_(camera_hal_delegate),
       capture_task_runner_(base::ThreadTaskRunnerHandle::Get()),
       camera_device_ipc_thread_(std::string("CameraDeviceIpcThread") +
                                 device_descriptor.device_id),
diff --git a/media/capture/video/chromeos/video_capture_device_chromeos_delegate.h b/media/capture/video/chromeos/video_capture_device_chromeos_delegate.h
index 02d2c3d..a0225c0 100644
--- a/media/capture/video/chromeos/video_capture_device_chromeos_delegate.h
+++ b/media/capture/video/chromeos/video_capture_device_chromeos_delegate.h
@@ -38,7 +38,7 @@
   VideoCaptureDeviceChromeOSDelegate(
       scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner,
       const VideoCaptureDeviceDescriptor& device_descriptor,
-      scoped_refptr<CameraHalDelegate> camera_hal_delegate,
+      CameraHalDelegate* camera_hal_delegate,
       base::OnceClosure cleanup_callback);
 
   VideoCaptureDeviceChromeOSDelegate(
@@ -76,7 +76,7 @@
 
   // A reference to the CameraHalDelegate instance in the VCD factory.  This is
   // used by AllocateAndStart to query camera info and create the camera device.
-  const scoped_refptr<CameraHalDelegate> camera_hal_delegate_;
+  CameraHalDelegate* camera_hal_delegate_;
 
   // A reference to the thread that all the VideoCaptureDevice interface methods
   // are expected to be called on.
diff --git a/media/capture/video/chromeos/video_capture_device_factory_chromeos.cc b/media/capture/video/chromeos/video_capture_device_factory_chromeos.cc
index c340d37..08ba87d 100644
--- a/media/capture/video/chromeos/video_capture_device_factory_chromeos.cc
+++ b/media/capture/video/chromeos/video_capture_device_factory_chromeos.cc
@@ -34,6 +34,7 @@
 
   camera_hal_delegate_->Reset();
   camera_hal_ipc_thread_.Stop();
+  camera_hal_delegate_.reset();
 }
 
 VideoCaptureErrorOrDevice VideoCaptureDeviceFactoryChromeOS::CreateDevice(
@@ -88,7 +89,7 @@
   }
 
   camera_hal_delegate_ =
-      new CameraHalDelegate(camera_hal_ipc_thread_.task_runner());
+      std::make_unique<CameraHalDelegate>(camera_hal_ipc_thread_.task_runner());
   if (!camera_hal_delegate_->RegisterCameraClient()) {
     LOG(ERROR) << "Failed to register camera client";
     return false;
diff --git a/media/capture/video/chromeos/video_capture_device_factory_chromeos.h b/media/capture/video/chromeos/video_capture_device_factory_chromeos.h
index 00e1543..5fad611 100644
--- a/media/capture/video/chromeos/video_capture_device_factory_chromeos.h
+++ b/media/capture/video/chromeos/video_capture_device_factory_chromeos.h
@@ -56,7 +56,7 @@
   // created on the thread on which Init is called.  All the Mojo communication
   // that |camera_hal_delegate_| issues and receives must be sequenced through
   // |camera_hal_ipc_thread_|.
-  scoped_refptr<CameraHalDelegate> camera_hal_delegate_;
+  std::unique_ptr<CameraHalDelegate> camera_hal_delegate_;
 
   bool initialized_;
 
diff --git a/media/capture/video/win/video_capture_device_mf_win.cc b/media/capture/video/win/video_capture_device_mf_win.cc
index b89fd7e..45e7a285 100644
--- a/media/capture/video/win/video_capture_device_mf_win.cc
+++ b/media/capture/video/win/video_capture_device_mf_win.cc
@@ -1121,6 +1121,8 @@
   // event. For the lack of any other events indicating success, we have to wait
   // for the first video frame to arrive before sending our |OnStarted| event to
   // |client_|.
+  // We still need to wait for MF_CAPTURE_ENGINE_PREVIEW_STARTED event to ensure
+  // that we won't call StopPreview before the preview is started.
   has_sent_on_started_to_client_ = false;
   hr = engine_->StartPreview();
   if (FAILED(hr)) {
@@ -1129,11 +1131,14 @@
     return;
   }
 
+  hr = WaitOnCaptureEvent(MF_CAPTURE_ENGINE_PREVIEW_STARTED);
+  if (SUCCEEDED(hr)) {
+    is_started_ = true;
+  }
+
   selected_video_capability_ =
       std::make_unique<CapabilityWin>(best_match_video_capability);
 
-  is_started_ = true;
-
   base::UmaHistogramEnumeration(
       "Media.VideoCapture.Win.Device.InternalPixelFormat",
       best_match_video_capability.source_pixel_format,
@@ -1152,8 +1157,12 @@
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   base::AutoLock lock(lock_);
 
-  if (is_started_ && engine_)
-    engine_->StopPreview();
+  if (is_started_ && engine_) {
+    HRESULT hr = engine_->StopPreview();
+    if (SUCCEEDED(hr)) {
+      WaitOnCaptureEvent(MF_CAPTURE_ENGINE_PREVIEW_STOPPED);
+    }
+  }
   is_started_ = false;
 
   client_.reset();
@@ -1738,16 +1747,22 @@
   media_event->GetStatus(&hr);
   media_event->GetExtendedType(&capture_event_guid);
 
-  // TODO(http://crbug.com/1093521): Add cases for Start
-  // MF_CAPTURE_ENGINE_PREVIEW_STARTED and MF_CAPTURE_ENGINE_PREVIEW_STOPPED
   // When MF_CAPTURE_ENGINE_ERROR is returned the captureengine object is no
   // longer valid.
   if (capture_event_guid == MF_CAPTURE_ENGINE_ERROR || FAILED(hr)) {
+    last_error_hr_ = hr;
     capture_error_.Signal();
     // There should always be a valid error
     hr = SUCCEEDED(hr) ? E_UNEXPECTED : hr;
-  } else if (capture_event_guid == MF_CAPTURE_ENGINE_INITIALIZED) {
-    capture_initialize_.Signal();
+  } else {
+    if (capture_event_guid == MF_CAPTURE_ENGINE_INITIALIZED) {
+      capture_initialize_.Signal();
+    } else if (capture_event_guid == MF_CAPTURE_ENGINE_PREVIEW_STOPPED) {
+      capture_stopped_.Signal();
+    } else if (capture_event_guid == MF_CAPTURE_ENGINE_PREVIEW_STARTED) {
+      capture_started_.Signal();
+    }
+    return;
   }
 
   // Lock is taken after events are signalled, because if the capture
@@ -1824,10 +1839,12 @@
   HRESULT hr = S_OK;
   HANDLE events[] = {nullptr, capture_error_.handle()};
 
-  // TODO(http://crbug.com/1093521): Add cases for Start
-  // MF_CAPTURE_ENGINE_PREVIEW_STARTED and MF_CAPTURE_ENGINE_PREVIEW_STOPPED
   if (capture_event_guid == MF_CAPTURE_ENGINE_INITIALIZED) {
     events[0] = capture_initialize_.handle();
+  } else if (capture_event_guid == MF_CAPTURE_ENGINE_PREVIEW_STOPPED) {
+    events[0] = capture_stopped_.handle();
+  } else if (capture_event_guid == MF_CAPTURE_ENGINE_PREVIEW_STARTED) {
+    events[0] = capture_started_.handle();
   } else {
     // no registered event handle for the event requested
     hr = E_NOTIMPL;
@@ -1845,7 +1862,10 @@
       LogError(FROM_HERE, hr);
       break;
     default:
-      hr = E_UNEXPECTED;
+      hr = last_error_hr_;
+      if (SUCCEEDED(hr)) {
+        hr = MF_E_UNEXPECTED;
+      }
       LogError(FROM_HERE, hr);
       break;
   }
diff --git a/media/capture/video/win/video_capture_device_mf_win.h b/media/capture/video/win/video_capture_device_mf_win.h
index b788812d..1c2ec4e 100644
--- a/media/capture/video/win/video_capture_device_mf_win.h
+++ b/media/capture/video/win/video_capture_device_mf_win.h
@@ -181,6 +181,9 @@
   base::queue<TakePhotoCallback> video_stream_take_photo_callbacks_;
   base::WaitableEvent capture_initialize_;
   base::WaitableEvent capture_error_;
+  base::WaitableEvent capture_stopped_;
+  base::WaitableEvent capture_started_;
+  HRESULT last_error_hr_ = S_OK;
   scoped_refptr<DXGIDeviceManager> dxgi_device_manager_;
   absl::optional<int> camera_rotation_;
   VideoCaptureParams params_;
diff --git a/media/capture/video/win/video_capture_device_mf_win_unittest.cc b/media/capture/video/win/video_capture_device_mf_win_unittest.cc
index 1c0eefb..99b2e57d 100644
--- a/media/capture/video/win/video_capture_device_mf_win_unittest.cc
+++ b/media/capture/video/win/video_capture_device_mf_win_unittest.cc
@@ -568,6 +568,7 @@
 
   IFACEMETHODIMP StartPreview(void) override {
     OnStartPreview();
+    FireCaptureEvent(MF_CAPTURE_ENGINE_PREVIEW_STARTED, S_OK);
     return S_OK;
   }
 
@@ -575,6 +576,7 @@
 
   IFACEMETHODIMP StopPreview(void) override {
     OnStopPreview();
+    FireCaptureEvent(MF_CAPTURE_ENGINE_PREVIEW_STOPPED, S_OK);
     return S_OK;
   }
 
diff --git a/media/gpu/windows/media_foundation_video_encode_accelerator_win.cc b/media/gpu/windows/media_foundation_video_encode_accelerator_win.cc
index ac4cca3..7cd58a2 100644
--- a/media/gpu/windows/media_foundation_video_encode_accelerator_win.cc
+++ b/media/gpu/windows/media_foundation_video_encode_accelerator_win.cc
@@ -990,7 +990,8 @@
       return MF_E_INVALID_STREAM_DATA;
     }
 
-    if (gmb->GetType() == gfx::GpuMemoryBufferType::DXGI_SHARED_HANDLE) {
+    if (gmb->GetType() == gfx::GpuMemoryBufferType::DXGI_SHARED_HANDLE &&
+        dxgi_device_manager_ != nullptr) {
       return PopulateInputSampleBufferGpu(std::move(frame));
     }
 
@@ -1087,6 +1088,7 @@
   DCHECK(frame->HasGpuMemoryBuffer());
   DCHECK_EQ(frame->GetGpuMemoryBuffer()->GetType(),
             gfx::GpuMemoryBufferType::DXGI_SHARED_HANDLE);
+  DCHECK(dxgi_device_manager_);
 
   gfx::GpuMemoryBufferHandle buffer_handle =
       frame->GetGpuMemoryBuffer()->CloneHandle();
diff --git a/media/mojo/mojom/BUILD.gn b/media/mojo/mojom/BUILD.gn
index dbac61f9..648df0e 100644
--- a/media/mojo/mojom/BUILD.gn
+++ b/media/mojo/mojom/BUILD.gn
@@ -167,6 +167,10 @@
           mojom = "media.mojom.AudioProcessingSettings"
           cpp = "::media::AudioProcessingSettings"
         },
+        {
+          mojom = "media.mojom.AudioProcessingStats"
+          cpp = "::media::AudioProcessingStats"
+        },
       ]
       traits_headers = [ "audio_processing_mojom_traits.h" ]
       traits_sources = [ "audio_processing_mojom_traits.cc" ]
diff --git a/media/mojo/mojom/audio_processing.mojom b/media/mojo/mojom/audio_processing.mojom
index ca7756e..b5ede7e8 100644
--- a/media/mojo/mojom/audio_processing.mojom
+++ b/media/mojo/mojom/audio_processing.mojom
@@ -4,7 +4,19 @@
 
 module media.mojom;
 
-import "mojo/public/mojom/base/file.mojom";
+// Stats from software audio processing in the audio service. Are passed upon
+// request from the renderer, see AudioProcessorControls::GetStats() in this
+// file.
+struct AudioProcessingStats {
+
+  // TODO(https://crbug.com/657632): Numeric values cannot be optional, so add
+  // flags for each of them.
+  bool has_echo_return_loss;
+  double echo_return_loss;
+
+  bool has_echo_return_loss_enhancement;
+  double echo_return_loss_enhancement;
+};
 
 // Settings for the software audio processing performed in the audio service.
 // The settings are determined in the renderer from media constraints passed by
@@ -23,10 +35,23 @@
 };
 
 // This interface is hosted in the audio service and called from the renderer.
-// It's only used when the audio processing is performed in the audio service.
+// It is only used when the audio processing is performed in the audio service.
 interface AudioProcessorControls {
-  // TODO(crbug.com/1284652): Add methods once this interface is plumbed through
-  // all layers.
+
+  // Request the latest stats from the audio processor. At the farthest level,
+  // this is triggered by calls from JavaScript, through some levels of
+  // indirection. (See: https://www.w3.org/TR/webrtc-stats/). Since there are no
+  // guarantees in the standard about the rate at which stats change, it is
+  // reasonable to let multiple user-facing calls result in just one call to
+  // this function.
+  GetStats() => (AudioProcessingStats stats);
+
+  // Sets a preferred number of capture audio channels. This allows the audio
+  // processor to avoid unnecessary computational load when the number of input
+  // audio channels exceeds what is used by the input consumers.
+  // Values less than 1 and greater than the input stream capacity are adjusted
+  // to the nearest valid number.
+  SetPreferredNumCaptureChannels(int32 num_preferred_channels);
 };
 
 struct AudioProcessingConfig {
diff --git a/media/mojo/mojom/audio_processing_mojom_traits.cc b/media/mojo/mojom/audio_processing_mojom_traits.cc
index 2f4a860..62d57ac1 100644
--- a/media/mojo/mojom/audio_processing_mojom_traits.cc
+++ b/media/mojo/mojom/audio_processing_mojom_traits.cc
@@ -5,6 +5,22 @@
 #include "media/mojo/mojom/audio_processing_mojom_traits.h"
 
 namespace mojo {
+namespace {
+// Deserializes has_field and field into a absl::optional.
+#define DESERIALIZE_INTO_OPT(field) \
+  if (input.has_##field())          \
+  out_stats->field = input.field()
+}  // namespace
+
+// static
+bool StructTraits<media::mojom::AudioProcessingStatsDataView,
+                  media::AudioProcessingStats>::
+    Read(media::mojom::AudioProcessingStatsDataView input,
+         media::AudioProcessingStats* out_stats) {
+  DESERIALIZE_INTO_OPT(echo_return_loss);
+  DESERIALIZE_INTO_OPT(echo_return_loss_enhancement);
+  return true;
+}
 
 // static
 bool StructTraits<media::mojom::AudioProcessingSettingsDataView,
diff --git a/media/mojo/mojom/audio_processing_mojom_traits.h b/media/mojo/mojom/audio_processing_mojom_traits.h
index 8f42535..6bd436f4 100644
--- a/media/mojo/mojom/audio_processing_mojom_traits.h
+++ b/media/mojo/mojom/audio_processing_mojom_traits.h
@@ -5,12 +5,37 @@
 #define MEDIA_MOJO_MOJOM_AUDIO_PROCESSING_MOJOM_TRAITS_H_
 
 #include "media/base/audio_processing.h"
+
 #include "media/mojo/mojom/audio_processing.mojom.h"
 #include "mojo/public/cpp/bindings/struct_traits.h"
 
 namespace mojo {
 
 template <>
+struct StructTraits<media::mojom::AudioProcessingStatsDataView,
+                    media::AudioProcessingStats> {
+ public:
+  static bool has_echo_return_loss(const media::AudioProcessingStats& input) {
+    return input.echo_return_loss.has_value();
+  }
+  static double echo_return_loss(const media::AudioProcessingStats& input) {
+    return input.echo_return_loss.value_or(0.0);
+  }
+
+  static bool has_echo_return_loss_enhancement(
+      const media::AudioProcessingStats& input) {
+    return input.echo_return_loss_enhancement.has_value();
+  }
+  static double echo_return_loss_enhancement(
+      const media::AudioProcessingStats& input) {
+    return input.echo_return_loss_enhancement.value_or(0.0);
+  }
+
+  static bool Read(media::mojom::AudioProcessingStatsDataView input,
+                   media::AudioProcessingStats* out_stats);
+};
+
+template <>
 struct StructTraits<media::mojom::AudioProcessingSettingsDataView,
                     media::AudioProcessingSettings> {
  public:
diff --git a/media/mojo/mojom/audio_processing_mojom_traits_unittest.cc b/media/mojo/mojom/audio_processing_mojom_traits_unittest.cc
index 0e10cf1..c95c6330 100644
--- a/media/mojo/mojom/audio_processing_mojom_traits_unittest.cc
+++ b/media/mojo/mojom/audio_processing_mojom_traits_unittest.cc
@@ -11,21 +11,7 @@
 
 namespace media {
 
-namespace {
-
-class AudioProcessingMojomTraitsTest : public testing::Test {
- public:
-  AudioProcessingMojomTraitsTest() = default;
-
-  AudioProcessingMojomTraitsTest(const AudioProcessingMojomTraitsTest&) =
-      delete;
-  AudioProcessingMojomTraitsTest& operator=(
-      const AudioProcessingMojomTraitsTest&) = delete;
-};
-
-}  // namespace
-
-TEST_F(AudioProcessingMojomTraitsTest, AudioProcessingSettings) {
+TEST(AudioProcessingMojomTraitsTest, AudioProcessingSettings) {
   AudioProcessingSettings settings_in;
   AudioProcessingSettings settings_out;
 
@@ -54,4 +40,29 @@
   EXPECT_EQ(settings_in, settings_out);
 }
 
+TEST(AudioProcessingMojomTraitsTest, AudioProcessingStats) {
+  AudioProcessingStats stats_in;
+  AudioProcessingStats stats_out;
+
+  mojo::test::SerializeAndDeserialize<media::mojom::AudioProcessingStats>(
+      stats_in, stats_out);
+
+  EXPECT_EQ(stats_in.echo_return_loss, stats_out.echo_return_loss);
+  EXPECT_EQ(stats_in.echo_return_loss_enhancement,
+            stats_out.echo_return_loss_enhancement);
+
+  // Set all fields to non-default values.
+  ASSERT_FALSE(stats_in.echo_return_loss);
+  ASSERT_FALSE(stats_in.echo_return_loss_enhancement);
+  stats_in.echo_return_loss = 1.0;
+  stats_in.echo_return_loss_enhancement = 2.0;
+
+  mojo::test::SerializeAndDeserialize<media::mojom::AudioProcessingStats>(
+      stats_in, stats_out);
+
+  EXPECT_EQ(stats_in.echo_return_loss, stats_out.echo_return_loss);
+  EXPECT_EQ(stats_in.echo_return_loss_enhancement,
+            stats_out.echo_return_loss_enhancement);
+}
+
 }  // namespace media
diff --git a/media/mojo/mojom/video_frame_metadata_mojom_traits.h b/media/mojo/mojom/video_frame_metadata_mojom_traits.h
index 16a24aaa..b87508d 100644
--- a/media/mojo/mojom/video_frame_metadata_mojom_traits.h
+++ b/media/mojo/mojom/video_frame_metadata_mojom_traits.h
@@ -160,6 +160,8 @@
                    media::VideoFrameMetadata* output);
 };
 
+#undef GENERATE_OPT_SERIALIZATION
+
 }  // namespace mojo
 
 #endif  // MEDIA_MOJO_MOJOM_VIDEO_FRAME_METADATA_MOJOM_TRAITS_H_
diff --git a/media/webrtc/audio_processor.cc b/media/webrtc/audio_processor.cc
index c1456671..338f704 100644
--- a/media/webrtc/audio_processor.cc
+++ b/media/webrtc/audio_processor.cc
@@ -380,7 +380,8 @@
 }
 
 webrtc::AudioProcessingStats AudioProcessor::GetStats() {
-  DCHECK(webrtc_audio_processing_);
+  if (!webrtc_audio_processing_)
+    return {};
   return webrtc_audio_processing_->GetStatistics();
 }
 
diff --git a/media/webrtc/audio_processor.h b/media/webrtc/audio_processor.h
index 4e60844..572ca95 100644
--- a/media/webrtc/audio_processor.h
+++ b/media/webrtc/audio_processor.h
@@ -123,8 +123,7 @@
   // Stops any ongoing aecdump.
   void OnStopDump();
 
-  // Returns statistics from the WebRTC audio processing module. Requires that
-  // WebRTC audio processing is enabled.
+  // Returns any available statistics from the WebRTC audio processing module.
   // May be called on any thread.
   webrtc::AudioProcessingStats GetStats();
 
diff --git a/mojo/core/data_pipe_consumer_dispatcher.cc b/mojo/core/data_pipe_consumer_dispatcher.cc
index 4e76209..05b95c54 100644
--- a/mojo/core/data_pipe_consumer_dispatcher.cc
+++ b/mojo/core/data_pipe_consumer_dispatcher.cc
@@ -14,6 +14,7 @@
 #include "base/bind.h"
 #include "base/logging.h"
 #include "base/memory/ref_counted.h"
+#include "base/numerics/checked_math.h"
 #include "base/trace_event/trace_event.h"
 #include "mojo/core/core.h"
 #include "mojo/core/data_pipe_control_message.h"
@@ -583,8 +584,10 @@
         TRACE_EVENT0("ipc",
                      "DataPipeConsumerDispatcher received DATA_WAS_WRITTEN");
 
-        if (static_cast<size_t>(bytes_available_) + m->num_bytes >
-            options_.capacity_num_bytes) {
+        uint32_t new_bytes_available;
+        if (!base::CheckAdd(bytes_available_, m->num_bytes)
+                 .AssignIfValid(&new_bytes_available) ||
+            new_bytes_available > options_.capacity_num_bytes) {
           DLOG(ERROR) << "Producer claims to have written too many bytes.";
           peer_closed_ = true;
           break;
@@ -594,7 +597,7 @@
                  << m->num_bytes << " bytes were written. [control_port="
                  << control_port_.name() << "]";
 
-        bytes_available_ += m->num_bytes;
+        bytes_available_ = new_bytes_available;
       }
     } while (message_event);
   }
diff --git a/mojo/core/data_pipe_producer_dispatcher.cc b/mojo/core/data_pipe_producer_dispatcher.cc
index 65f7261..f4281d9 100644
--- a/mojo/core/data_pipe_producer_dispatcher.cc
+++ b/mojo/core/data_pipe_producer_dispatcher.cc
@@ -12,6 +12,7 @@
 #include "base/bind.h"
 #include "base/logging.h"
 #include "base/memory/ref_counted.h"
+#include "base/numerics/checked_math.h"
 #include "base/trace_event/trace_event.h"
 #include "mojo/core/configuration.h"
 #include "mojo/core/core.h"
@@ -530,8 +531,10 @@
         TRACE_EVENT0("ipc",
                      "DataPipeProducerDispatcher received DATA_WAS_READ");
 
-        if (static_cast<size_t>(available_capacity_) + m->num_bytes >
-            options_.capacity_num_bytes) {
+        uint32_t new_available_capacity;
+        if (!base::CheckAdd(available_capacity_, m->num_bytes)
+                 .AssignIfValid(&new_available_capacity) ||
+            new_available_capacity > options_.capacity_num_bytes) {
           DLOG(ERROR) << "Consumer claims to have read too many bytes.";
           break;
         }
@@ -541,7 +544,7 @@
                  << " bytes were read. [control_port=" << control_port_.name()
                  << "]";
 
-        available_capacity_ += m->num_bytes;
+        available_capacity_ = new_available_capacity;
       }
     } while (message_event);
   }
diff --git a/sandbox/policy/BUILD.gn b/sandbox/policy/BUILD.gn
index b412c8d..ec6a1ab 100644
--- a/sandbox/policy/BUILD.gn
+++ b/sandbox/policy/BUILD.gn
@@ -59,6 +59,8 @@
       "linux/bpf_print_compositor_policy_linux.h",
       "linux/bpf_renderer_policy_linux.cc",
       "linux/bpf_renderer_policy_linux.h",
+      "linux/bpf_screen_ai_policy_linux.cc",
+      "linux/bpf_screen_ai_policy_linux.h",
       "linux/bpf_service_policy_linux.cc",
       "linux/bpf_service_policy_linux.h",
       "linux/bpf_speech_recognition_policy_linux.cc",
diff --git a/sandbox/policy/linux/bpf_screen_ai_policy_linux.cc b/sandbox/policy/linux/bpf_screen_ai_policy_linux.cc
new file mode 100644
index 0000000..d10fb1a2
--- /dev/null
+++ b/sandbox/policy/linux/bpf_screen_ai_policy_linux.cc
@@ -0,0 +1,47 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "sandbox/policy/linux/bpf_screen_ai_policy_linux.h"
+
+#include "sandbox/linux/bpf_dsl/bpf_dsl.h"
+#include "sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions.h"
+#include "sandbox/linux/system_headers/linux_syscalls.h"
+#include "sandbox/policy/linux/sandbox_linux.h"
+
+using sandbox::bpf_dsl::Allow;
+using sandbox::bpf_dsl::Arg;
+using sandbox::bpf_dsl::Error;
+using sandbox::bpf_dsl::If;
+using sandbox::bpf_dsl::ResultExpr;
+
+namespace sandbox::policy {
+
+ScreenAIProcessPolicy::ScreenAIProcessPolicy() = default;
+ScreenAIProcessPolicy::~ScreenAIProcessPolicy() = default;
+
+ResultExpr ScreenAIProcessPolicy::EvaluateSyscall(
+    int system_call_number) const {
+  auto* sandbox_linux = SandboxLinux::GetInstance();
+  if (sandbox_linux->ShouldBrokerHandleSyscall(system_call_number))
+    return sandbox_linux->HandleViaBroker(system_call_number);
+
+  switch (system_call_number) {
+    case __NR_getitimer:
+    case __NR_setitimer: {
+      const Arg<int> which(0);
+      return If(which == ITIMER_PROF, Allow()).Else(Error(EPERM));
+    }
+    case __NR_get_mempolicy: {
+      const Arg<unsigned long> which(4);
+      return If(which == 0, Allow()).Else(Error(EPERM));
+    }
+    case __NR_sched_getaffinity:
+      return RestrictSchedTarget(GetPolicyPid(), system_call_number);
+
+    default:
+      return BPFBasePolicy::EvaluateSyscall(system_call_number);
+  }
+}
+
+}  // namespace sandbox::policy
diff --git a/sandbox/policy/linux/bpf_screen_ai_policy_linux.h b/sandbox/policy/linux/bpf_screen_ai_policy_linux.h
new file mode 100644
index 0000000..c9df6f2
--- /dev/null
+++ b/sandbox/policy/linux/bpf_screen_ai_policy_linux.h
@@ -0,0 +1,29 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SANDBOX_POLICY_LINUX_BPF_SCREEN_AI_POLICY_LINUX_H_
+#define SANDBOX_POLICY_LINUX_BPF_SCREEN_AI_POLICY_LINUX_H_
+
+#include "sandbox/linux/bpf_dsl/bpf_dsl.h"
+#include "sandbox/policy/linux/bpf_base_policy_linux.h"
+
+namespace sandbox::policy {
+
+// The process policy for the sandboxed utility process that loads the Screen AI
+// on-device library.
+class SANDBOX_POLICY_EXPORT ScreenAIProcessPolicy : public BPFBasePolicy {
+ public:
+  ScreenAIProcessPolicy();
+
+  ScreenAIProcessPolicy(const ScreenAIProcessPolicy&) = delete;
+  ScreenAIProcessPolicy& operator=(const ScreenAIProcessPolicy&) = delete;
+
+  ~ScreenAIProcessPolicy() override;
+
+  bpf_dsl::ResultExpr EvaluateSyscall(int system_call_number) const override;
+};
+
+}  // namespace sandbox::policy
+
+#endif  // SANDBOX_POLICY_LINUX_BPF_SCREEN_AI_POLICY_LINUX_H_
diff --git a/sandbox/policy/linux/sandbox_seccomp_bpf_linux.cc b/sandbox/policy/linux/sandbox_seccomp_bpf_linux.cc
index 62c4551f..11819b5 100644
--- a/sandbox/policy/linux/sandbox_seccomp_bpf_linux.cc
+++ b/sandbox/policy/linux/sandbox_seccomp_bpf_linux.cc
@@ -48,6 +48,7 @@
 #include "sandbox/policy/linux/bpf_print_backend_policy_linux.h"
 #include "sandbox/policy/linux/bpf_print_compositor_policy_linux.h"
 #include "sandbox/policy/linux/bpf_renderer_policy_linux.h"
+#include "sandbox/policy/linux/bpf_screen_ai_policy_linux.h"
 #include "sandbox/policy/linux/bpf_service_policy_linux.h"
 #include "sandbox/policy/linux/bpf_speech_recognition_policy_linux.h"
 #include "sandbox/policy/linux/bpf_utility_policy_linux.h"
@@ -190,6 +191,8 @@
       return std::make_unique<ServiceProcessPolicy>();
     case sandbox::mojom::Sandbox::kSpeechRecognition:
       return std::make_unique<SpeechRecognitionProcessPolicy>();
+    case sandbox::mojom::Sandbox::kScreenAI:
+      return std::make_unique<ScreenAIProcessPolicy>();
 #if BUILDFLAG(IS_CHROMEOS_ASH)
     case sandbox::mojom::Sandbox::kHardwareVideoDecoding:
       // TODO(b/195769334): we're using the GPU process sandbox policy for now
@@ -259,6 +262,7 @@
     case sandbox::mojom::Sandbox::kLibassistant:
 #endif  // BUILDFLAG(ENABLE_CROS_LIBASSISTANT)
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+    case sandbox::mojom::Sandbox::kScreenAI:
     case sandbox::mojom::Sandbox::kAudio:
     case sandbox::mojom::Sandbox::kService:
     case sandbox::mojom::Sandbox::kServiceWithJit:
diff --git a/sandbox/policy/mojom/BUILD.gn b/sandbox/policy/mojom/BUILD.gn
index ebff0901..c914dba7 100644
--- a/sandbox/policy/mojom/BUILD.gn
+++ b/sandbox/policy/mojom/BUILD.gn
@@ -19,7 +19,10 @@
     enabled_features += [ "enable_plugins" ]
   }
   if (is_linux || is_chromeos) {
-    enabled_features += [ "has_zygote" ]
+    enabled_features += [
+      "has_zygote",
+      "is_linux_or_chromeos",
+    ]
   }
   if (enable_cros_libassistant) {
     enabled_features += [ "enable_cros_libassistant" ]
diff --git a/sandbox/policy/mojom/sandbox.mojom b/sandbox/policy/mojom/sandbox.mojom
index 916ead93..b4079d9 100644
--- a/sandbox/policy/mojom/sandbox.mojom
+++ b/sandbox/policy/mojom/sandbox.mojom
@@ -59,6 +59,10 @@
   // Like kUtility but allows loading of speech recognition libraries.
   kSpeechRecognition,
 
+  // Like kUtility but allows loading of screen AI library.
+  [EnableIf=is_linux_or_chromeos]
+  kScreenAI,
+
   // The PPAPI plugin process.
   [EnableIf=enable_plugins]
   kPpapi,
diff --git a/sandbox/policy/sandbox_type.cc b/sandbox/policy/sandbox_type.cc
index 944e9ba..7d457bf 100644
--- a/sandbox/policy/sandbox_type.cc
+++ b/sandbox/policy/sandbox_type.cc
@@ -72,6 +72,7 @@
 #endif  // // BUILDFLAG(IS_CHROMEOS_ASH)
 #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
     case Sandbox::kZygoteIntermediateSandbox:
+    case Sandbox::kScreenAI:
 #endif
     case Sandbox::kSpeechRecognition:
       return false;
@@ -144,6 +145,9 @@
 #if BUILDFLAG(IS_MAC)
     case Sandbox::kMirroring:
 #endif  // BUILDFLAG(IS_MAC)
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
+    case Sandbox::kScreenAI:
+#endif
     case Sandbox::kSpeechRecognition:
       DCHECK(command_line->GetSwitchValueASCII(switches::kProcessType) ==
              switches::kUtilityProcess);
@@ -257,6 +261,10 @@
       return switches::kServiceSandboxWithJit;
     case Sandbox::kSpeechRecognition:
       return switches::kSpeechRecognitionSandbox;
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
+    case Sandbox::kScreenAI:
+      return switches::kScreenAISandbox;
+#endif
 #if BUILDFLAG(IS_WIN)
     case Sandbox::kXrCompositing:
       return switches::kXrCompositingSandbox;
@@ -355,6 +363,10 @@
     return Sandbox::kAudio;
   if (sandbox_string == switches::kSpeechRecognitionSandbox)
     return Sandbox::kSpeechRecognition;
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
+  if (sandbox_string == switches::kScreenAISandbox)
+    return Sandbox::kScreenAI;
+#endif
 #if BUILDFLAG(IS_FUCHSIA)
   if (sandbox_string == switches::kVideoCaptureSandbox)
     return Sandbox::kVideoCapture;
diff --git a/sandbox/policy/switches.cc b/sandbox/policy/switches.cc
index 4ad0ff3..6b20a9564 100644
--- a/sandbox/policy/switches.cc
+++ b/sandbox/policy/switches.cc
@@ -36,6 +36,7 @@
 const char kAudioSandbox[] = "audio";
 const char kServiceSandbox[] = "service";
 const char kServiceSandboxWithJit[] = "service_with_jit";
+const char kScreenAISandbox[] = "screen_ai";
 const char kSpeechRecognitionSandbox[] = "speech_recognition";
 const char kVideoCaptureSandbox[] = "video_capture";
 
diff --git a/sandbox/policy/switches.h b/sandbox/policy/switches.h
index d07e692..4586cb2 100644
--- a/sandbox/policy/switches.h
+++ b/sandbox/policy/switches.h
@@ -37,6 +37,7 @@
 SANDBOX_POLICY_EXPORT extern const char kAudioSandbox[];
 SANDBOX_POLICY_EXPORT extern const char kServiceSandbox[];
 SANDBOX_POLICY_EXPORT extern const char kServiceSandboxWithJit[];
+SANDBOX_POLICY_EXPORT extern const char kScreenAISandbox[];
 SANDBOX_POLICY_EXPORT extern const char kSpeechRecognitionSandbox[];
 SANDBOX_POLICY_EXPORT extern const char kVideoCaptureSandbox[];
 
diff --git a/services/audio/audio_processor_handler.cc b/services/audio/audio_processor_handler.cc
index 488b6456..669e7502 100644
--- a/services/audio/audio_processor_handler.cc
+++ b/services/audio/audio_processor_handler.cc
@@ -4,16 +4,27 @@
 
 #include "services/audio/audio_processor_handler.h"
 
+#include "base/cxx17_backports.h"
 #include "base/trace_event/trace_event.h"
 #include "media/base/audio_bus.h"
+#include "media/base/audio_parameters.h"
 
 namespace audio {
 
 AudioProcessorHandler::AudioProcessorHandler(
     const media::AudioProcessingSettings& settings,
+    const media::AudioParameters& audio_format,
+    LogCallback log_callback,
+    DeliverProcessedAudioCallback deliver_processed_audio_callback,
     mojo::PendingReceiver<media::mojom::AudioProcessorControls>
         controls_receiver)
-    : receiver_(this, std::move(controls_receiver)) {
+    : audio_processor_(std::make_unique<media::AudioProcessor>(
+          std::move(deliver_processed_audio_callback),
+          std::move(log_callback),
+          settings,
+          /*input_format=*/audio_format,
+          /*output_format=*/audio_format)),
+      receiver_(this, std::move(controls_receiver)) {
   DCHECK(settings.NeedAudioModification());
 }
 
@@ -21,11 +32,46 @@
   DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_);
 }
 
+void AudioProcessorHandler::ProcessCapturedAudio(
+    const media::AudioBus& audio_source,
+    base::TimeTicks audio_capture_time,
+    double volume,
+    bool key_pressed) {
+  const int num_preferred_channels =
+      num_preferred_channels_.load(std::memory_order_acquire);
+  audio_processor_->ProcessCapturedAudio(audio_source, audio_capture_time,
+                                         num_preferred_channels, volume,
+                                         key_pressed);
+}
+
 void AudioProcessorHandler::OnPlayoutData(const media::AudioBus& audio_bus,
                                           int sample_rate,
                                           base::TimeDelta delay) {
   TRACE_EVENT2("audio", "AudioProcessorHandler::OnPlayoutData", " this ",
                static_cast<void*>(this), "delay", delay.InMillisecondsF());
-  // TODO(https://crbug.com/1215061): Forward playout data to audio processor.
+  // TODO(https://crbug.com/1292037): Ensure that the buffer size is supported,
+  // either through interface guarantees or rebuffering.
+  CHECK_EQ(audio_bus.frames(), sample_rate / 100);
+  audio_processor_->OnPlayoutData(audio_bus, sample_rate, delay);
+}
+
+void AudioProcessorHandler::GetStats(GetStatsCallback callback) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_);
+  media::AudioProcessingStats stats;
+  const webrtc::AudioProcessingStats processor_stats =
+      audio_processor_->GetStats();
+  stats.echo_return_loss = processor_stats.echo_return_loss;
+  stats.echo_return_loss_enhancement =
+      processor_stats.echo_return_loss_enhancement;
+  std::move(callback).Run(stats);
+}
+
+void AudioProcessorHandler::SetPreferredNumCaptureChannels(
+    int32_t num_preferred_channels) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_);
+  num_preferred_channels = base::clamp(
+      num_preferred_channels, 1, audio_processor_->OutputFormat().channels());
+  num_preferred_channels_.store(num_preferred_channels,
+                                std::memory_order_release);
 }
 }  // namespace audio
diff --git a/services/audio/audio_processor_handler.h b/services/audio/audio_processor_handler.h
index 7d9a146d..1dd53ce 100644
--- a/services/audio/audio_processor_handler.h
+++ b/services/audio/audio_processor_handler.h
@@ -5,44 +5,111 @@
 #ifndef SERVICES_AUDIO_AUDIO_PROCESSOR_HANDLER_H_
 #define SERVICES_AUDIO_AUDIO_PROCESSOR_HANDLER_H_
 
+#include <atomic>
+
 #include "base/sequence_checker.h"
 #include "media/base/audio_processing.h"
 #include "media/mojo/mojom/audio_processing.mojom.h"
+#include "media/webrtc/audio_processor.h"
 #include "mojo/public/cpp/bindings/receiver.h"
 #include "services/audio/reference_output.h"
 
 namespace media {
 class AudioBus;
+class AudioParameters;
 }  // namespace media
 
 namespace audio {
 
-// Encapsulates audio processing effects in the audio process.
-// TODO(https://crbug.com/1215061): Create and manage a media::AudioProcessor.
-// TODO(https://crbug.com/1284652): Add methods to AudioProcessorControls and
-//                                  implement them.
-// This class is currently a no-op implementation of ReferenceOutput::Listener.
+// Encapsulates audio processing effects in the audio process, using a
+// media::AudioProcessor. Forwards capture audio, playout audio, and
+// AudioProcessorControls calls to the processor.
+//
+// The class can be operated on by three different sequences:
+// - An owning sequence, which performs construction, destruction, getting
+// stats, and similar control flow.
+// - A capture thread, which calls ProcessCapturedAudio().
+// - A playout thread, which calls OnPlayoutData().
+//
+// All functions should be called on the owning thread, unless otherwise
+// specified. It is the responsibility of the owner to ensure that the playout
+// thread and capture thread stop calling into the AudioProcessorHandler before
+// destruction.
 class AudioProcessorHandler final
     : public ReferenceOutput::Listener,
       public media::mojom::AudioProcessorControls {
  public:
+  using DeliverProcessedAudioCallback =
+      media::AudioProcessor::DeliverProcessedAudioCallback;
+
+  using LogCallback = base::RepeatingCallback<void(base::StringPiece)>;
+
+  // |settings| specifies which audio processing effects to apply. Some effect
+  // must be required, i.e. the AudioProcessorHandler may only be created if
+  // |settings.NeedAudioModification()| is true.
+  // |audio_format| specifies the audio format, both before and after
+  // processing. If |settings|.NeedWebrtcAudioProcessing(), then
+  // |audio_format|.frames_per_buffer() must specify 10 ms.
+  // TODO(https://crbug.com/1298056): Support different input vs output format
+  // to avoid unnecessary resampling.
+  // |log_callback| is used for logging messages on the owning sequence.
+  // |deliver_processed_audio_callback| is used to deliver processed audio
+  // provided to ProcessCapturedAudio().
+  // |controls_receiver| calls are received by the AudioProcessorHandler.
   explicit AudioProcessorHandler(
       const media::AudioProcessingSettings& settings,
+      const media::AudioParameters& audio_format,
+      LogCallback log_callback,
+      DeliverProcessedAudioCallback deliver_processed_audio_callback,
       mojo::PendingReceiver<media::mojom::AudioProcessorControls>
           controls_receiver);
   AudioProcessorHandler(const AudioProcessorHandler&) = delete;
   AudioProcessorHandler& operator=(const AudioProcessorHandler&) = delete;
   ~AudioProcessorHandler() final;
 
+  // Processes and delivers capture audio.
+  // See media::AudioProcessor::ProcessCapturedAudio for API details.
+  // Called on the capture thread.
+  void ProcessCapturedAudio(const media::AudioBus& audio_source,
+                            base::TimeTicks audio_capture_time,
+                            double volume,
+                            bool key_pressed);
+
  private:
+  // Used in the mojom::AudioProcessorControls implementation.
+  using GetStatsCallback =
+      base::OnceCallback<void(const media::AudioProcessingStats& stats)>;
+
   // ReferenceOutput::Listener implementation.
+  // Called on the playout thread.
+  // TODO(https://crbug.com/1292037): Currently needs 10 ms of audio per call,
+  // but this is not guaranteed in the interface.
   void OnPlayoutData(const media::AudioBus& audio_bus,
                      int sample_rate,
                      base::TimeDelta delay) final;
 
+  // mojom::AudioProcessorControls implementation.
+  void GetStats(GetStatsCallback callback) final;
+  void SetPreferredNumCaptureChannels(int32_t num_preferred_channels) final;
+
   SEQUENCE_CHECKER(owning_sequence_);
 
-  mojo::Receiver<media::mojom::AudioProcessorControls> receiver_;
+  // The audio processor is accessed on all threads (OS capture thread, OS
+  // playout thread, owning sequence) and created / destroyed on the owning
+  // sequence.
+  const std::unique_ptr<media::AudioProcessor> audio_processor_;
+
+  mojo::Receiver<media::mojom::AudioProcessorControls> receiver_
+      GUARDED_BY_CONTEXT(owning_sequence_);
+
+  // The number of channels preferred by consumers of the captured audio.
+  // Initially, consumers are assumed to use mono audio in order to 1) avoid
+  // unnecessary computational load and 2) preserve the historical default.
+  // Written from the owning thread in SetPreferredNumCaptureChannels and read
+  // from the real-time capture thread in ProcessCapturedAudio.
+  // We use an atomic instead of a lock in order to avoid blocking on the
+  // real-time thread.
+  std::atomic<int32_t> num_preferred_channels_ = 1;
 };
 
 }  // namespace audio
diff --git a/services/audio/input_controller.cc b/services/audio/input_controller.cc
index be1037e1..e05e0d08 100644
--- a/services/audio/input_controller.cc
+++ b/services/audio/input_controller.cc
@@ -220,7 +220,7 @@
   weak_this_ = weak_ptr_factory_.GetWeakPtr();
 
 #if BUILDFLAG(CHROME_WIDE_ECHO_CANCELLATION)
-  MaybeSetUpAudioProcessing(std::move(processing_config),
+  MaybeSetUpAudioProcessing(std::move(processing_config), params,
                             device_output_listener);
 #endif
 
@@ -233,6 +233,7 @@
 #if BUILDFLAG(CHROME_WIDE_ECHO_CANCELLATION)
 void InputController::MaybeSetUpAudioProcessing(
     media::mojom::AudioProcessingConfigPtr processing_config,
+    const media::AudioParameters& params,
     DeviceOutputListener* device_output_listener) {
   if (!device_output_listener)
     return;
@@ -245,8 +246,14 @@
   ReferenceOutput::Listener* output_listener = nullptr;
   if (processing_config &&
       processing_config->settings.NeedAudioModification()) {
+    // Unretained() is safe, since |this| and |event_handler_| outlive
+    // |audio_processor_handler_|.
     audio_processor_handler_ = std::make_unique<AudioProcessorHandler>(
-        processing_config->settings,
+        processing_config->settings, params,
+        base::BindRepeating(&EventHandler::OnLog,
+                            base::Unretained(event_handler_)),
+        base::BindRepeating(&InputController::DeliverProcessedAudio,
+                            base::Unretained(this)),
         std::move(processing_config->controls_receiver));
     if (processing_config->settings.NeedPlayoutReference())
       output_listener = audio_processor_handler_.get();
@@ -296,6 +303,7 @@
 
   // Create the InputController object and ensure that it runs on
   // the audio-manager thread.
+  // Using `new` to access a non-public constructor.
   std::unique_ptr<InputController> controller =
       base::WrapUnique(new InputController(
           event_handler, sync_writer, user_input_monitor, activity_monitor,
@@ -725,7 +733,15 @@
                                               base::TimeTicks capture_time,
                                               double volume) {
   const bool key_pressed = CheckForKeyboardInput();
-  sync_writer_->Write(source, volume, key_pressed, capture_time);
+#if BUILDFLAG(CHROME_WIDE_ECHO_CANCELLATION)
+  if (audio_processor_handler_) {
+    audio_processor_handler_->ProcessCapturedAudio(*source, capture_time,
+                                                   volume, key_pressed);
+  } else
+#endif
+  {
+    sync_writer_->Write(source, volume, key_pressed, capture_time);
+  }
 
   float average_power_dbfs;
   int mic_volume_percent;
@@ -740,6 +756,22 @@
   }
 }
 
+#if BUILDFLAG(CHROME_WIDE_ECHO_CANCELLATION)
+void InputController::DeliverProcessedAudio(const media::AudioBus& audio_bus,
+                                            base::TimeTicks audio_capture_time,
+                                            absl::optional<double> new_volume) {
+  // When processing is performed in the audio service, the consumer is not
+  // expected to use the input volume and keypress information.
+  sync_writer_->Write(&audio_bus, /*volume=*/1.0,
+                      /*key_pressed=*/false, audio_capture_time);
+  if (new_volume) {
+    task_runner_->PostTask(
+        FROM_HERE,
+        base::BindOnce(&InputController::SetVolume, weak_this_, *new_volume));
+  }
+}
+#endif
+
 // static
 InputController::StreamType InputController::ParamsToStreamType(
     const media::AudioParameters& params) {
diff --git a/services/audio/input_controller.h b/services/audio/input_controller.h
index 222c714..eecd5184 100644
--- a/services/audio/input_controller.h
+++ b/services/audio/input_controller.h
@@ -264,7 +264,13 @@
   // processing components.
   void MaybeSetUpAudioProcessing(
       media::mojom::AudioProcessingConfigPtr processing_config,
+      const media::AudioParameters& params,
       DeviceOutputListener* device_output_listener);
+
+  // Used as a callback for |audio_processor_handler_|.
+  void DeliverProcessedAudio(const media::AudioBus& audio_bus,
+                             base::TimeTicks audio_capture_time,
+                             absl::optional<double> new_volume);
 #endif
 
   static StreamType ParamsToStreamType(const media::AudioParameters& params);
diff --git a/services/audio/input_controller_unittest.cc b/services/audio/input_controller_unittest.cc
index 3a809f5..65b9a2f 100644
--- a/services/audio/input_controller_unittest.cc
+++ b/services/audio/input_controller_unittest.cc
@@ -24,11 +24,11 @@
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
-using base::WaitableEvent;
 using ::testing::_;
 using ::testing::AtLeast;
 using ::testing::Exactly;
 using ::testing::InvokeWithoutArgs;
+using ::testing::NiceMock;
 using ::testing::NotNull;
 using ::testing::StrictMock;
 
@@ -40,8 +40,6 @@
 const media::ChannelLayout kChannelLayout = media::CHANNEL_LAYOUT_STEREO;
 const int kSamplesPerPacket = kSampleRate / 100;
 
-const double kMaxVolume = 1.0;
-
 // InputController will poll once every second, so wait at most a bit
 // more than that for the callbacks.
 constexpr base::TimeDelta kOnMutePollInterval = base::Milliseconds(1000);
@@ -94,26 +92,6 @@
   MOCK_METHOD0(OnInputStreamInactive, void());
 };
 
-class MockAudioInputStream : public media::AudioInputStream {
- public:
-  MockAudioInputStream() {}
-  ~MockAudioInputStream() override {}
-
-  void Start(AudioInputCallback*) override {}
-  void Stop() override {}
-  void Close() override {}
-  double GetMaxVolume() override { return kMaxVolume; }
-  double GetVolume() override { return 0; }
-  bool SetAutomaticGainControl(bool) override { return false; }
-  bool GetAutomaticGainControl() override { return false; }
-  bool IsMuted() override { return false; }
-  void SetOutputDeviceForAec(const std::string&) override {}
-
-  MOCK_METHOD0(Open, media::AudioInputStream::OpenOutcome());
-  MOCK_METHOD1(SetVolume, void(double));
-};
-
-// Parameter: use audio processing.
 template <base::test::TaskEnvironment::TimeSource TimeSource =
               base::test::TaskEnvironment::TimeSource::MOCK_TIME>
 class TimeSourceInputControllerTest : public ::testing::Test {
@@ -157,8 +135,6 @@
   MockUserInputMonitor user_input_monitor_;
   StrictMock<MockInputStreamActivityMonitor> mock_stream_activity_monitor_;
   media::AudioParameters params_;
-  MockAudioInputStream stream_;
-  base::test::ScopedFeatureList audio_processing_feature_;
 };
 
 using SystemTimeInputControllerTest = TimeSourceInputControllerTest<
@@ -245,9 +221,6 @@
 
 // Test that InputController sends OnMute callbacks properly.
 TEST_F(InputControllerTest, TestOnmutedCallbackInitiallyUnmuted) {
-  WaitableEvent callback_event(WaitableEvent::ResetPolicy::AUTOMATIC,
-                               WaitableEvent::InitialState::NOT_SIGNALED);
-
   EXPECT_CALL(event_handler_, OnCreated(false));
   EXPECT_CALL(sync_writer_, Close());
 
@@ -270,9 +243,6 @@
 }
 
 TEST_F(InputControllerTest, TestOnmutedCallbackInitiallyMuted) {
-  WaitableEvent callback_event(WaitableEvent::ResetPolicy::AUTOMATIC,
-                               WaitableEvent::InitialState::NOT_SIGNALED);
-
   EXPECT_CALL(event_handler_, OnCreated(true));
   EXPECT_CALL(sync_writer_, Close());
 
@@ -301,44 +271,69 @@
   MOCK_METHOD1(StopListening, void(ReferenceOutput::Listener*));
 };
 
-class InputControllerTestWithDeviceListener : public InputControllerTest {
+template <base::test::TaskEnvironment::TimeSource TimeSource =
+              base::test::TaskEnvironment::TimeSource::MOCK_TIME>
+class TimeSourceInputControllerTestWithDeviceListener
+    : public TimeSourceInputControllerTest<TimeSource> {
  protected:
   void CreateAudioController() final {
-    controller_ = InputController::Create(
-        audio_manager_.get(), &event_handler_, &sync_writer_,
-        &user_input_monitor_, &mock_stream_activity_monitor_,
-        &device_output_listener_, std::move(processing_config_), params_,
-        media::AudioDeviceDescription::kDefaultDeviceId, false);
+    // Must use |this| to access template base class members:
+    // https://stackoverflow.com/q/4643074
+    this->controller_ = InputController::Create(
+        this->audio_manager_.get(), &this->event_handler_, &this->sync_writer_,
+        &this->user_input_monitor_, &this->mock_stream_activity_monitor_,
+        &this->device_output_listener_, std::move(processing_config_),
+        this->params_, media::AudioDeviceDescription::kDefaultDeviceId, false);
   }
 
-  void SetupProcessingConfig(bool force_need_audio_modification) {
-    media::AudioProcessingSettings settings;
-    if (force_need_audio_modification) {
-      settings.echo_cancellation = true;
-    } else {
-      settings.echo_cancellation = false;
-      settings.noise_suppression = false;
-      settings.transient_noise_suppression = false;
-      settings.automatic_gain_control = false;
-      settings.experimental_automatic_gain_control = false;
-      settings.high_pass_filter = false;
-      settings.multi_channel_capture_processing = false;
-      settings.stereo_mirroring = false;
-      settings.force_apm_creation = false;
-    }
+  enum class AudioProcessingType {
+    // No effects, audio does not need to be modified.
+    kNone,
+    // Effects that modify audio but do not require a playout reference signal.
+    kWithoutPlayoutReference,
+    // Effects that require a playout reference signal.
+    kWithPlayoutReference
+  };
 
+  void SetupProcessingConfig(AudioProcessingType audio_processing_type) {
+    media::AudioProcessingSettings settings;
+    settings.echo_cancellation = false;
+    settings.noise_suppression = false;
+    settings.transient_noise_suppression = false;
+    settings.automatic_gain_control = false;
+    settings.experimental_automatic_gain_control = false;
+    settings.high_pass_filter = false;
+    settings.multi_channel_capture_processing = false;
+    settings.stereo_mirroring = false;
+    settings.force_apm_creation = false;
+    switch (audio_processing_type) {
+      case AudioProcessingType::kNone:
+        break;
+      case AudioProcessingType::kWithoutPlayoutReference:
+        settings.noise_suppression = true;
+        break;
+      case AudioProcessingType::kWithPlayoutReference:
+        settings.echo_cancellation = true;
+        break;
+    }
     processing_config_ = media::mojom::AudioProcessingConfig::New(
         remote_controls_.BindNewPipeAndPassReceiver(), settings);
   }
 
-  MockDeviceOutputListener device_output_listener_;
+  NiceMock<MockDeviceOutputListener> device_output_listener_;
   media::mojom::AudioProcessingConfigPtr processing_config_;
   mojo::Remote<media::mojom::AudioProcessorControls> remote_controls_;
 };
 
+using SystemTimeInputControllerTestWithDeviceListener =
+    TimeSourceInputControllerTestWithDeviceListener<
+        base::test::TaskEnvironment::TimeSource::SYSTEM_TIME>;
+using InputControllerTestWithDeviceListener =
+    TimeSourceInputControllerTestWithDeviceListener<>;
+
 TEST_F(InputControllerTestWithDeviceListener,
-       CreateWithAudioProcessingConfig_EchoCancellation) {
-  SetupProcessingConfig(/*force_need_audio_modification=*/true);
+       CreateWithAudioProcessingConfig_WithSomeEffectsEnabled) {
+  SetupProcessingConfig(AudioProcessingType::kWithoutPlayoutReference);
 
   CreateAudioController();
 
@@ -358,8 +353,8 @@
 }
 
 TEST_F(InputControllerTestWithDeviceListener,
-       CreateWithAudioProcessingConfig_WithoutEchoCancellation) {
-  SetupProcessingConfig(/*force_need_audio_modification=*/false);
+       CreateWithAudioProcessingConfig_WithoutEnablingEffects) {
+  SetupProcessingConfig(AudioProcessingType::kNone);
 
   CreateAudioController();
 
@@ -378,6 +373,26 @@
   controller_->Close();
 }
 
+TEST_F(
+    InputControllerTestWithDeviceListener,
+    CreateWithAudioProcessingConfig_DoesNotListenForPlayoutReferenceIfNotRequired) {
+  const std::string kOutputDeviceId = "0x123";
+  EXPECT_CALL(mock_stream_activity_monitor_, OnInputStreamActive()).Times(1);
+  EXPECT_CALL(mock_stream_activity_monitor_, OnInputStreamInactive()).Times(1);
+
+  EXPECT_CALL(device_output_listener_, StartListening(_, _)).Times(0);
+  EXPECT_CALL(device_output_listener_, StopListening(_)).Times(0);
+
+  SetupProcessingConfig(AudioProcessingType::kWithoutPlayoutReference);
+  CreateAudioController();
+
+  ASSERT_TRUE(controller_.get());
+
+  controller_->Record();
+  controller_->SetOutputDeviceForAec(kOutputDeviceId);
+  controller_->Close();
+}
+
 TEST_F(InputControllerTestWithDeviceListener, RecordBeforeSetOutputForAec) {
   const std::string kOutputDeviceId = "0x123";
   EXPECT_CALL(mock_stream_activity_monitor_, OnInputStreamActive()).Times(1);
@@ -439,6 +454,44 @@
   controller_->SetOutputDeviceForAec(kOtherOutputDeviceId);
   controller_->Close();
 }
+
+// Test a normal call sequence of create, record and close when audio processing
+// is enabled.
+// Note: Must use system time as MOCK_TIME does not support the threads created
+// by the FakeAudioInputStream. The callbacks to sync_writer_.Write() are on
+// that thread, and thus we must use SYSTEM_TIME.
+TEST_F(SystemTimeInputControllerTestWithDeviceListener, CreateRecordAndClose) {
+  EXPECT_CALL(event_handler_, OnCreated(_));
+  EXPECT_CALL(mock_stream_activity_monitor_, OnInputStreamActive()).Times(1);
+  EXPECT_CALL(mock_stream_activity_monitor_, OnInputStreamInactive()).Times(1);
+  SetupProcessingConfig(AudioProcessingType::kWithPlayoutReference);
+  CreateAudioController();
+  ASSERT_TRUE(controller_.get());
+
+  base::RunLoop loop;
+
+  {
+    // Wait for Write() to be called ten times.
+    testing::InSequence s;
+    EXPECT_CALL(user_input_monitor_, EnableKeyPressMonitoring());
+    EXPECT_CALL(sync_writer_, Write(NotNull(), _, _, _)).Times(Exactly(9));
+    EXPECT_CALL(sync_writer_, Write(NotNull(), _, _, _))
+        .Times(AtLeast(1))
+        .WillOnce(InvokeWithoutArgs([&]() { loop.Quit(); }));
+  }
+  controller_->Record();
+  loop.Run();
+
+  testing::Mock::VerifyAndClearExpectations(&user_input_monitor_);
+  testing::Mock::VerifyAndClearExpectations(&sync_writer_);
+
+  EXPECT_CALL(sync_writer_, Close());
+  EXPECT_CALL(user_input_monitor_, DisableKeyPressMonitoring());
+  controller_->Close();
+
+  task_environment_.RunUntilIdle();
+}
+
 #endif  // BUILDFLAG(CHROME_WIDE_ECHO_CANCELLATION)
 
 }  // namespace audio
diff --git a/testing/buildbot/chromium.android.fyi.json b/testing/buildbot/chromium.android.fyi.json
index c312da06..531bdf7 100644
--- a/testing/buildbot/chromium.android.fyi.json
+++ b/testing/buildbot/chromium.android.fyi.json
@@ -5460,7 +5460,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M100",
-              "revision": "version:100.0.4896.8"
+              "revision": "version:100.0.4896.9"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -5724,7 +5724,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M100",
-              "revision": "version:100.0.4896.8"
+              "revision": "version:100.0.4896.9"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
diff --git a/testing/buildbot/chromium.android.json b/testing/buildbot/chromium.android.json
index 8ce3bdc..665edcd 100644
--- a/testing/buildbot/chromium.android.json
+++ b/testing/buildbot/chromium.android.json
@@ -42606,7 +42606,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M100",
-              "revision": "version:100.0.4896.8"
+              "revision": "version:100.0.4896.9"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -42870,7 +42870,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M100",
-              "revision": "version:100.0.4896.8"
+              "revision": "version:100.0.4896.9"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -43138,7 +43138,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M100",
-              "revision": "version:100.0.4896.8"
+              "revision": "version:100.0.4896.9"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -43402,7 +43402,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M100",
-              "revision": "version:100.0.4896.8"
+              "revision": "version:100.0.4896.9"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -43741,7 +43741,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M100",
-              "revision": "version:100.0.4896.8"
+              "revision": "version:100.0.4896.9"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -44005,7 +44005,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M100",
-              "revision": "version:100.0.4896.8"
+              "revision": "version:100.0.4896.9"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -44344,7 +44344,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M100",
-              "revision": "version:100.0.4896.8"
+              "revision": "version:100.0.4896.9"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -44608,7 +44608,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M100",
-              "revision": "version:100.0.4896.8"
+              "revision": "version:100.0.4896.9"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
diff --git a/testing/buildbot/variants.pyl b/testing/buildbot/variants.pyl
index f7b87011..c3bfc5f 100644
--- a/testing/buildbot/variants.pyl
+++ b/testing/buildbot/variants.pyl
@@ -387,7 +387,7 @@
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M100',
-          'revision': 'version:100.0.4896.8',
+          'revision': 'version:100.0.4896.9',
         }
       ],
     },
@@ -459,7 +459,7 @@
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M100',
-          'revision': 'version:100.0.4896.8',
+          'revision': 'version:100.0.4896.9',
         }
       ],
     },
@@ -531,7 +531,7 @@
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M100',
-          'revision': 'version:100.0.4896.8',
+          'revision': 'version:100.0.4896.9',
         }
       ],
     },
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index c543556..aa99077 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -3515,7 +3515,7 @@
                 {
                     "name": "Enabled",
                     "enable_features": [
-                        "IOSTabGridSearch"
+                        "TabsSearch"
                     ]
                 }
             ]
diff --git a/third_party/abseil-cpp/BUILD.gn b/third_party/abseil-cpp/BUILD.gn
index ebe250f..a3f2f62a 100644
--- a/third_party/abseil-cpp/BUILD.gn
+++ b/third_party/abseil-cpp/BUILD.gn
@@ -188,14 +188,15 @@
         "absl/container:inlined_vector_test",
         "absl/container:node_slot_policy_test",
         "absl/container:sample_element_size_test",
-        "absl/hash:low_level_hash_test",
         "absl/hash:hash_test",
+        "absl/hash:low_level_hash_test",
         "absl/memory:memory_test",
         "absl/meta:type_traits_test",
         "absl/profiling:exponential_biased_test",
         "absl/profiling:periodic_sampler_test",
         "absl/status:statusor_test",
         "absl/strings:ascii_test",
+        "absl/strings:cord_data_edge_test",
         "absl/strings:cord_rep_btree_navigator_test",
         "absl/strings:cord_rep_btree_reader_test",
         "absl/strings:cord_rep_btree_test",
diff --git a/third_party/abseil-cpp/CMake/AbseilDll.cmake b/third_party/abseil-cpp/CMake/AbseilDll.cmake
index 14ce9d186f..0becc7a 100644
--- a/third_party/abseil-cpp/CMake/AbseilDll.cmake
+++ b/third_party/abseil-cpp/CMake/AbseilDll.cmake
@@ -204,6 +204,7 @@
   "strings/internal/charconv_bigint.h"
   "strings/internal/charconv_parse.cc"
   "strings/internal/charconv_parse.h"
+  "strings/internal/cord_data_edge.h"
   "strings/internal/cord_internal.cc"
   "strings/internal/cord_internal.h"
   "strings/internal/cord_rep_btree.cc"
diff --git a/third_party/abseil-cpp/README.chromium b/third_party/abseil-cpp/README.chromium
index 726a602..1b47c85 100644
--- a/third_party/abseil-cpp/README.chromium
+++ b/third_party/abseil-cpp/README.chromium
@@ -4,7 +4,7 @@
 License: Apache 2.0
 License File: LICENSE
 Version: 0
-Revision: c2ef7033380a3d8661fee76465097422170fb653
+Revision: 7f850b3167fb38e6b4a9ce1824e6fabd733b5d62
 Security Critical: yes
 
 Description:
diff --git a/third_party/abseil-cpp/absl/debugging/symbolize_elf.inc b/third_party/abseil-cpp/absl/debugging/symbolize_elf.inc
index 3ff343d..ddccd59 100644
--- a/third_party/abseil-cpp/absl/debugging/symbolize_elf.inc
+++ b/third_party/abseil-cpp/absl/debugging/symbolize_elf.inc
@@ -323,6 +323,7 @@
                                            const ptrdiff_t relocation,
                                            char *out, int out_size,
                                            char *tmp_buf, int tmp_buf_size);
+  const char *GetUncachedSymbol(const void *pc);
 
   enum {
     SYMBOL_BUF_SIZE = 3072,
@@ -1333,13 +1334,7 @@
 // they are called here as well.
 // To keep stack consumption low, we would like this function to not
 // get inlined.
-const char *Symbolizer::GetSymbol(const void *const pc) {
-  const char *entry = FindSymbolInCache(pc);
-  if (entry != nullptr) {
-    return entry;
-  }
-  symbol_buf_[0] = '\0';
-
+const char *Symbolizer::GetUncachedSymbol(const void *pc) {
   ObjFile *const obj = FindObjFile(pc, 1);
   ptrdiff_t relocation = 0;
   int fd = -1;
@@ -1427,6 +1422,42 @@
   return InsertSymbolInCache(pc, symbol_buf_);
 }
 
+const char *Symbolizer::GetSymbol(const void *pc) {
+  const char *entry = FindSymbolInCache(pc);
+  if (entry != nullptr) {
+    return entry;
+  }
+  symbol_buf_[0] = '\0';
+
+#ifdef __hppa__
+  {
+    // In some contexts (e.g., return addresses), PA-RISC uses the lowest two
+    // bits of the address to indicate the privilege level. Clear those bits
+    // before trying to symbolize.
+    const auto pc_bits = reinterpret_cast<uintptr_t>(pc);
+    const auto address = pc_bits & ~0x3;
+    entry = GetUncachedSymbol(reinterpret_cast<const void *>(address));
+    if (entry != nullptr) {
+      return entry;
+    }
+
+    // In some contexts, PA-RISC also uses bit 1 of the address to indicate that
+    // this is a cross-DSO function pointer. Such function pointers actually
+    // point to a procedure label, a struct whose first 32-bit (pointer) element
+    // actually points to the function text. With no symbol found for this
+    // address so far, try interpreting it as a cross-DSO function pointer and
+    // see how that goes.
+    if (pc_bits & 0x2) {
+      return GetUncachedSymbol(*reinterpret_cast<const void *const *>(address));
+    }
+
+    return nullptr;
+  }
+#else
+  return GetUncachedSymbol(pc);
+#endif
+}
+
 bool RemoveAllSymbolDecorators(void) {
   if (!g_decorators_mu.TryLock()) {
     // Someone else is using decorators. Get out.
diff --git a/third_party/abseil-cpp/absl/debugging/symbolize_test.cc b/third_party/abseil-cpp/absl/debugging/symbolize_test.cc
index c710a3da8..a62fa35 100644
--- a/third_party/abseil-cpp/absl/debugging/symbolize_test.cc
+++ b/third_party/abseil-cpp/absl/debugging/symbolize_test.cc
@@ -392,12 +392,14 @@
                 DummySymbolDecorator, &c_message),
             0);
 
-  char *address = reinterpret_cast<char *>(1);
-  EXPECT_STREQ("abc", TrySymbolize(address++));
+  // Use addresses 4 and 8 here to ensure that we always use valid addresses
+  // even on systems that require instructions to be 32-bit aligned.
+  char *address = reinterpret_cast<char *>(4);
+  EXPECT_STREQ("abc", TrySymbolize(address));
 
   EXPECT_TRUE(absl::debugging_internal::RemoveSymbolDecorator(ticket_b));
 
-  EXPECT_STREQ("ac", TrySymbolize(address++));
+  EXPECT_STREQ("ac", TrySymbolize(address + 4));
 
   // Cleanup: remove all remaining decorators so other stack traces don't
   // get mystery "ac" decoration.
diff --git a/third_party/abseil-cpp/absl/random/zipf_distribution.h b/third_party/abseil-cpp/absl/random/zipf_distribution.h
index ed4038f1..03497b1b 100644
--- a/third_party/abseil-cpp/absl/random/zipf_distribution.h
+++ b/third_party/abseil-cpp/absl/random/zipf_distribution.h
@@ -30,7 +30,7 @@
 ABSL_NAMESPACE_BEGIN
 
 // absl::zipf_distribution produces random integer-values in the range [0, k],
-// distributed according to the discrete probability function:
+// distributed according to the unnormalized discrete probability function:
 //
 //  P(x) = (v + x) ^ -q
 //
diff --git a/third_party/abseil-cpp/absl/strings/BUILD.bazel b/third_party/abseil-cpp/absl/strings/BUILD.bazel
index 129affec..5f122ee9 100644
--- a/third_party/abseil-cpp/absl/strings/BUILD.bazel
+++ b/third_party/abseil-cpp/absl/strings/BUILD.bazel
@@ -276,6 +276,7 @@
         "internal/cord_rep_ring.cc",
     ],
     hdrs = [
+        "internal/cord_data_edge.h",
         "internal/cord_internal.h",
         "internal/cord_rep_btree.h",
         "internal/cord_rep_btree_navigator.h",
@@ -308,6 +309,21 @@
 )
 
 cc_test(
+    name = "cord_data_edge_test",
+    size = "small",
+    srcs = ["internal/cord_data_edge_test.cc"],
+    copts = ABSL_TEST_COPTS,
+    visibility = ["//visibility:private"],
+    deps = [
+        ":cord_internal",
+        ":cord_rep_test_util",
+        ":strings",
+        "//absl/base:config",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
+cc_test(
     name = "cord_rep_btree_test",
     size = "medium",
     srcs = ["internal/cord_rep_btree_test.cc"],
diff --git a/third_party/abseil-cpp/absl/strings/BUILD.gn b/third_party/abseil-cpp/absl/strings/BUILD.gn
index e9d69f1..799027f 100644
--- a/third_party/abseil-cpp/absl/strings/BUILD.gn
+++ b/third_party/abseil-cpp/absl/strings/BUILD.gn
@@ -128,6 +128,7 @@
     "internal/cord_rep_ring.cc",
   ]
   public = [
+    "internal/cord_data_edge.h",
     "internal/cord_internal.h",
     "internal/cord_rep_btree.h",
     "internal/cord_rep_btree_navigator.h",
@@ -156,6 +157,19 @@
   ]
 }
 
+absl_source_set("cord_data_edge_test") {
+  testonly = true
+  sources = [ "internal/cord_data_edge_test.cc" ]
+  deps = [
+    ":cord_internal",
+    ":cord_rep_test_util",
+    ":strings",
+    "//third_party/abseil-cpp/absl/base:config",
+    "//third_party/googletest:gmock",
+    "//third_party/googletest:gtest",
+  ]
+}
+
 absl_source_set("cord_rep_btree_test") {
   testonly = true
   sources = [ "internal/cord_rep_btree_test.cc" ]
diff --git a/third_party/abseil-cpp/absl/strings/CMakeLists.txt b/third_party/abseil-cpp/absl/strings/CMakeLists.txt
index f31eef4..5d418c8 100644
--- a/third_party/abseil-cpp/absl/strings/CMakeLists.txt
+++ b/third_party/abseil-cpp/absl/strings/CMakeLists.txt
@@ -554,6 +554,7 @@
   NAME
     cord_internal
   HDRS
+    "internal/cord_data_edge.h"
     "internal/cord_internal.h"
     "internal/cord_rep_btree.h"
     "internal/cord_rep_btree_navigator.h"
@@ -934,6 +935,23 @@
 
 absl_cc_test(
   NAME
+    cord_data_edge_test
+  SRCS
+    "internal/cord_data_edge_test.cc"
+  COPTS
+    ${ABSL_TEST_COPTS}
+  DEPS
+    absl::base
+    absl::config
+    absl::cord_internal
+    absl::cord_rep_test_util
+    absl::core_headers
+    absl::strings
+    GTest::gmock_main
+)
+
+absl_cc_test(
+  NAME
     cord_rep_btree_test
   SRCS
     "internal/cord_rep_btree_test.cc"
diff --git a/third_party/abseil-cpp/absl/strings/cord.cc b/third_party/abseil-cpp/absl/strings/cord.cc
index 4ee722da..6547c2d 100644
--- a/third_party/abseil-cpp/absl/strings/cord.cc
+++ b/third_party/abseil-cpp/absl/strings/cord.cc
@@ -35,6 +35,7 @@
 #include "absl/container/fixed_array.h"
 #include "absl/container/inlined_vector.h"
 #include "absl/strings/escaping.h"
+#include "absl/strings/internal/cord_data_edge.h"
 #include "absl/strings/internal/cord_internal.h"
 #include "absl/strings/internal/cord_rep_btree.h"
 #include "absl/strings/internal/cord_rep_crc.h"
@@ -1094,35 +1095,6 @@
   }
 }
 
-Cord::ChunkIterator& Cord::ChunkIterator::AdvanceStack() {
-  auto& stack_of_right_children = stack_of_right_children_;
-  if (stack_of_right_children.empty()) {
-    assert(!current_chunk_.empty());  // Called on invalid iterator.
-    // We have reached the end of the Cord.
-    return *this;
-  }
-
-  // Process the next node on the stack.
-  CordRep* node = stack_of_right_children.back();
-  stack_of_right_children.pop_back();
-
-  // Get the child node if we encounter a SUBSTRING.
-  size_t offset = 0;
-  size_t length = node->length;
-  if (node->IsSubstring()) {
-    offset = node->substring()->start;
-    node = node->substring()->child;
-  }
-
-  assert(node->IsExternal() || node->IsFlat());
-  assert(length != 0);
-  const char* data =
-      node->IsExternal() ? node->external()->base : node->flat()->Data();
-  current_chunk_ = absl::string_view(data + offset, length);
-  current_leaf_ = node;
-  return *this;
-}
-
 Cord Cord::ChunkIterator::AdvanceAndReadBytes(size_t n) {
   ABSL_HARDENING_ASSERT(bytes_remaining_ >= n &&
                         "Attempted to iterate past `end()`");
@@ -1165,135 +1137,38 @@
     return subcord;
   }
 
-  auto& stack_of_right_children = stack_of_right_children_;
-  if (n < current_chunk_.size()) {
-    // Range to read is a proper subrange of the current chunk.
-    assert(current_leaf_ != nullptr);
-    CordRep* subnode = CordRep::Ref(current_leaf_);
-    const char* data = subnode->IsExternal() ? subnode->external()->base
-                                             : subnode->flat()->Data();
-    subnode = NewSubstring(subnode, current_chunk_.data() - data, n);
-    subcord.contents_.EmplaceTree(VerifyTree(subnode), method);
-    RemoveChunkPrefix(n);
-    return subcord;
-  }
-
-  // Range to read begins with a proper subrange of the current chunk.
-  assert(!current_chunk_.empty());
+  // Short circuit if reading the entire data edge.
   assert(current_leaf_ != nullptr);
-  CordRep* subnode = CordRep::Ref(current_leaf_);
-  if (current_chunk_.size() < subnode->length) {
-    const char* data = subnode->IsExternal() ? subnode->external()->base
-                                             : subnode->flat()->Data();
-    subnode = NewSubstring(subnode, current_chunk_.data() - data,
-                           current_chunk_.size());
-  }
-  n -= current_chunk_.size();
-  bytes_remaining_ -= current_chunk_.size();
-
-  // Process the next node(s) on the stack, reading whole subtrees depending on
-  // their length and how many bytes we are advancing.
-  CordRep* node = nullptr;
-  while (!stack_of_right_children.empty()) {
-    node = stack_of_right_children.back();
-    stack_of_right_children.pop_back();
-    if (node->length > n) break;
-    // TODO(qrczak): This might unnecessarily recreate existing concat nodes.
-    // Avoiding that would need pretty complicated logic (instead of
-    // current_leaf, keep current_subtree_ which points to the highest node
-    // such that the current leaf can be found on the path of left children
-    // starting from current_subtree_; delay creating subnode while node is
-    // below current_subtree_; find the proper node along the path of left
-    // children starting from current_subtree_ if this loop exits while staying
-    // below current_subtree_; etc.; alternatively, push parents instead of
-    // right children on the stack).
-    subnode = Concat(subnode, CordRep::Ref(node));
-    n -= node->length;
-    bytes_remaining_ -= node->length;
-    node = nullptr;
-  }
-
-  if (node == nullptr) {
-    // We have reached the end of the Cord.
-    assert(bytes_remaining_ == 0);
-    subcord.contents_.EmplaceTree(VerifyTree(subnode), method);
+  if (n == current_leaf_->length) {
+    bytes_remaining_ = 0;
+    current_chunk_ = {};
+    CordRep* tree = CordRep::Ref(current_leaf_);
+    subcord.contents_.EmplaceTree(VerifyTree(tree), method);
     return subcord;
   }
 
-  // Get the child node if we encounter a SUBSTRING.
-  size_t offset = 0;
-  size_t length = node->length;
-  if (node->IsSubstring()) {
-    offset = node->substring()->start;
-    node = node->substring()->child;
-  }
+  // From this point on, we need a partial substring node.
+  // Get pointer to the underlying flat or external data payload and
+  // compute data pointer and offset into current flat or external.
+  CordRep* payload = current_leaf_->IsSubstring()
+                         ? current_leaf_->substring()->child
+                         : current_leaf_;
+  const char* data = payload->IsExternal() ? payload->external()->base
+                                           : payload->flat()->Data();
+  const size_t offset = current_chunk_.data() - data;
 
-  // Range to read ends with a proper (possibly empty) subrange of the current
-  // chunk.
-  assert(node->IsExternal() || node->IsFlat());
-  assert(length > n);
-  if (n > 0) {
-    subnode = Concat(subnode, NewSubstring(CordRep::Ref(node), offset, n));
-  }
-  const char* data =
-      node->IsExternal() ? node->external()->base : node->flat()->Data();
-  current_chunk_ = absl::string_view(data + offset + n, length - n);
-  current_leaf_ = node;
+  CordRepSubstring* tree = new CordRepSubstring();
+  tree->tag = cord_internal::SUBSTRING;
+  tree->length = n;
+  tree->start = offset;
+  tree->child = CordRep::Ref(payload);
+
+  subcord.contents_.EmplaceTree(VerifyTree(tree), method);
   bytes_remaining_ -= n;
-  subcord.contents_.EmplaceTree(VerifyTree(subnode), method);
+  current_chunk_.remove_prefix(n);
   return subcord;
 }
 
-void Cord::ChunkIterator::AdvanceBytesSlowPath(size_t n) {
-  assert(bytes_remaining_ >= n && "Attempted to iterate past `end()`");
-  assert(n >= current_chunk_.size());  // This should only be called when
-                                       // iterating to a new node.
-
-  n -= current_chunk_.size();
-  bytes_remaining_ -= current_chunk_.size();
-
-  if (stack_of_right_children_.empty()) {
-    // We have reached the end of the Cord.
-    assert(bytes_remaining_ == 0);
-    return;
-  }
-
-  // Process the next node(s) on the stack, skipping whole subtrees depending on
-  // their length and how many bytes we are advancing.
-  CordRep* node = nullptr;
-  auto& stack_of_right_children = stack_of_right_children_;
-  while (!stack_of_right_children.empty()) {
-    node = stack_of_right_children.back();
-    stack_of_right_children.pop_back();
-    if (node->length > n) break;
-    n -= node->length;
-    bytes_remaining_ -= node->length;
-    node = nullptr;
-  }
-
-  if (node == nullptr) {
-    // We have reached the end of the Cord.
-    assert(bytes_remaining_ == 0);
-    return;
-  }
-
-  // Get the child node if we encounter a SUBSTRING.
-  size_t offset = 0;
-  size_t length = node->length;
-  if (node->IsSubstring()) {
-    offset = node->substring()->start;
-    node = node->substring()->child;
-  }
-
-  assert(node->IsExternal() || node->IsFlat());
-  assert(length > n);
-  const char* data =
-      node->IsExternal() ? node->external()->base : node->flat()->Data();
-  current_chunk_ = absl::string_view(data + offset + n, length - n);
-  current_leaf_ = node;
-  bytes_remaining_ -= n;
-}
-
 char Cord::operator[](size_t i) const {
   ABSL_HARDENING_ASSERT(i < size());
   size_t offset = i;
diff --git a/third_party/abseil-cpp/absl/strings/cord.h b/third_party/abseil-cpp/absl/strings/cord.h
index 7f34ef48..081b631 100644
--- a/third_party/abseil-cpp/absl/strings/cord.h
+++ b/third_party/abseil-cpp/absl/strings/cord.h
@@ -80,6 +80,7 @@
 #include "absl/functional/function_ref.h"
 #include "absl/meta/type_traits.h"
 #include "absl/strings/cord_analysis.h"
+#include "absl/strings/internal/cord_data_edge.h"
 #include "absl/strings/internal/cord_internal.h"
 #include "absl/strings/internal/cord_rep_btree.h"
 #include "absl/strings/internal/cord_rep_btree_reader.h"
@@ -388,12 +389,6 @@
     using CordRepBtree = absl::cord_internal::CordRepBtree;
     using CordRepBtreeReader = absl::cord_internal::CordRepBtreeReader;
 
-    // Stack of right children of concat nodes that we have to visit.
-    // Keep this at the end of the structure to avoid cache-thrashing.
-    // TODO(jgm): Benchmark to see if there's a more optimal value than 47 for
-    // the inlined vector size (47 exists for backward compatibility).
-    using Stack = absl::InlinedVector<absl::cord_internal::CordRep*, 47>;
-
     // Constructs a `begin()` iterator from `tree`. `tree` must not be null.
     explicit ChunkIterator(cord_internal::CordRep* tree);
 
@@ -409,17 +404,10 @@
     Cord AdvanceAndReadBytes(size_t n);
     void AdvanceBytes(size_t n);
 
-    // Stack specific operator++
-    ChunkIterator& AdvanceStack();
-
     // Btree specific operator++
     ChunkIterator& AdvanceBtree();
     void AdvanceBytesBtree(size_t n);
 
-    // Iterates `n` bytes, where `n` is expected to be greater than or equal to
-    // `current_chunk_.size()`.
-    void AdvanceBytesSlowPath(size_t n);
-
     // A view into bytes of the current `CordRep`. It may only be a view to a
     // suffix of bytes if this is being used by `CharIterator`.
     absl::string_view current_chunk_;
@@ -432,9 +420,6 @@
 
     // Cord reader for cord btrees. Empty if not traversing a btree.
     CordRepBtreeReader btree_reader_;
-
-    // See 'Stack' alias definition.
-    Stack stack_of_right_children_;
   };
 
   // Cord::ChunkIterator::chunk_begin()
@@ -725,7 +710,8 @@
   // be used by spelling absl::strings_internal::MakeStringConstant, which is
   // also an internal API.
   template <typename T>
-  explicit constexpr Cord(strings_internal::StringConstant<T>);
+  // NOLINTNEXTLINE(google-explicit-constructor)
+  constexpr Cord(strings_internal::StringConstant<T>);
 
  private:
   using CordRep = absl::cord_internal::CordRep;
@@ -1321,25 +1307,24 @@
   tree = cord_internal::SkipCrcNode(tree);
   if (tree->tag == cord_internal::BTREE) {
     current_chunk_ = btree_reader_.Init(tree->btree());
-    return;
+  } else {
+    current_leaf_ = tree;
+    current_chunk_ = cord_internal::EdgeData(tree);
   }
-
-  stack_of_right_children_.push_back(tree);
-  operator++();
 }
 
-inline Cord::ChunkIterator::ChunkIterator(cord_internal::CordRep* tree)
-    : bytes_remaining_(tree->length) {
+inline Cord::ChunkIterator::ChunkIterator(cord_internal::CordRep* tree) {
+  bytes_remaining_ = tree->length;
   InitTree(tree);
 }
 
-inline Cord::ChunkIterator::ChunkIterator(const Cord* cord)
-    : bytes_remaining_(cord->size()) {
-  if (cord->contents_.is_tree()) {
-    InitTree(cord->contents_.as_tree());
+inline Cord::ChunkIterator::ChunkIterator(const Cord* cord) {
+  if (CordRep* tree = cord->contents_.tree()) {
+    bytes_remaining_ = tree->length;
+    InitTree(tree);
   } else {
-    current_chunk_ =
-        absl::string_view(cord->contents_.data(), bytes_remaining_);
+    bytes_remaining_ = cord->contents_.inline_size();
+    current_chunk_ = {cord->contents_.data(), bytes_remaining_};
   }
 }
 
@@ -1369,8 +1354,11 @@
   assert(bytes_remaining_ >= current_chunk_.size());
   bytes_remaining_ -= current_chunk_.size();
   if (bytes_remaining_ > 0) {
-    return btree_reader_ ? AdvanceBtree() : AdvanceStack();
-  } else {
+    if (btree_reader_) {
+      return AdvanceBtree();
+    } else {
+      assert(!current_chunk_.empty());  // Called on invalid iterator.
+    }
     current_chunk_ = {};
   }
   return *this;
@@ -1411,7 +1399,11 @@
   if (ABSL_PREDICT_TRUE(n < current_chunk_.size())) {
     RemoveChunkPrefix(n);
   } else if (n != 0) {
-    btree_reader_ ? AdvanceBytesBtree(n) : AdvanceBytesSlowPath(n);
+    if (btree_reader_) {
+      AdvanceBytesBtree(n);
+    } else {
+      bytes_remaining_ = 0;
+    }
   }
 }
 
diff --git a/third_party/abseil-cpp/absl/strings/cord_analysis.cc b/third_party/abseil-cpp/absl/strings/cord_analysis.cc
index 3fa15b01..73d3c4e6 100644
--- a/third_party/abseil-cpp/absl/strings/cord_analysis.cc
+++ b/third_party/abseil-cpp/absl/strings/cord_analysis.cc
@@ -12,13 +12,15 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include "absl/strings/cord_analysis.h"
+
 #include <cstddef>
 #include <cstdint>
 
 #include "absl/base/attributes.h"
 #include "absl/base/config.h"
 #include "absl/container/inlined_vector.h"
-#include "absl/strings/cord_analysis.h"
+#include "absl/strings/internal/cord_data_edge.h"
 #include "absl/strings/internal/cord_internal.h"
 #include "absl/strings/internal/cord_rep_btree.h"
 #include "absl/strings/internal/cord_rep_crc.h"
@@ -98,16 +100,6 @@
   }
 };
 
-// Returns true if the provided rep is a valid data edge.
-bool IsDataEdge(const CordRep* rep) {
-  // The fast path is that `rep` is an EXTERNAL or FLAT node, making the below
-  // if a single, well predicted branch. We then repeat the FLAT or EXTERNAL
-  // check in the slow path the SUBSTRING check to optimize for the hot path.
-  if (rep->tag == EXTERNAL || rep->tag >= FLAT) return true;
-  if (rep->tag == SUBSTRING) rep = rep->substring()->child;
-  return rep->tag == EXTERNAL || rep->tag >= FLAT;
-}
-
 // Computes the estimated memory size of the provided data edge.
 // External reps are assumed 'heap allocated at their exact size'.
 template <Mode mode>
diff --git a/third_party/abseil-cpp/absl/strings/cord_test.cc b/third_party/abseil-cpp/absl/strings/cord_test.cc
index c26e506d..9dcc4ce5 100644
--- a/third_party/abseil-cpp/absl/strings/cord_test.cc
+++ b/third_party/abseil-cpp/absl/strings/cord_test.cc
@@ -1866,6 +1866,78 @@
   VerifyChunkIterator(subcords, 128);
 }
 
+
+TEST_P(CordTest, AdvanceAndReadOnDataEdge) {
+  RandomEngine rng(GTEST_FLAG_GET(random_seed));
+  const std::string data = RandomLowercaseString(&rng, 2000);
+  for (bool as_flat : {true, false}) {
+    SCOPED_TRACE(as_flat ? "Flat" : "External");
+
+    absl::Cord cord =
+        as_flat ? absl::Cord(data)
+                : absl::MakeCordFromExternal(data, [](absl::string_view) {});
+    auto it = cord.Chars().begin();
+#if !defined(NDEBUG) || ABSL_OPTION_HARDENED
+    EXPECT_DEATH_IF_SUPPORTED(cord.AdvanceAndRead(&it, 2001), ".*");
+#endif
+
+    it = cord.Chars().begin();
+    absl::Cord frag = cord.AdvanceAndRead(&it, 2000);
+    EXPECT_EQ(frag, data);
+    EXPECT_TRUE(it == cord.Chars().end());
+
+    it = cord.Chars().begin();
+    frag = cord.AdvanceAndRead(&it, 200);
+    EXPECT_EQ(frag, data.substr(0, 200));
+    EXPECT_FALSE(it == cord.Chars().end());
+
+    frag = cord.AdvanceAndRead(&it, 1500);
+    EXPECT_EQ(frag, data.substr(200, 1500));
+    EXPECT_FALSE(it == cord.Chars().end());
+
+    frag = cord.AdvanceAndRead(&it, 300);
+    EXPECT_EQ(frag, data.substr(1700, 300));
+    EXPECT_TRUE(it == cord.Chars().end());
+  }
+}
+
+TEST_P(CordTest, AdvanceAndReadOnSubstringDataEdge) {
+  RandomEngine rng(GTEST_FLAG_GET(random_seed));
+  const std::string data = RandomLowercaseString(&rng, 2500);
+  for (bool as_flat : {true, false}) {
+    SCOPED_TRACE(as_flat ? "Flat" : "External");
+
+    absl::Cord cord =
+        as_flat ? absl::Cord(data)
+                : absl::MakeCordFromExternal(data, [](absl::string_view) {});
+    cord = cord.Subcord(200, 2000);
+    const std::string substr = data.substr(200, 2000);
+
+    auto it = cord.Chars().begin();
+#if !defined(NDEBUG) || ABSL_OPTION_HARDENED
+    EXPECT_DEATH_IF_SUPPORTED(cord.AdvanceAndRead(&it, 2001), ".*");
+#endif
+
+    it = cord.Chars().begin();
+    absl::Cord frag = cord.AdvanceAndRead(&it, 2000);
+    EXPECT_EQ(frag, substr);
+    EXPECT_TRUE(it == cord.Chars().end());
+
+    it = cord.Chars().begin();
+    frag = cord.AdvanceAndRead(&it, 200);
+    EXPECT_EQ(frag, substr.substr(0, 200));
+    EXPECT_FALSE(it == cord.Chars().end());
+
+    frag = cord.AdvanceAndRead(&it, 1500);
+    EXPECT_EQ(frag, substr.substr(200, 1500));
+    EXPECT_FALSE(it == cord.Chars().end());
+
+    frag = cord.AdvanceAndRead(&it, 300);
+    EXPECT_EQ(frag, substr.substr(1700, 300));
+    EXPECT_TRUE(it == cord.Chars().end());
+  }
+}
+
 TEST_P(CordTest, CharIteratorTraits) {
   static_assert(std::is_copy_constructible<absl::Cord::CharIterator>::value,
                 "");
diff --git a/third_party/abseil-cpp/absl/strings/internal/cord_data_edge.h b/third_party/abseil-cpp/absl/strings/internal/cord_data_edge.h
new file mode 100644
index 0000000..e18b33e
--- /dev/null
+++ b/third_party/abseil-cpp/absl/strings/internal/cord_data_edge.h
@@ -0,0 +1,63 @@
+// Copyright 2022 The Abseil Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef ABSL_STRINGS_INTERNAL_CORD_DATA_EDGE_H_
+#define ABSL_STRINGS_INTERNAL_CORD_DATA_EDGE_H_
+
+#include <cassert>
+#include <cstddef>
+
+#include "absl/base/config.h"
+#include "absl/strings/internal/cord_internal.h"
+#include "absl/strings/internal/cord_rep_flat.h"
+#include "absl/strings/string_view.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace cord_internal {
+
+// Returns true if the provided rep is a FLAT, EXTERNAL or a SUBSTRING node
+// holding a FLAT or EXTERNAL child rep. Requires `rep != nullptr`.
+inline bool IsDataEdge(const CordRep* edge) {
+  assert(edge != nullptr);
+
+  // The fast path is that `edge` is an EXTERNAL or FLAT node, making the below
+  // if a single, well predicted branch. We then repeat the FLAT or EXTERNAL
+  // check in the slow path of the SUBSTRING check to optimize for the hot path.
+  if (edge->tag == EXTERNAL || edge->tag >= FLAT) return true;
+  if (edge->tag == SUBSTRING) edge = edge->substring()->child;
+  return edge->tag == EXTERNAL || edge->tag >= FLAT;
+}
+
+// Returns the `absl::string_view` data reference for the provided data edge.
+// Requires 'IsDataEdge(edge) == true`.
+inline absl::string_view EdgeData(const CordRep* edge) {
+  assert(IsDataEdge(edge));
+
+  size_t offset = 0;
+  const size_t length = edge->length;
+  if (edge->IsSubstring()) {
+    offset = edge->substring()->start;
+    edge = edge->substring()->child;
+  }
+  return edge->tag >= FLAT
+             ? absl::string_view{edge->flat()->Data() + offset, length}
+             : absl::string_view{edge->external()->base + offset, length};
+}
+
+}  // namespace cord_internal
+ABSL_NAMESPACE_END
+}  // namespace absl
+
+#endif  // ABSL_STRINGS_INTERNAL_CORD_DATA_EDGE_H_
diff --git a/third_party/abseil-cpp/absl/strings/internal/cord_data_edge_test.cc b/third_party/abseil-cpp/absl/strings/internal/cord_data_edge_test.cc
new file mode 100644
index 0000000..8fce3bc
--- /dev/null
+++ b/third_party/abseil-cpp/absl/strings/internal/cord_data_edge_test.cc
@@ -0,0 +1,130 @@
+// Copyright 2022 The Abseil Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "absl/strings/internal/cord_data_edge.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "absl/strings/internal/cord_internal.h"
+#include "absl/strings/internal/cord_rep_test_util.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace cord_internal {
+namespace {
+
+using ::absl::cordrep_testing::MakeExternal;
+using ::absl::cordrep_testing::MakeFlat;
+using ::absl::cordrep_testing::MakeSubstring;
+
+TEST(CordDataEdgeTest, IsDataEdgeOnFlat) {
+  CordRep* rep = MakeFlat("Lorem ipsum dolor sit amet, consectetur ...");
+  EXPECT_TRUE(IsDataEdge(rep));
+  CordRep::Unref(rep);
+}
+
+TEST(CordDataEdgeTest, IsDataEdgeOnExternal) {
+  CordRep* rep = MakeExternal("Lorem ipsum dolor sit amet, consectetur ...");
+  EXPECT_TRUE(IsDataEdge(rep));
+  CordRep::Unref(rep);
+}
+
+TEST(CordDataEdgeTest, IsDataEdgeOnSubstringOfFlat) {
+  CordRep* rep = MakeFlat("Lorem ipsum dolor sit amet, consectetur ...");
+  CordRep* substr = MakeSubstring(1, 20, rep);
+  EXPECT_TRUE(IsDataEdge(substr));
+  CordRep::Unref(substr);
+}
+
+TEST(CordDataEdgeTest, IsDataEdgeOnSubstringOfExternal) {
+  CordRep* rep = MakeExternal("Lorem ipsum dolor sit amet, consectetur ...");
+  CordRep* substr = MakeSubstring(1, 20, rep);
+  EXPECT_TRUE(IsDataEdge(substr));
+  CordRep::Unref(substr);
+}
+
+TEST(CordDataEdgeTest, IsDataEdgeOnBtree) {
+  CordRep* rep = MakeFlat("Lorem ipsum dolor sit amet, consectetur ...");
+  CordRepBtree* tree = CordRepBtree::New(rep);
+  EXPECT_FALSE(IsDataEdge(tree));
+  CordRep::Unref(tree);
+}
+
+TEST(CordDataEdgeTest, IsDataEdgeOnBadSubstr) {
+  CordRep* rep = MakeFlat("Lorem ipsum dolor sit amet, consectetur ...");
+  CordRep* substr = MakeSubstring(1, 18, MakeSubstring(1, 20, rep));
+  EXPECT_FALSE(IsDataEdge(substr));
+  CordRep::Unref(substr);
+}
+
+TEST(CordDataEdgeTest, EdgeDataOnFlat) {
+  absl::string_view value = "Lorem ipsum dolor sit amet, consectetur ...";
+  CordRep* rep = MakeFlat(value);
+  EXPECT_EQ(EdgeData(rep), value);
+  CordRep::Unref(rep);
+}
+
+TEST(CordDataEdgeTest, EdgeDataOnExternal) {
+  absl::string_view value = "Lorem ipsum dolor sit amet, consectetur ...";
+  CordRep* rep = MakeExternal(value);
+  EXPECT_EQ(EdgeData(rep), value);
+  CordRep::Unref(rep);
+}
+
+TEST(CordDataEdgeTest, EdgeDataOnSubstringOfFlat) {
+  absl::string_view value = "Lorem ipsum dolor sit amet, consectetur ...";
+  CordRep* rep = MakeFlat(value);
+  CordRep* substr = MakeSubstring(1, 20, rep);
+  EXPECT_EQ(EdgeData(substr), value.substr(1, 20));
+  CordRep::Unref(substr);
+}
+
+TEST(CordDataEdgeTest, EdgeDataOnSubstringOfExternal) {
+  absl::string_view value = "Lorem ipsum dolor sit amet, consectetur ...";
+  CordRep* rep = MakeExternal(value);
+  CordRep* substr = MakeSubstring(1, 20, rep);
+  EXPECT_EQ(EdgeData(substr), value.substr(1, 20));
+  CordRep::Unref(substr);
+}
+
+#if defined(GTEST_HAS_DEATH_TEST) && !defined(NDEBUG)
+
+TEST(CordDataEdgeTest, IsDataEdgeOnNullPtr) {
+  EXPECT_DEATH(IsDataEdge(nullptr), ".*");
+}
+
+TEST(CordDataEdgeTest, EdgeDataOnNullPtr) {
+  EXPECT_DEATH(EdgeData(nullptr), ".*");
+}
+
+TEST(CordDataEdgeTest, EdgeDataOnBtree) {
+  CordRep* rep = MakeFlat("Lorem ipsum dolor sit amet, consectetur ...");
+  CordRepBtree* tree = CordRepBtree::New(rep);
+  EXPECT_DEATH(EdgeData(tree), ".*");
+  CordRep::Unref(tree);
+}
+
+TEST(CordDataEdgeTest, EdgeDataOnBadSubstr) {
+  CordRep* rep = MakeFlat("Lorem ipsum dolor sit amet, consectetur ...");
+  CordRep* substr = MakeSubstring(1, 18, MakeSubstring(1, 20, rep));
+  EXPECT_DEATH(EdgeData(substr), ".*");
+  CordRep::Unref(substr);
+}
+
+#endif  // GTEST_HAS_DEATH_TEST && !NDEBUG
+
+}  // namespace
+}  // namespace cord_internal
+ABSL_NAMESPACE_END
+}  // namespace absl
diff --git a/third_party/abseil-cpp/absl/strings/internal/cord_rep_btree.cc b/third_party/abseil-cpp/absl/strings/internal/cord_rep_btree.cc
index 3ebd9f6..2b592b47 100644
--- a/third_party/abseil-cpp/absl/strings/internal/cord_rep_btree.cc
+++ b/third_party/abseil-cpp/absl/strings/internal/cord_rep_btree.cc
@@ -22,6 +22,7 @@
 #include "absl/base/attributes.h"
 #include "absl/base/config.h"
 #include "absl/base/internal/raw_logging.h"
+#include "absl/strings/internal/cord_data_edge.h"
 #include "absl/strings/internal/cord_internal.h"
 #include "absl/strings/internal/cord_rep_consume.h"
 #include "absl/strings/internal/cord_rep_flat.h"
@@ -69,7 +70,7 @@
       // indentation and prefix / labels keeps us within roughly 80-100 wide.
       constexpr size_t kMaxDataLength = 60;
       stream << ", data = \""
-             << CordRepBtree::EdgeData(r).substr(0, kMaxDataLength)
+             << EdgeData(r).substr(0, kMaxDataLength)
              << (r->length > kMaxDataLength ? "\"..." : "\"");
     }
     stream << '\n';
@@ -150,7 +151,7 @@
 CordRep* ResizeEdge(CordRep* edge, size_t length, bool is_mutable) {
   assert(length > 0);
   assert(length <= edge->length);
-  assert(CordRepBtree::IsDataEdge(edge));
+  assert(IsDataEdge(edge));
   if (length >= edge->length) return edge;
 
   if (is_mutable && (edge->tag >= FLAT || edge->tag == SUBSTRING)) {
@@ -207,7 +208,7 @@
 
 // Deletes a leaf node data edge. Requires `IsDataEdge(rep)`.
 void DeleteLeafEdge(CordRep* rep) {
-  assert(CordRepBtree::IsDataEdge(rep));
+  assert(IsDataEdge(rep));
   if (rep->tag >= FLAT) {
     CordRepFlat::Delete(rep->flat());
   } else if (rep->tag == EXTERNAL) {
diff --git a/third_party/abseil-cpp/absl/strings/internal/cord_rep_btree.h b/third_party/abseil-cpp/absl/strings/internal/cord_rep_btree.h
index df5c994..0e78e12 100644
--- a/third_party/abseil-cpp/absl/strings/internal/cord_rep_btree.h
+++ b/third_party/abseil-cpp/absl/strings/internal/cord_rep_btree.h
@@ -22,6 +22,7 @@
 #include "absl/base/config.h"
 #include "absl/base/internal/raw_logging.h"
 #include "absl/base/optimization.h"
+#include "absl/strings/internal/cord_data_edge.h"
 #include "absl/strings/internal/cord_internal.h"
 #include "absl/strings/internal/cord_rep_flat.h"
 #include "absl/strings/string_view.h"
@@ -310,13 +311,6 @@
   // Requires this instance to be a leaf node, and `index` to be valid index.
   inline absl::string_view Data(size_t index) const;
 
-  static const char* EdgeDataPtr(const CordRep* r);
-  static absl::string_view EdgeData(const CordRep* r);
-
-  // Returns true if the provided rep is a FLAT, EXTERNAL or a SUBSTRING node
-  // holding a FLAT or EXTERNAL child rep.
-  static bool IsDataEdge(const CordRep* rep);
-
   // Diagnostics: returns true if `tree` is valid and internally consistent.
   // If `shallow` is false, then the provided top level node and all child nodes
   // below it are recursively checked. If `shallow` is true, only the provided
@@ -631,34 +625,11 @@
   return {edges_ + begin, static_cast<size_t>(end - begin)};
 }
 
-inline const char* CordRepBtree::EdgeDataPtr(const CordRep* r) {
-  assert(IsDataEdge(r));
-  size_t offset = 0;
-  if (r->tag == SUBSTRING) {
-    offset = r->substring()->start;
-    r = r->substring()->child;
-  }
-  return (r->tag >= FLAT ? r->flat()->Data() : r->external()->base) + offset;
-}
-
-inline absl::string_view CordRepBtree::EdgeData(const CordRep* r) {
-  return absl::string_view(EdgeDataPtr(r), r->length);
-}
-
 inline absl::string_view CordRepBtree::Data(size_t index) const {
   assert(height() == 0);
   return EdgeData(Edge(index));
 }
 
-inline bool CordRepBtree::IsDataEdge(const CordRep* rep) {
-  // The fast path is that `rep` is an EXTERNAL or FLAT node, making the below
-  // if a single, well predicted branch. We then repeat the FLAT or EXTERNAL
-  // check in the slow path the SUBSTRING check to optimize for the hot path.
-  if (rep->tag == EXTERNAL || rep->tag >= FLAT) return true;
-  if (rep->tag == SUBSTRING) rep = rep->substring()->child;
-  return rep->tag == EXTERNAL || rep->tag >= FLAT;
-}
-
 inline CordRepBtree* CordRepBtree::New(int height) {
   CordRepBtree* tree = new CordRepBtree;
   tree->length = 0;
diff --git a/third_party/abseil-cpp/absl/strings/internal/cord_rep_btree_navigator.cc b/third_party/abseil-cpp/absl/strings/internal/cord_rep_btree_navigator.cc
index 19afbe90..9b896a3d 100644
--- a/third_party/abseil-cpp/absl/strings/internal/cord_rep_btree_navigator.cc
+++ b/third_party/abseil-cpp/absl/strings/internal/cord_rep_btree_navigator.cc
@@ -16,6 +16,7 @@
 
 #include <cassert>
 
+#include "absl/strings/internal/cord_data_edge.h"
 #include "absl/strings/internal/cord_internal.h"
 #include "absl/strings/internal/cord_rep_btree.h"
 
@@ -39,7 +40,7 @@
   assert(n <= rep->length);
   assert(offset < rep->length);
   assert(offset <= rep->length - n);
-  assert(CordRepBtree::IsDataEdge(rep));
+  assert(IsDataEdge(rep));
 
   if (n == 0) return nullptr;
   if (n == rep->length) return CordRep::Ref(rep);
diff --git a/third_party/abseil-cpp/absl/strings/internal/cord_rep_btree_reader.cc b/third_party/abseil-cpp/absl/strings/internal/cord_rep_btree_reader.cc
index 5dc7696..0d0e8601 100644
--- a/third_party/abseil-cpp/absl/strings/internal/cord_rep_btree_reader.cc
+++ b/third_party/abseil-cpp/absl/strings/internal/cord_rep_btree_reader.cc
@@ -17,6 +17,7 @@
 #include <cassert>
 
 #include "absl/base/config.h"
+#include "absl/strings/internal/cord_data_edge.h"
 #include "absl/strings/internal/cord_internal.h"
 #include "absl/strings/internal/cord_rep_btree.h"
 #include "absl/strings/internal/cord_rep_btree_navigator.h"
@@ -44,7 +45,7 @@
   // can directly return the substring into the current data edge as the next
   // chunk. We can easily establish from the above code that `navigator_.Next()`
   // has not been called as that requires `chunk_size` to be zero.
-  if (n < chunk_size) return CordRepBtree::EdgeData(edge).substr(result.n);
+  if (n < chunk_size) return EdgeData(edge).substr(result.n);
 
   // The amount of data taken from the last edge is `chunk_size` and `result.n`
   // contains the offset into the current edge trailing the read data (which can
@@ -60,7 +61,7 @@
   // We did not read all data, return remaining data from current edge.
   edge = navigator_.Current();
   remaining_ -= consumed_by_read + edge->length;
-  return CordRepBtree::EdgeData(edge).substr(result.n);
+  return EdgeData(edge).substr(result.n);
 }
 
 }  // namespace cord_internal
diff --git a/third_party/abseil-cpp/absl/strings/internal/cord_rep_btree_reader.h b/third_party/abseil-cpp/absl/strings/internal/cord_rep_btree_reader.h
index 7aa79dbf..8db8f8d 100644
--- a/third_party/abseil-cpp/absl/strings/internal/cord_rep_btree_reader.h
+++ b/third_party/abseil-cpp/absl/strings/internal/cord_rep_btree_reader.h
@@ -18,6 +18,7 @@
 #include <cassert>
 
 #include "absl/base/config.h"
+#include "absl/strings/internal/cord_data_edge.h"
 #include "absl/strings/internal/cord_internal.h"
 #include "absl/strings/internal/cord_rep_btree.h"
 #include "absl/strings/internal/cord_rep_btree_navigator.h"
@@ -167,7 +168,7 @@
   assert(tree != nullptr);
   const CordRep* edge = navigator_.InitFirst(tree);
   remaining_ = tree->length - edge->length;
-  return CordRepBtree::EdgeData(edge);
+  return EdgeData(edge);
 }
 
 inline absl::string_view CordRepBtreeReader::Next() {
@@ -175,7 +176,7 @@
   const CordRep* edge = navigator_.Next();
   assert(edge != nullptr);
   remaining_ -= edge->length;
-  return CordRepBtree::EdgeData(edge);
+  return EdgeData(edge);
 }
 
 inline absl::string_view CordRepBtreeReader::Skip(size_t skip) {
@@ -190,7 +191,7 @@
   // The combined length of all edges skipped before `pos.edge` is `skip -
   // pos.offset`, all of which are 'consumed', as well as the current edge.
   remaining_ -= skip - pos.offset + pos.edge->length;
-  return CordRepBtree::EdgeData(pos.edge).substr(pos.offset);
+  return EdgeData(pos.edge).substr(pos.offset);
 }
 
 inline absl::string_view CordRepBtreeReader::Seek(size_t offset) {
@@ -199,7 +200,7 @@
     remaining_ = 0;
     return {};
   }
-  absl::string_view chunk = CordRepBtree::EdgeData(pos.edge).substr(pos.offset);
+  absl::string_view chunk = EdgeData(pos.edge).substr(pos.offset);
   remaining_ = length() - offset - chunk.length();
   return chunk;
 }
diff --git a/third_party/abseil-cpp/absl/strings/internal/cord_rep_btree_test.cc b/third_party/abseil-cpp/absl/strings/internal/cord_rep_btree_test.cc
index 1d56b252..51b90db 100644
--- a/third_party/abseil-cpp/absl/strings/internal/cord_rep_btree_test.cc
+++ b/third_party/abseil-cpp/absl/strings/internal/cord_rep_btree_test.cc
@@ -25,6 +25,7 @@
 #include "absl/base/config.h"
 #include "absl/base/internal/raw_logging.h"
 #include "absl/cleanup/cleanup.h"
+#include "absl/strings/internal/cord_data_edge.h"
 #include "absl/strings/internal/cord_internal.h"
 #include "absl/strings/internal/cord_rep_test_util.h"
 #include "absl/strings/str_cat.h"
@@ -323,30 +324,26 @@
   CordRep* substr2 = MakeSubstring(1, 6, CordRep::Ref(external));
   CordRep* bad_substr = MakeSubstring(1, 2, CordRep::Ref(substr1));
 
-  EXPECT_TRUE(CordRepBtree::IsDataEdge(flat));
-  EXPECT_THAT(CordRepBtree::EdgeDataPtr(flat),
-              TypedEq<const void*>(flat->Data()));
-  EXPECT_THAT(CordRepBtree::EdgeData(flat), Eq("Hello world"));
+  EXPECT_TRUE(IsDataEdge(flat));
+  EXPECT_THAT(EdgeData(flat).data(), TypedEq<const void*>(flat->Data()));
+  EXPECT_THAT(EdgeData(flat), Eq("Hello world"));
 
-  EXPECT_TRUE(CordRepBtree::IsDataEdge(external));
-  EXPECT_THAT(CordRepBtree::EdgeDataPtr(external),
-              TypedEq<const void*>(external->base));
-  EXPECT_THAT(CordRepBtree::EdgeData(external), Eq("Hello external"));
+  EXPECT_TRUE(IsDataEdge(external));
+  EXPECT_THAT(EdgeData(external).data(), TypedEq<const void*>(external->base));
+  EXPECT_THAT(EdgeData(external), Eq("Hello external"));
 
-  EXPECT_TRUE(CordRepBtree::IsDataEdge(substr1));
-  EXPECT_THAT(CordRepBtree::EdgeDataPtr(substr1),
-              TypedEq<const void*>(flat->Data() + 1));
-  EXPECT_THAT(CordRepBtree::EdgeData(substr1), Eq("ello w"));
+  EXPECT_TRUE(IsDataEdge(substr1));
+  EXPECT_THAT(EdgeData(substr1).data(), TypedEq<const void*>(flat->Data() + 1));
+  EXPECT_THAT(EdgeData(substr1), Eq("ello w"));
 
-  EXPECT_TRUE(CordRepBtree::IsDataEdge(substr2));
-  EXPECT_THAT(CordRepBtree::EdgeDataPtr(substr2),
+  EXPECT_TRUE(IsDataEdge(substr2));
+  EXPECT_THAT(EdgeData(substr2).data(),
               TypedEq<const void*>(external->base + 1));
-  EXPECT_THAT(CordRepBtree::EdgeData(substr2), Eq("ello e"));
+  EXPECT_THAT(EdgeData(substr2), Eq("ello e"));
 
-  EXPECT_FALSE(CordRepBtree::IsDataEdge(bad_substr));
+  EXPECT_FALSE(IsDataEdge(bad_substr));
 #if defined(GTEST_HAS_DEATH_TEST) && !defined(NDEBUG)
-  EXPECT_DEATH(CordRepBtree::EdgeData(bad_substr), ".*");
-  EXPECT_DEATH(CordRepBtree::EdgeDataPtr(bad_substr), ".*");
+  EXPECT_DEATH(EdgeData(bad_substr), ".*");
 #endif
 
   CordRep::Unref(bad_substr);
diff --git a/third_party/abseil-cpp/ci/linux_docker_containers.sh b/third_party/abseil-cpp/ci/linux_docker_containers.sh
index 37be53104..0e05564f 100644
--- a/third_party/abseil-cpp/ci/linux_docker_containers.sh
+++ b/third_party/abseil-cpp/ci/linux_docker_containers.sh
@@ -16,6 +16,6 @@
 # Test scripts should source this file to get the identifiers.
 
 readonly LINUX_ALPINE_CONTAINER="gcr.io/google.com/absl-177019/alpine:20201026"
-readonly LINUX_CLANG_LATEST_CONTAINER="gcr.io/google.com/absl-177019/linux_hybrid-latest:20220113"
-readonly LINUX_GCC_LATEST_CONTAINER="gcr.io/google.com/absl-177019/linux_hybrid-latest:20220113"
+readonly LINUX_CLANG_LATEST_CONTAINER="gcr.io/google.com/absl-177019/linux_hybrid-latest:20220217"
+readonly LINUX_GCC_LATEST_CONTAINER="gcr.io/google.com/absl-177019/linux_hybrid-latest:20220217"
 readonly LINUX_GCC_FLOOR_CONTAINER="gcr.io/google.com/absl-177019/linux_gcc-floor:20210617"
diff --git a/third_party/abseil-cpp/symbols_arm64_dbg.def b/third_party/abseil-cpp/symbols_arm64_dbg.def
index 6d30a35..c096197 100644
--- a/third_party/abseil-cpp/symbols_arm64_dbg.def
+++ b/third_party/abseil-cpp/symbols_arm64_dbg.def
@@ -280,12 +280,10 @@
     ??$DivMod@$09@?$BigUnsigned@$03@strings_internal@absl@@AEAAIXZ
     ??$DivMod@$09@?$BigUnsigned@$0FE@@strings_internal@absl@@AEAAIXZ
     ??$EmplaceBack@AEBQEAUCordRep@cord_internal@absl@@@?$Storage@PEAUCordRep@cord_internal@absl@@$01V?$allocator@PEAUCordRep@cord_internal@absl@@@__1@std@@@inlined_vector_internal@absl@@QEAAAEAPEAUCordRep@cord_internal@2@AEBQEAU342@@Z
-    ??$EmplaceBack@AEBQEAUCordRep@cord_internal@absl@@@?$Storage@PEAUCordRep@cord_internal@absl@@$0CP@V?$allocator@PEAUCordRep@cord_internal@absl@@@__1@std@@@inlined_vector_internal@absl@@QEAAAEAPEAUCordRep@cord_internal@2@AEBQEAU342@@Z
     ??$EmplaceBack@PEAUCordRep@cord_internal@absl@@@?$Storage@PEAUCordRep@cord_internal@absl@@$0CP@V?$allocator@PEAUCordRep@cord_internal@absl@@@__1@std@@@inlined_vector_internal@absl@@QEAAAEAPEAUCordRep@cord_internal@2@$$QEAPEAU342@@Z
     ??$EmplaceBack@UPayload@status_internal@absl@@@?$Storage@UPayload@status_internal@absl@@$00V?$allocator@UPayload@status_internal@absl@@@__1@std@@@inlined_vector_internal@absl@@QEAAAEAUPayload@status_internal@2@$$QEAU342@@Z
     ??$EmplaceBack@USubRange@absl@@@?$Storage@USubRange@absl@@$0CP@V?$allocator@USubRange@absl@@@__1@std@@@inlined_vector_internal@absl@@QEAAAEAUSubRange@2@$$QEAU32@@Z
     ??$EmplaceBackSlow@AEBQEAUCordRep@cord_internal@absl@@@?$Storage@PEAUCordRep@cord_internal@absl@@$01V?$allocator@PEAUCordRep@cord_internal@absl@@@__1@std@@@inlined_vector_internal@absl@@AEAAAEAPEAUCordRep@cord_internal@2@AEBQEAU342@@Z
-    ??$EmplaceBackSlow@AEBQEAUCordRep@cord_internal@absl@@@?$Storage@PEAUCordRep@cord_internal@absl@@$0CP@V?$allocator@PEAUCordRep@cord_internal@absl@@@__1@std@@@inlined_vector_internal@absl@@AEAAAEAPEAUCordRep@cord_internal@2@AEBQEAU342@@Z
     ??$EmplaceBackSlow@PEAUCordRep@cord_internal@absl@@@?$Storage@PEAUCordRep@cord_internal@absl@@$0CP@V?$allocator@PEAUCordRep@cord_internal@absl@@@__1@std@@@inlined_vector_internal@absl@@AEAAAEAPEAUCordRep@cord_internal@2@$$QEAPEAU342@@Z
     ??$EmplaceBackSlow@UPayload@status_internal@absl@@@?$Storage@UPayload@status_internal@absl@@$00V?$allocator@UPayload@status_internal@absl@@@__1@std@@@inlined_vector_internal@absl@@AEAAAEAUPayload@status_internal@2@$$QEAU342@@Z
     ??$EmplaceBackSlow@USubRange@absl@@@?$Storage@USubRange@absl@@$0CP@V?$allocator@USubRange@absl@@@__1@std@@@inlined_vector_internal@absl@@AEAAAEAUSubRange@2@$$QEAU32@@Z
@@ -607,7 +605,6 @@
     ??$emplace_back@$$V@?$__split_buffer@UTransitionType@cctz@time_internal@absl@@AEAV?$allocator@UTransitionType@cctz@time_internal@absl@@@__1@std@@@__1@std@@QEAAXXZ
     ??$emplace_back@AEAVstring_view@absl@@AEBV12@AEA_K@?$vector@UViableSubstitution@strings_internal@absl@@V?$allocator@UViableSubstitution@strings_internal@absl@@@__1@std@@@__1@std@@QEAAAEAUViableSubstitution@strings_internal@absl@@AEAVstring_view@5@AEBV65@AEA_K@Z
     ??$emplace_back@AEBQEAUCordRep@cord_internal@absl@@@?$InlinedVector@PEAUCordRep@cord_internal@absl@@$01V?$allocator@PEAUCordRep@cord_internal@absl@@@__1@std@@@absl@@QEAAAEAPEAUCordRep@cord_internal@1@AEBQEAU231@@Z
-    ??$emplace_back@AEBQEAUCordRep@cord_internal@absl@@@?$InlinedVector@PEAUCordRep@cord_internal@absl@@$0CP@V?$allocator@PEAUCordRep@cord_internal@absl@@@__1@std@@@absl@@QEAAAEAPEAUCordRep@cord_internal@1@AEBQEAU231@@Z
     ??$emplace_back@PEAUCordRep@cord_internal@absl@@@?$InlinedVector@PEAUCordRep@cord_internal@absl@@$0CP@V?$allocator@PEAUCordRep@cord_internal@absl@@@__1@std@@@absl@@QEAAAEAPEAUCordRep@cord_internal@1@$$QEAPEAU231@@Z
     ??$emplace_back@UPayload@status_internal@absl@@@?$InlinedVector@UPayload@status_internal@absl@@$00V?$allocator@UPayload@status_internal@absl@@@__1@std@@@absl@@QEAAAEAUPayload@status_internal@1@$$QEAU231@@Z
     ??$emplace_back@USubRange@absl@@@?$InlinedVector@USubRange@absl@@$0CP@V?$allocator@USubRange@absl@@@__1@std@@@absl@@QEAAAEAUSubRange@1@$$QEAU21@@Z
@@ -1148,7 +1145,6 @@
     ??1?$vector@UViableSubstitution@strings_internal@absl@@V?$allocator@UViableSubstitution@strings_internal@absl@@@__1@std@@@__1@std@@QEAA@XZ
     ??1?$vector@VFormatArgImpl@str_format_internal@absl@@V?$allocator@VFormatArgImpl@str_format_internal@absl@@@__1@std@@@__1@std@@QEAA@XZ
     ??1BadStatusOrAccess@absl@@UEAA@XZ
-    ??1ChunkIterator@Cord@absl@@QEAA@XZ
     ??1CondVar@absl@@QEAA@XZ
     ??1Cord@absl@@QEAA@XZ
     ??1CordzHandle@cord_internal@absl@@MEAA@XZ
@@ -1478,8 +1474,6 @@
     ?AdvanceBtree@ChunkIterator@Cord@absl@@AEAAAEAV123@XZ
     ?AdvanceBytes@ChunkIterator@Cord@absl@@AEAAX_K@Z
     ?AdvanceBytesBtree@ChunkIterator@Cord@absl@@AEAAX_K@Z
-    ?AdvanceBytesSlowPath@ChunkIterator@Cord@absl@@AEAAX_K@Z
-    ?AdvanceStack@ChunkIterator@Cord@absl@@AEAAAEAV123@XZ
     ?Align@adl_barrier@internal_layout@container_internal@absl@@YA_K_K0@Z
     ?AlignBegin@CordRepBtree@cord_internal@absl@@AEAAXXZ
     ?AlignEnd@CordRepBtree@cord_internal@absl@@AEAAXXZ
@@ -1702,8 +1696,7 @@
     ?DurationFromTimeval@absl@@YA?AVDuration@1@Utimeval@@@Z
     ?Edge@CordRepBtree@cord_internal@absl@@QEBAPEAUCordRep@23@W4EdgeType@123@@Z
     ?Edge@CordRepBtree@cord_internal@absl@@QEBAPEAUCordRep@23@_K@Z
-    ?EdgeData@CordRepBtree@cord_internal@absl@@SA?AVstring_view@3@PEBUCordRep@23@@Z
-    ?EdgeDataPtr@CordRepBtree@cord_internal@absl@@SAPEBDPEBUCordRep@23@@Z
+    ?EdgeData@cord_internal@absl@@YA?AVstring_view@2@PEBUCordRep@12@@Z
     ?Edges@CordRepBtree@cord_internal@absl@@QEBA?AV?$Span@QEAUCordRep@cord_internal@absl@@@3@XZ
     ?Edges@CordRepBtree@cord_internal@absl@@QEBA?AV?$Span@QEAUCordRep@cord_internal@absl@@@3@_K0@Z
     ?EmplaceTree@InlineRep@Cord@absl@@QEAAXPEAUCordRep@cord_internal@3@AEBVInlineData@53@W4MethodIdentifier@CordzUpdateTracker@53@@Z
@@ -1997,7 +1990,7 @@
     ?IsCancelled@absl@@YA_NAEBVStatus@1@@Z
     ?IsCooperative@SpinLock@base_internal@absl@@CA_NW4SchedulingMode@23@@Z
     ?IsCrc@CordRep@cord_internal@absl@@QEBA_NXZ
-    ?IsDataEdge@CordRepBtree@cord_internal@absl@@SA_NPEBUCordRep@23@@Z
+    ?IsDataEdge@cord_internal@absl@@YA_NPEBUCordRep@12@@Z
     ?IsDataLoss@absl@@YA_NAEBVStatus@1@@Z
     ?IsDeadlineExceeded@absl@@YA_NAEBVStatus@1@@Z
     ?IsEmpty@Queue@CordzHandle@cord_internal@absl@@QEBA_NXZ
@@ -3268,7 +3261,6 @@
     ?probe@container_internal@absl@@YA?AV?$probe_seq@$07@12@PEBW4ctrl_t@12@_K1@Z
     ?push_back@?$InlinedVector@PEAUCordRep@cord_internal@absl@@$01V?$allocator@PEAUCordRep@cord_internal@absl@@@__1@std@@@absl@@QEAAXAEBQEAUCordRep@cord_internal@2@@Z
     ?push_back@?$InlinedVector@PEAUCordRep@cord_internal@absl@@$0CP@V?$allocator@PEAUCordRep@cord_internal@absl@@@__1@std@@@absl@@QEAAX$$QEAPEAUCordRep@cord_internal@2@@Z
-    ?push_back@?$InlinedVector@PEAUCordRep@cord_internal@absl@@$0CP@V?$allocator@PEAUCordRep@cord_internal@absl@@@__1@std@@@absl@@QEAAXAEBQEAUCordRep@cord_internal@2@@Z
     ?push_back@?$InlinedVector@UPayload@status_internal@absl@@$00V?$allocator@UPayload@status_internal@absl@@@__1@std@@@absl@@QEAAX$$QEAUPayload@status_internal@2@@Z
     ?push_back@?$InlinedVector@USubRange@absl@@$0CP@V?$allocator@USubRange@absl@@@__1@std@@@absl@@QEAAX$$QEAUSubRange@2@@Z
     ?push_back@?$__split_buffer@PEAPEBVImpl@time_zone@cctz@time_internal@absl@@AEAV?$allocator@PEAPEBVImpl@time_zone@cctz@time_internal@absl@@@__1@std@@@__1@std@@QEAAX$$QEAPEAPEBVImpl@time_zone@cctz@time_internal@absl@@@Z
diff --git a/third_party/abseil-cpp/symbols_arm64_rel.def b/third_party/abseil-cpp/symbols_arm64_rel.def
index 530ad452..36d84e6 100644
--- a/third_party/abseil-cpp/symbols_arm64_rel.def
+++ b/third_party/abseil-cpp/symbols_arm64_rel.def
@@ -42,11 +42,9 @@
     ??$Dispatch@_N@FormatArgImpl@str_format_internal@absl@@CA_NTData@012@VFormatConversionSpecImpl@12@PEAX@Z
     ??$DivMod@$09@?$BigUnsigned@$03@strings_internal@absl@@AEAAIXZ
     ??$DivMod@$09@?$BigUnsigned@$0FE@@strings_internal@absl@@AEAAIXZ
-    ??$EmplaceBack@AEBQEAUCordRep@cord_internal@absl@@@?$Storage@PEAUCordRep@cord_internal@absl@@$0CP@V?$allocator@PEAUCordRep@cord_internal@absl@@@__1@std@@@inlined_vector_internal@absl@@QEAAAEAPEAUCordRep@cord_internal@2@AEBQEAU342@@Z
     ??$EmplaceBack@PEAUCordRep@cord_internal@absl@@@?$Storage@PEAUCordRep@cord_internal@absl@@$0CP@V?$allocator@PEAUCordRep@cord_internal@absl@@@__1@std@@@inlined_vector_internal@absl@@QEAAAEAPEAUCordRep@cord_internal@2@$$QEAPEAU342@@Z
     ??$EmplaceBack@UPayload@status_internal@absl@@@?$Storage@UPayload@status_internal@absl@@$00V?$allocator@UPayload@status_internal@absl@@@__1@std@@@inlined_vector_internal@absl@@QEAAAEAUPayload@status_internal@2@$$QEAU342@@Z
     ??$EmplaceBack@USubRange@absl@@@?$Storage@USubRange@absl@@$0CP@V?$allocator@USubRange@absl@@@__1@std@@@inlined_vector_internal@absl@@QEAAAEAUSubRange@2@$$QEAU32@@Z
-    ??$EmplaceBackSlow@AEBQEAUCordRep@cord_internal@absl@@@?$Storage@PEAUCordRep@cord_internal@absl@@$0CP@V?$allocator@PEAUCordRep@cord_internal@absl@@@__1@std@@@inlined_vector_internal@absl@@AEAAAEAPEAUCordRep@cord_internal@2@AEBQEAU342@@Z
     ??$EmplaceBackSlow@PEAUCordRep@cord_internal@absl@@@?$Storage@PEAUCordRep@cord_internal@absl@@$0CP@V?$allocator@PEAUCordRep@cord_internal@absl@@@__1@std@@@inlined_vector_internal@absl@@AEAAAEAPEAUCordRep@cord_internal@2@$$QEAPEAU342@@Z
     ??$EmplaceBackSlow@UPayload@status_internal@absl@@@?$Storage@UPayload@status_internal@absl@@$00V?$allocator@UPayload@status_internal@absl@@@__1@std@@@inlined_vector_internal@absl@@AEAAAEAUPayload@status_internal@2@$$QEAU342@@Z
     ??$EmplaceBackSlow@USubRange@absl@@@?$Storage@USubRange@absl@@$0CP@V?$allocator@USubRange@absl@@@__1@std@@@inlined_vector_internal@absl@@AEAAAEAUSubRange@2@$$QEAU32@@Z
@@ -127,6 +125,7 @@
     ??0ByAnyChar@absl@@QEAA@Vstring_view@1@@Z
     ??0ByLength@absl@@QEAA@_J@Z
     ??0ByString@absl@@QEAA@Vstring_view@1@@Z
+    ??0ChunkIterator@Cord@absl@@AEAA@PEAUCordRep@cord_internal@2@@Z
     ??0ChunkIterator@Cord@absl@@AEAA@PEBV12@@Z
     ??0Condition@absl@@AEAA@XZ
     ??0Condition@absl@@QEAA@P6A_NPEAX@Z0@Z
@@ -231,8 +230,6 @@
     ?AddressIsReadable@debugging_internal@absl@@YA_NPEBX@Z
     ?AdvanceAndReadBytes@ChunkIterator@Cord@absl@@AEAA?AV23@_K@Z
     ?AdvanceBytesBtree@ChunkIterator@Cord@absl@@AEAAX_K@Z
-    ?AdvanceBytesSlowPath@ChunkIterator@Cord@absl@@AEAAX_K@Z
-    ?AdvanceStack@ChunkIterator@Cord@absl@@AEAAAEAV123@XZ
     ?AllocWithArena@LowLevelAlloc@base_internal@absl@@SAPEAX_KPEAUArena@123@@Z
     ?Allocate@?$MallocAdapter@V?$allocator@PEAUCordRep@cord_internal@absl@@@__1@std@@$0A@@inlined_vector_internal@absl@@SA?AU?$Allocation@V?$allocator@PEAUCordRep@cord_internal@absl@@@__1@std@@@23@AEAV?$allocator@PEAUCordRep@cord_internal@absl@@@__1@std@@_K@Z
     ?Allocate@?$MallocAdapter@V?$allocator@UPayload@status_internal@absl@@@__1@std@@$0A@@inlined_vector_internal@absl@@SA?AU?$Allocation@V?$allocator@UPayload@status_internal@absl@@@__1@std@@@23@AEAV?$allocator@UPayload@status_internal@absl@@@__1@std@@_K@Z
@@ -407,6 +404,7 @@
     ?Find@ByChar@absl@@QEBA?AVstring_view@2@V32@_K@Z
     ?Find@ByLength@absl@@QEBA?AVstring_view@2@V32@_K@Z
     ?Find@ByString@absl@@QEBA?AVstring_view@2@V32@_K@Z
+    ?FindFlatStartPiece@InlineRep@Cord@absl@@QEBA?AVstring_view@3@XZ
     ?FindPath@GraphCycles@synchronization_internal@absl@@QEBAHUGraphId@23@0HQEAU423@@Z
     ?FindSlow@CordRepRing@cord_internal@absl@@AEBA?AUPosition@123@I_K@Z
     ?FindTailSlow@CordRepRing@cord_internal@absl@@AEBA?AUPosition@123@I_K@Z
diff --git a/third_party/abseil-cpp/symbols_x64_dbg.def b/third_party/abseil-cpp/symbols_x64_dbg.def
index 6e31257..1a6b6dc 100644
--- a/third_party/abseil-cpp/symbols_x64_dbg.def
+++ b/third_party/abseil-cpp/symbols_x64_dbg.def
@@ -280,12 +280,10 @@
     ??$DivMod@$09@?$BigUnsigned@$03@strings_internal@absl@@AEAAIXZ
     ??$DivMod@$09@?$BigUnsigned@$0FE@@strings_internal@absl@@AEAAIXZ
     ??$EmplaceBack@AEBQEAUCordRep@cord_internal@absl@@@?$Storage@PEAUCordRep@cord_internal@absl@@$01V?$allocator@PEAUCordRep@cord_internal@absl@@@__1@std@@@inlined_vector_internal@absl@@QEAAAEAPEAUCordRep@cord_internal@2@AEBQEAU342@@Z
-    ??$EmplaceBack@AEBQEAUCordRep@cord_internal@absl@@@?$Storage@PEAUCordRep@cord_internal@absl@@$0CP@V?$allocator@PEAUCordRep@cord_internal@absl@@@__1@std@@@inlined_vector_internal@absl@@QEAAAEAPEAUCordRep@cord_internal@2@AEBQEAU342@@Z
     ??$EmplaceBack@PEAUCordRep@cord_internal@absl@@@?$Storage@PEAUCordRep@cord_internal@absl@@$0CP@V?$allocator@PEAUCordRep@cord_internal@absl@@@__1@std@@@inlined_vector_internal@absl@@QEAAAEAPEAUCordRep@cord_internal@2@$$QEAPEAU342@@Z
     ??$EmplaceBack@UPayload@status_internal@absl@@@?$Storage@UPayload@status_internal@absl@@$00V?$allocator@UPayload@status_internal@absl@@@__1@std@@@inlined_vector_internal@absl@@QEAAAEAUPayload@status_internal@2@$$QEAU342@@Z
     ??$EmplaceBack@USubRange@absl@@@?$Storage@USubRange@absl@@$0CP@V?$allocator@USubRange@absl@@@__1@std@@@inlined_vector_internal@absl@@QEAAAEAUSubRange@2@$$QEAU32@@Z
     ??$EmplaceBackSlow@AEBQEAUCordRep@cord_internal@absl@@@?$Storage@PEAUCordRep@cord_internal@absl@@$01V?$allocator@PEAUCordRep@cord_internal@absl@@@__1@std@@@inlined_vector_internal@absl@@AEAAAEAPEAUCordRep@cord_internal@2@AEBQEAU342@@Z
-    ??$EmplaceBackSlow@AEBQEAUCordRep@cord_internal@absl@@@?$Storage@PEAUCordRep@cord_internal@absl@@$0CP@V?$allocator@PEAUCordRep@cord_internal@absl@@@__1@std@@@inlined_vector_internal@absl@@AEAAAEAPEAUCordRep@cord_internal@2@AEBQEAU342@@Z
     ??$EmplaceBackSlow@PEAUCordRep@cord_internal@absl@@@?$Storage@PEAUCordRep@cord_internal@absl@@$0CP@V?$allocator@PEAUCordRep@cord_internal@absl@@@__1@std@@@inlined_vector_internal@absl@@AEAAAEAPEAUCordRep@cord_internal@2@$$QEAPEAU342@@Z
     ??$EmplaceBackSlow@UPayload@status_internal@absl@@@?$Storage@UPayload@status_internal@absl@@$00V?$allocator@UPayload@status_internal@absl@@@__1@std@@@inlined_vector_internal@absl@@AEAAAEAUPayload@status_internal@2@$$QEAU342@@Z
     ??$EmplaceBackSlow@USubRange@absl@@@?$Storage@USubRange@absl@@$0CP@V?$allocator@USubRange@absl@@@__1@std@@@inlined_vector_internal@absl@@AEAAAEAUSubRange@2@$$QEAU32@@Z
@@ -611,7 +609,6 @@
     ??$emplace_back@$$V@?$__split_buffer@UTransitionType@cctz@time_internal@absl@@AEAV?$allocator@UTransitionType@cctz@time_internal@absl@@@__1@std@@@__1@std@@QEAAXXZ
     ??$emplace_back@AEAVstring_view@absl@@AEBV12@AEA_K@?$vector@UViableSubstitution@strings_internal@absl@@V?$allocator@UViableSubstitution@strings_internal@absl@@@__1@std@@@__1@std@@QEAAAEAUViableSubstitution@strings_internal@absl@@AEAVstring_view@5@AEBV65@AEA_K@Z
     ??$emplace_back@AEBQEAUCordRep@cord_internal@absl@@@?$InlinedVector@PEAUCordRep@cord_internal@absl@@$01V?$allocator@PEAUCordRep@cord_internal@absl@@@__1@std@@@absl@@QEAAAEAPEAUCordRep@cord_internal@1@AEBQEAU231@@Z
-    ??$emplace_back@AEBQEAUCordRep@cord_internal@absl@@@?$InlinedVector@PEAUCordRep@cord_internal@absl@@$0CP@V?$allocator@PEAUCordRep@cord_internal@absl@@@__1@std@@@absl@@QEAAAEAPEAUCordRep@cord_internal@1@AEBQEAU231@@Z
     ??$emplace_back@PEAUCordRep@cord_internal@absl@@@?$InlinedVector@PEAUCordRep@cord_internal@absl@@$0CP@V?$allocator@PEAUCordRep@cord_internal@absl@@@__1@std@@@absl@@QEAAAEAPEAUCordRep@cord_internal@1@$$QEAPEAU231@@Z
     ??$emplace_back@UPayload@status_internal@absl@@@?$InlinedVector@UPayload@status_internal@absl@@$00V?$allocator@UPayload@status_internal@absl@@@__1@std@@@absl@@QEAAAEAUPayload@status_internal@1@$$QEAU231@@Z
     ??$emplace_back@USubRange@absl@@@?$InlinedVector@USubRange@absl@@$0CP@V?$allocator@USubRange@absl@@@__1@std@@@absl@@QEAAAEAUSubRange@1@$$QEAU21@@Z
@@ -1151,7 +1148,6 @@
     ??1?$vector@UViableSubstitution@strings_internal@absl@@V?$allocator@UViableSubstitution@strings_internal@absl@@@__1@std@@@__1@std@@QEAA@XZ
     ??1?$vector@VFormatArgImpl@str_format_internal@absl@@V?$allocator@VFormatArgImpl@str_format_internal@absl@@@__1@std@@@__1@std@@QEAA@XZ
     ??1BadStatusOrAccess@absl@@UEAA@XZ
-    ??1ChunkIterator@Cord@absl@@QEAA@XZ
     ??1CondVar@absl@@QEAA@XZ
     ??1Cord@absl@@QEAA@XZ
     ??1CordzHandle@cord_internal@absl@@MEAA@XZ
@@ -1481,8 +1477,6 @@
     ?AdvanceBtree@ChunkIterator@Cord@absl@@AEAAAEAV123@XZ
     ?AdvanceBytes@ChunkIterator@Cord@absl@@AEAAX_K@Z
     ?AdvanceBytesBtree@ChunkIterator@Cord@absl@@AEAAX_K@Z
-    ?AdvanceBytesSlowPath@ChunkIterator@Cord@absl@@AEAAX_K@Z
-    ?AdvanceStack@ChunkIterator@Cord@absl@@AEAAAEAV123@XZ
     ?Align@adl_barrier@internal_layout@container_internal@absl@@YA_K_K0@Z
     ?AlignBegin@CordRepBtree@cord_internal@absl@@AEAAXXZ
     ?AlignEnd@CordRepBtree@cord_internal@absl@@AEAAXXZ
@@ -1705,8 +1699,7 @@
     ?DurationFromTimeval@absl@@YA?AVDuration@1@Utimeval@@@Z
     ?Edge@CordRepBtree@cord_internal@absl@@QEBAPEAUCordRep@23@W4EdgeType@123@@Z
     ?Edge@CordRepBtree@cord_internal@absl@@QEBAPEAUCordRep@23@_K@Z
-    ?EdgeData@CordRepBtree@cord_internal@absl@@SA?AVstring_view@3@PEBUCordRep@23@@Z
-    ?EdgeDataPtr@CordRepBtree@cord_internal@absl@@SAPEBDPEBUCordRep@23@@Z
+    ?EdgeData@cord_internal@absl@@YA?AVstring_view@2@PEBUCordRep@12@@Z
     ?Edges@CordRepBtree@cord_internal@absl@@QEBA?AV?$Span@QEAUCordRep@cord_internal@absl@@@3@XZ
     ?Edges@CordRepBtree@cord_internal@absl@@QEBA?AV?$Span@QEAUCordRep@cord_internal@absl@@@3@_K0@Z
     ?EmplaceTree@InlineRep@Cord@absl@@QEAAXPEAUCordRep@cord_internal@3@AEBVInlineData@53@W4MethodIdentifier@CordzUpdateTracker@53@@Z
@@ -1999,7 +1992,7 @@
     ?IsCancelled@absl@@YA_NAEBVStatus@1@@Z
     ?IsCooperative@SpinLock@base_internal@absl@@CA_NW4SchedulingMode@23@@Z
     ?IsCrc@CordRep@cord_internal@absl@@QEBA_NXZ
-    ?IsDataEdge@CordRepBtree@cord_internal@absl@@SA_NPEBUCordRep@23@@Z
+    ?IsDataEdge@cord_internal@absl@@YA_NPEBUCordRep@12@@Z
     ?IsDataLoss@absl@@YA_NAEBVStatus@1@@Z
     ?IsDeadlineExceeded@absl@@YA_NAEBVStatus@1@@Z
     ?IsEmpty@Queue@CordzHandle@cord_internal@absl@@QEBA_NXZ
@@ -3267,7 +3260,6 @@
     ?probe@container_internal@absl@@YA?AV?$probe_seq@$0BA@@12@PEBW4ctrl_t@12@_K1@Z
     ?push_back@?$InlinedVector@PEAUCordRep@cord_internal@absl@@$01V?$allocator@PEAUCordRep@cord_internal@absl@@@__1@std@@@absl@@QEAAXAEBQEAUCordRep@cord_internal@2@@Z
     ?push_back@?$InlinedVector@PEAUCordRep@cord_internal@absl@@$0CP@V?$allocator@PEAUCordRep@cord_internal@absl@@@__1@std@@@absl@@QEAAX$$QEAPEAUCordRep@cord_internal@2@@Z
-    ?push_back@?$InlinedVector@PEAUCordRep@cord_internal@absl@@$0CP@V?$allocator@PEAUCordRep@cord_internal@absl@@@__1@std@@@absl@@QEAAXAEBQEAUCordRep@cord_internal@2@@Z
     ?push_back@?$InlinedVector@UPayload@status_internal@absl@@$00V?$allocator@UPayload@status_internal@absl@@@__1@std@@@absl@@QEAAX$$QEAUPayload@status_internal@2@@Z
     ?push_back@?$InlinedVector@USubRange@absl@@$0CP@V?$allocator@USubRange@absl@@@__1@std@@@absl@@QEAAX$$QEAUSubRange@2@@Z
     ?push_back@?$__split_buffer@PEAPEBVImpl@time_zone@cctz@time_internal@absl@@AEAV?$allocator@PEAPEBVImpl@time_zone@cctz@time_internal@absl@@@__1@std@@@__1@std@@QEAAX$$QEAPEAPEBVImpl@time_zone@cctz@time_internal@absl@@@Z
diff --git a/third_party/abseil-cpp/symbols_x64_rel.def b/third_party/abseil-cpp/symbols_x64_rel.def
index 1959e143..12a8bd5 100644
--- a/third_party/abseil-cpp/symbols_x64_rel.def
+++ b/third_party/abseil-cpp/symbols_x64_rel.def
@@ -42,11 +42,9 @@
     ??$Dispatch@_N@FormatArgImpl@str_format_internal@absl@@CA_NTData@012@VFormatConversionSpecImpl@12@PEAX@Z
     ??$DivMod@$09@?$BigUnsigned@$03@strings_internal@absl@@AEAAIXZ
     ??$DivMod@$09@?$BigUnsigned@$0FE@@strings_internal@absl@@AEAAIXZ
-    ??$EmplaceBack@AEBQEAUCordRep@cord_internal@absl@@@?$Storage@PEAUCordRep@cord_internal@absl@@$0CP@V?$allocator@PEAUCordRep@cord_internal@absl@@@__1@std@@@inlined_vector_internal@absl@@QEAAAEAPEAUCordRep@cord_internal@2@AEBQEAU342@@Z
     ??$EmplaceBack@PEAUCordRep@cord_internal@absl@@@?$Storage@PEAUCordRep@cord_internal@absl@@$0CP@V?$allocator@PEAUCordRep@cord_internal@absl@@@__1@std@@@inlined_vector_internal@absl@@QEAAAEAPEAUCordRep@cord_internal@2@$$QEAPEAU342@@Z
     ??$EmplaceBack@UPayload@status_internal@absl@@@?$Storage@UPayload@status_internal@absl@@$00V?$allocator@UPayload@status_internal@absl@@@__1@std@@@inlined_vector_internal@absl@@QEAAAEAUPayload@status_internal@2@$$QEAU342@@Z
     ??$EmplaceBack@USubRange@absl@@@?$Storage@USubRange@absl@@$0CP@V?$allocator@USubRange@absl@@@__1@std@@@inlined_vector_internal@absl@@QEAAAEAUSubRange@2@$$QEAU32@@Z
-    ??$EmplaceBackSlow@AEBQEAUCordRep@cord_internal@absl@@@?$Storage@PEAUCordRep@cord_internal@absl@@$0CP@V?$allocator@PEAUCordRep@cord_internal@absl@@@__1@std@@@inlined_vector_internal@absl@@AEAAAEAPEAUCordRep@cord_internal@2@AEBQEAU342@@Z
     ??$EmplaceBackSlow@PEAUCordRep@cord_internal@absl@@@?$Storage@PEAUCordRep@cord_internal@absl@@$0CP@V?$allocator@PEAUCordRep@cord_internal@absl@@@__1@std@@@inlined_vector_internal@absl@@AEAAAEAPEAUCordRep@cord_internal@2@$$QEAPEAU342@@Z
     ??$EmplaceBackSlow@UPayload@status_internal@absl@@@?$Storage@UPayload@status_internal@absl@@$00V?$allocator@UPayload@status_internal@absl@@@__1@std@@@inlined_vector_internal@absl@@AEAAAEAUPayload@status_internal@2@$$QEAU342@@Z
     ??$EmplaceBackSlow@USubRange@absl@@@?$Storage@USubRange@absl@@$0CP@V?$allocator@USubRange@absl@@@__1@std@@@inlined_vector_internal@absl@@AEAAAEAUSubRange@2@$$QEAU32@@Z
@@ -198,7 +196,6 @@
     ??ACord@absl@@QEBAD_K@Z
     ??BCord@absl@@QEBA?AV?$basic_string@DU?$char_traits@D@__1@std@@V?$allocator@D@23@@__1@std@@XZ
     ??Bint128@absl@@QEBANXZ
-    ??EChunkIterator@Cord@absl@@QEAAAEAV012@XZ
     ??Gdetail@cctz@time_internal@absl@@YA?AV?$civil_time@Uday_tag@detail@cctz@time_internal@absl@@@0123@V40123@_J@Z
     ??Gdetail@cctz@time_internal@absl@@YA?AV?$civil_time@Usecond_tag@detail@cctz@time_internal@absl@@@0123@V40123@_J@Z
     ??Hdetail@cctz@time_internal@absl@@YA?AV?$civil_time@Uday_tag@detail@cctz@time_internal@absl@@@0123@V40123@_J@Z
@@ -206,6 +203,8 @@
     ??Kabsl@@YA?AVuint128@0@V10@0@Z
     ??Labsl@@YA?AVint128@0@V10@0@Z
     ??Labsl@@YA?AVuint128@0@V10@0@Z
+    ??R<lambda_1>@?0??CompareSlowPath@Cord@absl@@AEBAHAEBV23@_K1@Z@QEBA?A?<auto>@@PEAVChunkIterator@23@PEAVstring_view@3@@Z
+    ??R<lambda_1>@?0??CompareSlowPath@Cord@absl@@AEBAHVstring_view@3@_K1@Z@QEBA?A?<auto>@@PEAVChunkIterator@23@PEAV43@@Z
     ??R?$RandenPool@E@random_internal@absl@@QEAAEXZ
     ??R?$RandenPool@G@random_internal@absl@@QEAAGXZ
     ??R?$RandenPool@I@random_internal@absl@@QEAAIXZ
@@ -233,8 +232,6 @@
     ?AddressIsReadable@debugging_internal@absl@@YA_NPEBX@Z
     ?AdvanceAndReadBytes@ChunkIterator@Cord@absl@@AEAA?AV23@_K@Z
     ?AdvanceBytesBtree@ChunkIterator@Cord@absl@@AEAAX_K@Z
-    ?AdvanceBytesSlowPath@ChunkIterator@Cord@absl@@AEAAX_K@Z
-    ?AdvanceStack@ChunkIterator@Cord@absl@@AEAAAEAV123@XZ
     ?AllocWithArena@LowLevelAlloc@base_internal@absl@@SAPEAX_KPEAUArena@123@@Z
     ?AlreadyExistsError@absl@@YA?AVStatus@1@Vstring_view@1@@Z
     ?Append@?$AppendUninitializedTraits@V?$basic_string@DU?$char_traits@D@__1@std@@V?$allocator@D@23@@__1@std@@X@strings_internal@absl@@SAXPEAV?$basic_string@DU?$char_traits@D@__1@std@@V?$allocator@D@23@@__1@std@@_K@Z
@@ -305,7 +302,6 @@
     ?Compare@Cord@absl@@QEBAHVstring_view@2@@Z
     ?CompareImpl@Cord@absl@@AEBAHAEBV12@@Z
     ?CompareSlowPath@Cord@absl@@AEBAHAEBV12@_K1@Z
-    ?CompareSlowPath@Cord@absl@@AEBAHVstring_view@2@_K1@Z
     ?Consume@cord_internal@absl@@YAXPEAUCordRep@12@V?$FunctionRef@$$A6AXPEAUCordRep@cord_internal@absl@@_K1@Z@2@@Z
     ?ConsumeBeginTo@CordRepBtree@cord_internal@absl@@CAPEAV123@PEAV123@_K1@Z
     ?ConsumeUnboundConversion@str_format_internal@absl@@YAPEBDPEBD0PEAUUnboundConversion@12@PEAH@Z
diff --git a/third_party/abseil-cpp/symbols_x64_rel_asan.def b/third_party/abseil-cpp/symbols_x64_rel_asan.def
index f8dbd50..d912375 100644
--- a/third_party/abseil-cpp/symbols_x64_rel_asan.def
+++ b/third_party/abseil-cpp/symbols_x64_rel_asan.def
@@ -41,11 +41,9 @@
     ??$Dispatch@_N@FormatArgImpl@str_format_internal@absl@@CA_NTData@012@VFormatConversionSpecImpl@12@PEAX@Z
     ??$DivMod@$09@?$BigUnsigned@$03@strings_internal@absl@@AEAAIXZ
     ??$DivMod@$09@?$BigUnsigned@$0FE@@strings_internal@absl@@AEAAIXZ
-    ??$EmplaceBack@AEBQEAUCordRep@cord_internal@absl@@@?$Storage@PEAUCordRep@cord_internal@absl@@$0CP@V?$allocator@PEAUCordRep@cord_internal@absl@@@__1@std@@@inlined_vector_internal@absl@@QEAAAEAPEAUCordRep@cord_internal@2@AEBQEAU342@@Z
     ??$EmplaceBack@PEAUCordRep@cord_internal@absl@@@?$Storage@PEAUCordRep@cord_internal@absl@@$0CP@V?$allocator@PEAUCordRep@cord_internal@absl@@@__1@std@@@inlined_vector_internal@absl@@QEAAAEAPEAUCordRep@cord_internal@2@$$QEAPEAU342@@Z
     ??$EmplaceBack@UPayload@status_internal@absl@@@?$Storage@UPayload@status_internal@absl@@$00V?$allocator@UPayload@status_internal@absl@@@__1@std@@@inlined_vector_internal@absl@@QEAAAEAUPayload@status_internal@2@$$QEAU342@@Z
     ??$EmplaceBack@USubRange@absl@@@?$Storage@USubRange@absl@@$0CP@V?$allocator@USubRange@absl@@@__1@std@@@inlined_vector_internal@absl@@QEAAAEAUSubRange@2@$$QEAU32@@Z
-    ??$EmplaceBackSlow@AEBQEAUCordRep@cord_internal@absl@@@?$Storage@PEAUCordRep@cord_internal@absl@@$0CP@V?$allocator@PEAUCordRep@cord_internal@absl@@@__1@std@@@inlined_vector_internal@absl@@AEAAAEAPEAUCordRep@cord_internal@2@AEBQEAU342@@Z
     ??$EmplaceBackSlow@PEAUCordRep@cord_internal@absl@@@?$Storage@PEAUCordRep@cord_internal@absl@@$0CP@V?$allocator@PEAUCordRep@cord_internal@absl@@@__1@std@@@inlined_vector_internal@absl@@AEAAAEAPEAUCordRep@cord_internal@2@$$QEAPEAU342@@Z
     ??$EmplaceBackSlow@UPayload@status_internal@absl@@@?$Storage@UPayload@status_internal@absl@@$00V?$allocator@UPayload@status_internal@absl@@@__1@std@@@inlined_vector_internal@absl@@AEAAAEAUPayload@status_internal@2@$$QEAU342@@Z
     ??$EmplaceBackSlow@USubRange@absl@@@?$Storage@USubRange@absl@@$0CP@V?$allocator@USubRange@absl@@@__1@std@@@inlined_vector_internal@absl@@AEAAAEAUSubRange@2@$$QEAU32@@Z
@@ -238,8 +236,6 @@
     ?AddressIsReadable@debugging_internal@absl@@YA_NPEBX@Z
     ?AdvanceAndReadBytes@ChunkIterator@Cord@absl@@AEAA?AV23@_K@Z
     ?AdvanceBytesBtree@ChunkIterator@Cord@absl@@AEAAX_K@Z
-    ?AdvanceBytesSlowPath@ChunkIterator@Cord@absl@@AEAAX_K@Z
-    ?AdvanceStack@ChunkIterator@Cord@absl@@AEAAAEAV123@XZ
     ?AllocWithArena@LowLevelAlloc@base_internal@absl@@SAPEAX_KPEAUArena@123@@Z
     ?AlreadyExistsError@absl@@YA?AVStatus@1@Vstring_view@1@@Z
     ?Append@?$AppendUninitializedTraits@V?$basic_string@DU?$char_traits@D@__1@std@@V?$allocator@D@23@@__1@std@@X@strings_internal@absl@@SAXPEAV?$basic_string@DU?$char_traits@D@__1@std@@V?$allocator@D@23@@__1@std@@_K@Z
@@ -310,7 +306,6 @@
     ?Compare@Cord@absl@@QEBAHVstring_view@2@@Z
     ?CompareImpl@Cord@absl@@AEBAHAEBV12@@Z
     ?CompareSlowPath@Cord@absl@@AEBAHAEBV12@_K1@Z
-    ?CompareSlowPath@Cord@absl@@AEBAHVstring_view@2@_K1@Z
     ?Consume@cord_internal@absl@@YAXPEAUCordRep@12@V?$FunctionRef@$$A6AXPEAUCordRep@cord_internal@absl@@_K1@Z@2@@Z
     ?ConsumeBeginTo@CordRepBtree@cord_internal@absl@@CAPEAV123@PEAV123@_K1@Z
     ?ConsumeUnboundConversion@str_format_internal@absl@@YAPEBDPEBD0PEAUUnboundConversion@12@PEAH@Z
@@ -410,6 +405,7 @@
     ?Find@ByChar@absl@@QEBA?AVstring_view@2@V32@_K@Z
     ?Find@ByLength@absl@@QEBA?AVstring_view@2@V32@_K@Z
     ?Find@ByString@absl@@QEBA?AVstring_view@2@V32@_K@Z
+    ?FindFlatStartPiece@InlineRep@Cord@absl@@QEBA?AVstring_view@3@XZ
     ?FindPath@GraphCycles@synchronization_internal@absl@@QEBAHUGraphId@23@0HQEAU423@@Z
     ?FindSlow@CordRepRing@cord_internal@absl@@AEBA?AUPosition@123@I_K@Z
     ?FindTailSlow@CordRepRing@cord_internal@absl@@AEBA?AUPosition@123@I_K@Z
diff --git a/third_party/abseil-cpp/symbols_x86_dbg.def b/third_party/abseil-cpp/symbols_x86_dbg.def
index 64ebcb7..11fac5c 100644
--- a/third_party/abseil-cpp/symbols_x86_dbg.def
+++ b/third_party/abseil-cpp/symbols_x86_dbg.def
@@ -280,12 +280,10 @@
     ??$DivMod@$09@?$BigUnsigned@$03@strings_internal@absl@@AAEIXZ
     ??$DivMod@$09@?$BigUnsigned@$0FE@@strings_internal@absl@@AAEIXZ
     ??$EmplaceBack@ABQAUCordRep@cord_internal@absl@@@?$Storage@PAUCordRep@cord_internal@absl@@$01V?$allocator@PAUCordRep@cord_internal@absl@@@__1@std@@@inlined_vector_internal@absl@@QAEAAPAUCordRep@cord_internal@2@ABQAU342@@Z
-    ??$EmplaceBack@ABQAUCordRep@cord_internal@absl@@@?$Storage@PAUCordRep@cord_internal@absl@@$0CP@V?$allocator@PAUCordRep@cord_internal@absl@@@__1@std@@@inlined_vector_internal@absl@@QAEAAPAUCordRep@cord_internal@2@ABQAU342@@Z
     ??$EmplaceBack@PAUCordRep@cord_internal@absl@@@?$Storage@PAUCordRep@cord_internal@absl@@$0CP@V?$allocator@PAUCordRep@cord_internal@absl@@@__1@std@@@inlined_vector_internal@absl@@QAEAAPAUCordRep@cord_internal@2@$$QAPAU342@@Z
     ??$EmplaceBack@UPayload@status_internal@absl@@@?$Storage@UPayload@status_internal@absl@@$00V?$allocator@UPayload@status_internal@absl@@@__1@std@@@inlined_vector_internal@absl@@QAEAAUPayload@status_internal@2@$$QAU342@@Z
     ??$EmplaceBack@USubRange@absl@@@?$Storage@USubRange@absl@@$0CP@V?$allocator@USubRange@absl@@@__1@std@@@inlined_vector_internal@absl@@QAEAAUSubRange@2@$$QAU32@@Z
     ??$EmplaceBackSlow@ABQAUCordRep@cord_internal@absl@@@?$Storage@PAUCordRep@cord_internal@absl@@$01V?$allocator@PAUCordRep@cord_internal@absl@@@__1@std@@@inlined_vector_internal@absl@@AAEAAPAUCordRep@cord_internal@2@ABQAU342@@Z
-    ??$EmplaceBackSlow@ABQAUCordRep@cord_internal@absl@@@?$Storage@PAUCordRep@cord_internal@absl@@$0CP@V?$allocator@PAUCordRep@cord_internal@absl@@@__1@std@@@inlined_vector_internal@absl@@AAEAAPAUCordRep@cord_internal@2@ABQAU342@@Z
     ??$EmplaceBackSlow@PAUCordRep@cord_internal@absl@@@?$Storage@PAUCordRep@cord_internal@absl@@$0CP@V?$allocator@PAUCordRep@cord_internal@absl@@@__1@std@@@inlined_vector_internal@absl@@AAEAAPAUCordRep@cord_internal@2@$$QAPAU342@@Z
     ??$EmplaceBackSlow@UPayload@status_internal@absl@@@?$Storage@UPayload@status_internal@absl@@$00V?$allocator@UPayload@status_internal@absl@@@__1@std@@@inlined_vector_internal@absl@@AAEAAUPayload@status_internal@2@$$QAU342@@Z
     ??$EmplaceBackSlow@USubRange@absl@@@?$Storage@USubRange@absl@@$0CP@V?$allocator@USubRange@absl@@@__1@std@@@inlined_vector_internal@absl@@AAEAAUSubRange@2@$$QAU32@@Z
@@ -607,7 +605,6 @@
     ??$emplace_back@$$V@?$__split_buffer@UTransitionType@cctz@time_internal@absl@@AAV?$allocator@UTransitionType@cctz@time_internal@absl@@@__1@std@@@__1@std@@QAEXXZ
     ??$emplace_back@AAVstring_view@absl@@ABV12@AAI@?$vector@UViableSubstitution@strings_internal@absl@@V?$allocator@UViableSubstitution@strings_internal@absl@@@__1@std@@@__1@std@@QAEAAUViableSubstitution@strings_internal@absl@@AAVstring_view@5@ABV65@AAI@Z
     ??$emplace_back@ABQAUCordRep@cord_internal@absl@@@?$InlinedVector@PAUCordRep@cord_internal@absl@@$01V?$allocator@PAUCordRep@cord_internal@absl@@@__1@std@@@absl@@QAEAAPAUCordRep@cord_internal@1@ABQAU231@@Z
-    ??$emplace_back@ABQAUCordRep@cord_internal@absl@@@?$InlinedVector@PAUCordRep@cord_internal@absl@@$0CP@V?$allocator@PAUCordRep@cord_internal@absl@@@__1@std@@@absl@@QAEAAPAUCordRep@cord_internal@1@ABQAU231@@Z
     ??$emplace_back@PAUCordRep@cord_internal@absl@@@?$InlinedVector@PAUCordRep@cord_internal@absl@@$0CP@V?$allocator@PAUCordRep@cord_internal@absl@@@__1@std@@@absl@@QAEAAPAUCordRep@cord_internal@1@$$QAPAU231@@Z
     ??$emplace_back@UPayload@status_internal@absl@@@?$InlinedVector@UPayload@status_internal@absl@@$00V?$allocator@UPayload@status_internal@absl@@@__1@std@@@absl@@QAEAAUPayload@status_internal@1@$$QAU231@@Z
     ??$emplace_back@USubRange@absl@@@?$InlinedVector@USubRange@absl@@$0CP@V?$allocator@USubRange@absl@@@__1@std@@@absl@@QAEAAUSubRange@1@$$QAU21@@Z
@@ -1146,7 +1143,6 @@
     ??1?$vector@UViableSubstitution@strings_internal@absl@@V?$allocator@UViableSubstitution@strings_internal@absl@@@__1@std@@@__1@std@@QAE@XZ
     ??1?$vector@VFormatArgImpl@str_format_internal@absl@@V?$allocator@VFormatArgImpl@str_format_internal@absl@@@__1@std@@@__1@std@@QAE@XZ
     ??1BadStatusOrAccess@absl@@UAE@XZ
-    ??1ChunkIterator@Cord@absl@@QAE@XZ
     ??1CondVar@absl@@QAE@XZ
     ??1Cord@absl@@QAE@XZ
     ??1CordzHandle@cord_internal@absl@@MAE@XZ
@@ -1476,8 +1472,6 @@
     ?AdvanceBtree@ChunkIterator@Cord@absl@@AAEAAV123@XZ
     ?AdvanceBytes@ChunkIterator@Cord@absl@@AAEXI@Z
     ?AdvanceBytesBtree@ChunkIterator@Cord@absl@@AAEXI@Z
-    ?AdvanceBytesSlowPath@ChunkIterator@Cord@absl@@AAEXI@Z
-    ?AdvanceStack@ChunkIterator@Cord@absl@@AAEAAV123@XZ
     ?Align@adl_barrier@internal_layout@container_internal@absl@@YAIII@Z
     ?AlignBegin@CordRepBtree@cord_internal@absl@@AAEXXZ
     ?AlignEnd@CordRepBtree@cord_internal@absl@@AAEXXZ
@@ -1700,8 +1694,7 @@
     ?DurationFromTimeval@absl@@YA?AVDuration@1@Utimeval@@@Z
     ?Edge@CordRepBtree@cord_internal@absl@@QBEPAUCordRep@23@I@Z
     ?Edge@CordRepBtree@cord_internal@absl@@QBEPAUCordRep@23@W4EdgeType@123@@Z
-    ?EdgeData@CordRepBtree@cord_internal@absl@@SA?AVstring_view@3@PBUCordRep@23@@Z
-    ?EdgeDataPtr@CordRepBtree@cord_internal@absl@@SAPBDPBUCordRep@23@@Z
+    ?EdgeData@cord_internal@absl@@YA?AVstring_view@2@PBUCordRep@12@@Z
     ?Edges@CordRepBtree@cord_internal@absl@@QBE?AV?$Span@QAUCordRep@cord_internal@absl@@@3@II@Z
     ?Edges@CordRepBtree@cord_internal@absl@@QBE?AV?$Span@QAUCordRep@cord_internal@absl@@@3@XZ
     ?EmplaceTree@InlineRep@Cord@absl@@QAEXPAUCordRep@cord_internal@3@ABVInlineData@53@W4MethodIdentifier@CordzUpdateTracker@53@@Z
@@ -1994,7 +1987,7 @@
     ?IsCancelled@absl@@YA_NABVStatus@1@@Z
     ?IsCooperative@SpinLock@base_internal@absl@@CA_NW4SchedulingMode@23@@Z
     ?IsCrc@CordRep@cord_internal@absl@@QBE_NXZ
-    ?IsDataEdge@CordRepBtree@cord_internal@absl@@SA_NPBUCordRep@23@@Z
+    ?IsDataEdge@cord_internal@absl@@YA_NPBUCordRep@12@@Z
     ?IsDataLoss@absl@@YA_NABVStatus@1@@Z
     ?IsDeadlineExceeded@absl@@YA_NABVStatus@1@@Z
     ?IsEmpty@Queue@CordzHandle@cord_internal@absl@@QBE_NXZ
@@ -3262,7 +3255,6 @@
     ?probe@container_internal@absl@@YA?AV?$probe_seq@$0BA@@12@PBW4ctrl_t@12@II@Z
     ?push_back@?$InlinedVector@PAUCordRep@cord_internal@absl@@$01V?$allocator@PAUCordRep@cord_internal@absl@@@__1@std@@@absl@@QAEXABQAUCordRep@cord_internal@2@@Z
     ?push_back@?$InlinedVector@PAUCordRep@cord_internal@absl@@$0CP@V?$allocator@PAUCordRep@cord_internal@absl@@@__1@std@@@absl@@QAEX$$QAPAUCordRep@cord_internal@2@@Z
-    ?push_back@?$InlinedVector@PAUCordRep@cord_internal@absl@@$0CP@V?$allocator@PAUCordRep@cord_internal@absl@@@__1@std@@@absl@@QAEXABQAUCordRep@cord_internal@2@@Z
     ?push_back@?$InlinedVector@UPayload@status_internal@absl@@$00V?$allocator@UPayload@status_internal@absl@@@__1@std@@@absl@@QAEX$$QAUPayload@status_internal@2@@Z
     ?push_back@?$InlinedVector@USubRange@absl@@$0CP@V?$allocator@USubRange@absl@@@__1@std@@@absl@@QAEX$$QAUSubRange@2@@Z
     ?push_back@?$__split_buffer@PAPBVImpl@time_zone@cctz@time_internal@absl@@AAV?$allocator@PAPBVImpl@time_zone@cctz@time_internal@absl@@@__1@std@@@__1@std@@QAEX$$QAPAPBVImpl@time_zone@cctz@time_internal@absl@@@Z
diff --git a/third_party/abseil-cpp/symbols_x86_rel.def b/third_party/abseil-cpp/symbols_x86_rel.def
index 6b72311..58a8867 100644
--- a/third_party/abseil-cpp/symbols_x86_rel.def
+++ b/third_party/abseil-cpp/symbols_x86_rel.def
@@ -42,11 +42,9 @@
     ??$Dispatch@_N@FormatArgImpl@str_format_internal@absl@@CA_NTData@012@VFormatConversionSpecImpl@12@PAX@Z
     ??$DivMod@$09@?$BigUnsigned@$03@strings_internal@absl@@AAEIXZ
     ??$DivMod@$09@?$BigUnsigned@$0FE@@strings_internal@absl@@AAEIXZ
-    ??$EmplaceBack@ABQAUCordRep@cord_internal@absl@@@?$Storage@PAUCordRep@cord_internal@absl@@$0CP@V?$allocator@PAUCordRep@cord_internal@absl@@@__1@std@@@inlined_vector_internal@absl@@QAEAAPAUCordRep@cord_internal@2@ABQAU342@@Z
     ??$EmplaceBack@PAUCordRep@cord_internal@absl@@@?$Storage@PAUCordRep@cord_internal@absl@@$0CP@V?$allocator@PAUCordRep@cord_internal@absl@@@__1@std@@@inlined_vector_internal@absl@@QAEAAPAUCordRep@cord_internal@2@$$QAPAU342@@Z
     ??$EmplaceBack@UPayload@status_internal@absl@@@?$Storage@UPayload@status_internal@absl@@$00V?$allocator@UPayload@status_internal@absl@@@__1@std@@@inlined_vector_internal@absl@@QAEAAUPayload@status_internal@2@$$QAU342@@Z
     ??$EmplaceBack@USubRange@absl@@@?$Storage@USubRange@absl@@$0CP@V?$allocator@USubRange@absl@@@__1@std@@@inlined_vector_internal@absl@@QAEAAUSubRange@2@$$QAU32@@Z
-    ??$EmplaceBackSlow@ABQAUCordRep@cord_internal@absl@@@?$Storage@PAUCordRep@cord_internal@absl@@$0CP@V?$allocator@PAUCordRep@cord_internal@absl@@@__1@std@@@inlined_vector_internal@absl@@AAEAAPAUCordRep@cord_internal@2@ABQAU342@@Z
     ??$EmplaceBackSlow@PAUCordRep@cord_internal@absl@@@?$Storage@PAUCordRep@cord_internal@absl@@$0CP@V?$allocator@PAUCordRep@cord_internal@absl@@@__1@std@@@inlined_vector_internal@absl@@AAEAAPAUCordRep@cord_internal@2@$$QAPAU342@@Z
     ??$EmplaceBackSlow@UPayload@status_internal@absl@@@?$Storage@UPayload@status_internal@absl@@$00V?$allocator@UPayload@status_internal@absl@@@__1@std@@@inlined_vector_internal@absl@@AAEAAUPayload@status_internal@2@$$QAU342@@Z
     ??$EmplaceBackSlow@USubRange@absl@@@?$Storage@USubRange@absl@@$0CP@V?$allocator@USubRange@absl@@@__1@std@@@inlined_vector_internal@absl@@AAEAAUSubRange@2@$$QAU32@@Z
@@ -196,7 +194,6 @@
     ??ACord@absl@@QBEDI@Z
     ??BCord@absl@@QBE?AV?$basic_string@DU?$char_traits@D@__1@std@@V?$allocator@D@23@@__1@std@@XZ
     ??Bint128@absl@@QBENXZ
-    ??EChunkIterator@Cord@absl@@QAEAAV012@XZ
     ??Gdetail@cctz@time_internal@absl@@YA?AV?$civil_time@Uday_tag@detail@cctz@time_internal@absl@@@0123@V40123@_J@Z
     ??Gdetail@cctz@time_internal@absl@@YA?AV?$civil_time@Usecond_tag@detail@cctz@time_internal@absl@@@0123@V40123@_J@Z
     ??Kabsl@@YA?AVint128@0@V10@0@Z
@@ -230,8 +227,6 @@
     ?AddressIsReadable@debugging_internal@absl@@YA_NPBX@Z
     ?AdvanceAndReadBytes@ChunkIterator@Cord@absl@@AAE?AV23@I@Z
     ?AdvanceBytesBtree@ChunkIterator@Cord@absl@@AAEXI@Z
-    ?AdvanceBytesSlowPath@ChunkIterator@Cord@absl@@AAEXI@Z
-    ?AdvanceStack@ChunkIterator@Cord@absl@@AAEAAV123@XZ
     ?AllocWithArena@LowLevelAlloc@base_internal@absl@@SAPAXIPAUArena@123@@Z
     ?Allocate@?$MallocAdapter@V?$allocator@PAUCordRep@cord_internal@absl@@@__1@std@@$0A@@inlined_vector_internal@absl@@SA?AU?$Allocation@V?$allocator@PAUCordRep@cord_internal@absl@@@__1@std@@@23@AAV?$allocator@PAUCordRep@cord_internal@absl@@@__1@std@@I@Z
     ?Allocate@?$MallocAdapter@V?$allocator@UPayload@status_internal@absl@@@__1@std@@$0A@@inlined_vector_internal@absl@@SA?AU?$Allocation@V?$allocator@UPayload@status_internal@absl@@@__1@std@@@23@AAV?$allocator@UPayload@status_internal@absl@@@__1@std@@I@Z
diff --git a/third_party/blink/perf_tests/css/HasSiblingDescendantInvalidation.html b/third_party/blink/perf_tests/css/HasSiblingDescendantInvalidation.html
new file mode 100644
index 0000000..6ec486b
--- /dev/null
+++ b/third_party/blink/perf_tests/css/HasSiblingDescendantInvalidation.html
@@ -0,0 +1,66 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="../resources/runner.js"></script>
+<style>
+  div { color: grey }
+  .a:has(~ .b .c) { color: green }
+</style>
+</head>
+<body>
+<div id=container><div class=a></div></div>
+<script>
+
+function addChildren(element, numChildren, idPrefix)
+{
+    for (var i = 0; i < numChildren; i++) {
+        var child = document.createElement("div");
+        child.id = idPrefix  + i;
+        element.appendChild(child);
+    }
+}
+
+function makeTree(element, depth, fanOut, idPrefix)
+{
+    if (depth <= 0)
+        return;
+    addChildren(element, fanOut, idPrefix);
+    for (var child = element.firstChild; child.nextSibling; child = child.nextSibling) {
+        makeTree(child, depth - 1, fanOut, child.id);
+    }
+    if (child)
+        makeTree(child, depth - 1, fanOut, child.id);
+}
+
+for (var i = 0; i < 32; i++) {
+    var child = document.createElement("div");
+    child.id = "child"  + i;
+    child.classList.add("b");
+    container.appendChild(child);
+    makeTree(child, 6, 2, child.id + "_")
+}
+
+container.offsetHeight; // force recalc style
+
+var runFunction = function()
+{
+  child0_0.classList.toggle("c");
+  container.offsetHeight; // force recalc style
+  document.querySelectorAll(".a").forEach(function(e) {
+  child0_0.classList.toggle("c");
+  container.offsetHeight; // force recalc style
+
+  child31_111111.classList.toggle("c");
+  container.offsetHeight; // force recalc style
+  child31_111111.classList.toggle("c");
+  container.offsetHeight; // force recalc style
+}
+
+PerfTestRunner.measureRunsPerSecond({
+  description: "Measures performance of the '.a:has(~ .b .c)' invalidation with a single subject element.",
+  run: runFunction
+});
+
+</script>
+</body>
+</html>
diff --git a/third_party/blink/perf_tests/css/HasSiblingDescendantInvalidationAllSubjects.html b/third_party/blink/perf_tests/css/HasSiblingDescendantInvalidationAllSubjects.html
new file mode 100644
index 0000000..a4c9364d
--- /dev/null
+++ b/third_party/blink/perf_tests/css/HasSiblingDescendantInvalidationAllSubjects.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="../resources/runner.js"></script>
+<style>
+  div { color: grey }
+  .a:has(~ .b .c) { color: green }
+</style>
+</head>
+<body>
+<div id=container></div></div>
+<script>
+
+function makeTree(element, depth, children) {
+  if (depth <= 0) {
+    var child = document.createElement("div");
+    child.id = 'sibling_descendant';
+    element.appendChild(child);
+    return;
+  }
+
+  for (var i = 0; i < children - 1; i++) {
+    var child = document.createElement("div");
+    child.classList.add("a");
+    child.id = 'subject_' + depth + '_' + i;
+    element.appendChild(child);
+  }
+  var child = document.createElement("div");
+  child.classList.add("b");
+  element.appendChild(child);
+  makeTree(child, depth - 1, children);
+}
+
+makeTree(container, 10, 10);
+container.offsetHeight; // force recalc style
+
+var runFunction = function()
+{
+  sibling_descendant.classList.toggle("c");
+  container.offsetHeight; // force recalc style
+  sibling_descendant.classList.toggle("c");
+  container.offsetHeight; // force recalc style
+}
+
+PerfTestRunner.measureRunsPerSecond({
+  description: "Measures performance of the '.a:has(~ .b .c)' invalidation with all subject elements.",
+  run: runFunction
+});
+
+</script>
+</body>
+</html>
diff --git a/third_party/blink/perf_tests/css/HasSiblingInvalidation.html b/third_party/blink/perf_tests/css/HasSiblingInvalidation.html
new file mode 100644
index 0000000..1a44f2fb
--- /dev/null
+++ b/third_party/blink/perf_tests/css/HasSiblingInvalidation.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="../resources/runner.js"></script>
+<style>
+  div { color: grey }
+  .a:has(~ .b) { color: green }
+</style>
+</head>
+<body>
+<div id=container><div class=a></div></div>
+<script>
+
+function makeTree(siblings) {
+  for (var i = 0; i < siblings; i++) {
+    var child = document.createElement("div");
+    child.id = "child" + i;
+    container.appendChild(child);
+  }
+}
+
+makeTree(2048);
+container.offsetHeight; // force recalc style
+
+var runFunction = function()
+{
+  child0.classList.toggle("b");
+  container.offsetHeight; // force recalc style
+  child0.classList.toggle("b");
+  container.offsetHeight; // force recalc style
+
+  child2047.classList.toggle("b");
+  container.offsetHeight; // force recalc style
+  child2047.classList.toggle("b");
+  container.offsetHeight; // force recalc style
+}
+
+PerfTestRunner.measureRunsPerSecond({
+  description: "Measures performance of the '.a:has(~ .b)' invalidation with a single subject element.",
+  run: runFunction
+});
+
+</script>
+</body>
+</html>
diff --git a/third_party/blink/perf_tests/css/HasSiblingInvalidationAllSubjects.html b/third_party/blink/perf_tests/css/HasSiblingInvalidationAllSubjects.html
new file mode 100644
index 0000000..154f559
--- /dev/null
+++ b/third_party/blink/perf_tests/css/HasSiblingInvalidationAllSubjects.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="../resources/runner.js"></script>
+<style>
+  div { color: grey }
+  .a:has(~ .b) { color: green }
+</style>
+</head>
+<body>
+<div id=container><div class=a></div></div>
+<script>
+
+function makeTree(siblings) {
+  for (var i = 0; i < siblings; i++) {
+    var child = document.createElement("div");
+    child.classList.add("a");
+    child.id = "child" + i;
+    container.appendChild(child);
+  }
+}
+
+makeTree(100);
+container.offsetHeight; // force recalc style
+
+var runFunction = function()
+{
+  child0.classList.toggle("b");
+  container.offsetHeight; // force recalc style
+  child0.classList.toggle("b");
+  container.offsetHeight; // force recalc style
+
+  child99.classList.toggle("b");
+  container.offsetHeight; // force recalc style
+  child99.classList.toggle("b");
+  container.offsetHeight; // force recalc style
+}
+
+PerfTestRunner.measureRunsPerSecond({
+  description: "Measures performance of the '.a:has(~ .b)' invalidation with all subject elements.",
+  run: runFunction
+});
+
+</script>
+</body>
+</html>
diff --git a/third_party/blink/renderer/core/css/affected_by_pseudo_test.cc b/third_party/blink/renderer/core/css/affected_by_pseudo_test.cc
index f597a21..98425b9 100644
--- a/third_party/blink/renderer/core/css/affected_by_pseudo_test.cc
+++ b/third_party/blink/renderer/core/css/affected_by_pseudo_test.cc
@@ -26,6 +26,18 @@
   void SetHtmlInnerHTML(const char* html_content);
   void CheckElementsForFocus(ElementResult expected[],
                              unsigned expected_count) const;
+
+  enum AffectedByFlagName {
+    kAffectedBySubjectHas,
+    kAffectedByNonSubjectHas,
+    kAncestorsOrAncestorSiblingsAffectedByHas,
+    kSiblingsAffectedByHas,
+    kAffectedByPseudoInSubjectHas,
+    kAncestorsAffectedByHoverInSubjectHas
+  };
+  void CheckAffectedByFlagsForHas(
+      const char* element_id,
+      std::map<AffectedByFlagName, bool> expected) const;
 };
 
 void AffectedByPseudoTest::SetHtmlInnerHTML(const char* html_content) {
@@ -51,6 +63,55 @@
   DCHECK_EQ(i, expected_count);
 }
 
+void AffectedByPseudoTest::CheckAffectedByFlagsForHas(
+    const char* element_id,
+    std::map<AffectedByFlagName, bool> expected) const {
+  bool actual;
+  const char* flag_name = nullptr;
+  for (auto iter : expected) {
+    switch (iter.first) {
+      case kAffectedBySubjectHas:
+        actual = GetElementById(element_id)
+                     ->GetComputedStyle()
+                     ->AffectedBySubjectHas();
+        flag_name = "AffectedBySubjectHas";
+        break;
+      case kAffectedByNonSubjectHas:
+        actual = GetElementById(element_id)->AffectedByNonSubjectHas();
+        flag_name = "AffectedByNonSubjectHas";
+        break;
+      case kAncestorsOrAncestorSiblingsAffectedByHas:
+        actual = GetElementById(element_id)
+                     ->AncestorsOrAncestorSiblingsAffectedByHas();
+        flag_name = "AncestorsOrAncestorSiblingsAffectedByHas";
+        break;
+      case kSiblingsAffectedByHas:
+        actual = GetElementById(element_id)->SiblingsAffectedByHas();
+        flag_name = "SiblingsAffectedByHas";
+        break;
+      case kAffectedByPseudoInSubjectHas:
+        actual = GetElementById(element_id)
+                     ->GetComputedStyle()
+                     ->AffectedByPseudoInSubjectHas();
+        flag_name = "AffectedByPseudoInSubjectHas";
+        break;
+      case kAncestorsAffectedByHoverInSubjectHas:
+        actual = GetElementById(element_id)
+                     ->GetComputedStyle()
+                     ->AncestorsAffectedByHoverInSubjectHas();
+        flag_name = "AncestorsAffectedByHoverInSubjectHas";
+        break;
+    }
+    DCHECK(flag_name);
+    if (iter.second == actual)
+      continue;
+
+    ADD_FAILURE() << "#" << element_id << " : " << flag_name << " should be "
+                  << (iter.second ? "true" : "false") << " but "
+                  << (actual ? "true" : "false");
+  }
+}
+
 // ":focus div" will mark ascendants of all divs with
 // childrenOrSiblingsAffectedByFocus.
 TEST_F(AffectedByPseudoTest, FocusedAscendant) {
@@ -328,7 +389,8 @@
   EXPECT_FALSE(GetElementById("div1")->GetComputedStyle()->AffectedByHover());
 }
 
-TEST_F(AffectedByPseudoTest, AffectedByHasAndAncestorsAffectedByHas) {
+TEST_F(AffectedByPseudoTest,
+       AffectedBySubjectHasAndAncestorsOrAncestorSiblingsAffectedByHas) {
   SetHtmlInnerHTML(R"HTML(
     <style>.a:has(.b) { background-color: lime; }</style>
     <div id=div1>
@@ -348,36 +410,36 @@
   )HTML");
 
   UpdateAllLifecyclePhasesForTest();
-  EXPECT_FALSE(
-      GetElementById("div1")->GetComputedStyle()->AffectedBySubjectHas());
-  EXPECT_FALSE(GetElementById("div1")->AncestorsAffectedByHas());
-  EXPECT_TRUE(
-      GetElementById("div2")->GetComputedStyle()->AffectedBySubjectHas());
-  EXPECT_FALSE(GetElementById("div2")->AncestorsAffectedByHas());
-  EXPECT_FALSE(
-      GetElementById("div3")->GetComputedStyle()->AffectedBySubjectHas());
-  EXPECT_TRUE(GetElementById("div3")->AncestorsAffectedByHas());
-  EXPECT_FALSE(
-      GetElementById("div4")->GetComputedStyle()->AffectedBySubjectHas());
-  EXPECT_TRUE(GetElementById("div4")->AncestorsAffectedByHas());
-  EXPECT_TRUE(
-      GetElementById("div5")->GetComputedStyle()->AffectedBySubjectHas());
-  EXPECT_FALSE(GetElementById("div5")->AncestorsAffectedByHas());
-  EXPECT_FALSE(
-      GetElementById("div6")->GetComputedStyle()->AffectedBySubjectHas());
-  EXPECT_FALSE(GetElementById("div6")->AncestorsAffectedByHas());
-  EXPECT_FALSE(
-      GetElementById("div7")->GetComputedStyle()->AffectedBySubjectHas());
-  EXPECT_TRUE(GetElementById("div7")->AncestorsAffectedByHas());
-  EXPECT_FALSE(
-      GetElementById("div8")->GetComputedStyle()->AffectedBySubjectHas());
-  EXPECT_FALSE(GetElementById("div8")->AncestorsAffectedByHas());
-  EXPECT_FALSE(
-      GetElementById("div9")->GetComputedStyle()->AffectedBySubjectHas());
-  EXPECT_FALSE(GetElementById("div9")->AncestorsAffectedByHas());
-  EXPECT_FALSE(
-      GetElementById("div10")->GetComputedStyle()->AffectedBySubjectHas());
-  EXPECT_FALSE(GetElementById("div10")->AncestorsAffectedByHas());
+  CheckAffectedByFlagsForHas(
+      "div1", {{kAffectedBySubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div2", {{kAffectedBySubjectHas, true},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div3", {{kAffectedBySubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, true}});
+  CheckAffectedByFlagsForHas(
+      "div4", {{kAffectedBySubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, true}});
+  CheckAffectedByFlagsForHas(
+      "div5", {{kAffectedBySubjectHas, true},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div6", {{kAffectedBySubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div7", {{kAffectedBySubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, true}});
+  CheckAffectedByFlagsForHas(
+      "div8", {{kAffectedBySubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div9", {{kAffectedBySubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div10", {{kAffectedBySubjectHas, false},
+                {kAncestorsOrAncestorSiblingsAffectedByHas, false}});
 
   unsigned start_count = GetStyleEngine().StyleForElementCount();
   GetElementById("div10")->setAttribute(html_names::kClassAttr, "b");
@@ -410,15 +472,15 @@
   element_count = GetStyleEngine().StyleForElementCount() - start_count;
   ASSERT_EQ(1U, element_count);
 
-  EXPECT_TRUE(
-      GetElementById("div5")->GetComputedStyle()->AffectedBySubjectHas());
-  EXPECT_FALSE(GetElementById("div5")->AncestorsAffectedByHas());
-  EXPECT_FALSE(
-      GetElementById("div6")->GetComputedStyle()->AffectedBySubjectHas());
-  EXPECT_TRUE(GetElementById("div6")->AncestorsAffectedByHas());
-  EXPECT_FALSE(
-      GetElementById("div7")->GetComputedStyle()->AffectedBySubjectHas());
-  EXPECT_TRUE(GetElementById("div7")->AncestorsAffectedByHas());
+  CheckAffectedByFlagsForHas(
+      "div5", {{kAffectedBySubjectHas, true},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div6", {{kAffectedBySubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, true}});
+  CheckAffectedByFlagsForHas(
+      "div7", {{kAffectedBySubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, true}});
 }
 
 TEST_F(AffectedByPseudoTest,
@@ -450,78 +512,42 @@
   )HTML");
 
   UpdateAllLifecyclePhasesForTest();
-  EXPECT_TRUE(GetElementById("div2")
-                  ->GetComputedStyle()
-                  ->AffectedByPseudoInSubjectHas());
-  EXPECT_FALSE(GetElementById("div2")
-                   ->GetComputedStyle()
-                   ->AncestorsAffectedByHoverInSubjectHas());
-  EXPECT_FALSE(GetElementById("div3")
-                   ->GetComputedStyle()
-                   ->AffectedByPseudoInSubjectHas());
-  EXPECT_FALSE(GetElementById("div3")
-                   ->GetComputedStyle()
-                   ->AncestorsAffectedByHoverInSubjectHas());
-  EXPECT_FALSE(GetElementById("div4")
-                   ->GetComputedStyle()
-                   ->AffectedByPseudoInSubjectHas());
-  EXPECT_FALSE(GetElementById("div4")
-                   ->GetComputedStyle()
-                   ->AncestorsAffectedByHoverInSubjectHas());
-  EXPECT_TRUE(GetElementById("div5")
-                  ->GetComputedStyle()
-                  ->AffectedByPseudoInSubjectHas());
-  EXPECT_TRUE(GetElementById("div5")
-                  ->GetComputedStyle()
-                  ->AncestorsAffectedByHoverInSubjectHas());
-  EXPECT_FALSE(GetElementById("div6")
-                   ->GetComputedStyle()
-                   ->AffectedByPseudoInSubjectHas());
-  EXPECT_TRUE(GetElementById("div6")
-                  ->GetComputedStyle()
-                  ->AncestorsAffectedByHoverInSubjectHas());
-  EXPECT_FALSE(GetElementById("div7")
-                   ->GetComputedStyle()
-                   ->AffectedByPseudoInSubjectHas());
-  EXPECT_TRUE(GetElementById("div7")
-                  ->GetComputedStyle()
-                  ->AncestorsAffectedByHoverInSubjectHas());
-  EXPECT_TRUE(GetElementById("div8")
-                  ->GetComputedStyle()
-                  ->AffectedByPseudoInSubjectHas());
-  EXPECT_TRUE(GetElementById("div8")
-                  ->GetComputedStyle()
-                  ->AncestorsAffectedByHoverInSubjectHas());
-  EXPECT_FALSE(GetElementById("div9")
-                   ->GetComputedStyle()
-                   ->AffectedByPseudoInSubjectHas());
-  EXPECT_TRUE(GetElementById("div9")
-                  ->GetComputedStyle()
-                  ->AncestorsAffectedByHoverInSubjectHas());
-  EXPECT_FALSE(GetElementById("div10")
-                   ->GetComputedStyle()
-                   ->AffectedByPseudoInSubjectHas());
-  EXPECT_TRUE(GetElementById("div10")
-                  ->GetComputedStyle()
-                  ->AncestorsAffectedByHoverInSubjectHas());
-  EXPECT_FALSE(GetElementById("div11")
-                   ->GetComputedStyle()
-                   ->AffectedByPseudoInSubjectHas());
-  EXPECT_FALSE(GetElementById("div11")
-                   ->GetComputedStyle()
-                   ->AncestorsAffectedByHoverInSubjectHas());
-  EXPECT_FALSE(GetElementById("div12")
-                   ->GetComputedStyle()
-                   ->AffectedByPseudoInSubjectHas());
-  EXPECT_FALSE(GetElementById("div12")
-                   ->GetComputedStyle()
-                   ->AncestorsAffectedByHoverInSubjectHas());
-  EXPECT_FALSE(GetElementById("div13")
-                   ->GetComputedStyle()
-                   ->AffectedByPseudoInSubjectHas());
-  EXPECT_FALSE(GetElementById("div13")
-                   ->GetComputedStyle()
-                   ->AncestorsAffectedByHoverInSubjectHas());
+  CheckAffectedByFlagsForHas("div2",
+                             {{kAffectedByPseudoInSubjectHas, true},
+                              {kAncestorsAffectedByHoverInSubjectHas, false}});
+  CheckAffectedByFlagsForHas("div3",
+                             {{kAffectedByPseudoInSubjectHas, false},
+                              {kAncestorsAffectedByHoverInSubjectHas, false}});
+  CheckAffectedByFlagsForHas("div4",
+                             {{kAffectedByPseudoInSubjectHas, false},
+                              {kAncestorsAffectedByHoverInSubjectHas, false}});
+  CheckAffectedByFlagsForHas("div5",
+                             {{kAffectedByPseudoInSubjectHas, true},
+                              {kAncestorsAffectedByHoverInSubjectHas, true}});
+  CheckAffectedByFlagsForHas("div6",
+                             {{kAffectedByPseudoInSubjectHas, false},
+                              {kAncestorsAffectedByHoverInSubjectHas, true}});
+  CheckAffectedByFlagsForHas("div7",
+                             {{kAffectedByPseudoInSubjectHas, false},
+                              {kAncestorsAffectedByHoverInSubjectHas, true}});
+  CheckAffectedByFlagsForHas("div8",
+                             {{kAffectedByPseudoInSubjectHas, true},
+                              {kAncestorsAffectedByHoverInSubjectHas, true}});
+  CheckAffectedByFlagsForHas("div9",
+                             {{kAffectedByPseudoInSubjectHas, false},
+                              {kAncestorsAffectedByHoverInSubjectHas, true}});
+  CheckAffectedByFlagsForHas("div10",
+                             {{kAffectedByPseudoInSubjectHas, false},
+                              {kAncestorsAffectedByHoverInSubjectHas, true}});
+  CheckAffectedByFlagsForHas("div11",
+                             {{kAffectedByPseudoInSubjectHas, false},
+                              {kAncestorsAffectedByHoverInSubjectHas, false}});
+  CheckAffectedByFlagsForHas("div12",
+                             {{kAffectedByPseudoInSubjectHas, false},
+                              {kAncestorsAffectedByHoverInSubjectHas, false}});
+  CheckAffectedByFlagsForHas("div13",
+                             {{kAffectedByPseudoInSubjectHas, false},
+                              {kAncestorsAffectedByHoverInSubjectHas, false}});
 
   unsigned start_count = GetStyleEngine().StyleForElementCount();
   GetElementById("div3")->SetHovered(true);
@@ -546,24 +572,15 @@
   element_count = GetStyleEngine().StyleForElementCount() - start_count;
   ASSERT_EQ(3U, element_count);
 
-  EXPECT_TRUE(GetElementById("div2")
-                  ->GetComputedStyle()
-                  ->AffectedByPseudoInSubjectHas());
-  EXPECT_TRUE(GetElementById("div2")
-                  ->GetComputedStyle()
-                  ->AncestorsAffectedByHoverInSubjectHas());
-  EXPECT_FALSE(GetElementById("div3")
-                   ->GetComputedStyle()
-                   ->AffectedByPseudoInSubjectHas());
-  EXPECT_TRUE(GetElementById("div3")
-                  ->GetComputedStyle()
-                  ->AncestorsAffectedByHoverInSubjectHas());
-  EXPECT_FALSE(GetElementById("div4")
-                   ->GetComputedStyle()
-                   ->AffectedByPseudoInSubjectHas());
-  EXPECT_TRUE(GetElementById("div4")
-                  ->GetComputedStyle()
-                  ->AncestorsAffectedByHoverInSubjectHas());
+  CheckAffectedByFlagsForHas("div2",
+                             {{kAffectedByPseudoInSubjectHas, true},
+                              {kAncestorsAffectedByHoverInSubjectHas, true}});
+  CheckAffectedByFlagsForHas("div3",
+                             {{kAffectedByPseudoInSubjectHas, false},
+                              {kAncestorsAffectedByHoverInSubjectHas, true}});
+  CheckAffectedByFlagsForHas("div4",
+                             {{kAffectedByPseudoInSubjectHas, false},
+                              {kAncestorsAffectedByHoverInSubjectHas, true}});
 
   start_count = GetStyleEngine().StyleForElementCount();
   GetElementById("div3")->setAttribute(html_names::kClassAttr, "b");
@@ -571,24 +588,15 @@
   element_count = GetStyleEngine().StyleForElementCount() - start_count;
   ASSERT_EQ(1U, element_count);
 
-  EXPECT_TRUE(GetElementById("div2")
-                  ->GetComputedStyle()
-                  ->AffectedByPseudoInSubjectHas());
-  EXPECT_TRUE(GetElementById("div2")
-                  ->GetComputedStyle()
-                  ->AncestorsAffectedByHoverInSubjectHas());
-  EXPECT_FALSE(GetElementById("div3")
-                   ->GetComputedStyle()
-                   ->AffectedByPseudoInSubjectHas());
-  EXPECT_TRUE(GetElementById("div3")
-                  ->GetComputedStyle()
-                  ->AncestorsAffectedByHoverInSubjectHas());
-  EXPECT_FALSE(GetElementById("div4")
-                   ->GetComputedStyle()
-                   ->AffectedByPseudoInSubjectHas());
-  EXPECT_TRUE(GetElementById("div4")
-                  ->GetComputedStyle()
-                  ->AncestorsAffectedByHoverInSubjectHas());
+  CheckAffectedByFlagsForHas("div2",
+                             {{kAffectedByPseudoInSubjectHas, true},
+                              {kAncestorsAffectedByHoverInSubjectHas, true}});
+  CheckAffectedByFlagsForHas("div3",
+                             {{kAffectedByPseudoInSubjectHas, false},
+                              {kAncestorsAffectedByHoverInSubjectHas, true}});
+  CheckAffectedByFlagsForHas("div4",
+                             {{kAffectedByPseudoInSubjectHas, false},
+                              {kAncestorsAffectedByHoverInSubjectHas, true}});
 
   start_count = GetStyleEngine().StyleForElementCount();
   GetElementById("div3")->SetHovered(true);
@@ -612,24 +620,15 @@
   element_count = GetStyleEngine().StyleForElementCount() - start_count;
   ASSERT_EQ(1U, element_count);
 
-  EXPECT_TRUE(GetElementById("div2")
-                  ->GetComputedStyle()
-                  ->AffectedByPseudoInSubjectHas());
-  EXPECT_TRUE(GetElementById("div2")
-                  ->GetComputedStyle()
-                  ->AncestorsAffectedByHoverInSubjectHas());
-  EXPECT_FALSE(GetElementById("div3")
-                   ->GetComputedStyle()
-                   ->AffectedByPseudoInSubjectHas());
-  EXPECT_TRUE(GetElementById("div3")
-                  ->GetComputedStyle()
-                  ->AncestorsAffectedByHoverInSubjectHas());
-  EXPECT_FALSE(GetElementById("div4")
-                   ->GetComputedStyle()
-                   ->AffectedByPseudoInSubjectHas());
-  EXPECT_TRUE(GetElementById("div4")
-                  ->GetComputedStyle()
-                  ->AncestorsAffectedByHoverInSubjectHas());
+  CheckAffectedByFlagsForHas("div2",
+                             {{kAffectedByPseudoInSubjectHas, true},
+                              {kAncestorsAffectedByHoverInSubjectHas, true}});
+  CheckAffectedByFlagsForHas("div3",
+                             {{kAffectedByPseudoInSubjectHas, false},
+                              {kAncestorsAffectedByHoverInSubjectHas, true}});
+  CheckAffectedByFlagsForHas("div4",
+                             {{kAffectedByPseudoInSubjectHas, false},
+                              {kAncestorsAffectedByHoverInSubjectHas, true}});
 
   start_count = GetStyleEngine().StyleForElementCount();
   GetElementById("div4")->setAttribute(html_names::kClassAttr, "");
@@ -637,24 +636,15 @@
   element_count = GetStyleEngine().StyleForElementCount() - start_count;
   ASSERT_EQ(3U, element_count);
 
-  EXPECT_TRUE(GetElementById("div2")
-                  ->GetComputedStyle()
-                  ->AffectedByPseudoInSubjectHas());
-  EXPECT_FALSE(GetElementById("div2")
-                   ->GetComputedStyle()
-                   ->AncestorsAffectedByHoverInSubjectHas());
-  EXPECT_FALSE(GetElementById("div3")
-                   ->GetComputedStyle()
-                   ->AffectedByPseudoInSubjectHas());
-  EXPECT_FALSE(GetElementById("div3")
-                   ->GetComputedStyle()
-                   ->AncestorsAffectedByHoverInSubjectHas());
-  EXPECT_FALSE(GetElementById("div4")
-                   ->GetComputedStyle()
-                   ->AffectedByPseudoInSubjectHas());
-  EXPECT_FALSE(GetElementById("div4")
-                   ->GetComputedStyle()
-                   ->AncestorsAffectedByHoverInSubjectHas());
+  CheckAffectedByFlagsForHas("div2",
+                             {{kAffectedByPseudoInSubjectHas, true},
+                              {kAncestorsAffectedByHoverInSubjectHas, false}});
+  CheckAffectedByFlagsForHas("div3",
+                             {{kAffectedByPseudoInSubjectHas, false},
+                              {kAncestorsAffectedByHoverInSubjectHas, false}});
+  CheckAffectedByFlagsForHas("div4",
+                             {{kAffectedByPseudoInSubjectHas, false},
+                              {kAncestorsAffectedByHoverInSubjectHas, false}});
 
   start_count = GetStyleEngine().StyleForElementCount();
   GetElementById("div6")->SetHovered(true);
@@ -706,7 +696,7 @@
 }
 
 TEST_F(AffectedByPseudoTest,
-       AffectedByNonSubjectHasHasAndAncestorsAffectedByHas) {
+       AffectedByNonSubjectHasHasAndAncestorsOrAncestorSiblingsAffectedByHas) {
   SetHtmlInnerHTML(R"HTML(
     <style>.a:has(.b) .c { background-color: lime; }</style>
     <div id=div1>
@@ -723,20 +713,27 @@
   )HTML");
 
   UpdateAllLifecyclePhasesForTest();
-  EXPECT_FALSE(GetElementById("div1")->AffectedByNonSubjectHas());
-  EXPECT_FALSE(GetElementById("div1")->AncestorsAffectedByHas());
-  EXPECT_FALSE(GetElementById("div2")->AffectedByNonSubjectHas());
-  EXPECT_FALSE(GetElementById("div2")->AncestorsAffectedByHas());
-  EXPECT_FALSE(GetElementById("div3")->AffectedByNonSubjectHas());
-  EXPECT_FALSE(GetElementById("div3")->AncestorsAffectedByHas());
-  EXPECT_FALSE(GetElementById("div4")->AffectedByNonSubjectHas());
-  EXPECT_FALSE(GetElementById("div4")->AncestorsAffectedByHas());
-  EXPECT_FALSE(GetElementById("div5")->AffectedByNonSubjectHas());
-  EXPECT_FALSE(GetElementById("div5")->AncestorsAffectedByHas());
-  EXPECT_FALSE(GetElementById("div6")->AffectedByNonSubjectHas());
-  EXPECT_FALSE(GetElementById("div6")->AncestorsAffectedByHas());
-  EXPECT_FALSE(GetElementById("div7")->AffectedByNonSubjectHas());
-  EXPECT_FALSE(GetElementById("div7")->AncestorsAffectedByHas());
+  CheckAffectedByFlagsForHas(
+      "div1", {{kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div2", {{kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div3", {{kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div4", {{kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div5", {{kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div6", {{kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div7", {{kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false}});
 
   unsigned start_count = GetStyleEngine().StyleForElementCount();
   GetElementById("div7")->setAttribute(html_names::kClassAttr, "c");
@@ -745,20 +742,27 @@
       GetStyleEngine().StyleForElementCount() - start_count;
   ASSERT_EQ(1U, element_count);
 
-  EXPECT_FALSE(GetElementById("div1")->AffectedByNonSubjectHas());
-  EXPECT_FALSE(GetElementById("div1")->AncestorsAffectedByHas());
-  EXPECT_TRUE(GetElementById("div2")->AffectedByNonSubjectHas());
-  EXPECT_FALSE(GetElementById("div2")->AncestorsAffectedByHas());
-  EXPECT_FALSE(GetElementById("div3")->AffectedByNonSubjectHas());
-  EXPECT_TRUE(GetElementById("div3")->AncestorsAffectedByHas());
-  EXPECT_FALSE(GetElementById("div4")->AffectedByNonSubjectHas());
-  EXPECT_FALSE(GetElementById("div4")->AncestorsAffectedByHas());
-  EXPECT_FALSE(GetElementById("div5")->AffectedByNonSubjectHas());
-  EXPECT_FALSE(GetElementById("div5")->AncestorsAffectedByHas());
-  EXPECT_FALSE(GetElementById("div6")->AffectedByNonSubjectHas());
-  EXPECT_TRUE(GetElementById("div6")->AncestorsAffectedByHas());
-  EXPECT_FALSE(GetElementById("div7")->AffectedByNonSubjectHas());
-  EXPECT_TRUE(GetElementById("div7")->AncestorsAffectedByHas());
+  CheckAffectedByFlagsForHas(
+      "div1", {{kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div2", {{kAffectedByNonSubjectHas, true},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div3", {{kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, true}});
+  CheckAffectedByFlagsForHas(
+      "div4", {{kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div5", {{kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div6", {{kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, true}});
+  CheckAffectedByFlagsForHas(
+      "div7", {{kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, true}});
 
   start_count = GetStyleEngine().StyleForElementCount();
   GetElementById("div6")->setAttribute(html_names::kClassAttr, "");
@@ -766,20 +770,27 @@
   element_count = GetStyleEngine().StyleForElementCount() - start_count;
   ASSERT_EQ(1U, element_count);
 
-  EXPECT_FALSE(GetElementById("div1")->AffectedByNonSubjectHas());
-  EXPECT_FALSE(GetElementById("div1")->AncestorsAffectedByHas());
-  EXPECT_TRUE(GetElementById("div2")->AffectedByNonSubjectHas());
-  EXPECT_FALSE(GetElementById("div2")->AncestorsAffectedByHas());
-  EXPECT_FALSE(GetElementById("div3")->AffectedByNonSubjectHas());
-  EXPECT_TRUE(GetElementById("div3")->AncestorsAffectedByHas());
-  EXPECT_FALSE(GetElementById("div4")->AffectedByNonSubjectHas());
-  EXPECT_TRUE(GetElementById("div4")->AncestorsAffectedByHas());
-  EXPECT_FALSE(GetElementById("div5")->AffectedByNonSubjectHas());
-  EXPECT_TRUE(GetElementById("div5")->AncestorsAffectedByHas());
-  EXPECT_FALSE(GetElementById("div6")->AffectedByNonSubjectHas());
-  EXPECT_TRUE(GetElementById("div6")->AncestorsAffectedByHas());
-  EXPECT_FALSE(GetElementById("div7")->AffectedByNonSubjectHas());
-  EXPECT_TRUE(GetElementById("div7")->AncestorsAffectedByHas());
+  CheckAffectedByFlagsForHas(
+      "div1", {{kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div2", {{kAffectedByNonSubjectHas, true},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div3", {{kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, true}});
+  CheckAffectedByFlagsForHas(
+      "div4", {{kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, true}});
+  CheckAffectedByFlagsForHas(
+      "div5", {{kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, true}});
+  CheckAffectedByFlagsForHas(
+      "div6", {{kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, true}});
+  CheckAffectedByFlagsForHas(
+      "div7", {{kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, true}});
 
   start_count = GetStyleEngine().StyleForElementCount();
   GetElementById("div5")->setAttribute(html_names::kClassAttr, "b");
@@ -787,20 +798,1509 @@
   element_count = GetStyleEngine().StyleForElementCount() - start_count;
   ASSERT_EQ(1U, element_count);
 
-  EXPECT_FALSE(GetElementById("div1")->AffectedByNonSubjectHas());
-  EXPECT_FALSE(GetElementById("div1")->AncestorsAffectedByHas());
-  EXPECT_TRUE(GetElementById("div2")->AffectedByNonSubjectHas());
-  EXPECT_FALSE(GetElementById("div2")->AncestorsAffectedByHas());
-  EXPECT_FALSE(GetElementById("div3")->AffectedByNonSubjectHas());
-  EXPECT_TRUE(GetElementById("div3")->AncestorsAffectedByHas());
-  EXPECT_FALSE(GetElementById("div4")->AffectedByNonSubjectHas());
-  EXPECT_TRUE(GetElementById("div4")->AncestorsAffectedByHas());
-  EXPECT_FALSE(GetElementById("div5")->AffectedByNonSubjectHas());
-  EXPECT_TRUE(GetElementById("div5")->AncestorsAffectedByHas());
-  EXPECT_FALSE(GetElementById("div6")->AffectedByNonSubjectHas());
-  EXPECT_TRUE(GetElementById("div6")->AncestorsAffectedByHas());
-  EXPECT_FALSE(GetElementById("div7")->AffectedByNonSubjectHas());
-  EXPECT_TRUE(GetElementById("div7")->AncestorsAffectedByHas());
+  CheckAffectedByFlagsForHas(
+      "div1", {{kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div2", {{kAffectedByNonSubjectHas, true},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div3", {{kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, true}});
+  CheckAffectedByFlagsForHas(
+      "div4", {{kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, true}});
+  CheckAffectedByFlagsForHas(
+      "div5", {{kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, true}});
+  CheckAffectedByFlagsForHas(
+      "div6", {{kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, true}});
+  CheckAffectedByFlagsForHas(
+      "div7", {{kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, true}});
+}
+
+TEST_F(AffectedByPseudoTest,
+       AffectedByNonSubjectHasHasAndSiblingsAffectedByHas) {
+  SetHtmlInnerHTML(R"HTML(
+    <style>.a:has(~ .b) .c { background-color: lime; }</style>
+    <div id=div1>
+      <div id=div2 class='a'>
+        <div id=div3></div>
+      </div>
+      <div id=div4></div>
+      <div id=div5 class='b'></div>
+    </div>
+  )HTML");
+
+  UpdateAllLifecyclePhasesForTest();
+  CheckAffectedByFlagsForHas(
+      "div1", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div2", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div3", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div4", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div5", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+
+  unsigned start_count = GetStyleEngine().StyleForElementCount();
+  GetElementById("div3")->setAttribute(html_names::kClassAttr, "c");
+  UpdateAllLifecyclePhasesForTest();
+  unsigned element_count =
+      GetStyleEngine().StyleForElementCount() - start_count;
+  ASSERT_EQ(1U, element_count);
+
+  CheckAffectedByFlagsForHas(
+      "div1", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div2", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, true},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div3", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div4", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, true}});
+  CheckAffectedByFlagsForHas(
+      "div5", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, true}});
+
+  start_count = GetStyleEngine().StyleForElementCount();
+  GetElementById("div5")->setAttribute(html_names::kClassAttr, "");
+  UpdateAllLifecyclePhasesForTest();
+  element_count = GetStyleEngine().StyleForElementCount() - start_count;
+  ASSERT_EQ(1U, element_count);
+}
+
+TEST_F(AffectedByPseudoTest, AffectedBySubjectHasComplexCase1) {
+  SetHtmlInnerHTML(R"HTML(
+    <style>.a:has(.b ~ .c) { background-color: lime; }</style>
+    <div id=div1>
+      <div id=div2 class='a'>
+        <div id=div3></div>
+        <div id=div4>
+          <div id=div5></div>
+          <div id=div6 class='b'></div>
+          <div id=div7></div>
+          <div id=div8 class='c'></div>
+          <div id=div9></div>
+        </div>
+      </div>
+    </div>
+  )HTML");
+
+  UpdateAllLifecyclePhasesForTest();
+  CheckAffectedByFlagsForHas(
+      "div1", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div2", {{kAffectedBySubjectHas, true},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div3", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas("div4",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas("div5",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas("div6",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas("div7",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas("div8",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas("div9",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+
+  unsigned start_count = GetStyleEngine().StyleForElementCount();
+  GetElementById("div8")->setAttribute(html_names::kClassAttr, "");
+  UpdateAllLifecyclePhasesForTest();
+  unsigned element_count =
+      GetStyleEngine().StyleForElementCount() - start_count;
+  ASSERT_EQ(1U, element_count);
+
+  CheckAffectedByFlagsForHas(
+      "div1", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div2", {{kAffectedBySubjectHas, true},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas("div3",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas("div4",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas("div5",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas("div6",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas("div7",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas("div8",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas("div9",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+}
+
+TEST_F(AffectedByPseudoTest, AffectedBySubjectHasComplexCase2) {
+  SetHtmlInnerHTML(R"HTML(
+    <style>.a:has(~ .b .c) { background-color: lime; }</style>
+    <div id=div1>
+      <div id=div2 class='a'>
+        <div id=div3></div>
+      </div>
+      <div id=div4>
+        <div id=div5></div>
+      </div>
+      <div id=div6 class='b'>
+        <div id=div7></div>
+        <div id=div8>
+          <div id=div9></div>
+          <div id=div10 class='c'></div>
+        </div>
+      </div>
+    </div>
+  )HTML");
+
+  UpdateAllLifecyclePhasesForTest();
+  CheckAffectedByFlagsForHas(
+      "div1", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div2", {{kAffectedBySubjectHas, true},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div3", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div4", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, true}});
+  CheckAffectedByFlagsForHas(
+      "div5", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div6", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, true}});
+  CheckAffectedByFlagsForHas(
+      "div7", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas("div8",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div9", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas("div10",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+
+  unsigned start_count = GetStyleEngine().StyleForElementCount();
+  GetElementById("div6")->setAttribute(html_names::kClassAttr, "");
+  UpdateAllLifecyclePhasesForTest();
+  unsigned element_count =
+      GetStyleEngine().StyleForElementCount() - start_count;
+  ASSERT_EQ(1U, element_count);
+
+  CheckAffectedByFlagsForHas(
+      "div1", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div2", {{kAffectedBySubjectHas, true},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div3", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div4", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, true}});
+  CheckAffectedByFlagsForHas("div5",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div6", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, true}});
+  CheckAffectedByFlagsForHas("div7",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas("div8",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas("div9",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas("div10",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+}
+
+TEST_F(AffectedByPseudoTest, AffectedBySubjectHasComplexCase3) {
+  SetHtmlInnerHTML(R"HTML(
+    <style>.a:has(.b ~ .c .d) { background-color: lime; }</style>
+    <div id=div1>
+      <div id=div2 class='a'>
+        <div id=div3></div>
+        <div id=div4>
+          <div id=div5></div>
+          <div id=div6 class='b'></div>
+          <div id=div7></div>
+          <div id=div8 class='c'>
+            <div id=div9></div>
+            <div id=div10>
+              <div id=div11></div>
+              <div id=div12 class='d'></div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  )HTML");
+
+  UpdateAllLifecyclePhasesForTest();
+  CheckAffectedByFlagsForHas(
+      "div1", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div2", {{kAffectedBySubjectHas, true},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas("div3",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas("div4",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas("div5",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas("div6",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas("div7",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas("div8",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas("div9",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas("div10",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div11", {{kAffectedBySubjectHas, false},
+                {kAffectedByNonSubjectHas, false},
+                {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+                {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas("div12",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+
+  unsigned start_count = GetStyleEngine().StyleForElementCount();
+  GetElementById("div8")->setAttribute(html_names::kClassAttr, "");
+  UpdateAllLifecyclePhasesForTest();
+  unsigned element_count =
+      GetStyleEngine().StyleForElementCount() - start_count;
+  ASSERT_EQ(1U, element_count);
+
+  CheckAffectedByFlagsForHas(
+      "div1", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div2", {{kAffectedBySubjectHas, true},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas("div3",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas("div4",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas("div5",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas("div6",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas("div7",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas("div8",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas("div9",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas("div10",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas("div11",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas("div12",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+}
+
+TEST_F(AffectedByPseudoTest, AffectedBySubjectHasComplexCase4) {
+  SetHtmlInnerHTML(R"HTML(
+    <style>.a:has(~ .b .c ~ .d .e) { background-color: lime; }</style>
+    <div id=div1>
+      <div id=div2 class='a'>
+        <div id=div3></div>
+      </div>
+      <div id=div4>
+        <div id=div5></div>
+      </div>
+      <div id=div6 class='b'>
+        <div id=div7></div>
+        <div id=div8>
+          <div id=div9></div>
+          <div id=div10 class='c'></div>
+          <div id=div11></div>
+          <div id=div12 class='d'>
+            <div id=div13></div>
+            <div id=div14 class='e'></div>
+          </div>
+        </div>
+      </div>
+    </div>
+  )HTML");
+
+  UpdateAllLifecyclePhasesForTest();
+  CheckAffectedByFlagsForHas(
+      "div1", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div2", {{kAffectedBySubjectHas, true},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div3", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div4", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, true}});
+  CheckAffectedByFlagsForHas(
+      "div5", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div6", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, true}});
+  CheckAffectedByFlagsForHas("div7",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas("div8",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas("div9",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas("div10",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas("div11",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas("div12",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div13", {{kAffectedBySubjectHas, false},
+                {kAffectedByNonSubjectHas, false},
+                {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+                {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas("div14",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+
+  unsigned start_count = GetStyleEngine().StyleForElementCount();
+  GetElementById("div6")->setAttribute(html_names::kClassAttr, "");
+  UpdateAllLifecyclePhasesForTest();
+  unsigned element_count =
+      GetStyleEngine().StyleForElementCount() - start_count;
+  ASSERT_EQ(1U, element_count);
+
+  CheckAffectedByFlagsForHas(
+      "div1", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div2", {{kAffectedBySubjectHas, true},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div3", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div4", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, true}});
+  CheckAffectedByFlagsForHas("div5",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div6", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, true}});
+  CheckAffectedByFlagsForHas("div7",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas("div8",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas("div9",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas("div10",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas("div11",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas("div12",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas("div13",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas("div14",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+}
+
+TEST_F(AffectedByPseudoTest, AffectedBySubjectHasComplexCase5) {
+  SetHtmlInnerHTML(R"HTML(
+    <style>.a:has(~ .b .c) { background-color: lime; }</style>
+    <div id=div1>
+      <div id=div2 class='a'></div>
+      <div id=div3></div>
+      <div id=div4 class='b'>
+        <div id=div5 class='a'></div>
+        <div id=div6></div>
+        <div id=div7 class='b'>
+          <div id=div8 class='a'></div>
+          <div id=div9></div>
+          <div id=div10 class='b'>
+            <div id=div11 class='c'></div>
+          </div>
+          <div id=div12></div>
+        </div>
+        <div id=div13></div>
+      </div>
+      <div id=div14></div>
+    </div>
+  )HTML");
+
+  UpdateAllLifecyclePhasesForTest();
+  CheckAffectedByFlagsForHas(
+      "div1", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div2", {{kAffectedBySubjectHas, true},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div3", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, true}});
+  CheckAffectedByFlagsForHas(
+      "div4", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, true}});
+  CheckAffectedByFlagsForHas(
+      "div5", {{kAffectedBySubjectHas, true},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div6", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, true}});
+  CheckAffectedByFlagsForHas("div7",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, true}});
+  CheckAffectedByFlagsForHas(
+      "div8", {{kAffectedBySubjectHas, true},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div9", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, true}});
+  CheckAffectedByFlagsForHas("div10",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, true}});
+  CheckAffectedByFlagsForHas("div11",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas("div12",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, true}});
+  CheckAffectedByFlagsForHas("div13",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, true}});
+  CheckAffectedByFlagsForHas(
+      "div14", {{kAffectedBySubjectHas, false},
+                {kAffectedByNonSubjectHas, false},
+                {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+                {kSiblingsAffectedByHas, true}});
+}
+
+TEST_F(AffectedByPseudoTest, AffectedBySubjectHasComplexCase6) {
+  SetHtmlInnerHTML(R"HTML(
+    <style>.a:has(~ .b .c) { background-color: lime; }</style>
+    <div id=div1>
+      <div id=div2 class='a'></div>
+      <div id=div3></div>
+      <div id=div4 class='b'>
+        <div id=div5 class='a'></div>
+        <div id=div6></div>
+        <div id=div7 class='b'>
+          <div id=div8 class='a'></div>
+          <div id=div9></div>
+          <div id=div10 class='b'>
+            <div id=div11></div>
+          </div>
+          <div id=div12></div>
+        </div>
+        <div id=div13></div>
+      </div>
+      <div id=div14></div>
+    </div>
+  )HTML");
+
+  UpdateAllLifecyclePhasesForTest();
+  CheckAffectedByFlagsForHas(
+      "div1", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div2", {{kAffectedBySubjectHas, true},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div3", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, true}});
+  CheckAffectedByFlagsForHas(
+      "div4", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, true}});
+  CheckAffectedByFlagsForHas("div5",
+                             {{kAffectedBySubjectHas, true},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas("div6",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, true}});
+  CheckAffectedByFlagsForHas("div7",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, true}});
+  CheckAffectedByFlagsForHas("div8",
+                             {{kAffectedBySubjectHas, true},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas("div9",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, true}});
+  CheckAffectedByFlagsForHas("div10",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, true}});
+  CheckAffectedByFlagsForHas("div11",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas("div12",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, true}});
+  CheckAffectedByFlagsForHas("div13",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, true}});
+  CheckAffectedByFlagsForHas(
+      "div14", {{kAffectedBySubjectHas, false},
+                {kAffectedByNonSubjectHas, false},
+                {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+                {kSiblingsAffectedByHas, true}});
+}
+
+TEST_F(AffectedByPseudoTest, AffectedBySubjectHasComplexCase7) {
+  SetHtmlInnerHTML(R"HTML(
+    <style>.a:has(+ .b .c) { background-color: lime; }</style>
+    <div id=div1>
+      <div id=div2></div>
+      <div id=div3 class='a'></div>
+      <div id=div4 class='b'>
+        <div id=div5></div>
+        <div id=div6 class='a'></div>
+        <div id=div7 class='b'>
+          <div id=div8></div>
+          <div id=div9 class='a'></div>
+          <div id=div10 class='b'>
+            <div id=div11></div>
+          </div>
+          <div id=div12></div>
+        </div>
+        <div id=div13></div>
+      </div>
+      <div id=div14></div>
+    </div>
+  )HTML");
+
+  UpdateAllLifecyclePhasesForTest();
+  CheckAffectedByFlagsForHas(
+      "div1", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div2", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div3", {{kAffectedBySubjectHas, true},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div4", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, true}});
+  CheckAffectedByFlagsForHas("div5",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas("div6",
+                             {{kAffectedBySubjectHas, true},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas("div7",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, true}});
+  CheckAffectedByFlagsForHas("div8",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas("div9",
+                             {{kAffectedBySubjectHas, true},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas("div10",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, true}});
+  CheckAffectedByFlagsForHas("div11",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas("div12",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas("div13",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div14", {{kAffectedBySubjectHas, false},
+                {kAffectedByNonSubjectHas, false},
+                {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+                {kSiblingsAffectedByHas, false}});
+}
+
+TEST_F(AffectedByPseudoTest, AffectedByNonSubjectHasComplexCase1) {
+  SetHtmlInnerHTML(R"HTML(
+    <style>.a:has(~ .b .c) .d { background-color: lime; }</style>
+    <div id=div1>
+      <div id=div2 class='a'>
+        <div id=div3></div>
+      </div>
+      <div id=div4>
+        <div id=div5>
+          <div id=div6></div>
+        </div>
+      </div>
+      <div id=div7 class='b'>
+        <div id=div8>
+          <div id=div9 class='c'></div>
+        </div>
+      </div>
+    </div>
+  )HTML");
+
+  UpdateAllLifecyclePhasesForTest();
+  CheckAffectedByFlagsForHas(
+      "div1", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div2", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div3", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div4", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div5", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div6", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div7", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div8", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div9", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+
+  unsigned start_count = GetStyleEngine().StyleForElementCount();
+  GetElementById("div3")->setAttribute(html_names::kClassAttr, "d");
+  UpdateAllLifecyclePhasesForTest();
+  unsigned element_count =
+      GetStyleEngine().StyleForElementCount() - start_count;
+  ASSERT_EQ(1U, element_count);
+
+  CheckAffectedByFlagsForHas(
+      "div1", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div2", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, true},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div3", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div4", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, true}});
+  CheckAffectedByFlagsForHas(
+      "div5", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div6", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div7", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, true}});
+  CheckAffectedByFlagsForHas("div8",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas("div9",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+
+  start_count = GetStyleEngine().StyleForElementCount();
+  GetElementById("div9")->setAttribute(html_names::kClassAttr, "");
+  UpdateAllLifecyclePhasesForTest();
+  element_count = GetStyleEngine().StyleForElementCount() - start_count;
+  ASSERT_EQ(1U, element_count);
+
+  CheckAffectedByFlagsForHas(
+      "div1", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div2", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, true},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div3", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div4", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, true}});
+  CheckAffectedByFlagsForHas("div5",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas("div6",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div7", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, true}});
+  CheckAffectedByFlagsForHas("div8",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas("div9",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+}
+
+TEST_F(AffectedByPseudoTest, AffectedByNonSubjectHasComplexCase2) {
+  SetHtmlInnerHTML(R"HTML(
+    <style>.a:has(~ .b .c) ~ .d { background-color: lime; }</style>
+    <div id=div1>
+      <div id=div2 class='a'>
+        <div id=div3></div>
+      </div>
+      <div id=div4>
+        <div id=div5>
+          <div id=div6></div>
+        </div>
+      </div>
+      <div id=div7 class='b'>
+        <div id=div8>
+          <div id=div9 class='c'></div>
+        </div>
+      </div>
+    </div>
+  )HTML");
+
+  UpdateAllLifecyclePhasesForTest();
+  CheckAffectedByFlagsForHas(
+      "div1", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div2", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div3", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div4", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div5", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div6", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div7", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div8", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div9", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+
+  unsigned start_count = GetStyleEngine().StyleForElementCount();
+  GetElementById("div4")->setAttribute(html_names::kClassAttr, "d");
+  UpdateAllLifecyclePhasesForTest();
+  unsigned element_count =
+      GetStyleEngine().StyleForElementCount() - start_count;
+  ASSERT_EQ(1U, element_count);
+
+  CheckAffectedByFlagsForHas(
+      "div1", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div2", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, true},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div3", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div4", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, true}});
+  CheckAffectedByFlagsForHas(
+      "div5", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div6", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div7", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, true}});
+  CheckAffectedByFlagsForHas("div8",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas("div9",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+
+  start_count = GetStyleEngine().StyleForElementCount();
+  GetElementById("div9")->setAttribute(html_names::kClassAttr, "");
+  UpdateAllLifecyclePhasesForTest();
+  element_count = GetStyleEngine().StyleForElementCount() - start_count;
+  ASSERT_EQ(1U, element_count);
+
+  CheckAffectedByFlagsForHas(
+      "div1", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div2", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, true},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div3", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div4", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, true}});
+  CheckAffectedByFlagsForHas("div5",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas("div6",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div7", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, true}});
+  CheckAffectedByFlagsForHas("div8",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas("div9",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+}
+
+TEST_F(AffectedByPseudoTest, AffectedByNonSubjectHasComplexCase3) {
+  SetHtmlInnerHTML(R"HTML(
+    <style>.a:has(~ .b > .c > .d) ~ .e { background-color: lime; }</style>
+    <div id=div1>
+      <div id=div2 class='a'>
+        <div id=div3></div>
+      </div>
+      <div id=div4>
+        <div id=div5>
+          <div id=div6></div>
+        </div>
+      </div>
+      <div id=div7 class='b'>
+        <div id=div8 class='c'>
+          <div id=div9 class='d'>
+            <div id=div10></div>
+          </div>
+        </div>
+      </div>
+    </div>
+  )HTML");
+
+  UpdateAllLifecyclePhasesForTest();
+  CheckAffectedByFlagsForHas(
+      "div1", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div2", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div3", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div4", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div5", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div6", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div7", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div8", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div9", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div10", {{kAffectedBySubjectHas, false},
+                {kAffectedByNonSubjectHas, false},
+                {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+                {kSiblingsAffectedByHas, false}});
+
+  unsigned start_count = GetStyleEngine().StyleForElementCount();
+  GetElementById("div4")->setAttribute(html_names::kClassAttr, "e");
+  UpdateAllLifecyclePhasesForTest();
+  unsigned element_count =
+      GetStyleEngine().StyleForElementCount() - start_count;
+  ASSERT_EQ(1U, element_count);
+
+  CheckAffectedByFlagsForHas(
+      "div1", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div2", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, true},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div3", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div4", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, true}});
+  CheckAffectedByFlagsForHas(
+      "div5", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div6", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div7", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, true}});
+  CheckAffectedByFlagsForHas("div8",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas("div9",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div10", {{kAffectedBySubjectHas, false},
+                {kAffectedByNonSubjectHas, false},
+                {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+                {kSiblingsAffectedByHas, false}});
+
+  start_count = GetStyleEngine().StyleForElementCount();
+  GetElementById("div8")->setAttribute(html_names::kClassAttr, "");
+  UpdateAllLifecyclePhasesForTest();
+  element_count = GetStyleEngine().StyleForElementCount() - start_count;
+  ASSERT_EQ(1U, element_count);
+
+  CheckAffectedByFlagsForHas(
+      "div1", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div2", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, true},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div3", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div4", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, true}});
+  CheckAffectedByFlagsForHas("div5",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas("div6",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div7", {{kAffectedBySubjectHas, false},
+               {kAffectedByNonSubjectHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, true}});
+  CheckAffectedByFlagsForHas("div8",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas("div9",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas(
+      "div10", {{kAffectedBySubjectHas, false},
+                {kAffectedByNonSubjectHas, false},
+                {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+                {kSiblingsAffectedByHas, false}});
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/css/cascade_layer.h b/third_party/blink/renderer/core/css/cascade_layer.h
index c65c7857..0d8e599 100644
--- a/third_party/blink/renderer/core/css/cascade_layer.h
+++ b/third_party/blink/renderer/core/css/cascade_layer.h
@@ -26,6 +26,11 @@
     return direct_sub_layers_;
   }
 
+  // Getting or setting the order of a layer is only valid for canonical cascade
+  // layers i.e. the unique layer representation for a particular tree scope.
+  const absl::optional<unsigned> GetOrder() const { return order_; }
+  void SetOrder(unsigned order) { order_ = order; }
+
   CascadeLayer* GetOrAddSubLayer(const StyleRuleBase::LayerName& name);
 
   void Trace(blink::Visitor*) const;
@@ -40,6 +45,7 @@
   CascadeLayer* FindDirectSubLayer(const AtomicString&) const;
   void ComputeLayerOrderInternal(unsigned* next);
 
+  absl::optional<unsigned> order_;
   AtomicString name_;
   HeapVector<Member<CascadeLayer>> direct_sub_layers_;
 };
diff --git a/third_party/blink/renderer/core/css/cascade_layer_map.cc b/third_party/blink/renderer/core/css/cascade_layer_map.cc
index 62e0cc1..03b12d4e 100644
--- a/third_party/blink/renderer/core/css/cascade_layer_map.cc
+++ b/third_party/blink/renderer/core/css/cascade_layer_map.cc
@@ -32,12 +32,10 @@
   }
 }
 
-void ComputeLayerOrder(const CascadeLayer& layer,
-                       unsigned& next,
-                       LayerOrderMap& layer_order_map) {
+void ComputeLayerOrder(CascadeLayer& layer, unsigned& next) {
   for (const auto& sub_layer : layer.GetDirectSubLayers())
-    ComputeLayerOrder(*sub_layer, next, layer_order_map);
-  layer_order_map.insert(&layer, next++);
+    ComputeLayerOrder(*sub_layer, next);
+  layer.SetOrder(next++);
 }
 
 }  // namespace
@@ -55,15 +53,14 @@
   }
 
   unsigned next = 0;
-  LayerOrderMap canonical_layer_order_map;
-  ComputeLayerOrder(*canonical_root_layer, next, canonical_layer_order_map);
+  ComputeLayerOrder(*canonical_root_layer, next);
 
-  canonical_layer_order_map.Set(canonical_root_layer, kImplicitOuterLayerOrder);
+  canonical_root_layer->SetOrder(kImplicitOuterLayerOrder);
 
   for (const auto& iter : canonical_layer_map) {
     const CascadeLayer* layer_from_sheet = iter.key;
     const CascadeLayer* canonical_layer = iter.value;
-    unsigned layer_order = canonical_layer_order_map.at(canonical_layer);
+    unsigned layer_order = canonical_layer->GetOrder().value();
     layer_order_map_.insert(layer_from_sheet, layer_order);
 
 #if DCHECK_IS_ON()
diff --git a/third_party/blink/renderer/core/css/has_argument_match_context.cc b/third_party/blink/renderer/core/css/has_argument_match_context.cc
index 74fdb6d..e20afe2 100644
--- a/third_party/blink/renderer/core/css/has_argument_match_context.cc
+++ b/third_party/blink/renderer/core/css/has_argument_match_context.cc
@@ -29,6 +29,9 @@
   CSSSelector::RelationType relation = CSSSelector::kSubSelector;
   depth_limit_ = 0;
   adjacent_distance_limit_ = 0;
+  bool contains_child_or_descendant_combinator = false;
+  bool sibling_combinator_at_leftmost = false;
+
   // The explicit ':scope' in ':has' argument selector is not considered
   // for getting the depth and adjacent distance.
   // TODO(blee@igalia.com) Need to clarify the :scope dependency in relative
@@ -43,6 +46,11 @@
         leftmost_relation_ = relation;
         [[fallthrough]];
       case CSSSelector::kDescendant:
+        if (sibling_combinator_at_leftmost) {
+          sibling_combinator_at_leftmost = false;
+          sibling_combinator_between_child_or_descendant_combinator_ = true;
+        }
+        contains_child_or_descendant_combinator = true;
         depth_limit_ = kInfiniteDepth;
         adjacent_distance_limit_ = 0;
         break;
@@ -51,6 +59,11 @@
         leftmost_relation_ = relation;
         [[fallthrough]];
       case CSSSelector::kChild:
+        if (sibling_combinator_at_leftmost) {
+          sibling_combinator_at_leftmost = false;
+          sibling_combinator_between_child_or_descendant_combinator_ = true;
+        }
+        contains_child_or_descendant_combinator = true;
         if (DepthFixed()) {
           depth_limit_++;
           adjacent_distance_limit_ = 0;
@@ -61,6 +74,10 @@
         leftmost_relation_ = relation;
         [[fallthrough]];
       case CSSSelector::kDirectAdjacent:
+        if (contains_child_or_descendant_combinator)
+          sibling_combinator_at_leftmost = true;
+        else
+          sibling_combinator_at_rightmost_ = true;
         if (AdjacentDistanceFixed())
           adjacent_distance_limit_++;
         break;
@@ -69,6 +86,10 @@
         leftmost_relation_ = relation;
         [[fallthrough]];
       case CSSSelector::kIndirectAdjacent:
+        if (contains_child_or_descendant_combinator)
+          sibling_combinator_at_leftmost = true;
+        else
+          sibling_combinator_at_rightmost_ = true;
         adjacent_distance_limit_ = kInfiniteAdjacentDistance;
         break;
 
@@ -166,4 +187,56 @@
   DCHECK(current_);
 }
 
+AffectedByHasIterator::AffectedByHasIterator(
+    HasArgumentSubtreeIterator& iterator_at_matched)
+    : iterator_at_matched_(iterator_at_matched),
+      depth_(iterator_at_matched_.Depth()),
+      current_(iterator_at_matched_.CurrentElement()) {
+  DCHECK_GE(depth_, 0);
+}
+
+bool AffectedByHasIterator::NeedsTraverseSiblings() {
+  // When the current element is at the same depth of the subselector-matched
+  // element, we can determine whether the sibling traversal is needed or not
+  // by checking whether the rightmost combinator is an adjacent combinator.
+  // When the current element is not at the same depth of the subselector-
+  // matched element, we can determine whether the sibling traversal is needed
+  // or not by checking whether an adjacent combinator is between child or
+  // descendant combinator.
+  DCHECK_LE(depth_, iterator_at_matched_.Depth());
+  return iterator_at_matched_.Depth() == depth_
+             ? iterator_at_matched_.Context().SiblingCombinatorAtRightmost()
+             : iterator_at_matched_.Context()
+                   .SiblingCombinatorBetweenChildOrDescendantCombinator();
+}
+
+bool AffectedByHasIterator::AtEnd() const {
+  DCHECK_GE(iterator_at_matched_.Depth(), 0);
+  return current_ == iterator_at_matched_.ScopeElement();
+}
+
+void AffectedByHasIterator::operator++() {
+  DCHECK(current_);
+  DCHECK_GE(iterator_at_matched_.Depth(), 0);
+
+  if (depth_ == 0) {
+    current_ = Traversal<Element>::PreviousSibling(*current_);
+    DCHECK(current_);
+    return;
+  }
+
+  Element* previous = nullptr;
+  if (NeedsTraverseSiblings() &&
+      (previous = Traversal<Element>::PreviousSibling(*current_))) {
+    current_ = previous;
+    DCHECK(current_);
+    return;
+  }
+
+  DCHECK_GT(depth_, 0);
+  depth_--;
+  current_ = current_->parentElement();
+  DCHECK(current_);
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/css/has_argument_match_context.h b/third_party/blink/renderer/core/css/has_argument_match_context.h
index 0d7d2cf..5ec9e41 100644
--- a/third_party/blink/renderer/core/css/has_argument_match_context.h
+++ b/third_party/blink/renderer/core/css/has_argument_match_context.h
@@ -27,6 +27,13 @@
     return leftmost_relation_;
   }
 
+  inline bool SiblingCombinatorAtRightmost() const {
+    return sibling_combinator_at_rightmost_;
+  }
+  inline bool SiblingCombinatorBetweenChildOrDescendantCombinator() const {
+    return sibling_combinator_between_child_or_descendant_combinator_;
+  }
+
  private:
   const static int kInfiniteDepth = std::numeric_limits<int>::max();
   const static int kInfiniteAdjacentDistance = std::numeric_limits<int>::max();
@@ -151,6 +158,11 @@
   CSSSelector::RelationType leftmost_relation_;
   int adjacent_distance_limit_;
   int depth_limit_;
+
+  // Indicates the selector's combinator information which can be used for
+  // sibling traversal after subselector matched.
+  bool sibling_combinator_at_rightmost_{false};
+  bool sibling_combinator_between_child_or_descendant_combinator_{false};
 };
 
 // Subtree traversal iterator class for ':has' argument matching. To
@@ -202,8 +214,11 @@
   Element* CurrentElement() const { return current_; }
   bool AtEnd() const { return !current_; }
   bool AtFixedDepth() const { return depth_ == context_.DepthLimit(); }
+  bool UnderDepthLimit() const { return depth_ <= context_.DepthLimit(); }
   bool AtSiblingOfHasScope() const { return depth_ == 0; }
-  int Depth() const { return depth_; }
+  inline int Depth() const { return depth_; }
+  inline Element* ScopeElement() const { return has_scope_element_; }
+  inline const HasArgumentMatchContext& Context() const { return context_; }
 
  private:
   inline Element* LastWithin(Element*);
@@ -215,6 +230,30 @@
   Element* traversal_end_{nullptr};
 };
 
+// Iterator class to traverse siblings, ancestors and ancestor siblings of the
+// HasArgumentSubtreeIterator's current element until reach to the scope
+// element.
+// This iterator is used to set the 'AncestorsOrAncestorSiblingsAffectedByHas'
+// or 'SiblingsAffectedByHas' flags of those elements before returning early
+// from the ':has()' argument subtree traversal.
+class AffectedByHasIterator {
+  STACK_ALLOCATED();
+
+ public:
+  explicit AffectedByHasIterator(HasArgumentSubtreeIterator&);
+  void operator++();
+  Element* CurrentElement() const { return current_; }
+  bool AtEnd() const;
+  bool AtSiblingOfHasScope() const { return depth_ == 0; }
+
+ private:
+  inline bool NeedsTraverseSiblings();
+
+  const HasArgumentSubtreeIterator& iterator_at_matched_;
+  int depth_;
+  Element* current_;
+};
+
 }  // namespace blink
 
 #endif  // THIRD_PARTY_BLINK_RENDERER_CORE_CSS_HAS_ARGUMENT_MATCH_CONTEXT_H_
diff --git a/third_party/blink/renderer/core/css/selector_checker.cc b/third_party/blink/renderer/core/css/selector_checker.cc
index b3d3d04d..f36d925 100644
--- a/third_party/blink/renderer/core/css/selector_checker.cc
+++ b/third_party/blink/renderer/core/css/selector_checker.cc
@@ -655,12 +655,70 @@
   return true;
 }
 
+namespace {
+
+Element* TraverseToParent(Element* element) {
+  return element->parentElement();
+}
+
+Element* TraverseToPreviousSibling(Element* element) {
+  return ElementTraversal::PreviousSibling(*element);
+}
+
+inline bool CacheMatchedElementsAndReturnMatchedResultForIndirectRelation(
+    Element* has_scope_element,
+    HeapVector<Member<Element>>& has_argument_leftmost_compound_matches,
+    ElementHasMatchedMap& map,
+    Element* (*next)(Element*)) {
+  bool selector_matched = false;
+  for (auto leftmost : has_argument_leftmost_compound_matches) {
+    for (Element* has_matched_element = next(leftmost); has_matched_element;
+         has_matched_element = next(has_matched_element)) {
+      if (has_matched_element == has_scope_element)
+        selector_matched = true;
+      auto cache_result = map.insert(has_matched_element, true);
+      if (cache_result.is_new_entry)
+        continue;
+      if (cache_result.stored_value->value)
+        break;
+      cache_result.stored_value->value = true;
+    }
+  }
+  return selector_matched;
+}
+
+inline bool CacheMatchedElementsAndReturnMatchedResultForDirectRelation(
+    Element* has_scope_element,
+    HeapVector<Member<Element>>& has_argument_leftmost_compound_matches,
+    ElementHasMatchedMap& map,
+    Element* (*next)(Element*)) {
+  bool selector_matched = false;
+  for (auto leftmost : has_argument_leftmost_compound_matches) {
+    if (Element* has_matched_element = next(leftmost)) {
+      map.Set(has_matched_element, true);
+      if (has_matched_element == has_scope_element)
+        selector_matched = true;
+    }
+  }
+  return selector_matched;
+}
+
+inline void SetAffectedByHasFlag(Element* element,
+                                 bool is_sibling_of_has_scope) {
+  if (is_sibling_of_has_scope)
+    element->SetSiblingsAffectedByHas();
+  else
+    element->SetAncestorsOrAncestorSiblingsAffectedByHas();
+}
+
+}  // namespace
+
 bool SelectorChecker::CheckPseudoHas(const SelectorCheckingContext& context,
                                      MatchResult& result) const {
-  Document& document = context.element->GetDocument();
+  Element* has_scope_element = context.element;
+  Document& document = has_scope_element->GetDocument();
   DCHECK(document.GetHasMatchedCacheScope());
-  Element* element = context.element;
-  SelectorCheckingContext sub_context(element);
+  SelectorCheckingContext sub_context(has_scope_element);
   // TODO(blee@igalia.com) Need to clarify the :scope dependency in relative
   // selector definition.
   // - spec : https://www.w3.org/TR/selectors-4/#relative
@@ -676,6 +734,9 @@
        selector; selector = CSSSelectorList::Next(*selector)) {
     ElementHasMatchedMap& map =
         HasMatchedCacheScope::GetCacheForSelector(&document, selector);
+    HasArgumentMatchContext has_argument_match_context(selector);
+    CSSSelector::RelationType leftmost_relation =
+        has_argument_match_context.LeftmostRelation();
 
     // Get the cache item of matching ':has(<selector>)' on the element
     // to skip argument matching on the subtree elements
@@ -684,8 +745,38 @@
     //    move to the next argument selector.
     //  - Otherwise, mark the element as checked but not matched.
     {  // Limit the the AddResult scope to prevent SECURITY_DCHECK
-      auto cache_result = map.insert(element, false);  // Mark as checked
+      auto cache_result =
+          map.insert(has_scope_element, false);  // Mark as checked
       if (!cache_result.is_new_entry) {        // Was already marked as checked
+
+        // The SiblingsAffectedByHas flag is set only when an element is a
+        // sibling of the :has() scope element during the subselector matching.
+        // But during the matching, the matching status of some elements are
+        // cached and those element cannot be a sibling of the :has() scope
+        // element in case that the subselector starts with adjacent combinator.
+        // 1. MatchSelector() gives some possibly matched elements, and those
+        //    are cached as matched.
+        // 2. If an element is not matched, then it is cached as not matched.
+        // We need to set missing SiblingAffectedByHas flags for the cached
+        // elements before returning early.
+        switch (leftmost_relation) {
+          case CSSSelector::kRelativeDirectAdjacent:
+            if (Element* next_sibling =
+                    ElementTraversal::NextSibling(*has_scope_element))
+              next_sibling->SetSiblingsAffectedByHas();
+            break;
+          case CSSSelector::kRelativeIndirectAdjacent:
+            for (Element* next_sibling =
+                     ElementTraversal::NextSibling(*has_scope_element);
+                 next_sibling && !next_sibling->SiblingsAffectedByHas();
+                 next_sibling = ElementTraversal::NextSibling(*next_sibling)) {
+              next_sibling->SetSiblingsAffectedByHas();
+            }
+            break;
+          default:
+            break;
+        }
+
         if (cache_result.stored_value->value)  // Was already marked as matched
           return true;
         continue;
@@ -693,7 +784,6 @@
     }
 
     sub_context.selector = selector;
-    HasArgumentMatchContext has_argument_match_context(selector);
 
     bool depth_fixed = has_argument_match_context.DepthFixed();
 
@@ -735,24 +825,38 @@
     // - csswg issue : https://github.com/w3c/csswg-drafts/issues/6399
     if (!depth_fixed) {
       sub_context.relative_leftmost_element =
-          &element->GetTreeScope().RootNode();
+          &has_scope_element->GetTreeScope().RootNode();
     } else if (has_argument_match_context.AdjacentDistanceFixed()) {
-      if (ContainerNode* parent_node = element->parentNode()) {
+      if (ContainerNode* parent_node = has_scope_element->parentNode()) {
         sub_context.relative_leftmost_element =
             Traversal<Element>::FirstChild(*parent_node);
       } else {
-        sub_context.relative_leftmost_element = element;
+        sub_context.relative_leftmost_element = has_scope_element;
       }
     } else {
-      sub_context.relative_leftmost_element = element;
+      sub_context.relative_leftmost_element = has_scope_element;
     }
 
     bool selector_matched = false;
-    for (HasArgumentSubtreeIterator iterator(*element,
+    for (HasArgumentSubtreeIterator iterator(*has_scope_element,
                                              has_argument_match_context);
          !iterator.AtEnd(); ++iterator) {
-      if (depth_fixed && !iterator.AtFixedDepth())
+      if (depth_fixed && !iterator.AtFixedDepth()) {
+        // We can skip subselector matching on some elements at a certain depth
+        // when the subselector doesn't have descendant combinator. (e.g. For
+        // the style rule '.a:has(> .b > .c) {}', we don't need to match the
+        // subselector '> .b > .c' on the child of '.a' or the great-grand-child
+        // of '.a'.)
+        // But this can break the upward tree walk because the 'affected-by'
+        // flags of the skipped elements will not set.
+        // To prevent this, marks the flags of skipped elements if those are
+        // under the depth limit.
+        if (iterator.UnderDepthLimit()) {
+          SetAffectedByHasFlag(iterator.CurrentElement(),
+                               iterator.AtSiblingOfHasScope());
+        }
         continue;
+      }
       sub_context.element = iterator.CurrentElement();
       HeapVector<Member<Element>> has_argument_leftmost_compound_matches;
       MatchResult sub_result;
@@ -761,54 +865,30 @@
 
       MatchSelector(sub_context, sub_result);
 
-      switch (has_argument_match_context.LeftmostRelation()) {
+      switch (leftmost_relation) {
         case CSSSelector::kRelativeDescendant:
-          map.insert(iterator.CurrentElement(), false);  // Mark as checked
-          if (!has_argument_leftmost_compound_matches.IsEmpty()) {
-            sub_context.element =
-                has_argument_leftmost_compound_matches.front();
-            for (sub_context.element = ParentElement(sub_context);
-                 sub_context.element;
-                 sub_context.element = ParentElement(sub_context)) {
-              map.Set(sub_context.element, true);  // Mark as matched
-              if (sub_context.element == element)
-                selector_matched = true;
-            }
-          }
+          selector_matched =
+              CacheMatchedElementsAndReturnMatchedResultForIndirectRelation(
+                  has_scope_element, has_argument_leftmost_compound_matches,
+                  map, TraverseToParent);
           break;
         case CSSSelector::kRelativeChild:
-          for (auto leftmost : has_argument_leftmost_compound_matches) {
-            Element* parent = leftmost->parentElement();
-            map.Set(parent, true);  // Mark as matched
-            if (parent == element)
-              selector_matched = true;
-          }
+          selector_matched =
+              CacheMatchedElementsAndReturnMatchedResultForDirectRelation(
+                  has_scope_element, has_argument_leftmost_compound_matches,
+                  map, TraverseToParent);
           break;
         case CSSSelector::kRelativeDirectAdjacent:
-          if (!depth_fixed && !iterator.AtSiblingOfHasScope())
-            map.insert(iterator.CurrentElement(), false);  // Mark as checked
-          for (auto leftmost : has_argument_leftmost_compound_matches) {
-            if (Element* sibling =
-                    Traversal<Element>::PreviousSibling(*leftmost)) {
-              map.Set(sibling, true);  // Mark as matched
-              if (sibling == element)
-                selector_matched = true;
-            }
-          }
+          selector_matched =
+              CacheMatchedElementsAndReturnMatchedResultForDirectRelation(
+                  has_scope_element, has_argument_leftmost_compound_matches,
+                  map, TraverseToPreviousSibling);
           break;
         case CSSSelector::kRelativeIndirectAdjacent:
-          if (!depth_fixed)
-            map.insert(iterator.CurrentElement(), false);  // Mark as checked
-          for (auto leftmost : has_argument_leftmost_compound_matches) {
-            for (Element* sibling =
-                     Traversal<Element>::PreviousSibling(*leftmost);
-                 sibling;
-                 sibling = Traversal<Element>::PreviousSibling(*sibling)) {
-              map.Set(sibling, true);  // Mark as matched
-              if (sibling == element)
-                selector_matched = true;
-            }
-          }
+          selector_matched =
+              CacheMatchedElementsAndReturnMatchedResultForIndirectRelation(
+                  has_scope_element, has_argument_leftmost_compound_matches,
+                  map, TraverseToPreviousSibling);
           break;
         default:
           NOTREACHED();
@@ -816,37 +896,36 @@
       }
 
       if (selector_matched) {
-        // Need to walk up ancestors to set 'AncestorsAffectedByHas' flag so
-        // that the StyleEngine can walk up to find the elements affected by
-        // subject or non-subject :has().
+        // Need to walk up to set 'AncestorsOrAncestorSiblingsAffectedByHas'
+        // or 'SiblingsAffectedByHas' flag so that the StyleEngine can walk up
+        // to find the elements affected by subject or non-subject :has().
         //
         // StyleEngine tries to find elements affected by :has() by walking up
-        // ancestors of a mutated element only when an element marked as
-        // 'AncestorsAffectedByHas'. If an ancestor of the mutated element
-        // is not 'AncestorsAffectedByHas' element, then StyleEngine will stop
-        // the upward tree walk at the element.
+        // siblings or ancestors a mutated element only when an element has the
+        // flags set. If an element doesn't have those flags set, then the
+        // StyleEngine will stop the upward tree walk at the element.
         //
         // HasArgumentSubtreeIterator traverses the sub-tree in the reversed
         // DOM tree walk order for preventing O(n^2) matching problem of
         // multiple elements affected by :has(). Due to this traversal order,
-        // this early returning can break the upward tree walk. To prevent the
-        // problem, marks all ancestors as 'AncestorsAffectedByHas' before
-        // returning.
+        // this early returning can break the upward tree walk.
         //
-        // Similar to the DynamicRestyleFlags in the ContainerNode, this flag
+        // To prevent the problem, walks up until reach to the scope element
+        // and marks elements as 'AncestorsOrAncestorSiblingsAffectedByHas' or
+        // 'SiblingsAffectedByHas' before returning.
+        //
+        // Similar to the DynamicRestyleFlags in the ContainerNode, these flags
         // will never be reset.
-        //
-        // TODO(blee@igalia.com) Need to traverse to siblings and siblings of
-        // ancestors to support sibling combinator and complex selector in
-        // :has() argument.
-        for (Element* parent = iterator.CurrentElement();
-             parent && parent != element; parent = parent->parentElement()) {
-          parent->SetAncestorsAffectedByHas();
+        for (AffectedByHasIterator affected_by_has_iterator(iterator);
+             !affected_by_has_iterator.AtEnd(); ++affected_by_has_iterator) {
+          SetAffectedByHasFlag(affected_by_has_iterator.CurrentElement(),
+                               affected_by_has_iterator.AtSiblingOfHasScope());
         }
         return true;
-      } else {
-        iterator.CurrentElement()->SetAncestorsAffectedByHas();
       }
+
+      SetAffectedByHasFlag(iterator.CurrentElement(),
+                           iterator.AtSiblingOfHasScope());
     }
   }
   return false;
diff --git a/third_party/blink/renderer/core/css/style_engine.cc b/third_party/blink/renderer/core/css/style_engine.cc
index 8d5cff51..60f9c0d 100644
--- a/third_party/blink/renderer/core/css/style_engine.cc
+++ b/third_party/blink/renderer/core/css/style_engine.cc
@@ -941,12 +941,39 @@
   return element.parentNode()->GetStyleChangeType() == kSubtreeStyleChange;
 }
 
-void StyleEngine::InvalidateAncestorsAffectedByHasInternal(
-    Element* element,
+namespace {
+
+bool PossiblyAffectingHasState(Element& element) {
+  return element.AncestorsOrAncestorSiblingsAffectedByHas() ||
+         element.SiblingsAffectedByHas();
+}
+
+inline Element* SelfOrPreviousSibling(Node* node) {
+  if (!node)
+    return nullptr;
+  if (Element* element = DynamicTo<Element>(node))
+    return element;
+  return ElementTraversal::PreviousSibling(*node);
+}
+
+}  // namespace
+
+void StyleEngine::InvalidateAncestorsOrSiblingsAffectedByHasInternal(
+    Element* parent,
+    Element* previous_sibling,
     bool for_pseudo_change) {
   const RuleFeatureSet& features = GetRuleFeatureSet();
 
+  bool traverse_ancestors = false;
+  bool traverse_siblings = false;
+  Element* element = previous_sibling ? previous_sibling : parent;
+
+  DCHECK(element);
+
   while (element) {
+    traverse_ancestors |= element->AncestorsOrAncestorSiblingsAffectedByHas();
+    traverse_siblings = element->SiblingsAffectedByHas();
+
     const ComputedStyle* style = element->GetComputedStyle();
 
     if (style && style->AffectedBySubjectHas() &&
@@ -970,23 +997,56 @@
                                                              *element);
     }
 
-    // Stop walk up when the 'AncestorsAffectedByHas' flag is false.
-    if (!element->AncestorsAffectedByHas())
+    if (traverse_siblings) {
+      previous_sibling = ElementTraversal::PreviousSibling(*element);
+      if (previous_sibling) {
+        element = previous_sibling;
+        continue;
+      }
+    }
+
+    if (!traverse_ancestors)
       return;
 
     element = element->parentElement();
+    traverse_ancestors = false;
   }
 }
 
-void StyleEngine::InvalidateAncestorsAffectedByHasForPseudoChange(
-    Element* element) {
-  InvalidateAncestorsAffectedByHasInternal(element,
-                                           true /* for_pseudo_change */);
+void StyleEngine::InvalidateAncestorsOrSiblingsAffectedByHasForPseudoChange(
+    Element& changed_element) {
+  InvalidateAncestorsOrSiblingsAffectedByHasForPseudoChange(
+      changed_element.AncestorsOrAncestorSiblingsAffectedByHas()
+          ? changed_element.parentElement()
+          : nullptr,
+      changed_element.SiblingsAffectedByHas()
+          ? ElementTraversal::PreviousSibling(changed_element)
+          : nullptr);
 }
 
-void StyleEngine::InvalidateAncestorsAffectedByHas(Element* element) {
-  InvalidateAncestorsAffectedByHasInternal(element,
-                                           false /* for_pseudo_change */);
+void StyleEngine::InvalidateAncestorsOrSiblingsAffectedByHasForPseudoChange(
+    Element* parent,
+    Element* previous_sibling) {
+  InvalidateAncestorsOrSiblingsAffectedByHasInternal(
+      parent, previous_sibling, true /* for_pseudo_change */);
+}
+
+void StyleEngine::InvalidateAncestorsOrSiblingsAffectedByHas(
+    Element& changed_element) {
+  InvalidateAncestorsOrSiblingsAffectedByHas(
+      changed_element.AncestorsOrAncestorSiblingsAffectedByHas()
+          ? changed_element.parentElement()
+          : nullptr,
+      changed_element.SiblingsAffectedByHas()
+          ? ElementTraversal::PreviousSibling(changed_element)
+          : nullptr);
+}
+
+void StyleEngine::InvalidateAncestorsOrSiblingsAffectedByHas(
+    Element* parent,
+    Element* previous_sibling) {
+  InvalidateAncestorsOrSiblingsAffectedByHasInternal(
+      parent, previous_sibling, false /* for_pseudo_change */);
 }
 
 void StyleEngine::ClassChangedForElement(
@@ -998,11 +1058,11 @@
   const RuleFeatureSet& features = GetRuleFeatureSet();
 
   if (RuntimeEnabledFeatures::CSSPseudoHasEnabled() &&
-      element.AncestorsAffectedByHas()) {
+      PossiblyAffectingHasState(element)) {
     unsigned changed_size = changed_classes.size();
     for (unsigned i = 0; i < changed_size; ++i) {
       if (features.NeedsHasInvalidationForClass(changed_classes[i])) {
-        InvalidateAncestorsAffectedByHas(element.parentElement());
+        InvalidateAncestorsOrSiblingsAffectedByHas(element);
         break;
       }
     }
@@ -1035,7 +1095,7 @@
   bool needs_schedule_invalidation = !IsSubtreeAndSiblingsStyleDirty(element);
   bool possibly_affecting_has_state =
       RuntimeEnabledFeatures::CSSPseudoHasEnabled() &&
-      element.AncestorsAffectedByHas();
+      PossiblyAffectingHasState(element);
   if (!needs_schedule_invalidation && !possibly_affecting_has_state)
     return;
 
@@ -1094,7 +1154,7 @@
   }
 
   if (affecting_has_state)
-    InvalidateAncestorsAffectedByHas(element.parentElement());
+    InvalidateAncestorsOrSiblingsAffectedByHas(element);
 }
 
 namespace {
@@ -1124,9 +1184,9 @@
   const RuleFeatureSet& features = GetRuleFeatureSet();
 
   if (RuntimeEnabledFeatures::CSSPseudoHasEnabled() &&
-      element.AncestorsAffectedByHas()) {
+      PossiblyAffectingHasState(element)) {
     if (features.NeedsHasInvalidationForAttribute(attribute_name))
-      InvalidateAncestorsAffectedByHas(element.parentElement());
+      InvalidateAncestorsOrSiblingsAffectedByHas(element);
   }
 
   if (IsSubtreeAndSiblingsStyleDirty(element))
@@ -1155,10 +1215,10 @@
   const RuleFeatureSet& features = GetRuleFeatureSet();
 
   if (RuntimeEnabledFeatures::CSSPseudoHasEnabled() &&
-      element.AncestorsAffectedByHas()) {
+      PossiblyAffectingHasState(element)) {
     if ((!old_id.IsEmpty() && features.NeedsHasInvalidationForId(old_id)) ||
         (!new_id.IsEmpty() && features.NeedsHasInvalidationForId(new_id))) {
-      InvalidateAncestorsAffectedByHas(element.parentElement());
+      InvalidateAncestorsOrSiblingsAffectedByHas(element);
     }
   }
 
@@ -1188,9 +1248,9 @@
   const RuleFeatureSet& features = GetRuleFeatureSet();
 
   if (invalidate_ancestors && RuntimeEnabledFeatures::CSSPseudoHasEnabled() &&
-      element.AncestorsAffectedByHas()) {
+      PossiblyAffectingHasState(element)) {
     if (features.NeedsHasInvalidationForPseudoClass(pseudo_type))
-      InvalidateAncestorsAffectedByHasForPseudoChange(element.parentElement());
+      InvalidateAncestorsOrSiblingsAffectedByHasForPseudoChange(element);
   }
 
   if (!invalidate_descendants_or_siblings ||
@@ -1401,7 +1461,9 @@
                                                          *document_);
 }
 
-void StyleEngine::ElementInsertedOrRemoved(Element* parent, Element& element) {
+void StyleEngine::ElementInsertedOrRemoved(Element* parent,
+                                           Node* node_before_change,
+                                           Element& element) {
   if (!RuntimeEnabledFeatures::CSSPseudoHasEnabled() || !parent)
     return;
 
@@ -1410,13 +1472,17 @@
 
   const RuleFeatureSet& features = GetRuleFeatureSet();
 
-  if (features.NeedsHasInvalidationForElement(element))
-    InvalidateAncestorsAffectedByHas(parent);
-  else if (features.NeedsHasInvalidationForPseudoStateChange())
-    InvalidateAncestorsAffectedByHasForPseudoChange(parent);
+  if (features.NeedsHasInvalidationForElement(element)) {
+    InvalidateAncestorsOrSiblingsAffectedByHas(
+        parent, SelfOrPreviousSibling(node_before_change));
+  } else if (features.NeedsHasInvalidationForPseudoStateChange()) {
+    InvalidateAncestorsOrSiblingsAffectedByHasForPseudoChange(
+        parent, SelfOrPreviousSibling(node_before_change));
+  }
 }
 
 void StyleEngine::SubtreeInsertedOrRemoved(Element* parent,
+                                           Node* node_before_change,
                                            Element& subtree_root) {
   if (!RuntimeEnabledFeatures::CSSPseudoHasEnabled() || !parent)
     return;
@@ -1428,13 +1494,16 @@
   for (Element& element :
        ElementTraversal::InclusiveDescendantsOf(subtree_root)) {
     if (features.NeedsHasInvalidationForElement(element)) {
-      InvalidateAncestorsAffectedByHas(parent);
+      InvalidateAncestorsOrSiblingsAffectedByHas(
+          parent, SelfOrPreviousSibling(node_before_change));
       return;
     }
   }
 
-  if (features.NeedsHasInvalidationForPseudoStateChange())
-    InvalidateAncestorsAffectedByHasForPseudoChange(parent);
+  if (features.NeedsHasInvalidationForPseudoStateChange()) {
+    InvalidateAncestorsOrSiblingsAffectedByHasForPseudoChange(
+        parent, SelfOrPreviousSibling(node_before_change));
+  }
 }
 
 void StyleEngine::InvalidateStyle() {
diff --git a/third_party/blink/renderer/core/css/style_engine.h b/third_party/blink/renderer/core/css/style_engine.h
index 98c678e..bdf0eed 100644
--- a/third_party/blink/renderer/core/css/style_engine.h
+++ b/third_party/blink/renderer/core/css/style_engine.h
@@ -390,8 +390,12 @@
                                         InvalidationScope =
                                             kInvalidateCurrentScope);
   void ScheduleCustomElementInvalidations(HashSet<AtomicString> tag_names);
-  void ElementInsertedOrRemoved(Element* parent, Element& element);
-  void SubtreeInsertedOrRemoved(Element* parent, Element& subtree_root);
+  void ElementInsertedOrRemoved(Element* parent,
+                                Node* node_before_change,
+                                Element& element);
+  void SubtreeInsertedOrRemoved(Element* parent,
+                                Node* node_before_change,
+                                Element& subtree_root);
 
   void NodeWillBeRemoved(Node&);
   void ChildrenRemoved(ContainerNode& parent);
@@ -695,11 +699,20 @@
   // container.
   void RebuildFieldSetContainer(HTMLFieldSetElement& fieldset);
 
-  // Walk-up to invalidate elements affected by :has() state change
-  void InvalidateAncestorsAffectedByHasInternal(Element*,
-                                                bool for_pseudo_change);
-  void InvalidateAncestorsAffectedByHas(Element*);
-  void InvalidateAncestorsAffectedByHasForPseudoChange(Element*);
+  // Invalidate ancestors or siblings affected by :has() state change
+  void InvalidateAncestorsOrSiblingsAffectedByHasInternal(
+      Element* parent,
+      Element* previous_sibling,
+      bool for_pseudo_change);
+  inline void InvalidateAncestorsOrSiblingsAffectedByHas(
+      Element& changed_element);
+  void InvalidateAncestorsOrSiblingsAffectedByHas(Element* parent,
+                                                  Element* previous_sibling);
+  inline void InvalidateAncestorsOrSiblingsAffectedByHasForPseudoChange(
+      Element& changed_element);
+  void InvalidateAncestorsOrSiblingsAffectedByHasForPseudoChange(
+      Element* parent,
+      Element* previous_sibling);
 
   Member<Document> document_;
 
diff --git a/third_party/blink/renderer/core/dom/element.cc b/third_party/blink/renderer/core/dom/element.cc
index d3fd7d0..2e3c712 100644
--- a/third_party/blink/renderer/core/dom/element.cc
+++ b/third_party/blink/renderer/core/dom/element.cc
@@ -4200,8 +4200,8 @@
             : kSiblingElementInserted,
         changed_element, change.sibling_before_change,
         change.sibling_after_change);
-    GetDocument().GetStyleEngine().SubtreeInsertedOrRemoved(this,
-                                                            *changed_element);
+    GetDocument().GetStyleEngine().SubtreeInsertedOrRemoved(
+        this, change.sibling_before_change, *changed_element);
   }
 
   if (ShadowRoot* shadow_root = GetShadowRoot())
@@ -4214,7 +4214,7 @@
   CheckForSiblingStyleChanges(kFinishedParsingChildren, nullptr, lastChild(),
                               nullptr);
   GetDocument().GetStyleEngine().ElementInsertedOrRemoved(parentElement(),
-                                                          *this);
+                                                          lastChild(), *this);
 }
 
 AttrNodeList* Element::GetAttrNodeList() {
@@ -4846,14 +4846,24 @@
   EnsureElementRareData().SetAffectedByNonSubjectHas();
 }
 
-bool Element::AncestorsAffectedByHas() const {
+bool Element::AncestorsOrAncestorSiblingsAffectedByHas() const {
   if (HasRareData())
-    return GetElementRareData()->AncestorsAffectedByHas();
+    return GetElementRareData()->AncestorsOrAncestorSiblingsAffectedByHas();
   return false;
 }
 
-void Element::SetAncestorsAffectedByHas() {
-  EnsureElementRareData().SetAncestorsAffectedByHas();
+void Element::SetAncestorsOrAncestorSiblingsAffectedByHas() {
+  EnsureElementRareData().SetAncestorsOrAncestorSiblingsAffectedByHas();
+}
+
+bool Element::SiblingsAffectedByHas() const {
+  if (HasRareData())
+    return GetElementRareData()->SiblingsAffectedByHas();
+  return false;
+}
+
+void Element::SetSiblingsAffectedByHas() {
+  EnsureElementRareData().SetSiblingsAffectedByHas();
 }
 
 bool Element::UpdateForceLegacyLayout(const ComputedStyle& new_style,
diff --git a/third_party/blink/renderer/core/dom/element.h b/third_party/blink/renderer/core/dom/element.h
index 306b771..69c5026 100644
--- a/third_party/blink/renderer/core/dom/element.h
+++ b/third_party/blink/renderer/core/dom/element.h
@@ -1042,8 +1042,10 @@
 
   bool AffectedByNonSubjectHas() const;
   void SetAffectedByNonSubjectHas();
-  bool AncestorsAffectedByHas() const;
-  void SetAncestorsAffectedByHas();
+  bool AncestorsOrAncestorSiblingsAffectedByHas() const;
+  void SetAncestorsOrAncestorSiblingsAffectedByHas();
+  bool SiblingsAffectedByHas() const;
+  void SetSiblingsAffectedByHas();
 
   void SaveIntrinsicSize(ResizeObserverSize* size);
   const ResizeObserverSize* LastIntrinsicSize() const;
diff --git a/third_party/blink/renderer/core/dom/element_rare_data.cc b/third_party/blink/renderer/core/dom/element_rare_data.cc
index d59074f10..d577ad79 100644
--- a/third_party/blink/renderer/core/dom/element_rare_data.cc
+++ b/third_party/blink/renderer/core/dom/element_rare_data.cc
@@ -57,7 +57,8 @@
       has_undo_stack_(false),
       scrollbar_pseudo_element_styles_depend_on_font_metrics_(false),
       affected_by_non_subject_has_(false),
-      ancestors_affected_by_has_(false) {
+      ancestors_or_ancestor_siblings_affected_by_has_(false),
+      siblings_affected_by_has_(false) {
   // When The ElementSuperRareData flag is disabled, then always initialize
   // ElementSuperRareData immediately in order to measure the memory usage
   // improvements.
diff --git a/third_party/blink/renderer/core/dom/element_rare_data.h b/third_party/blink/renderer/core/dom/element_rare_data.h
index a06eb4f..ccb3be12 100644
--- a/third_party/blink/renderer/core/dom/element_rare_data.h
+++ b/third_party/blink/renderer/core/dom/element_rare_data.h
@@ -346,8 +346,14 @@
   }
   bool AffectedByNonSubjectHas() const { return affected_by_non_subject_has_; }
   void SetAffectedByNonSubjectHas() { affected_by_non_subject_has_ = true; }
-  bool AncestorsAffectedByHas() const { return ancestors_affected_by_has_; }
-  void SetAncestorsAffectedByHas() { ancestors_affected_by_has_ = true; }
+  bool AncestorsOrAncestorSiblingsAffectedByHas() const {
+    return ancestors_or_ancestor_siblings_affected_by_has_;
+  }
+  void SetAncestorsOrAncestorSiblingsAffectedByHas() {
+    ancestors_or_ancestor_siblings_affected_by_has_ = true;
+  }
+  bool SiblingsAffectedByHas() const { return siblings_affected_by_has_; }
+  void SetSiblingsAffectedByHas() { siblings_affected_by_has_ = true; }
 
   AccessibleNode* GetAccessibleNode() const {
     if (super_rare_data_)
@@ -468,26 +474,36 @@
   unsigned scrollbar_pseudo_element_styles_depend_on_font_metrics_ : 1;
 
   // Flags for :has() invalidation.
-  // - affected_by_non_subject_has_ : Indicates that this element may match a
-  //                                  non-subject :has() selector, which means
-  //                                  we need to schedule descendant and sibling
-  //                                  invalidation sets on this element when
-  //                                  the :has() state changes.
-  // - ancestors_affected_by_has_ : Indicates that this element possibly matches
-  //                                any of the :has() subselectors, so we need
-  //                                to walk ancestors to find the elements
-  //                                affected by subject or non-subject :has()
-  //                                state change. Please refer the comments in
-  //                                SelectorChecker::CheckPseudoHas() for more
-  //                                details.
   //
-  // Example 1) Subject :has()
+  // - affected_by_non_subject_has_ :
+  //     Indicates that this element may match a non-subject :has() selector,
+  //     which means we need to schedule descendant and sibling invalidation
+  //     sets on this element when the :has() state changes.
+  //
+  // - ancestors_or_ancestor_siblings_affected_by_has_
+  //     Indicates that this element possibly matches any of the :has()
+  //     subselectors, and we need to walk ancestors or siblings of ancestors to
+  //     find the elements affected by subject or non-subject :has() state
+  //     change. Please refer the comments in SelectorChecker::CheckPseudoHas()
+  //     for more details.
+  //
+  // - siblings_affected_by_has_
+  //     Indicates that this element possibly matches any of the :has()
+  //     subselectors, and we need to walk siblings to find the elements
+  //     affected by subject or non-subject :has() state change.
+  //
+  // Please refer the comments in WalkUpAndSetDynamicRestyleFlagForHas() in
+  // selector_checker.cc and HasArgumentSubtreeIterator::operator++() for more
+  // details.
+  //
+  //
+  // Example 1) Subject :has() (has only descendant relationship)
   //  <style> .a:has(.b) {...} </style>
   //  <div>
   //    <div class=a>  <!-- AffectedBySubjectHas (computed style extra flag) -->
-  //      <div>           <!-- AncestorsAffectedByHas -->
-  //        <div></div>   <!-- AncestorsAffectedByHas -->
-  //        <div></div>   <!-- AncestorsAffectedByHas -->
+  //      <div>           <!-- AncestorsOrAncestorSiblingsAffectedByHas -->
+  //        <div></div>   <!-- AncestorsOrAncestorSiblingsAffectedByHas -->
+  //        <div></div>   <!-- AncestorsOrAncestorSiblingsAffectedByHas -->
   //      </div>
   //    </div>
   //  </div>
@@ -496,16 +512,42 @@
   // Example 2) Non-subject :has()
   //  <style> .a:has(.b) .c {...} </style>
   //  <div>
-  //    <div class=a>             <!-- AffectedByNonSubjectHas -->
-  //      <div>                   <!-- AncestorsAffectedByHas -->
-  //        <div></div>           <!-- AncestorsAffectedByHas -->
-  //        <div class=c></div>   <!-- AncestorsAffectedByHas -->
+  //    <div class=a>          <!-- AffectedByNonSubjectHas -->
+  //      <div>                <!-- AncestorsOrAncestorSiblingsAffectedByHas -->
+  //        <div></div>        <!-- AncestorsOrAncestorSiblingsAffectedByHas -->
+  //        <div class=c></div><!-- AncestorsOrAncestorSiblingsAffectedByHas -->
   //      </div>
   //    </div>
   //  </div>
   //
+  //
+  // Example 3) Subject :has() (has only sibling relationship)
+  //  <style> .a:has(~ .b) {...} </style>
+  //  <div>
+  //    <div></div>
+  //    <div class=a>  <!-- AffectedBySubjectHas (computed style extra flag) -->
+  //      <div></div>
+  //    </div>
+  //    <div></div>    <!-- SiblingsAffectedByHas -->
+  //    <div></div>    <!-- SiblingsAffectedByHas -->
+  //  </div>
+  //
+  //
+  // Example 4) Subject :has() (has both sibling and descendant relationship)
+  //  <style> .a:has(~ .b .c) {...} </style>
+  //  <div>
+  //    <div></div>
+  //    <div class=a>  <!-- AffectedBySubjectHas (computed style extra flag) -->
+  //    </div>
+  //    <div>          <!-- SiblingsAffectedByHas -->
+  //      <div></div>  <!-- AncestorsOrAncestorSiblingsAffectedByHas -->
+  //      <div></div>  <!-- AncestorsOrAncestorSiblingsAffectedByHas -->
+  //    </div>
+  //  </div>
+  //
   unsigned affected_by_non_subject_has_ : 1;
-  unsigned ancestors_affected_by_has_ : 1;
+  unsigned ancestors_or_ancestor_siblings_affected_by_has_ : 1;
+  unsigned siblings_affected_by_has_ : 1;
 };
 
 inline LayoutSize DefaultMinimumSizeForResizing() {
diff --git a/third_party/blink/renderer/modules/credentialmanager/credentials_container.cc b/third_party/blink/renderer/modules/credentialmanager/credentials_container.cc
index 2aa6f0f..3d7c0b2 100644
--- a/third_party/blink/renderer/modules/credentialmanager/credentials_container.cc
+++ b/third_party/blink/renderer/modules/credentialmanager/credentials_container.cc
@@ -1269,6 +1269,10 @@
 
   auto* credential_manager =
       CredentialManagerProxy::From(script_state)->CredentialManager();
+
+  DCHECK_NE(mojom::blink::CredentialType::EMPTY,
+            CredentialInfo::From(credential)->type);
+
   credential_manager->Store(
       CredentialInfo::From(credential),
       WTF::Bind(&OnStoreComplete,
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_canvas_context.cc b/third_party/blink/renderer/modules/webgpu/gpu_canvas_context.cc
index fc710f7b..b9f4a1e6 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_canvas_context.cc
+++ b/third_party/blink/renderer/modules/webgpu/gpu_canvas_context.cc
@@ -176,12 +176,8 @@
 #if !BUILDFLAG(IS_MAC)
     case WGPUTextureFormat_RGBA8Unorm:
 #endif
-      break;
     case WGPUTextureFormat_RGBA16Float:
-      configured_device_->InjectError(
-          WGPUErrorType_Validation,
-          "rgba16float swap chain is not yet supported");
-      return;
+      break;
     default:
       configured_device_->InjectError(WGPUErrorType_Validation,
                                       "unsupported swap chain format");
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_swap_chain.cc b/third_party/blink/renderer/modules/webgpu/gpu_swap_chain.cc
index b92cf11b..4776766 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_swap_chain.cc
+++ b/third_party/blink/renderer/modules/webgpu/gpu_swap_chain.cc
@@ -96,8 +96,11 @@
   const auto& sk_image_sync_token =
       transferable_resource.mailbox_holder.sync_token;
 
-  const SkImageInfo sk_image_info = SkImageInfo::MakeN32Premul(
-      transferable_resource.size.width(), transferable_resource.size.height());
+  auto sk_color_type = viz::ResourceFormatToClosestSkColorType(
+      /*gpu_compositing=*/true, transferable_resource.format);
+
+  const SkImageInfo sk_image_info = SkImageInfo::Make(
+      size_.width(), size_.height(), sk_color_type, kPremul_SkAlphaType);
 
   return AcceleratedStaticBitmapImage::CreateFromCanvasMailbox(
       sk_image_mailbox, sk_image_sync_token, /* shared_image_texture_id = */ 0,
diff --git a/third_party/blink/web_tests/FlagExpectations/disable-layout-ng b/third_party/blink/web_tests/FlagExpectations/disable-layout-ng
index 76084463..1ecce887 100644
--- a/third_party/blink/web_tests/FlagExpectations/disable-layout-ng
+++ b/third_party/blink/web_tests/FlagExpectations/disable-layout-ng
@@ -13,6 +13,10 @@
 # Tests that fail in legacy but pass in NG
 
 # ====== New tests from wpt-importer added here ======
+crbug.com/626703 external/wpt/geolocation-API/enabled-by-feature-policy-attribute-redirect-on-load.https.sub.html [ Timeout ]
+crbug.com/626703 external/wpt/geolocation-API/enabled-by-feature-policy-attribute.https.sub.html [ Timeout ]
+crbug.com/626703 external/wpt/geolocation-API/enabled-by-feature-policy.https.sub.html [ Timeout ]
+crbug.com/626703 external/wpt/geolocation-API/enabled-on-self-origin-by-feature-policy.https.sub.html [ Timeout ]
 crbug.com/626703 external/wpt/css/css-grid/abspos/positioned-grid-items-025.html [ Failure ]
 crbug.com/626703 external/wpt/wasm/webapi/esm-integration/exported-names.tentative.html [ Timeout ]
 crbug.com/626703 external/wpt/wasm/webapi/esm-integration/js-wasm-cycle.tentative.html [ Timeout ]
diff --git a/third_party/blink/web_tests/FlagExpectations/disable-site-isolation-trials b/third_party/blink/web_tests/FlagExpectations/disable-site-isolation-trials
index a440ab6..4da46ed 100644
--- a/third_party/blink/web_tests/FlagExpectations/disable-site-isolation-trials
+++ b/third_party/blink/web_tests/FlagExpectations/disable-site-isolation-trials
@@ -44,6 +44,10 @@
 crbug.com/1209223 external/wpt/html/browsers/browsing-the-web/navigating-across-documents/javascript-url-security-check-same-origin-domain.sub.html [ Failure ]
 
 # ====== New tests from wpt-importer added here ======
+crbug.com/626703 external/wpt/geolocation-API/enabled-by-feature-policy-attribute-redirect-on-load.https.sub.html [ Timeout ]
+crbug.com/626703 external/wpt/geolocation-API/enabled-by-feature-policy-attribute.https.sub.html [ Timeout ]
+crbug.com/626703 external/wpt/geolocation-API/enabled-by-feature-policy.https.sub.html [ Timeout ]
+crbug.com/626703 external/wpt/geolocation-API/enabled-on-self-origin-by-feature-policy.https.sub.html [ Timeout ]
 crbug.com/626703 external/wpt/wasm/webapi/esm-integration/exported-names.tentative.html [ Timeout ]
 crbug.com/626703 external/wpt/wasm/webapi/esm-integration/js-wasm-cycle.tentative.html [ Timeout ]
 crbug.com/626703 external/wpt/wasm/webapi/esm-integration/wasm-import-wasm-export.tentative.html [ Timeout ]
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index b9dd08a..357d2dc 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -2794,39 +2794,39 @@
 crbug.com/1040611 external/wpt/uievents/order-of-events/mouse-events/wheel-scrolling.html [ Failure Timeout ]
 
 # crbug.com/1298321: "element click intercepted error" when <body style="zoom:2">.
-crbug.com/1298321 fast/forms/calendar-picker/calendar-picker-appearance-zoom200.html [ Pass Failure Timeout ]
-crbug.com/1298321 fast/forms/calendar-picker/date-picker-appearance-zoom150.html [ Pass Failure Timeout ]
-crbug.com/1298321 fast/forms/select-popup/popup-menu-appearance-zoom.html [ Pass Failure Timeout ]
-crbug.com/1298321 fast/forms/suggestion-picker/date-suggestion-picker-appearance-zoom200.html [ Pass Failure Timeout ]
-crbug.com/1298321 virtual/scalefactor200withzoom/fast/hidpi/static/popup-menu-appearance.html [ Pass Failure Timeout ]
-crbug.com/1298321 virtual/scalefactor200withzoom/fast/hidpi/static/calendar-picker-appearance.html [ Pass Failure Timeout ]
-crbug.com/1298321 virtual/scalefactor200withzoom/fast/hidpi/static/data-suggestion-picker-appearance.html [ Pass Failure Timeout ]
-crbug.com/1298321 virtual/scalefactor200/fast/hidpi/static/calendar-picker-appearance.html [ Pass Failure Timeout ]
-crbug.com/1298321 virtual/scalefactor200/fast/hidpi/static/data-suggestion-picker-appearance.html [ Pass Failure Timeout ]
-crbug.com/1298321 virtual/scalefactor200/fast/hidpi/static/popup-menu-appearance.html [ Pass Failure Timeout ]
-crbug.com/1298321 virtual/scalefactor150/fast/hidpi/static/calendar-picker-appearance.html [ Pass Failure Timeout ]
-crbug.com/1298321 virtual/scalefactor150/fast/hidpi/static/data-suggestion-picker-appearance.html [ Pass Failure Timeout ]
-crbug.com/1298321 virtual/scalefactor150/fast/hidpi/static/popup-menu-appearance.html [ Pass Failure Timeout ]
+crbug.com/1298321 fast/forms/calendar-picker/calendar-picker-appearance-zoom200.html [ Failure Pass Timeout ]
+crbug.com/1298321 fast/forms/calendar-picker/date-picker-appearance-zoom150.html [ Failure Pass Timeout ]
+crbug.com/1298321 fast/forms/select-popup/popup-menu-appearance-zoom.html [ Failure Pass Timeout ]
+crbug.com/1298321 fast/forms/suggestion-picker/date-suggestion-picker-appearance-zoom200.html [ Failure Pass Timeout ]
+crbug.com/1298321 virtual/scalefactor200withzoom/fast/hidpi/static/popup-menu-appearance.html [ Failure Pass Timeout ]
+crbug.com/1298321 virtual/scalefactor200withzoom/fast/hidpi/static/calendar-picker-appearance.html [ Failure Pass Timeout ]
+crbug.com/1298321 virtual/scalefactor200withzoom/fast/hidpi/static/data-suggestion-picker-appearance.html [ Failure Pass Timeout ]
+crbug.com/1298321 virtual/scalefactor200/fast/hidpi/static/calendar-picker-appearance.html [ Failure Pass Timeout ]
+crbug.com/1298321 virtual/scalefactor200/fast/hidpi/static/data-suggestion-picker-appearance.html [ Failure Pass Timeout ]
+crbug.com/1298321 virtual/scalefactor200/fast/hidpi/static/popup-menu-appearance.html [ Failure Pass Timeout ]
+crbug.com/1298321 virtual/scalefactor150/fast/hidpi/static/calendar-picker-appearance.html [ Failure Pass Timeout ]
+crbug.com/1298321 virtual/scalefactor150/fast/hidpi/static/data-suggestion-picker-appearance.html [ Failure Pass Timeout ]
+crbug.com/1298321 virtual/scalefactor150/fast/hidpi/static/popup-menu-appearance.html [ Failure Pass Timeout ]
 
 # crbug.com/1299212: These need more work after the revamp of picker-common.js
-crbug.com/1299212 fast/forms/select-popup/popup-menu-appearance-coarse.html [ Pass Failure Timeout ]
-crbug.com/1299212 fast/forms/select-popup/popup-menu-position.html [ Pass Failure Timeout ]
-crbug.com/1299212 http/tests/webfont/popup-menu-load-webfont-after-open.html [ Pass Failure Timeout ]
-crbug.com/1299212 crbug.com/1047176 fast/forms/suggestion-picker/date-suggestion-picker-mouse-operations.html [ Pass Failure Timeout ]
-crbug.com/1299212 crbug.com/1047176 fast/forms/suggestion-picker/week-suggestion-picker-mouse-operations.html [ Pass Failure Timeout ]
-crbug.com/1299212 crbug.com/1047176 fast/forms/suggestion-picker/month-suggestion-picker-mouse-operations.html [ Pass Failure Timeout ]
-crbug.com/1299212 crbug.com/1047176 fast/forms/suggestion-picker/time-suggestion-picker-mouse-operations.html [ Pass Failure Timeout ]
-crbug.com/1299212 crbug.com/1047176 fast/forms/suggestion-picker/datetimelocal-suggestion-picker-mouse-operations.html [ Pass Failure Timeout ]
-crbug.com/1299212 fast/forms/color-scheme/select/select-appearance-after-closing-popup.html [ Pass Failure Timeout Skip ]
-crbug.com/1299212 fast/forms/color-scheme/select/select-popup-appearance-basic.html [ Pass Failure Timeout Skip ]
-crbug.com/1299212 fast/forms/select/menulist-popup-type-ahead-style-change.html [ Pass Failure Timeout ]
+crbug.com/1299212 fast/forms/select-popup/popup-menu-appearance-coarse.html [ Failure Pass Timeout ]
+crbug.com/1299212 fast/forms/select-popup/popup-menu-position.html [ Failure Pass Timeout ]
+crbug.com/1299212 http/tests/webfont/popup-menu-load-webfont-after-open.html [ Failure Pass Timeout ]
+crbug.com/1299212 crbug.com/1047176 fast/forms/suggestion-picker/date-suggestion-picker-mouse-operations.html [ Failure Pass Timeout ]
+crbug.com/1299212 crbug.com/1047176 fast/forms/suggestion-picker/week-suggestion-picker-mouse-operations.html [ Failure Pass Timeout ]
+crbug.com/1299212 crbug.com/1047176 fast/forms/suggestion-picker/month-suggestion-picker-mouse-operations.html [ Failure Pass Timeout ]
+crbug.com/1299212 crbug.com/1047176 fast/forms/suggestion-picker/time-suggestion-picker-mouse-operations.html [ Failure Pass Timeout ]
+crbug.com/1299212 crbug.com/1047176 fast/forms/suggestion-picker/datetimelocal-suggestion-picker-mouse-operations.html [ Failure Pass Timeout ]
+crbug.com/1299212 fast/forms/color-scheme/select/select-appearance-after-closing-popup.html [ Failure Pass Skip Timeout ]
+crbug.com/1299212 fast/forms/color-scheme/select/select-popup-appearance-basic.html [ Failure Pass Skip Timeout ]
+crbug.com/1299212 fast/forms/select/menulist-popup-type-ahead-style-change.html [ Failure Pass Timeout ]
 # This might be just flaky on Windows:
-crbug.com/1299212 virtual/scroll-unification/fast/forms/suggestion-picker/* [ Pass Failure Timeout ]
+crbug.com/1299212 virtual/scroll-unification/fast/forms/suggestion-picker/* [ Failure Pass Timeout ]
 # At least scrollbars, if not window sizes, are flaky on Win7:
-crbug.com/1299212 [ Win7 ] fast/forms/select-popup/popup-menu-appearance-tall.html [ Pass Failure Timeout ]
-crbug.com/1299212 [ Win7 ] fast/forms/suggestion-picker/* [ Pass Failure Timeout ]
-crbug.com/1299212 [ Win7 ] virtual/scroll-unification/fast/forms/suggestion-picker/time-suggestion-picker-appearance-rtl.html [ Pass Failure Timeout ]
-crbug.com/1299212 [ Linux ] fast/forms/select/menulist-popup-mutation-crash.html [ Failure Timeout Pass ]
+crbug.com/1299212 [ Win7 ] fast/forms/select-popup/popup-menu-appearance-tall.html [ Failure Pass Timeout ]
+crbug.com/1299212 [ Win7 ] fast/forms/suggestion-picker/* [ Failure Pass Timeout ]
+crbug.com/1299212 [ Win7 ] virtual/scroll-unification/fast/forms/suggestion-picker/time-suggestion-picker-appearance-rtl.html [ Failure Pass Timeout ]
+crbug.com/1299212 [ Linux ] fast/forms/select/menulist-popup-mutation-crash.html [ Failure Pass Timeout ]
 
 # isInputPending requires threaded compositing and layerized iframes
 crbug.com/910421 external/wpt/is-input-pending/* [ Skip ]
@@ -3319,6 +3319,16 @@
 crbug.com/626703 virtual/prerender/external/wpt/speculation-rules/prerender/workers.html [ Failure ]
 
 # ====== New tests from wpt-importer added here ======
+crbug.com/626703 external/wpt/geolocation-API/enabled-by-feature-policy-attribute-redirect-on-load.https.sub.html [ Timeout ]
+crbug.com/626703 external/wpt/geolocation-API/enabled-by-feature-policy-attribute.https.sub.html [ Timeout ]
+crbug.com/626703 external/wpt/geolocation-API/enabled-by-feature-policy.https.sub.html [ Timeout ]
+crbug.com/626703 external/wpt/geolocation-API/enabled-on-self-origin-by-feature-policy.https.sub.html [ Timeout ]
+crbug.com/626703 [ Mac10.15 ] virtual/off-main-thread-css-paint/external/wpt/css/css-paint-api/registered-property-interpolation-005.https.html [ Failure ]
+crbug.com/626703 external/wpt/html/semantics/interactive-elements/the-dialog-element/modal-dialog-in-replaced-renderer.html [ Failure ]
+crbug.com/626703 external/wpt/html/semantics/interactive-elements/the-dialog-element/modal-dialog-in-table-column.html [ Failure ]
+crbug.com/626703 virtual/dialogfocus-old-behavior/external/wpt/html/semantics/interactive-elements/the-dialog-element/modal-dialog-in-replaced-renderer.html [ Failure ]
+crbug.com/626703 virtual/dialogfocus-old-behavior/external/wpt/html/semantics/interactive-elements/the-dialog-element/modal-dialog-in-table-column.html [ Failure ]
+crbug.com/626703 [ Mac10.15 ] virtual/off-main-thread-css-paint/external/wpt/css/css-paint-api/parse-input-arguments-021.https.html [ Failure ]
 crbug.com/626703 external/wpt/wasm/webapi/esm-integration/exported-names.tentative.html [ Timeout ]
 crbug.com/626703 external/wpt/wasm/webapi/esm-integration/js-wasm-cycle.tentative.html [ Timeout ]
 crbug.com/626703 external/wpt/wasm/webapi/esm-integration/wasm-import-wasm-export.tentative.html [ Timeout ]
@@ -7583,11 +7593,11 @@
 crbug.com/1173382 [ Win ] virtual/stable/http/tests/navigation/replacestate-base-legal.html [ Failure Pass ]
 
 # Sheriff 2022-02-22
-crbug.com/1299858 [ Win ] http/tests/fetch/serviceworker-proxied/thorough/nocors-base-https-other-https.html [ Skip Timeout Pass ]
-crbug.com/1299858 [ Win ] http/tests/fetch/serviceworker/thorough/redirect-nocors-base-https-other-https.html [ Skip Timeout Pass ]
-crbug.com/1299858 [ Win ] http/tests/fetch/window/thorough/cors-base-https-other-https.html [ Skip Timeout Pass ]
-crbug.com/1299858 [ Win ] http/tests/fetch/workers/thorough/cookie-nocors-base-https-other-https.html [ Skip Timeout Pass ]
+crbug.com/1299858 [ Win ] http/tests/fetch/serviceworker-proxied/thorough/nocors-base-https-other-https.html [ Pass Skip Timeout ]
+crbug.com/1299858 [ Win ] http/tests/fetch/serviceworker/thorough/redirect-nocors-base-https-other-https.html [ Pass Skip Timeout ]
+crbug.com/1299858 [ Win ] http/tests/fetch/window/thorough/cors-base-https-other-https.html [ Pass Skip Timeout ]
+crbug.com/1299858 [ Win ] http/tests/fetch/workers/thorough/cookie-nocors-base-https-other-https.html [ Pass Skip Timeout ]
 crbug.com/1299903 [ Win7 ] virtual/prerender/external/wpt/speculation-rules/prerender/restriction-window-move.html [ Failure Pass ]
-crbug.com/1299946 [ Mac ] external/wpt/css/css-sizing/min-content-negative-margin-crash.html [ Timeout Pass ]
-crbug.com/1299948 [ Mac ] external/wpt/css/css-tables/crashtests/textarea-intrinsic-size-crash.html [ Timeout Pass ]
-crbug.com/1299972 [ Linux ] screen_orientation/screenorientation-unsupported-no-crash.html [ Failure Timeout Pass ]
+crbug.com/1299946 [ Mac ] external/wpt/css/css-sizing/min-content-negative-margin-crash.html [ Pass Timeout ]
+crbug.com/1299948 [ Mac ] external/wpt/css/css-tables/crashtests/textarea-intrinsic-size-crash.html [ Pass Timeout ]
+crbug.com/1299972 [ Linux ] screen_orientation/screenorientation-unsupported-no-crash.html [ Failure Pass Timeout ]
diff --git a/third_party/blink/web_tests/WebGPUExpectations b/third_party/blink/web_tests/WebGPUExpectations
index 2e63dfd..ba20e98 100644
--- a/third_party/blink/web_tests/WebGPUExpectations
+++ b/third_party/blink/web_tests/WebGPUExpectations
@@ -83,7 +83,12 @@
 # These tests aren't working on CQ, unclear whether the test or harness (or Chrome) is broken.
 # Mac: mostly works
 # Linux: Crashes
-crbug.com/1083478 [ Linux ] wpt_internal/webgpu/web_platform/reftests/* [ Skip ]
+crbug.com/1083478 [ Linux ] wpt_internal/webgpu/web_platform/reftests/canvas_complex_bgra8unorm_copy.https.html [ Skip ]
+crbug.com/1083478 [ Linux ] wpt_internal/webgpu/web_platform/reftests/canvas_complex_bgra8unorm_draw.https.html [ Skip ]
+crbug.com/1083478 [ Linux ] wpt_internal/webgpu/web_platform/reftests/canvas_composite_alpha_bgra8unorm_opaque.https.html [ Skip ]
+crbug.com/1083478 [ Linux ] wpt_internal/webgpu/web_platform/reftests/canvas_composite_alpha_bgra8unorm_premultiplied.https.html [ Skip ]
+crbug.com/1083478 [ Linux ] wpt_internal/webgpu/web_platform/reftests/canvas_size_different_with_back_buffer_size.https.html [ Skip ]
+
 # Mac/Win: Shifted by about half a pixel
 crbug.com/1083478 [ Win ] wpt_internal/webgpu/web_platform/reftests/canvas_composite_alpha_bgra8unorm_opaque.https.html [ Failure ]
 crbug.com/1083478 [ Mac ] wpt_internal/webgpu/web_platform/reftests/canvas_composite_alpha_bgra8unorm_opaque.https.html [ Failure ]
diff --git a/third_party/blink/web_tests/external/Version b/third_party/blink/web_tests/external/Version
index a450c168..063d3ef 100644
--- a/third_party/blink/web_tests/external/Version
+++ b/third_party/blink/web_tests/external/Version
@@ -1 +1 @@
-Version: 8fa6e1db9bcdd4f8a65055eceab4c6b9f132b1a2
+Version: 14e674b84241f534a4be0de230ae307429681d5e
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 0d1e90f2cc..cf692e1 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
@@ -1354,6 +1354,13 @@
         {}
        ]
       ],
+      "relayout-nested-with-oof.html": [
+       "bdc7147337fed88b29bfb9af36b645756695d6f6",
+       [
+        null,
+        {}
+       ]
+      ],
       "specified-height-with-just-spanner-and-oof.html": [
        "3c4d51b0f433f4cbdb9e76f4301162924b6918f1",
        [
@@ -218944,7 +218951,7 @@
         ]
        ],
        "modal-dialog-in-replaced-renderer.html": [
-        "09490519fd7de1ffd0d53939fcbda6275cbb5d6f",
+        "75727b42f0429e59ffa1940eef768060e706af8c",
         [
          null,
          [
@@ -218957,7 +218964,7 @@
         ]
        ],
        "modal-dialog-in-table-column.html": [
-        "89ee6977716bc961010b519224ff86253ce27b9c",
+        "3d72826b963feaceae0af44ff07ffca67fa70879",
         [
          null,
          [
@@ -284219,6 +284226,10 @@
       "59652e2e7ae0056a6cc4be7f004b6d0151fb9d44",
       []
      ],
+     "feature-policy-geolocation.html": [
+      "81943845447e6c6b7962b3961996e45c12e04cd4",
+      []
+     ],
      "feature-policy-idle-detection-worker.html": [
       "1ef5298abf1b2062f98c8aba5675decd36c5313d",
       []
@@ -287432,6 +287443,18 @@
      "5c49de21a3b53dfe928d30a36400469d8238bb1d",
      []
     ],
+    "disabled-by-feature-policy.https.sub.html.headers": [
+     "7e75481ea6d71080aaef8b43e774f5da9c9741e5",
+     []
+    ],
+    "enabled-by-feature-policy.https.sub.html.headers": [
+     "40e9bc16ff98867d0d048fe3a48237a8189ee317",
+     []
+    ],
+    "enabled-on-self-origin-by-feature-policy.https.sub.html.headers": [
+     "b83264eee76491342a6328ca2ae82b7fb777cc37",
+     []
+    ],
     "getCurrentPosition_IDL.https-expected.txt": [
      "8781bccd59718f81b29112d2efbfe6407f697e52",
      []
@@ -299538,11 +299561,11 @@
         []
        ],
        "modal-dialog-in-replaced-renderer-ref.html": [
-        "65886154df3dc3c462a87021693f5ca0fe78894a",
+        "c837503cafbcdb56c995e9b93f49cabde14293b0",
         []
        ],
        "modal-dialog-in-table-column-ref.html": [
-        "38b628c3092ab223010db32373e786787e4355b0",
+        "0310d1ba2437a59235a980f5fce2d95f2775b8fc",
         []
        ],
        "modal-dialog-in-visibility-hidden-expected.txt": [
@@ -304358,10 +304381,6 @@
      "b4a346ae68cb4726c82ff7bbdc1617164bcea293",
      []
     ],
-    "input-events-get-target-ranges-forwarddelete.tentative.html.ini": [
-     "aa9f10bfec564d3548013091daf87089e8bcb39a",
-     []
-    ],
     "input-events-get-target-ranges-joining-dl-element-and-another-list.tentative.html.ini": [
      "3682b5332d210f74721284474c626db0712515fc",
      []
@@ -423205,7 +423224,7 @@
    },
    "geolocation-API": {
     "PositionOptions.https.html": [
-     "828224d50de5ad2c21a3e0b343dbfa6affb9e602",
+     "6b36d66d7358cccd6fe5143a1504a17db4170af4",
      [
       null,
       {
@@ -423213,13 +423232,54 @@
       }
      ]
     ],
-    "clearWatch_TypeError.html": [
-     "14b861bfc0aa3cbab46624ffde54a7825d7d76ab",
+    "clearWatch_TypeError.https.html": [
+     "37ebeca885ff5df0447b18fdbf58cc4222c33ac9",
      [
       null,
       {}
      ]
     ],
+    "disabled-by-feature-policy.https.sub.html": [
+     "26fa7218a6d32a36ee4f99439580bcd19ddd7639",
+     [
+      null,
+      {
+       "testdriver": true
+      }
+     ]
+    ],
+    "enabled-by-feature-policy-attribute-redirect-on-load.https.sub.html": [
+     "30de411cb7a342d105e2c6e0ae21c4d64c8f05dd",
+     [
+      null,
+      {}
+     ]
+    ],
+    "enabled-by-feature-policy-attribute.https.sub.html": [
+     "49a6d777d66fa09df8d9ec38543f1097b95b6ffc",
+     [
+      null,
+      {}
+     ]
+    ],
+    "enabled-by-feature-policy.https.sub.html": [
+     "955ed10632e84f52f231a4a0beeedb1ae5a7fc35",
+     [
+      null,
+      {
+       "testdriver": true
+      }
+     ]
+    ],
+    "enabled-on-self-origin-by-feature-policy.https.sub.html": [
+     "12ff86fa4e554dc02524877ffb534e4c9cc11c5c",
+     [
+      null,
+      {
+       "testdriver": true
+      }
+     ]
+    ],
     "getCurrentPosition_IDL.https.html": [
      "c7ee10e7eb883f8eb7571065cf7925472d548c94",
      [
@@ -423229,8 +423289,8 @@
       }
      ]
     ],
-    "getCurrentPosition_TypeError.html": [
-     "6726e8ab6c7d892c4e93cf212c94f6622f89c116",
+    "getCurrentPosition_TypeError.https.html": [
+     "953efb259bf931bce9c45e126a9938b0f0cbdafd",
      [
       null,
       {}
@@ -423244,10 +423304,12 @@
      ]
     ],
     "getCurrentPosition_permission_deny.https.html": [
-     "50129608ac7e6e8d64e6dd966eda1172b5929a70",
+     "297f9a2629737907b66743dfa8fd43c3b97f3020",
      [
       null,
-      {}
+      {
+       "testdriver": true
+      }
      ]
     ],
     "idlharness.https.window.js": [
@@ -423269,7 +423331,7 @@
      ]
     ],
     "non-fully-active.https.html": [
-     "d75c787cabf861aed96664eab1805a76be98516b",
+     "13853c0ef860a6d099172c91db960b720760662a",
      [
       null,
       {
@@ -423284,18 +423346,20 @@
       {}
      ]
     ],
-    "watchPosition_TypeError.html": [
-     "dd8287f5713ea7ce0a13a5096c45d38f640553a0",
+    "watchPosition_TypeError.https.html": [
+     "64f0616949f3c00ed0d24f7d34f173c69e8e65d6",
      [
       null,
       {}
      ]
     ],
     "watchPosition_permission_deny.https.html": [
-     "1e2a3c4bf4c4be0b050cffef2b2ba208e1bedc0c",
+     "d47f2b5594a55ca9852c874b9fa6b2880690dbf3",
      [
       null,
-      {}
+      {
+       "testdriver": true
+      }
      ]
     ]
    },
@@ -470155,7 +470219,7 @@
      ]
     ],
     "inert-iframe-hittest.tentative.html": [
-     "63e347725ee360a0f0e0f240b673afcd2a31ea08",
+     "bcc542d35edff1bf93fdb7bcc6185fceb0d130d6",
      [
       null,
       {
@@ -470164,7 +470228,7 @@
      ]
     ],
     "inert-iframe-tabbing.tentative.html": [
-     "e346993cdcc2c66d4dcad27696ab1587e88bbb6d",
+     "a0146b74cc2abdb9590c322ec1a98d54c1adbf62",
      [
       null,
       {
diff --git a/third_party/blink/web_tests/external/wpt/css/css-contain/container-queries/custom-layout-container-001.https.html b/third_party/blink/web_tests/external/wpt/css/css-contain/container-queries/custom-layout-container-001.https.html
new file mode 100644
index 0000000..ef8c5cc
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-contain/container-queries/custom-layout-container-001.https.html
@@ -0,0 +1,66 @@
+<!doctype html>
+<html class=reftest-wait>
+<title>CSS Container Queries Test: Size queries on CSS Layout API containers</title>
+<link rel="help" href="https://drafts.csswg.org/css-contain-3/#size-container">
+<link rel="help" href="https://drafts.css-houdini.org/css-layout-api/">
+<link rel="match" href="/css/reference/ref-filled-green-100px-square-only.html">
+<script src="/common/reftest-wait.js"></script>
+<script src="/common/worklet-reftest.js"></script>
+<style>
+  #test1 {
+    width: 400px;
+    height: 100px;
+  }
+  #outer {
+    display: inline; /* Shouldn't pass without layout api support */
+    display: layout(half);
+    height: 100px;
+    container-type: inline-size;
+  }
+  @container size(width = 400px) {
+    #inner {
+      display: inline; /* Shouldn't pass without layout api support */
+      display: layout(half);
+      height: 100px;
+      container-type: inline-size;
+    }
+  }
+  @container size(width = 200px) {
+    #green {
+      background-color: green;
+      height: 100px;
+    }
+  }
+</style>
+<p>Test passes if there is a filled green square.</p>
+<div id="test1">
+  <div id="outer">
+    <div id="inner">
+      <div id="green"></div>
+    </div>
+  </div>
+</div>
+
+<script id="code" type="text/worklet">
+  registerLayout("half", class {
+    async intrinsicSizes() {}
+    async layout(children, edges, constraints, styleMap) {
+      const childInlineSize = constraints.fixedInlineSize / 2;
+      const childFragments = await Promise.all(children.map((child) => {
+        return child.layoutNextFragment({fixedInlineSize: childInlineSize});
+      }));
+
+      for (let childFragment of childFragments) {
+        childFragment.inlineOffset = 0;
+        childFragment.blockOffset = 0;
+      }
+      const autoBlockSize = 100;
+      return {autoBlockSize, childFragments};
+    }
+  });
+</script>
+
+<script>
+  importWorkletAndTerminateTestAfterAsyncPaint(CSS.layoutWorklet, document.getElementById("code").textContent);
+</script>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/selectors/invalidation/has-in-adjacent-position-expected.txt b/third_party/blink/web_tests/external/wpt/css/selectors/invalidation/has-in-adjacent-position-expected.txt
deleted file mode 100644
index 4eca5d7..0000000
--- a/third_party/blink/web_tests/external/wpt/css/selectors/invalidation/has-in-adjacent-position-expected.txt
+++ /dev/null
@@ -1,75 +0,0 @@
-This is a testharness.js-based test.
-Found 71 tests; 57 PASS, 14 FAIL, 0 TIMEOUT, 0 NOTRUN.
-PASS Initial color
-PASS add .test to previous_sibling
-PASS remove .test from previous_sibling
-PASS add .test to previous_sibling_child
-PASS remove .test from previous_sibling_child
-PASS add .test to previous_sibling_descendant
-PASS remove .test from previous_sibling_descendant
-FAIL add .test to subject assert_equals: expected "rgb(0, 0, 255)" but got "rgb(128, 128, 128)"
-PASS remove .test from subject
-FAIL add .test to next_sibling assert_equals: expected "rgb(255, 255, 0)" but got "rgb(128, 128, 128)"
-PASS remove .test from next_sibling
-FAIL add .test to next_sibling_child assert_equals: expected "rgb(128, 0, 128)" but got "rgb(128, 128, 128)"
-PASS remove .test from next_sibling_child
-FAIL add .test to next_sibling_descendant assert_equals: expected "rgb(128, 0, 128)" but got "rgb(128, 128, 128)"
-PASS remove .test from next_sibling_descendant
-PASS insert element div.test before previous_sibling
-PASS remove element div.test before previous_sibling
-PASS insert element div.test before previous_sibling_child
-PASS remove element div.test before previous_sibling_child
-PASS insert element div.test before previous_sibling_descendant
-PASS remove element div.test before previous_sibling_descendant
-PASS insert element div.test before subject
-PASS remove element div.test before subject
-PASS insert element div.test before next_sibling
-PASS remove element div.test before next_sibling
-FAIL insert element div.test before next_sibling_child assert_equals: expected "rgb(128, 0, 128)" but got "rgb(128, 128, 128)"
-PASS remove element div.test before next_sibling_child
-FAIL insert element div.test before next_sibling_descendant assert_equals: expected "rgb(128, 0, 128)" but got "rgb(128, 128, 128)"
-PASS remove element div.test before next_sibling_descendant
-PASS insert element div.test after previous_sibling
-PASS remove element div.test after previous_sibling
-PASS insert element div.test after previous_sibling_child
-PASS remove element div.test after previous_sibling_child
-PASS insert element div.test after previous_sibling_descendant
-PASS remove element div.test after previous_sibling_descendant
-PASS insert element div.test after subject
-PASS remove element div.test after subject
-FAIL insert element div.test after next_sibling assert_equals: expected "rgb(255, 255, 0)" but got "rgb(128, 128, 128)"
-PASS remove element div.test after next_sibling
-FAIL insert element div.test after next_sibling_child assert_equals: expected "rgb(128, 0, 128)" but got "rgb(128, 128, 128)"
-PASS remove element div.test after next_sibling_child
-FAIL insert element div.test after next_sibling_descendant assert_equals: expected "rgb(128, 0, 128)" but got "rgb(128, 128, 128)"
-PASS remove element div.test after next_sibling_descendant
-PASS insert tree div>div.test before previous_sibling
-PASS remove tree div>div.test before previous_sibling
-PASS insert tree div>div.test before previous_sibling_child
-PASS remove tree div>div.test before previous_sibling_child
-PASS insert tree div>div.test before previous_sibling_descendant
-PASS remove tree div>div.test before previous_sibling_descendant
-PASS insert tree div>div.test before subject
-PASS remove tree div>div.test before subject
-PASS insert tree div>div.test before next_sibling
-PASS remove tree div>div.test before next_sibling
-FAIL insert tree div>div.test before next_sibling_child assert_equals: expected "rgb(128, 0, 128)" but got "rgb(128, 128, 128)"
-PASS remove tree div>div.test before next_sibling_child
-FAIL insert tree div>div.test before next_sibling_descendant assert_equals: expected "rgb(128, 0, 128)" but got "rgb(128, 128, 128)"
-PASS remove tree div>div.test before next_sibling_descendant
-PASS insert tree div>div.test after previous_sibling
-PASS remove tree div>div.test after previous_sibling
-PASS insert tree div>div.test after previous_sibling_child
-PASS remove tree div>div.test after previous_sibling_child
-PASS insert tree div>div.test after previous_sibling_descendant
-PASS remove tree div>div.test after previous_sibling_descendant
-PASS insert tree div>div.test after subject
-PASS remove tree div>div.test after subject
-FAIL insert tree div>div.test after next_sibling assert_equals: expected "rgb(128, 0, 128)" but got "rgb(128, 128, 128)"
-PASS remove tree div>div.test after next_sibling
-FAIL insert tree div>div.test after next_sibling_child assert_equals: expected "rgb(128, 0, 128)" but got "rgb(128, 128, 128)"
-PASS remove tree div>div.test after next_sibling_child
-FAIL insert tree div>div.test after next_sibling_descendant assert_equals: expected "rgb(128, 0, 128)" but got "rgb(128, 128, 128)"
-PASS remove tree div>div.test after next_sibling_descendant
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/css/selectors/invalidation/has-in-ancestor-position-expected.txt b/third_party/blink/web_tests/external/wpt/css/selectors/invalidation/has-in-ancestor-position-expected.txt
deleted file mode 100644
index 010e5e18..0000000
--- a/third_party/blink/web_tests/external/wpt/css/selectors/invalidation/has-in-ancestor-position-expected.txt
+++ /dev/null
@@ -1,85 +0,0 @@
-This is a testharness.js-based test.
-Found 81 tests; 64 PASS, 17 FAIL, 0 TIMEOUT, 0 NOTRUN.
-PASS Initial color
-PASS add .test to subject_ancestor
-PASS remove .test from subject_ancestor
-PASS add .test to subject_parent
-PASS remove .test from subject_parent
-PASS add .test to subject
-PASS remove .test from subject
-PASS add .test to subject_child
-PASS remove .test from subject_child
-PASS add .test to subject_descendant
-PASS remove .test from subject_descendant
-FAIL add .test to next_sibling assert_equals: expected "rgb(0, 0, 255)" but got "rgb(128, 128, 128)"
-PASS remove .test from next_sibling
-FAIL add .test to next_sibling_child assert_equals: expected "rgb(255, 192, 203)" but got "rgb(128, 128, 128)"
-PASS remove .test from next_sibling_child
-FAIL add .test to next_sibling_descendant assert_equals: expected "rgb(255, 192, 203)" but got "rgb(128, 128, 128)"
-PASS remove .test from next_sibling_descendant
-PASS insert element div.test before subject_ancestor
-PASS remove element div.test before subject_ancestor
-PASS insert element div.test before subject_parent
-PASS remove element div.test before subject_parent
-PASS insert element div.test before subject
-PASS remove element div.test before subject
-PASS insert element div.test before subject_child
-PASS remove element div.test before subject_child
-PASS insert element div.test before subject_descendant
-PASS remove element div.test before subject_descendant
-FAIL insert element div.test before next_sibling assert_equals: expected "rgb(0, 0, 255)" but got "rgb(128, 128, 128)"
-PASS remove element div.test before next_sibling
-FAIL insert element div.test before next_sibling_child assert_equals: expected "rgb(255, 192, 203)" but got "rgb(128, 128, 128)"
-PASS remove element div.test before next_sibling_child
-FAIL insert element div.test before next_sibling_descendant assert_equals: expected "rgb(255, 192, 203)" but got "rgb(128, 128, 128)"
-PASS remove element div.test before next_sibling_descendant
-FAIL insert element div.test after subject_ancestor assert_equals: expected "rgb(0, 0, 255)" but got "rgb(128, 128, 128)"
-PASS remove element div.test after subject_ancestor
-PASS insert element div.test after subject_parent
-PASS remove element div.test after subject_parent
-PASS insert element div.test after subject
-PASS remove element div.test after subject
-PASS insert element div.test after subject_child
-PASS remove element div.test after subject_child
-PASS insert element div.test after subject_descendant
-PASS remove element div.test after subject_descendant
-FAIL insert element div.test after next_sibling assert_equals: expected "rgb(255, 255, 0)" but got "rgb(128, 128, 128)"
-PASS remove element div.test after next_sibling
-FAIL insert element div.test after next_sibling_child assert_equals: expected "rgb(255, 192, 203)" but got "rgb(128, 128, 128)"
-PASS remove element div.test after next_sibling_child
-FAIL insert element div.test after next_sibling_descendant assert_equals: expected "rgb(255, 192, 203)" but got "rgb(128, 128, 128)"
-PASS remove element div.test after next_sibling_descendant
-PASS insert tree div>div.test before subject_ancestor
-PASS remove tree div>div.test before subject_ancestor
-PASS insert tree div>div.test before subject_parent
-PASS remove tree div>div.test before subject_parent
-PASS insert tree div>div.test before subject
-PASS remove tree div>div.test before subject
-PASS insert tree div>div.test before subject_child
-PASS remove tree div>div.test before subject_child
-PASS insert tree div>div.test before subject_descendant
-PASS remove tree div>div.test before subject_descendant
-FAIL insert tree div>div.test before next_sibling assert_equals: expected "rgb(255, 192, 203)" but got "rgb(128, 128, 128)"
-PASS remove tree div>div.test before next_sibling
-FAIL insert tree div>div.test before next_sibling_child assert_equals: expected "rgb(255, 192, 203)" but got "rgb(128, 128, 128)"
-PASS remove tree div>div.test before next_sibling_child
-FAIL insert tree div>div.test before next_sibling_descendant assert_equals: expected "rgb(255, 192, 203)" but got "rgb(128, 128, 128)"
-PASS remove tree div>div.test before next_sibling_descendant
-FAIL insert tree div>div.test after subject_ancestor assert_equals: expected "rgb(255, 192, 203)" but got "rgb(128, 128, 128)"
-PASS remove tree div>div.test after subject_ancestor
-PASS insert tree div>div.test after subject_parent
-PASS remove tree div>div.test after subject_parent
-PASS insert tree div>div.test after subject
-PASS remove tree div>div.test after subject
-PASS insert tree div>div.test after subject_child
-PASS remove tree div>div.test after subject_child
-PASS insert tree div>div.test after subject_descendant
-PASS remove tree div>div.test after subject_descendant
-FAIL insert tree div>div.test after next_sibling assert_equals: expected "rgb(128, 0, 128)" but got "rgb(128, 128, 128)"
-PASS remove tree div>div.test after next_sibling
-FAIL insert tree div>div.test after next_sibling_child assert_equals: expected "rgb(255, 192, 203)" but got "rgb(128, 128, 128)"
-PASS remove tree div>div.test after next_sibling_child
-FAIL insert tree div>div.test after next_sibling_descendant assert_equals: expected "rgb(255, 192, 203)" but got "rgb(128, 128, 128)"
-PASS remove tree div>div.test after next_sibling_descendant
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/css/selectors/invalidation/has-in-parent-position-expected.txt b/third_party/blink/web_tests/external/wpt/css/selectors/invalidation/has-in-parent-position-expected.txt
deleted file mode 100644
index 5cec677..0000000
--- a/third_party/blink/web_tests/external/wpt/css/selectors/invalidation/has-in-parent-position-expected.txt
+++ /dev/null
@@ -1,55 +0,0 @@
-This is a testharness.js-based test.
-Found 51 tests; 49 PASS, 2 FAIL, 0 TIMEOUT, 0 NOTRUN.
-PASS Initial color
-PASS add .test to subject_ancestor
-PASS remove .test from subject_ancestor
-PASS add .test to subject_parent
-PASS remove .test from subject_parent
-PASS add .test to subject
-PASS remove .test from subject
-PASS add .test to subject_child
-PASS remove .test from subject_child
-PASS add .test to subject_descendant
-PASS remove .test from subject_descendant
-PASS insert element div.test before subject_ancestor
-PASS remove element div.test before subject_ancestor
-PASS insert element div.test before subject_parent
-PASS remove element div.test before subject_parent
-PASS insert element div.test before subject
-PASS remove element div.test before subject
-PASS insert element div.test before subject_child
-PASS remove element div.test before subject_child
-PASS insert element div.test before subject_descendant
-PASS remove element div.test before subject_descendant
-PASS insert element div.test after subject_ancestor
-PASS remove element div.test after subject_ancestor
-FAIL insert element div.test after subject_parent assert_equals: expected "rgb(0, 0, 255)" but got "rgb(128, 128, 128)"
-PASS remove element div.test after subject_parent
-PASS insert element div.test after subject
-PASS remove element div.test after subject
-PASS insert element div.test after subject_child
-PASS remove element div.test after subject_child
-PASS insert element div.test after subject_descendant
-PASS remove element div.test after subject_descendant
-PASS insert tree div>div.test before subject_ancestor
-PASS remove tree div>div.test before subject_ancestor
-PASS insert tree div>div.test before subject_parent
-PASS remove tree div>div.test before subject_parent
-PASS insert tree div>div.test before subject
-PASS remove tree div>div.test before subject
-PASS insert tree div>div.test before subject_child
-PASS remove tree div>div.test before subject_child
-PASS insert tree div>div.test before subject_descendant
-PASS remove tree div>div.test before subject_descendant
-PASS insert tree div>div.test after subject_ancestor
-PASS remove tree div>div.test after subject_ancestor
-FAIL insert tree div>div.test after subject_parent assert_equals: expected "rgb(255, 192, 203)" but got "rgb(128, 128, 128)"
-PASS remove tree div>div.test after subject_parent
-PASS insert tree div>div.test after subject
-PASS remove tree div>div.test after subject
-PASS insert tree div>div.test after subject_child
-PASS remove tree div>div.test after subject_child
-PASS insert tree div>div.test after subject_descendant
-PASS remove tree div>div.test after subject_descendant
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/css/selectors/invalidation/has-in-sibling-position-expected.txt b/third_party/blink/web_tests/external/wpt/css/selectors/invalidation/has-in-sibling-position-expected.txt
deleted file mode 100644
index 4eca5d7..0000000
--- a/third_party/blink/web_tests/external/wpt/css/selectors/invalidation/has-in-sibling-position-expected.txt
+++ /dev/null
@@ -1,75 +0,0 @@
-This is a testharness.js-based test.
-Found 71 tests; 57 PASS, 14 FAIL, 0 TIMEOUT, 0 NOTRUN.
-PASS Initial color
-PASS add .test to previous_sibling
-PASS remove .test from previous_sibling
-PASS add .test to previous_sibling_child
-PASS remove .test from previous_sibling_child
-PASS add .test to previous_sibling_descendant
-PASS remove .test from previous_sibling_descendant
-FAIL add .test to subject assert_equals: expected "rgb(0, 0, 255)" but got "rgb(128, 128, 128)"
-PASS remove .test from subject
-FAIL add .test to next_sibling assert_equals: expected "rgb(255, 255, 0)" but got "rgb(128, 128, 128)"
-PASS remove .test from next_sibling
-FAIL add .test to next_sibling_child assert_equals: expected "rgb(128, 0, 128)" but got "rgb(128, 128, 128)"
-PASS remove .test from next_sibling_child
-FAIL add .test to next_sibling_descendant assert_equals: expected "rgb(128, 0, 128)" but got "rgb(128, 128, 128)"
-PASS remove .test from next_sibling_descendant
-PASS insert element div.test before previous_sibling
-PASS remove element div.test before previous_sibling
-PASS insert element div.test before previous_sibling_child
-PASS remove element div.test before previous_sibling_child
-PASS insert element div.test before previous_sibling_descendant
-PASS remove element div.test before previous_sibling_descendant
-PASS insert element div.test before subject
-PASS remove element div.test before subject
-PASS insert element div.test before next_sibling
-PASS remove element div.test before next_sibling
-FAIL insert element div.test before next_sibling_child assert_equals: expected "rgb(128, 0, 128)" but got "rgb(128, 128, 128)"
-PASS remove element div.test before next_sibling_child
-FAIL insert element div.test before next_sibling_descendant assert_equals: expected "rgb(128, 0, 128)" but got "rgb(128, 128, 128)"
-PASS remove element div.test before next_sibling_descendant
-PASS insert element div.test after previous_sibling
-PASS remove element div.test after previous_sibling
-PASS insert element div.test after previous_sibling_child
-PASS remove element div.test after previous_sibling_child
-PASS insert element div.test after previous_sibling_descendant
-PASS remove element div.test after previous_sibling_descendant
-PASS insert element div.test after subject
-PASS remove element div.test after subject
-FAIL insert element div.test after next_sibling assert_equals: expected "rgb(255, 255, 0)" but got "rgb(128, 128, 128)"
-PASS remove element div.test after next_sibling
-FAIL insert element div.test after next_sibling_child assert_equals: expected "rgb(128, 0, 128)" but got "rgb(128, 128, 128)"
-PASS remove element div.test after next_sibling_child
-FAIL insert element div.test after next_sibling_descendant assert_equals: expected "rgb(128, 0, 128)" but got "rgb(128, 128, 128)"
-PASS remove element div.test after next_sibling_descendant
-PASS insert tree div>div.test before previous_sibling
-PASS remove tree div>div.test before previous_sibling
-PASS insert tree div>div.test before previous_sibling_child
-PASS remove tree div>div.test before previous_sibling_child
-PASS insert tree div>div.test before previous_sibling_descendant
-PASS remove tree div>div.test before previous_sibling_descendant
-PASS insert tree div>div.test before subject
-PASS remove tree div>div.test before subject
-PASS insert tree div>div.test before next_sibling
-PASS remove tree div>div.test before next_sibling
-FAIL insert tree div>div.test before next_sibling_child assert_equals: expected "rgb(128, 0, 128)" but got "rgb(128, 128, 128)"
-PASS remove tree div>div.test before next_sibling_child
-FAIL insert tree div>div.test before next_sibling_descendant assert_equals: expected "rgb(128, 0, 128)" but got "rgb(128, 128, 128)"
-PASS remove tree div>div.test before next_sibling_descendant
-PASS insert tree div>div.test after previous_sibling
-PASS remove tree div>div.test after previous_sibling
-PASS insert tree div>div.test after previous_sibling_child
-PASS remove tree div>div.test after previous_sibling_child
-PASS insert tree div>div.test after previous_sibling_descendant
-PASS remove tree div>div.test after previous_sibling_descendant
-PASS insert tree div>div.test after subject
-PASS remove tree div>div.test after subject
-FAIL insert tree div>div.test after next_sibling assert_equals: expected "rgb(128, 0, 128)" but got "rgb(128, 128, 128)"
-PASS remove tree div>div.test after next_sibling
-FAIL insert tree div>div.test after next_sibling_child assert_equals: expected "rgb(128, 0, 128)" but got "rgb(128, 128, 128)"
-PASS remove tree div>div.test after next_sibling_child
-FAIL insert tree div>div.test after next_sibling_descendant assert_equals: expected "rgb(128, 0, 128)" but got "rgb(128, 128, 128)"
-PASS remove tree div>div.test after next_sibling_descendant
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/css/selectors/invalidation/has-sibling-expected.txt b/third_party/blink/web_tests/external/wpt/css/selectors/invalidation/has-sibling-expected.txt
deleted file mode 100644
index 01f64f5..0000000
--- a/third_party/blink/web_tests/external/wpt/css/selectors/invalidation/has-sibling-expected.txt
+++ /dev/null
@@ -1,75 +0,0 @@
-This is a testharness.js-based test.
-Found 71 tests; 36 PASS, 35 FAIL, 0 TIMEOUT, 0 NOTRUN.
-PASS initial_color
-FAIL add .test to first_sibling assert_equals: expected "rgb(0, 128, 0)" but got "rgb(128, 128, 128)"
-PASS remove .test from first_sibling
-FAIL add .test to second_sibling assert_equals: expected "rgb(255, 0, 0)" but got "rgb(128, 128, 128)"
-PASS remove .test from second_sibling
-FAIL add .test to third_sibling assert_equals: expected "rgb(255, 0, 0)" but got "rgb(128, 128, 128)"
-PASS remove .test from third_sibling
-FAIL add .test to first_sibling_child assert_equals: expected "rgb(255, 192, 203)" but got "rgb(128, 128, 128)"
-PASS remove .test from first_sibling_child
-FAIL add .test to first_sibling_descendant assert_equals: expected "rgb(255, 255, 0)" but got "rgb(128, 128, 128)"
-PASS remove .test from first_sibling_descendant
-FAIL add .test to third_sibling_child assert_equals: expected "rgb(128, 0, 128)" but got "rgb(128, 128, 128)"
-PASS remove .test from third_sibling_child
-FAIL add .test to third_sibling_descendant assert_equals: expected "rgb(0, 0, 255)" but got "rgb(128, 128, 128)"
-PASS remove .test from third_sibling_descendant
-FAIL insert element div.test before first_sibling assert_equals: expected "rgb(0, 128, 0)" but got "rgb(128, 128, 128)"
-PASS remove element div.test before first_sibling
-FAIL insert element div.test before second_sibling assert_equals: expected "rgb(255, 0, 0)" but got "rgb(128, 128, 128)"
-PASS remove element div.test before second_sibling
-FAIL insert element div.test before third_sibling assert_equals: expected "rgb(255, 0, 0)" but got "rgb(128, 128, 128)"
-PASS remove element div.test before third_sibling
-FAIL insert element div.test before first_sibling_child assert_equals: expected "rgb(255, 192, 203)" but got "rgb(128, 128, 128)"
-PASS remove element div.test before first_sibling_child
-FAIL insert element div.test before first_sibling_descendant assert_equals: expected "rgb(255, 255, 0)" but got "rgb(128, 128, 128)"
-PASS remove element div.test before first_sibling_descendant
-FAIL insert element div.test before third_sibling_child assert_equals: expected "rgb(128, 0, 128)" but got "rgb(128, 128, 128)"
-PASS remove element div.test before third_sibling_child
-FAIL insert element div.test before third_sibling_descendant assert_equals: expected "rgb(0, 0, 255)" but got "rgb(128, 128, 128)"
-PASS remove element div.test before third_sibling_descendant
-FAIL insert element div.test after first_sibling assert_equals: expected "rgb(255, 0, 0)" but got "rgb(128, 128, 128)"
-PASS remove element div.test after first_sibling
-FAIL insert element div.test after second_sibling assert_equals: expected "rgb(255, 0, 0)" but got "rgb(128, 128, 128)"
-PASS remove element div.test after second_sibling
-FAIL insert element div.test after third_sibling assert_equals: expected "rgb(255, 0, 0)" but got "rgb(128, 128, 128)"
-PASS remove element div.test after third_sibling
-FAIL insert element div.test after first_sibling_child assert_equals: expected "rgb(255, 192, 203)" but got "rgb(128, 128, 128)"
-PASS remove element div.test after first_sibling_child
-FAIL insert element div.test after first_sibling_descendant assert_equals: expected "rgb(255, 255, 0)" but got "rgb(128, 128, 128)"
-PASS remove element div.test after first_sibling_descendant
-FAIL insert element div.test after third_sibling_child assert_equals: expected "rgb(128, 0, 128)" but got "rgb(128, 128, 128)"
-PASS remove element div.test after third_sibling_child
-FAIL insert element div.test after third_sibling_descendant assert_equals: expected "rgb(0, 0, 255)" but got "rgb(128, 128, 128)"
-PASS remove element div.test after third_sibling_descendant
-FAIL insert tree div>div.test before first_sibling assert_equals: expected "rgb(255, 192, 203)" but got "rgb(128, 128, 128)"
-PASS remove tree div>div.test before first_sibling
-FAIL insert tree div>div.test before second_sibling assert_equals: expected "rgb(128, 0, 128)" but got "rgb(128, 128, 128)"
-PASS remove tree div>div.test before second_sibling
-FAIL insert tree div>div.test before third_sibling assert_equals: expected "rgb(128, 0, 128)" but got "rgb(128, 128, 128)"
-PASS remove tree div>div.test before third_sibling
-FAIL insert tree div>div.test before first_sibling_child assert_equals: expected "rgb(255, 255, 0)" but got "rgb(128, 128, 128)"
-PASS remove tree div>div.test before first_sibling_child
-FAIL insert tree div>div.test before first_sibling_descendant assert_equals: expected "rgb(255, 255, 0)" but got "rgb(128, 128, 128)"
-PASS remove tree div>div.test before first_sibling_descendant
-FAIL insert tree div>div.test before third_sibling_child assert_equals: expected "rgb(0, 0, 255)" but got "rgb(128, 128, 128)"
-PASS remove tree div>div.test before third_sibling_child
-FAIL insert tree div>div.test before third_sibling_descendant assert_equals: expected "rgb(0, 0, 255)" but got "rgb(128, 128, 128)"
-PASS remove tree div>div.test before third_sibling_descendant
-FAIL insert tree div>div.test after first_sibling assert_equals: expected "rgb(128, 0, 128)" but got "rgb(128, 128, 128)"
-PASS remove tree div>div.test after first_sibling
-FAIL insert tree div>div.test after second_sibling assert_equals: expected "rgb(128, 0, 128)" but got "rgb(128, 128, 128)"
-PASS remove tree div>div.test after second_sibling
-FAIL insert tree div>div.test after third_sibling assert_equals: expected "rgb(128, 0, 128)" but got "rgb(128, 128, 128)"
-PASS remove tree div>div.test after third_sibling
-FAIL insert tree div>div.test after first_sibling_child assert_equals: expected "rgb(255, 255, 0)" but got "rgb(128, 128, 128)"
-PASS remove tree div>div.test after first_sibling_child
-FAIL insert tree div>div.test after first_sibling_descendant assert_equals: expected "rgb(255, 255, 0)" but got "rgb(128, 128, 128)"
-PASS remove tree div>div.test after first_sibling_descendant
-FAIL insert tree div>div.test after third_sibling_child assert_equals: expected "rgb(0, 0, 255)" but got "rgb(128, 128, 128)"
-PASS remove tree div>div.test after third_sibling_child
-FAIL insert tree div>div.test after third_sibling_descendant assert_equals: expected "rgb(0, 0, 255)" but got "rgb(128, 128, 128)"
-PASS remove tree div>div.test after third_sibling_descendant
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/feature-policy/resources/feature-policy-geolocation.html b/third_party/blink/web_tests/external/wpt/feature-policy/resources/feature-policy-geolocation.html
new file mode 100644
index 0000000..8194384
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/feature-policy/resources/feature-policy-geolocation.html
@@ -0,0 +1,21 @@
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script>
+  "use strict";
+
+  Promise.resolve().then(async () => {
+    await test_driver.set_permission(
+      { name: "geolocation" },
+      "granted",
+      false
+    );
+    try {
+      await new Promise((resolve, reject) => {
+        navigator.geolocation.getCurrentPosition(resolve, reject);
+      });
+      window.parent.postMessage({ enabled: true }, "*");
+    } catch (e) {
+      window.parent.postMessage({ enabled: false }, "*");
+    }
+  });
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/geolocation-API/PositionOptions.https.html b/third_party/blink/web_tests/external/wpt/geolocation-API/PositionOptions.https.html
index 828224d..6b36d66d 100644
--- a/third_party/blink/web_tests/external/wpt/geolocation-API/PositionOptions.https.html
+++ b/third_party/blink/web_tests/external/wpt/geolocation-API/PositionOptions.https.html
@@ -1,101 +1,93 @@
-<!DOCTYPE HTML>
-<meta charset="utf-8">
+<!DOCTYPE html>
+<meta charset="utf-8" />
 <title>Geolocation Test: PositionOptions tests</title>
-<link rel="help" href="http://www.w3.org/TR/geolocation-API/#position_options_interface">
+<link
+  rel="help"
+  href="http://www.w3.org/TR/geolocation-API/#position_options_interface"
+/>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="/resources/testdriver.js"></script>
 <script src="/resources/testdriver-vendor.js"></script>
-<script src='support.js'></script>
 
 <script>
-// Rewrite http://dev.w3.org/geo/api/test-suite/t.html?00123
-test(function() {
-  try {
-    geo.getCurrentPosition(dummyFunction, null, {enableHighAccuracy: "boom"});
-    geo.getCurrentPosition(dummyFunction, null, {enableHighAccuracy: 321});
-    geo.getCurrentPosition(dummyFunction, null, {enableHighAccuracy: -Infinity});
-    geo.getCurrentPosition(dummyFunction, null, {enableHighAccuracy: {foo: 5}});
-  } catch(e) {
-    assert_unreached('An exception was thrown unexpectedly: ' + e.message);
-  }
-}, 'Call getCurrentPosition with wrong type for enableHighAccuracy. No exception expected.');
+  const resetPermission = () => {
+    return test_driver.set_permission({ name: "geolocation" }, "prompt");
+  };
+  const invalidValues = ["boom", 321, -Infinity, { foo: 5 }];
 
-// Rewrite http://dev.w3.org/geo/api/test-suite/t.html?00124
-test(function() {
-  try {
-    geo.watchPosition(dummyFunction, null, {enableHighAccuracy: "boom"});
-    geo.watchPosition(dummyFunction, null, {enableHighAccuracy: 321});
-    geo.watchPosition(dummyFunction, null, {enableHighAccuracy: -Infinity});
-    geo.watchPosition(dummyFunction, null, {enableHighAccuracy: {foo: 5}});
-  } catch(e) {
-    assert_unreached('An exception was thrown unexpectedly: ' + e.message);
-  }
-}, 'Call watchPosition with wrong type for enableHighAccuracy. No exception expected.');
+  promise_test(async (t) => {
+    t.add_cleanup(resetPermission);
+    await test_driver.set_permission({ name: "geolocation" }, "granted");
+    for (const enableHighAccuracy of invalidValues) {
+      navigator.geolocation.getCurrentPosition(() => {}, null, {
+        enableHighAccuracy,
+      });
+    }
+  }, "Call getCurrentPosition with wrong type for enableHighAccuracy. No exception expected.");
 
-// Rewrite http://dev.w3.org/geo/api/test-suite/t.html?00086, 00088, 00091 and 00092
-promise_test(async function() {
-  await test_driver.set_permission({name: 'geolocation'}, 'granted');
+  promise_test(async (t) => {
+    t.add_cleanup(resetPermission);
+    await test_driver.set_permission({ name: "geolocation" }, "granted");
+    for (const enableHighAccuracy of invalidValues) {
+      const id = navigator.geolocation.watchPosition(() => {}, null, {
+        enableHighAccuracy,
+      });
+      navigator.geolocation.clearWatch(id);
+    }
+  }, "Call watchPosition with wrong type for enableHighAccuracy. No exception expected.");
 
-  var t86 = async_test('Set timeout and maximumAge to 0, check that timeout error raised (getCurrentPosition)'),
-      t88 = async_test('Set timeout and maximumAge to 0, check that timeout error raised (watchPosition)'),
-      t91 = async_test('Check that a negative timeout value is equivalent to a 0 timeout value (getCurrentLocation)'),
-      t92 = async_test('Check that a negative timeout value is equivalent to a 0 timeout value (watchPosition)');
-
-  try {
-    geo.getCurrentPosition(
-        t86.unreached_func('A success callback was invoked unexpectedly'),
-        t86.step_func_done(function(err) {
-          assert_equals(err.code, err.TIMEOUT);
-        }),
-        {timeout: 0, maximumAge: 0}
-    );
-  } catch(e) {
-    t86.step(function() {
-      assert_unreached('An exception was thrown unexpectedly: ' + e.message);
+  promise_test(async (t) => {
+    t.add_cleanup(resetPermission);
+    await test_driver.set_permission({ name: "geolocation" }, "granted");
+    const error = await new Promise((resolve, reject) => {
+      navigator.geolocation.getCurrentPosition(reject, resolve, {
+        timeout: 0,
+        maxAge: 0,
+      });
     });
-  }
+    assert_equals(error.code, GeolocationPositionError.TIMEOUT);
+  }, "Set timeout and maximumAge to 0, check that timeout error raised (getCurrentPosition)");
 
-  try {
-    geo.watchPosition(
-        t88.unreached_func('A success callback was invoked unexpectedly'),
-        t88.step_func_done(function(err) {
-          assert_equals(err.code, err.TIMEOUT);
-        }),
-        {timeout: 0, maximumAge: 0}
-    );
-  } catch(e) {
-    t88.step(function() {
-      assert_unreached('An exception was thrown unexpectedly: ' + e.message);
+  promise_test(async (t) => {
+    t.add_cleanup(resetPermission);
+    await test_driver.set_permission({ name: "geolocation" }, "granted");
+    let watchId;
+    const error = await new Promise((resolve, reject) => {
+      watchId = navigator.geolocation.watchPosition(reject, resolve, {
+        timeout: 0,
+        maxAge: 0,
+      });
     });
-  }
+    assert_equals(error.code, GeolocationPositionError.TIMEOUT);
+    navigator.geolocation.clearWatch(watchId);
+  }, "Set timeout and maximumAge to 0, check that timeout error raised (watchPosition)");
 
-  try {
-    geo.getCurrentPosition(
-        t91.unreached_func('A success callback was invoked unexpectedly'),
-        t91.step_func_done(function(err) {
-          assert_equals(err.code, err.TIMEOUT);
-        }),
-        {timeout:-1, maximumAge: 0}
-    );
-  } catch(e) {
-    t91.step(function() {
-      assert_unreached('An exception was thrown unexpectedly: ' + e.message);
+  promise_test(async (t) => {
+    t.add_cleanup(resetPermission);
+    await test_driver.set_permission({ name: "geolocation" }, "granted");
+    let watchId;
+    const error = await new Promise((resolve, reject) => {
+      watchId = navigator.geolocation.getCurrentPosition(reject, resolve, {
+        timeout: -100,
+        maxAge: -100,
+      });
     });
-  }
+    assert_equals(error.code, GeolocationPositionError.TIMEOUT);
+    navigator.geolocation.clearWatch(watchId);
+  }, "Check that a negative timeout and maxAge values are clamped to 0 (getCurrentPosition)");
 
-  try {
-    geo.watchPosition(
-        t92.unreached_func('A success callback was invoked unexpectedly'),
-        t92.step_func_done(function(err) {
-          assert_equals(err.code, err.TIMEOUT);
-        }),
-        {timeout: -1, maximumAge: 0}
-    );
-  } catch(e) {
-    t92.step(function() {
-      assert_unreached('An exception was thrown unexpectedly: ' + e.message);
+  promise_test(async (t) => {
+    t.add_cleanup(resetPermission);
+    await test_driver.set_permission({ name: "geolocation" }, "granted");
+    let watchId;
+    const error = await new Promise((resolve, reject) => {
+      watchId = navigator.geolocation.watchPosition(reject, resolve, {
+        timeout: -100,
+        maxAge: -100,
+      });
     });
-  }
-}, 'PositionOptions tests');
+    assert_equals(error.code, GeolocationPositionError.TIMEOUT);
+    navigator.geolocation.clearWatch(watchId);
+  }, "Check that a negative timeout and maxAge values are clamped to 0 (watchPosition)");
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/geolocation-API/clearWatch_TypeError.html b/third_party/blink/web_tests/external/wpt/geolocation-API/clearWatch_TypeError.html
deleted file mode 100644
index 14b861b..0000000
--- a/third_party/blink/web_tests/external/wpt/geolocation-API/clearWatch_TypeError.html
+++ /dev/null
@@ -1,22 +0,0 @@
-<!DOCTYPE HTML>
-<meta charset='utf-8'>
-<title>Geolocation Test: clearWatch TypeError tests</title>
-<link rel='help' href='http://www.w3.org/TR/geolocation-API/#clear-watch'>
-<script src='/resources/testharness.js'></script>
-<script src='/resources/testharnessreport.js'></script>
-<script src='support.js'></script>
-
-<div id='log'></div>
-
-<script>
-// Rewrite http://dev.w3.org/geo/api/test-suite/t.html?00080
-test(function() {
-  try {
-    geo.clearWatch(-1);
-    geo.clearWatch(0);
-    geo.clearWatch(1);
-  } catch(e) {
-    assert_unreached('An exception was thrown unexpectedly: ' + e.message);
-  }
-}, 'Test that calling clearWatch with invalid watch IDs does not cause an exception');
-</script>
diff --git a/third_party/blink/web_tests/external/wpt/geolocation-API/clearWatch_TypeError.https.html b/third_party/blink/web_tests/external/wpt/geolocation-API/clearWatch_TypeError.https.html
new file mode 100644
index 0000000..37ebeca
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/geolocation-API/clearWatch_TypeError.https.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset="utf-8" />
+<title>Geolocation Test: clearWatch TypeError tests</title>
+<link rel="help" href="http://www.w3.org/TR/geolocation-API/#clear-watch" />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+  for (const invalidId of [NaN, -1, 0, 1, 2147483648, Infinity, -Infinity]) {
+    test(() => {
+      navigator.geolocation.clearWatch(invalidId);
+    }, `Test that calling clearWatch with invalid watch ID (${invalidId}) does not cause an exception`);
+  }
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/geolocation-API/disabled-by-feature-policy.https.sub.html b/third_party/blink/web_tests/external/wpt/geolocation-API/disabled-by-feature-policy.https.sub.html
new file mode 100644
index 0000000..26fa721
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/geolocation-API/disabled-by-feature-policy.https.sub.html
@@ -0,0 +1,71 @@
+<!DOCTYPE html>
+<meta charset="utf-8" />
+<body>
+  <script src="/resources/testharness.js"></script>
+  <script src="/resources/testharnessreport.js"></script>
+  <script src="/resources/testdriver.js"></script>
+  <script src="/resources/testdriver-vendor.js"></script>
+  <script src="/feature-policy/resources/featurepolicy.js"></script>
+  <script>
+    "use strict";
+
+    const same_origin_src =
+      "/feature-policy/resources/feature-policy-geolocation.html";
+    const cross_origin_src =
+      "https://{{domains[www]}}:{{ports[https][0]}}" + same_origin_src;
+
+    promise_test(async (t) => {
+      await test_driver.set_permission(
+        { name: "geolocation" },
+        "granted",
+        false
+      );
+
+      const posError = await new Promise((resolve, reject) => {
+        navigator.geolocation.getCurrentPosition(reject, resolve);
+      });
+
+      assert_true(
+        posError instanceof GeolocationPositionError,
+        "Expected instance of GeolocationPositionError"
+      );
+
+      assert_equals(
+        posError.code,
+        GeolocationPositionError.prototype.PERMISSION_DENIED,
+        "Expected PERMISSION_DENIED"
+      );
+
+      const watchError = await new Promise((resolve, reject) => {
+        navigator.geolocation.watchPosition(reject, resolve);
+      });
+      assert_true(
+        watchError instanceof GeolocationPositionError,
+        "Expected instance of GeolocationPositionError"
+      );
+      assert_equals(
+        watchError.code,
+        GeolocationPositionError.prototype.PERMISSION_DENIED,
+        "Expected PERMISSION_DENIED"
+      );
+    }, "Feature-Policy header geolocation : 'none' disallows the top-level document.");
+
+    async_test((t) => {
+      test_feature_availability(
+        "geolocation",
+        t,
+        same_origin_src,
+        expect_feature_unavailable_default
+      );
+    }, "Feature-Policy header geolocation : 'none' disallows same-origin iframes.");
+
+    async_test((t) => {
+      test_feature_availability(
+        "geolocation",
+        t,
+        cross_origin_src,
+        expect_feature_unavailable_default
+      );
+    }, "Feature-Policy header geolocation 'none' disallows cross-origin iframes.");
+  </script>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/geolocation-API/disabled-by-feature-policy.https.sub.html.headers b/third_party/blink/web_tests/external/wpt/geolocation-API/disabled-by-feature-policy.https.sub.html.headers
new file mode 100644
index 0000000..7e75481
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/geolocation-API/disabled-by-feature-policy.https.sub.html.headers
@@ -0,0 +1 @@
+Feature-Policy: geolocation 'none'
diff --git a/third_party/blink/web_tests/external/wpt/geolocation-API/enabled-by-feature-policy-attribute-redirect-on-load.https.sub.html b/third_party/blink/web_tests/external/wpt/geolocation-API/enabled-by-feature-policy-attribute-redirect-on-load.https.sub.html
new file mode 100644
index 0000000..30de411
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/geolocation-API/enabled-by-feature-policy-attribute-redirect-on-load.https.sub.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/feature-policy/resources/featurepolicy.js"></script>
+<script>
+  "use strict";
+
+  const relative_path = "/feature-policy/resources/feature-policy-geolocation.html";
+  const base_src = "/feature-policy/resources/redirect-on-load.html#";
+  const same_origin_src = base_src + relative_path;
+  const cross_origin_src =
+    base_src + "https://{{domains[www]}}:{{ports[https][0]}}" + relative_path;
+
+  async_test(t => {
+    test_feature_availability(
+      'geolocation',
+      t,
+      same_origin_src,
+      expect_feature_available_default,
+      "geolocation"
+    );
+  }, 'Feature-Policy allow="geolocation" allows same-origin relocation');
+
+  async_test(t => {
+    test_feature_availability(
+      'geolocation',
+      t,
+      cross_origin_src,
+      expect_feature_unavailable_default,
+      "geolocation"
+    );
+  }, 'Feature-Policy allow="geolocation" disallows cross-origin relocation');
+
+</script>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/geolocation-API/enabled-by-feature-policy-attribute.https.sub.html b/third_party/blink/web_tests/external/wpt/geolocation-API/enabled-by-feature-policy-attribute.https.sub.html
new file mode 100644
index 0000000..49a6d77
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/geolocation-API/enabled-by-feature-policy-attribute.https.sub.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/feature-policy/resources/featurepolicy.js"></script>
+<script>
+  "use strict";
+
+  const same_origin_src =
+    "/feature-policy/resources/feature-policy-geolocation.html";
+  const cross_origin_src =
+    "https://{{domains[www]}}:{{ports[https][0]}}" + same_origin_src;
+
+  async_test(t => {
+    test_feature_availability(
+      "geolocation",
+      t,
+      same_origin_src,
+      expect_feature_available_default,
+      "geolocation"
+    );
+  }, 'Feature policy "geolocation" can be enabled in same-origin iframe using allow="geolocation" attribute');
+
+  async_test(t => {
+    test_feature_availability(
+      "geolocation",
+      t,
+      cross_origin_src,
+      expect_feature_available_default,
+      "geolocation"
+    );
+  }, 'Feature policy "geolocation" can be enabled in cross-origin iframe using allow="geolocation" attribute');
+</script>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/geolocation-API/enabled-by-feature-policy.https.sub.html b/third_party/blink/web_tests/external/wpt/geolocation-API/enabled-by-feature-policy.https.sub.html
new file mode 100644
index 0000000..955ed10
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/geolocation-API/enabled-by-feature-policy.https.sub.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<meta charset="utf-8" />
+<body>
+  <script src="/resources/testharness.js"></script>
+  <script src="/resources/testharnessreport.js"></script>
+  <script src="/resources/testdriver.js"></script>
+  <script src="/resources/testdriver-vendor.js"></script>
+  <script src="/feature-policy/resources/featurepolicy.js"></script>
+  <script>
+    const same_origin_src =
+      "/feature-policy/resources/feature-policy-geolocation.html";
+    const cross_origin_src =
+      "https://{{domains[www]}}:{{ports[https][0]}}" + same_origin_src;
+
+    promise_test(async (t) => {
+
+      await test_driver.set_permission(
+        { name: "geolocation" },
+        "granted",
+        false
+      );
+
+      const result = await new Promise((resolve, reject) => {
+        navigator.geolocation.getCurrentPosition(resolve, reject);
+      });
+
+      assert_true(
+        result instanceof GeolocationPosition,
+        "Expected a GeolocationPosition"
+      );
+    }, "Feature-Policy header geolocation: * allows the top-level document.");
+
+    async_test((t) => {
+      test_feature_availability(
+        "geolocation",
+        t,
+        same_origin_src,
+        expect_feature_available_default
+      );
+    }, "Feature-Policy header geolocation: * allows same-origin iframes.");
+
+    async_test((t) => {
+      test_feature_availability(
+        "geolocation",
+        t,
+        cross_origin_src,
+        expect_feature_available_default
+      );
+    }, "Feature-Policy header geolocation: * allows cross-origin iframes.");
+  </script>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/geolocation-API/enabled-by-feature-policy.https.sub.html.headers b/third_party/blink/web_tests/external/wpt/geolocation-API/enabled-by-feature-policy.https.sub.html.headers
new file mode 100644
index 0000000..40e9bc1
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/geolocation-API/enabled-by-feature-policy.https.sub.html.headers
@@ -0,0 +1 @@
+Feature-Policy: geolocation *
diff --git a/third_party/blink/web_tests/external/wpt/geolocation-API/enabled-on-self-origin-by-feature-policy.https.sub.html b/third_party/blink/web_tests/external/wpt/geolocation-API/enabled-on-self-origin-by-feature-policy.https.sub.html
new file mode 100644
index 0000000..12ff86f
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/geolocation-API/enabled-on-self-origin-by-feature-policy.https.sub.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<body>
+  <script src="/resources/testharness.js"></script>
+  <script src="/resources/testharnessreport.js"></script>
+  <script src="/resources/testdriver.js"></script>
+  <script src="/resources/testdriver-vendor.js"></script>
+  <script src="/feature-policy/resources/featurepolicy.js"></script>
+
+  <script>
+    "use strict";
+
+    const same_origin_src =
+      "/feature-policy/resources/feature-policy-geolocation.html";
+    const cross_origin_src =
+      "https://{{domains[www]}}:{{ports[https][0]}}" + same_origin_src;
+
+    promise_test(async (t) => {
+      await test_driver.set_permission(
+        { name: "geolocation" },
+        "granted",
+        false
+      );
+      await new Promise(resolve => {
+        navigator.geolocation.getCurrentPosition(resolve);
+      })
+    }, 'Feature-Policy header geolocation "self" allows the top-level document.');
+
+    async_test((t) => {
+      test_feature_availability(
+        "geolocation",
+        t,
+        same_origin_src,
+        expect_feature_available_default
+      );
+    }, 'Feature-Policy header geolocation "self" allows same-origin iframes.');
+
+    async_test((t) => {
+      test_feature_availability(
+        "geolocation",
+        t,
+        cross_origin_src,
+        expect_feature_unavailable_default
+      );
+    }, 'Feature-Policy header geolocation "self" disallows cross-origin iframes.');
+  </script>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/geolocation-API/enabled-on-self-origin-by-feature-policy.https.sub.html.headers b/third_party/blink/web_tests/external/wpt/geolocation-API/enabled-on-self-origin-by-feature-policy.https.sub.html.headers
new file mode 100644
index 0000000..b83264e
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/geolocation-API/enabled-on-self-origin-by-feature-policy.https.sub.html.headers
@@ -0,0 +1 @@
+Feature-Policy: geolocation 'self'
diff --git a/third_party/blink/web_tests/external/wpt/geolocation-API/getCurrentPosition_TypeError.html b/third_party/blink/web_tests/external/wpt/geolocation-API/getCurrentPosition_TypeError.https.html
similarity index 82%
rename from third_party/blink/web_tests/external/wpt/geolocation-API/getCurrentPosition_TypeError.html
rename to third_party/blink/web_tests/external/wpt/geolocation-API/getCurrentPosition_TypeError.https.html
index 6726e8a..953efb2 100644
--- a/third_party/blink/web_tests/external/wpt/geolocation-API/getCurrentPosition_TypeError.html
+++ b/third_party/blink/web_tests/external/wpt/geolocation-API/getCurrentPosition_TypeError.https.html
@@ -4,52 +4,47 @@
 <link rel='help' href='http://www.w3.org/TR/geolocation-API/'>
 <script src='/resources/testharness.js'></script>
 <script src='/resources/testharnessreport.js'></script>
-<script src='support.js'></script>
-
-<div id='log'></div>
 
 <script>
 // Rewrite http://dev.w3.org/geo/api/test-suite/t.html?00027
 test(function() {
   assert_throws_js(TypeError, function() {
-    geo.getCurrentPosition();
+    navigator.geolocation.getCurrentPosition();
   });
 }, 'Call getCurrentPosition without arguments, check that exception is thrown');
 
 // Rewrite http://dev.w3.org/geo/api/test-suite/t.html?00011
 test(function() {
   assert_throws_js(TypeError, function() {
-    geo.getCurrentPosition(null);
+    navigator.geolocation.getCurrentPosition(null);
   });
 }, 'Call getCurrentPosition with null success callback, check that exception is thrown');
 
 // Rewrite http://dev.w3.org/geo/api/test-suite/t.html?00013
 test(function() {
   assert_throws_js(TypeError, function() {
-    geo.getCurrentPosition(null, null);
+    navigator.geolocation.getCurrentPosition(null, null);
   });
 }, 'Call getCurrentPosition with null success and error callbacks, check that exception is thrown');
 
 // Rewrite http://dev.w3.org/geo/api/test-suite/t.html?00028
 test(function() {
   assert_throws_js(TypeError, function() {
-    geo.getCurrentPosition(3);
+    navigator.geolocation.getCurrentPosition(3);
   });
 }, 'Call getCurrentPosition() with wrong type for first argument. Exception expected.');
 
 // Rewrite http://dev.w3.org/geo/api/test-suite/t.html?00029
 test(function() {
   assert_throws_js(TypeError, function() {
-    geo.getCurrentPosition(dummyFunction, 4);
+    navigator.geolocation.getCurrentPosition(()=>{}, 4);
   });
 }, 'Call getCurrentPosition() with wrong type for second argument. Exception expected.');
 
 // Rewrite http://dev.w3.org/geo/api/test-suite/t.html?00030
 test(function() {
   assert_throws_js(TypeError, function() {
-    geo.getCurrentPosition(dummyFunction, dummyFunction, 4);
+    navigator.geolocation.getCurrentPosition(()=>{}, ()=>{}, 4);
   });
 }, 'Call getCurrentPosition() with wrong type for third argument. Exception expected.');
-
-done();
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/geolocation-API/getCurrentPosition_permission_deny.https.html b/third_party/blink/web_tests/external/wpt/geolocation-API/getCurrentPosition_permission_deny.https.html
index 5012960..297f9a2 100644
--- a/third_party/blink/web_tests/external/wpt/geolocation-API/getCurrentPosition_permission_deny.https.html
+++ b/third_party/blink/web_tests/external/wpt/geolocation-API/getCurrentPosition_permission_deny.https.html
@@ -1,32 +1,24 @@
-<!DOCTYPE HTML>
-<meta charset='utf-8'>
+<!DOCTYPE html>
+<meta charset="utf-8" />
 <title>Geolocation Test: getCurrentPosition location access denied</title>
-<link rel='help' href='http://www.w3.org/TR/geolocation-API/#privacy_for_uas'>
-<script src='/resources/testharness.js'></script>
-<script src='/resources/testharnessreport.js'></script>
-<script src='support.js'></script>
-
-<p>Clear all Geolocation permissions before running this test. If prompted for permission, please deny.</p>
-<div id='log'></div>
-
+<link rel="help" href="http://www.w3.org/TR/geolocation-API/#privacy_for_uas" />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
 <script>
-// Rewrite http://dev.w3.org/geo/api/test-suite/t.html?00001
-var t = async_test('User denies access, check that error callback is called with correct code'),
-    onSuccess, onError, hasMethodReturned = false;
-
-t.step(function() {
-  onSuccess = t.step_func_done(function(pos) {
-    assert_unreached('A success callback was invoked unexpectedly with position ' + positionToString(pos));
-  });
-
-  onError =  t.step_func_done(function(err) {
-    // http://dev.w3.org/geo/api/test-suite/t.html?00031
-    assert_true(hasMethodReturned, 'Check that getCurrentPosition returns synchronously before any callbacks are invoked');
-    assert_equals(err.code, err.PERMISSION_DENIED, errorToString(err));
-  });
-
-  geo.getCurrentPosition(onSuccess, onError);
-  hasMethodReturned = true;
-});
-
+  promise_test(async (t) => {
+    t.add_cleanup(() => {
+      return test_driver.set_permission({ name: "geolocation" }, "prompt");
+    });
+    await test_driver.set_permission({ name: "geolocation" }, "denied");
+    const result = await new Promise((resolve, reject) => {
+      navigator.geolocation.getCurrentPosition(reject, resolve);
+    });
+    assert_true(
+      result instanceof GeolocationPositionError,
+      "should be a GeolocationPositionError"
+    );
+    assert_equals(result.code, result.PERMISSION_DENIED);
+  }, "User denies access, check that error callback is called with correct code");
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/geolocation-API/non-fully-active.https.html b/third_party/blink/web_tests/external/wpt/geolocation-API/non-fully-active.https.html
index d75c787..13853c0 100644
--- a/third_party/blink/web_tests/external/wpt/geolocation-API/non-fully-active.https.html
+++ b/third_party/blink/web_tests/external/wpt/geolocation-API/non-fully-active.https.html
@@ -9,7 +9,10 @@
 <script src="support.js"></script>
 <body></body>
 <script>
-  promise_test(async function () {
+  promise_test(async (t) => {
+    t.add_cleanup(() => {
+      return test_driver.set_permission({ name: "geolocation" }, "prompt");
+    });
     await test_driver.set_permission({ name: "geolocation" }, "granted");
 
     // Create the iframe, wait for it to load...
diff --git a/third_party/blink/web_tests/external/wpt/geolocation-API/watchPosition_TypeError.html b/third_party/blink/web_tests/external/wpt/geolocation-API/watchPosition_TypeError.html
deleted file mode 100644
index dd8287f..0000000
--- a/third_party/blink/web_tests/external/wpt/geolocation-API/watchPosition_TypeError.html
+++ /dev/null
@@ -1,55 +0,0 @@
-<!DOCTYPE HTML>
-<meta charset='utf-8'>
-<title>Geolocation Test: watchPosition TypeError tests</title>
-<link rel='help' href='http://www.w3.org/TR/geolocation-API/'>
-<script src='/resources/testharness.js'></script>
-<script src='/resources/testharnessreport.js'></script>
-<script src='support.js'></script>
-
-<div id='log'></div>
-
-<script>
-// Rewrite http://dev.w3.org/geo/api/test-suite/t.html?00058
-test(function() {
-  assert_throws_js(TypeError, function() {
-    geo.watchPosition();
-  });
-}, 'Call watchPosition without arguments, check that exception is thrown');
-
-// Rewrite http://dev.w3.org/geo/api/test-suite/t.html?00015
-test(function() {
-  assert_throws_js(TypeError, function() {
-    geo.watchPosition(null);
-  });
-}, 'Call watchPosition with null success callback, check that exception is thrown');
-
-// Rewrite http://dev.w3.org/geo/api/test-suite/t.html?00017
-test(function() {
-  assert_throws_js(TypeError, function() {
-    geo.watchPosition(null, null);
-  });
-}, 'Call watchPosition with null success and error callbacks, check that exception is thrown');
-
-// Rewrite http://dev.w3.org/geo/api/test-suite/t.html?00059
-test(function() {
-  assert_throws_js(TypeError, function() {
-    geo.watchPosition(3);
-  });
-}, 'Call watchPosition() with wrong type for first argument. Exception expected.');
-
-// Rewrite http://dev.w3.org/geo/api/test-suite/t.html?00060
-test(function() {
-  assert_throws_js(TypeError, function() {
-    geo.watchPosition(dummyFunction, 4);
-  });
-}, 'Call watchPosition() with wrong type for second argument. Exception expected.');
-
-// Rewrite http://dev.w3.org/geo/api/test-suite/t.html?00061
-test(function() {
-  assert_throws_js(TypeError, function() {
-    geo.watchPosition(dummyFunction, dummyFunction, 4);
-  });
-}, 'Call watchPosition() with wrong type for third argument. Exception expected.');
-
-done();
-</script>
diff --git a/third_party/blink/web_tests/external/wpt/geolocation-API/watchPosition_TypeError.https.html b/third_party/blink/web_tests/external/wpt/geolocation-API/watchPosition_TypeError.https.html
new file mode 100644
index 0000000..64f0616
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/geolocation-API/watchPosition_TypeError.https.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<meta charset="utf-8" />
+<title>Geolocation Test: watchPosition TypeError tests</title>
+<link rel="help" href="http://www.w3.org/TR/geolocation-API/" />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+  // Rewrite http://dev.w3.org/geo/api/test-suite/t.html?00058
+  test(() => {
+    assert_throws_js(TypeError, () => {
+      navigator.geolocation.watchPosition();
+    });
+  }, "Call watchPosition without arguments, check that exception is thrown");
+
+  // Rewrite http://dev.w3.org/geo/api/test-suite/t.html?00015
+  test(() => {
+    assert_throws_js(TypeError, () => {
+      navigator.geolocation.watchPosition(null);
+    });
+  }, "Call watchPosition with null success callback, check that exception is thrown");
+
+  // Rewrite http://dev.w3.org/geo/api/test-suite/t.html?00017
+  test(() => {
+    assert_throws_js(TypeError, () => {
+      navigator.geolocation.watchPosition(null, null);
+    });
+  }, "Call watchPosition with null success and error callbacks, check that exception is thrown");
+
+  // Rewrite http://dev.w3.org/geo/api/test-suite/t.html?00059
+  test(() => {
+    assert_throws_js(TypeError, () => {
+      navigator.geolocation.watchPosition(3);
+    });
+  }, "Call watchPosition() with wrong type for first argument. Exception expected.");
+
+  // Rewrite http://dev.w3.org/geo/api/test-suite/t.html?00060
+  test(() => {
+    assert_throws_js(TypeError, () => {
+      navigator.geolocation.watchPosition(() => {}, 4);
+    });
+  }, "Call watchPosition() with wrong type for second argument. Exception expected.");
+
+  // Rewrite http://dev.w3.org/geo/api/test-suite/t.html?00061
+  test(() => {
+    assert_throws_js(TypeError, () => {
+      navigator.geolocation.watchPosition(
+        () => {},
+        () => {},
+        4
+      );
+    });
+  }, "Call watchPosition() with wrong type for third argument. Exception expected.");
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/geolocation-API/watchPosition_permission_deny.https.html b/third_party/blink/web_tests/external/wpt/geolocation-API/watchPosition_permission_deny.https.html
index 1e2a3c4..d47f2b55 100644
--- a/third_party/blink/web_tests/external/wpt/geolocation-API/watchPosition_permission_deny.https.html
+++ b/third_party/blink/web_tests/external/wpt/geolocation-API/watchPosition_permission_deny.https.html
@@ -1,33 +1,45 @@
-<!DOCTYPE HTML>
-<meta charset='utf-8'>
+<!DOCTYPE html>
+<meta charset="utf-8" />
 <title>Geolocation Test: watchPosition location access denied</title>
-<link rel='help' href='http://www.w3.org/TR/geolocation-API/#watch-position'>
-<script src='/resources/testharness.js'></script>
-<script src='/resources/testharnessreport.js'></script>
-<script src='support.js'></script>
-
-<p>Clear all Geolocation permissions before running this test. If prompted for permission, please deny.</p>
-<div id='log'></div>
-
+<link rel="help" href="http://www.w3.org/TR/geolocation-API/#privacy_for_uas" />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
 <script>
-// Rewrite http://dev.w3.org/geo/api/test-suite/t.html?00062
-var t = async_test('Check that watchPosition returns synchronously before any callbacks are invoked.'),
-    id, checkMethodHasReturned, hasMethodReturned = false;
+  const resetPermission = () => {
+    return test_driver.set_permission({ name: "geolocation" }, "prompt");
+  };
 
-checkMethodHasReturned = t.step_func_done(function() {
-  assert_true(hasMethodReturned);
-});
+  promise_test(async (t) => {
+    t.add_cleanup(resetPermission);
+    await test_driver.set_permission({ name: "geolocation" }, "denied");
+    let errorCBCalled = false;
+    let id;
+    const errorPromise = new Promise((resolve, reject) => {
+      id = navigator.geolocation.watchPosition(reject, () => {
+        errorCBCalled = true;
+        resolve();
+      });
+    });
+    assert_false(
+      errorCBCalled,
+      "error callback must not be called synchronously"
+    );
+    await errorPromise;
+    navigator.geolocation.clearWatch(id);
+  }, "Check that watchPosition returns synchronously before any callbacks are invoked.");
 
-try {
-  id = geo.watchPosition(checkMethodHasReturned, checkMethodHasReturned);
-  hasMethodReturned = true;
-} catch(e) {
-  t.unreached_func('An exception was thrown unexpectedly: ' + e.message);
-}
-
-// Rewrite http://dev.w3.org/geo/api/test-suite/t.html?00151
-test(function() {
-  assert_greater_than_equal(id, -2147483648);
-  assert_less_than_equal(id, 2147483647);
-}, 'Check that watchPosition returns a long');
+  promise_test(async (t) => {
+    t.add_cleanup(resetPermission);
+    await test_driver.set_permission({ name: "geolocation" }, "denied");
+    const promise = new Promise((resolve, reject) => {
+      navigator.geolocation.watchPosition(reject, resolve);
+    });
+    const result = await promise;
+    assert_true(
+      result instanceof GeolocationPositionError,
+      "expected GeolocationPositionError"
+    );
+  }, "User denies access, check that error callback is called.");
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/interactive-elements/the-dialog-element/modal-dialog-in-replaced-renderer-ref.html b/third_party/blink/web_tests/external/wpt/html/semantics/interactive-elements/the-dialog-element/modal-dialog-in-replaced-renderer-ref.html
index 6588615..c837503 100644
--- a/third_party/blink/web_tests/external/wpt/html/semantics/interactive-elements/the-dialog-element/modal-dialog-in-replaced-renderer-ref.html
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/interactive-elements/the-dialog-element/modal-dialog-in-replaced-renderer-ref.html
@@ -12,13 +12,7 @@
 </style>
 </head>
 <body>
-<p>Bug <a href="http://webkit.org/b/103477">103477</a>: Make
-NodeRenderingContext::parentRenderer and nextRenderer top layer aware
-<p>The test passes if you see a green square near the top and green rectangle in the center of the viewport.
+<p>The test passes if you see a green square near the top of the viewport.
 <div></div>
-<dialog id="dialog"></dialog>
-<script>
-document.getElementById('dialog').showModal();
-</script>
 </body>
 </html>
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/interactive-elements/the-dialog-element/modal-dialog-in-replaced-renderer.html b/third_party/blink/web_tests/external/wpt/html/semantics/interactive-elements/the-dialog-element/modal-dialog-in-replaced-renderer.html
index 09490519..75727b4 100644
--- a/third_party/blink/web_tests/external/wpt/html/semantics/interactive-elements/the-dialog-element/modal-dialog-in-replaced-renderer.html
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/interactive-elements/the-dialog-element/modal-dialog-in-replaced-renderer.html
@@ -1,6 +1,7 @@
 <!DOCTYPE html>
 <html>
 <head>
+<title>Modal dialog inside replaced renderer should not generate box</title>
 <link rel="match" href="modal-dialog-in-replaced-renderer-ref.html">
 <link rel="help" href="https://fullscreen.spec.whatwg.org/#new-stacking-layer">
 <style>
@@ -14,9 +15,7 @@
 </style>
 </head>
 <body>
-<p>Bug <a href="http://webkit.org/b/103477">103477</a>: Make
-NodeRenderingContext::parentRenderer and nextRenderer top layer aware
-<p>The test passes if you see a green square near the top and green rectangle in the center of the viewport.
+<p>The test passes if you see a green square near the top of the viewport.
 <div>
 <dialog id="dialog"></dialog>
 </div>
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/interactive-elements/the-dialog-element/modal-dialog-in-table-column-ref.html b/third_party/blink/web_tests/external/wpt/html/semantics/interactive-elements/the-dialog-element/modal-dialog-in-table-column-ref.html
index 38b628c..0310d1b 100644
--- a/third_party/blink/web_tests/external/wpt/html/semantics/interactive-elements/the-dialog-element/modal-dialog-in-table-column-ref.html
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/interactive-elements/the-dialog-element/modal-dialog-in-table-column-ref.html
@@ -9,12 +9,6 @@
 </style>
 </head>
 <body>
-<p>Bug <a href="http://webkit.org/b/103477">103477</a>: Make
-NodeRenderingContext::parentRenderer and nextRenderer top layer aware
-<p>The test passes if you see a green rectangle in the center of the viewport.
-<dialog id="dialog"></dialog>
-<script>
-document.getElementById('dialog').showModal();
-</script>
+<p>The test passes if you see no green rectangle.
 </body>
 </html>
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/interactive-elements/the-dialog-element/modal-dialog-in-table-column.html b/third_party/blink/web_tests/external/wpt/html/semantics/interactive-elements/the-dialog-element/modal-dialog-in-table-column.html
index 89ee6977..3d72826b 100644
--- a/third_party/blink/web_tests/external/wpt/html/semantics/interactive-elements/the-dialog-element/modal-dialog-in-table-column.html
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/interactive-elements/the-dialog-element/modal-dialog-in-table-column.html
@@ -1,6 +1,7 @@
 <!DOCTYPE html>
 <html>
 <head>
+<title>Modal dialog inside display: table-column should not generate box</title>
 <link rel="match" href="modal-dialog-in-table-column-ref.html">
 <link rel="help" href="https://fullscreen.spec.whatwg.org/#new-stacking-layer">
 <style>
@@ -14,9 +15,7 @@
 </style>
 </head>
 <body>
-<p>Bug <a href="http://webkit.org/b/103477">103477</a>: Make
-NodeRenderingContext::parentRenderer and nextRenderer top layer aware
-<p>The test passes if you see a green rectangle in the center of the viewport.
+<p>The test passes if you see no green rectangle.
 <div>
 <dialog id="dialog"></dialog>
 </div>
diff --git a/third_party/blink/web_tests/external/wpt/inert/inert-iframe-hittest.tentative.html b/third_party/blink/web_tests/external/wpt/inert/inert-iframe-hittest.tentative.html
index 63e3477..bcc542d 100644
--- a/third_party/blink/web_tests/external/wpt/inert/inert-iframe-hittest.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/inert/inert-iframe-hittest.tentative.html
@@ -15,6 +15,10 @@
 </div>
 
 <script>
+const events = [
+  "mousedown", "mouseenter", "mousemove", "mouseover",
+  "pointerdown", "pointerenter", "pointermove", "pointerover",
+];
 const iframe = document.getElementById("iframe");
 let iframeDoc;
 let target;
@@ -34,57 +38,64 @@
   });
 });
 
-promise_test(async function() {
-  let reachedTarget = false;
-  target.addEventListener("mousedown", () => {
-    reachedTarget = true;
-  }, { once: true });
-
-  let reachedWrapper = false;
-  wrapper.addEventListener("mousedown", () => {
-    reachedWrapper = true;
-  }, { once: true });
+async function mouseDownAndGetEvents(test) {
+  const receivedEvents = {
+    target: [],
+    wrapper: [],
+  };
+  for (let event of events) {
+    target.addEventListener(event, () => {
+      receivedEvents.target.push(event);
+    }, { once: true, capture: true });
+    wrapper.addEventListener(event, () => {
+      receivedEvents.wrapper.push(event);
+    }, { once: true, capture: true });
+  }
 
   await new test_driver.Actions()
      .pointerMove(0, 0, { origin: wrapper })
      .pointerDown()
      .send();
-  this.add_cleanup(() => test_driver.click(document.body));
+  test.add_cleanup(() => test_driver.click(document.body));
+
+  // Exact order of events is not interoperable.
+  receivedEvents.target.sort();
+  receivedEvents.wrapper.sort();
+  return receivedEvents;
+}
+
+promise_test(async function() {
+  const receivedEvents = await mouseDownAndGetEvents(this);
+  assert_array_equals(receivedEvents.target, [], "target got no event");
+  assert_array_equals(receivedEvents.wrapper, events, "wrapper got all events");
 
   assert_false(target.matches(":focus"), "target is not focused");
   assert_false(target.matches(":active"), "target is not active");
   assert_false(target.matches(":hover"), "target is not hovered");
-  assert_false(reachedTarget, "target didn't get event");
-
   assert_true(wrapper.matches(":hover"), "wrapper is hovered");
-  assert_true(reachedWrapper, "wrapper got event");
 }, "Hit-testing doesn't reach contents of an inert iframe");
 
 promise_test(async function() {
   iframe.inert = false;
 
-  let reachedTarget = false;
-  target.addEventListener("mousedown", () => {
-    reachedTarget = true;
-  }, { once: true });
-
-  let reachedWrapper = false;
-  wrapper.addEventListener("mousedown", () => {
-    reachedWrapper = true;
-  }, { once: true });
-
-  await new test_driver.Actions()
-     .pointerMove(0, 0, { origin: wrapper })
-     .pointerDown()
-     .send();
-  this.add_cleanup(() => test_driver.click(document.body));
+  const receivedEvents = await mouseDownAndGetEvents(this);
+  assert_array_equals(receivedEvents.target, events, "target got all events");
+  if (receivedEvents.wrapper.length === 2) {
+    // Firefox is unstable, sometimes missing the mouse events.
+    assert_array_equals(
+      receivedEvents.wrapper,
+      ["pointerenter", "pointerover"],
+      "wrapper got enter and over pointer events");
+  } else {
+    assert_array_equals(
+      receivedEvents.wrapper,
+      ["mouseenter", "mouseover", "pointerenter", "pointerover"],
+      "wrapper got enter and over events");
+  }
 
   assert_true(target.matches(":focus"), "target is focused");
   assert_true(target.matches(":active"), "target is active");
   assert_true(target.matches(":hover"), "target is hovered");
-  assert_true(reachedTarget, "target got event");
-
   assert_true(wrapper.matches(":hover"), "wrapper is hovered");
-  assert_false(reachedWrapper, "wrapper didn't get event");
 }, "Hit-testing can reach contents of a no longer inert iframe");
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/inert/inert-iframe-tabbing.tentative.html b/third_party/blink/web_tests/external/wpt/inert/inert-iframe-tabbing.tentative.html
index e346993..a0146b7 100644
--- a/third_party/blink/web_tests/external/wpt/inert/inert-iframe-tabbing.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/inert/inert-iframe-tabbing.tentative.html
@@ -4,63 +4,120 @@
 <link rel="author" title="Oriol Brufau" href="mailto:obrufau@igalia.com">
 <link rel="help" href="https://html.spec.whatwg.org/multipage/interaction.html#inert">
 <link rel="help" href="https://html.spec.whatwg.org/multipage/interaction.html#sequential-focus-navigation">
-<meta assert="assert" content="Contents of an inert iframe can't be focused by tabbing">
+<meta assert="assert" content="Tabbing can't enter an inert iframe from the outside, but can move within it and can leave it if focus is already there.">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="/resources/testdriver.js"></script>
 <script src="/resources/testdriver-vendor.js"></script>
 
-<div id="start" tabindex="0">start</div>
+<div id="before" tabindex="0">before</div>
 <div id="inert" inert>
   <iframe id="iframe"></iframe>
 </div>
-<div id="end" tabindex="0">start</a>
+<div id="after" tabindex="0">after</a>
 
 <script>
 const tabKey = "\uE004";
-const start = document.getElementById("start");
+const before = document.getElementById("before");
 const inert = document.getElementById("inert");
-const end = document.getElementById("end");
+const after = document.getElementById("after");
 const iframe = document.getElementById("iframe");
 let iframeDoc;
-let target;
+let start;
+let end;
 
 promise_setup(async () => {
   await new Promise(resolve => {
     iframe.addEventListener("load", resolve, { once: true });
-    iframe.srcdoc = `<div id="target" tabindex="0">target</div>`;
+    iframe.srcdoc = `
+      <div id="start" tabindex="0">target</div>
+      <div id="end" tabindex="0">target</div>
+    `;
   });
   iframeDoc = iframe.contentDocument;
-  target = iframeDoc.getElementById("target");
+  start = iframeDoc.getElementById("start");
+  end = iframeDoc.getElementById("end");
 });
 
 promise_test(async function() {
-  start.focus();
-  assert_equals(document.activeElement, start, "start got focus");
+  before.focus();
+  assert_equals(document.activeElement, before, "#before got outer focus");
   assert_false(iframeDoc.hasFocus(), "iframeDoc doesn't have focus");
 
   await test_driver.send_keys(document.activeElement, tabKey);
-  assert_equals(document.activeElement, end, "end got focus");
+  assert_equals(document.activeElement, after, "#after got outer focus");
   assert_false(iframeDoc.hasFocus(), "iframeDoc still doesn't have focus");
-}, "Sequential navigation skips contents of inert iframe");
+}, "Sequential navigation can't enter an inert iframe");
 
 promise_test(async function() {
   start.focus();
-  assert_equals(document.activeElement, start, "start got focus");
-  assert_false(iframeDoc.hasFocus(), "iframeDoc doesn't have focus");
+  assert_equals(document.activeElement, iframe, "#iframe got outer focus");
+  assert_equals(iframeDoc.activeElement, start, "#start got inner focus");
+  assert_true(iframeDoc.hasFocus(), "iframeDoc has focus");
 
+  await test_driver.send_keys(iframeDoc.activeElement, tabKey);
+  assert_equals(document.activeElement, iframe, "#iframe still has outer focus");
+  assert_equals(iframeDoc.activeElement, end, "#end got inner focus");
+  assert_true(iframeDoc.hasFocus(), "iframeDoc still has focus");
+}, "Sequential navigation can move within an inert iframe");
+
+promise_test(async function() {
+  end.focus();
+  assert_equals(document.activeElement, iframe, "#iframe got outer focus");
+  assert_equals(iframeDoc.activeElement, end, "#end got inner focus");
+  assert_true(iframeDoc.hasFocus(), "iframeDoc has focus");
+
+  await test_driver.send_keys(iframeDoc.activeElement, tabKey);
+  assert_equals(document.activeElement, after, "#after got outer focus");
+  assert_false(iframeDoc.hasFocus(), "iframeDoc doesn't have focus");
+}, "Sequential navigation can leave an inert iframe");
+
+// Test again without inertness.
+
+promise_test(async function() {
   inert.inert = false;
 
+  before.focus();
+  assert_equals(document.activeElement, before, "#before got outer focus");
+  assert_false(iframeDoc.hasFocus(), "iframeDoc doesn't have focus");
+
   await test_driver.send_keys(document.activeElement, tabKey);
-  assert_equals(document.activeElement, iframe, "iframe got focus");
+  assert_equals(document.activeElement, iframe, "#iframe got outer focus");
   assert_true(iframeDoc.hasFocus(), "iframeDoc has focus");
 
   // The document element is also focusable in Firefox.
   if (iframeDoc.activeElement === iframeDoc.documentElement) {
     await test_driver.send_keys(document.activeElement, tabKey);
-    assert_equals(document.activeElement, iframe, "iframe got focus");
+    assert_equals(document.activeElement, iframe, "#iframe got outer focus");
     assert_true(iframeDoc.hasFocus(), "iframeDoc has focus");
   }
-  assert_equals(iframeDoc.activeElement, target, "target got focus");
-}, "Sequential navigation can pick contents of a no longer inert iframe");
+  assert_equals(iframeDoc.activeElement, start, "#start got inner focus");
+}, "Sequential navigation can enter a no longer inert iframe");
+
+promise_test(async function() {
+  inert.inert = false;
+
+  start.focus();
+  assert_equals(document.activeElement, iframe, "#iframe got outer focus");
+  assert_equals(iframeDoc.activeElement, start, "#start got inner focus");
+  assert_true(iframeDoc.hasFocus(), "iframeDoc has focus");
+
+  await test_driver.send_keys(iframeDoc.activeElement, tabKey);
+  assert_equals(document.activeElement, iframe, "#iframe still has outer focus");
+  assert_equals(iframeDoc.activeElement, end, "#end got inner focus");
+  assert_true(iframeDoc.hasFocus(), "iframeDoc still has focus");
+}, "Sequential navigation can move within a no longer inert iframe");
+
+promise_test(async function() {
+  inert.inert = false;
+
+  end.focus();
+  assert_equals(document.activeElement, iframe, "#iframe got outer focus");
+  assert_equals(iframeDoc.activeElement, end, "#end got inner focus");
+  assert_true(iframeDoc.hasFocus(), "iframeDoc has focus");
+
+  await test_driver.send_keys(iframeDoc.activeElement, tabKey);
+  assert_equals(document.activeElement, after, "#after got outer focus");
+  assert_false(iframeDoc.hasFocus(), "iframeDoc doesn't have focus");
+}, "Sequential navigation can leave a no longer inert iframe");
 </script>
diff --git a/third_party/blink/web_tests/flag-specific/highdpi/external/wpt/css/selectors/invalidation/has-sibling-expected.txt b/third_party/blink/web_tests/flag-specific/highdpi/external/wpt/css/selectors/invalidation/has-sibling-expected.txt
deleted file mode 100644
index 01f64f5..0000000
--- a/third_party/blink/web_tests/flag-specific/highdpi/external/wpt/css/selectors/invalidation/has-sibling-expected.txt
+++ /dev/null
@@ -1,75 +0,0 @@
-This is a testharness.js-based test.
-Found 71 tests; 36 PASS, 35 FAIL, 0 TIMEOUT, 0 NOTRUN.
-PASS initial_color
-FAIL add .test to first_sibling assert_equals: expected "rgb(0, 128, 0)" but got "rgb(128, 128, 128)"
-PASS remove .test from first_sibling
-FAIL add .test to second_sibling assert_equals: expected "rgb(255, 0, 0)" but got "rgb(128, 128, 128)"
-PASS remove .test from second_sibling
-FAIL add .test to third_sibling assert_equals: expected "rgb(255, 0, 0)" but got "rgb(128, 128, 128)"
-PASS remove .test from third_sibling
-FAIL add .test to first_sibling_child assert_equals: expected "rgb(255, 192, 203)" but got "rgb(128, 128, 128)"
-PASS remove .test from first_sibling_child
-FAIL add .test to first_sibling_descendant assert_equals: expected "rgb(255, 255, 0)" but got "rgb(128, 128, 128)"
-PASS remove .test from first_sibling_descendant
-FAIL add .test to third_sibling_child assert_equals: expected "rgb(128, 0, 128)" but got "rgb(128, 128, 128)"
-PASS remove .test from third_sibling_child
-FAIL add .test to third_sibling_descendant assert_equals: expected "rgb(0, 0, 255)" but got "rgb(128, 128, 128)"
-PASS remove .test from third_sibling_descendant
-FAIL insert element div.test before first_sibling assert_equals: expected "rgb(0, 128, 0)" but got "rgb(128, 128, 128)"
-PASS remove element div.test before first_sibling
-FAIL insert element div.test before second_sibling assert_equals: expected "rgb(255, 0, 0)" but got "rgb(128, 128, 128)"
-PASS remove element div.test before second_sibling
-FAIL insert element div.test before third_sibling assert_equals: expected "rgb(255, 0, 0)" but got "rgb(128, 128, 128)"
-PASS remove element div.test before third_sibling
-FAIL insert element div.test before first_sibling_child assert_equals: expected "rgb(255, 192, 203)" but got "rgb(128, 128, 128)"
-PASS remove element div.test before first_sibling_child
-FAIL insert element div.test before first_sibling_descendant assert_equals: expected "rgb(255, 255, 0)" but got "rgb(128, 128, 128)"
-PASS remove element div.test before first_sibling_descendant
-FAIL insert element div.test before third_sibling_child assert_equals: expected "rgb(128, 0, 128)" but got "rgb(128, 128, 128)"
-PASS remove element div.test before third_sibling_child
-FAIL insert element div.test before third_sibling_descendant assert_equals: expected "rgb(0, 0, 255)" but got "rgb(128, 128, 128)"
-PASS remove element div.test before third_sibling_descendant
-FAIL insert element div.test after first_sibling assert_equals: expected "rgb(255, 0, 0)" but got "rgb(128, 128, 128)"
-PASS remove element div.test after first_sibling
-FAIL insert element div.test after second_sibling assert_equals: expected "rgb(255, 0, 0)" but got "rgb(128, 128, 128)"
-PASS remove element div.test after second_sibling
-FAIL insert element div.test after third_sibling assert_equals: expected "rgb(255, 0, 0)" but got "rgb(128, 128, 128)"
-PASS remove element div.test after third_sibling
-FAIL insert element div.test after first_sibling_child assert_equals: expected "rgb(255, 192, 203)" but got "rgb(128, 128, 128)"
-PASS remove element div.test after first_sibling_child
-FAIL insert element div.test after first_sibling_descendant assert_equals: expected "rgb(255, 255, 0)" but got "rgb(128, 128, 128)"
-PASS remove element div.test after first_sibling_descendant
-FAIL insert element div.test after third_sibling_child assert_equals: expected "rgb(128, 0, 128)" but got "rgb(128, 128, 128)"
-PASS remove element div.test after third_sibling_child
-FAIL insert element div.test after third_sibling_descendant assert_equals: expected "rgb(0, 0, 255)" but got "rgb(128, 128, 128)"
-PASS remove element div.test after third_sibling_descendant
-FAIL insert tree div>div.test before first_sibling assert_equals: expected "rgb(255, 192, 203)" but got "rgb(128, 128, 128)"
-PASS remove tree div>div.test before first_sibling
-FAIL insert tree div>div.test before second_sibling assert_equals: expected "rgb(128, 0, 128)" but got "rgb(128, 128, 128)"
-PASS remove tree div>div.test before second_sibling
-FAIL insert tree div>div.test before third_sibling assert_equals: expected "rgb(128, 0, 128)" but got "rgb(128, 128, 128)"
-PASS remove tree div>div.test before third_sibling
-FAIL insert tree div>div.test before first_sibling_child assert_equals: expected "rgb(255, 255, 0)" but got "rgb(128, 128, 128)"
-PASS remove tree div>div.test before first_sibling_child
-FAIL insert tree div>div.test before first_sibling_descendant assert_equals: expected "rgb(255, 255, 0)" but got "rgb(128, 128, 128)"
-PASS remove tree div>div.test before first_sibling_descendant
-FAIL insert tree div>div.test before third_sibling_child assert_equals: expected "rgb(0, 0, 255)" but got "rgb(128, 128, 128)"
-PASS remove tree div>div.test before third_sibling_child
-FAIL insert tree div>div.test before third_sibling_descendant assert_equals: expected "rgb(0, 0, 255)" but got "rgb(128, 128, 128)"
-PASS remove tree div>div.test before third_sibling_descendant
-FAIL insert tree div>div.test after first_sibling assert_equals: expected "rgb(128, 0, 128)" but got "rgb(128, 128, 128)"
-PASS remove tree div>div.test after first_sibling
-FAIL insert tree div>div.test after second_sibling assert_equals: expected "rgb(128, 0, 128)" but got "rgb(128, 128, 128)"
-PASS remove tree div>div.test after second_sibling
-FAIL insert tree div>div.test after third_sibling assert_equals: expected "rgb(128, 0, 128)" but got "rgb(128, 128, 128)"
-PASS remove tree div>div.test after third_sibling
-FAIL insert tree div>div.test after first_sibling_child assert_equals: expected "rgb(255, 255, 0)" but got "rgb(128, 128, 128)"
-PASS remove tree div>div.test after first_sibling_child
-FAIL insert tree div>div.test after first_sibling_descendant assert_equals: expected "rgb(255, 255, 0)" but got "rgb(128, 128, 128)"
-PASS remove tree div>div.test after first_sibling_descendant
-FAIL insert tree div>div.test after third_sibling_child assert_equals: expected "rgb(0, 0, 255)" but got "rgb(128, 128, 128)"
-PASS remove tree div>div.test after third_sibling_child
-FAIL insert tree div>div.test after third_sibling_descendant assert_equals: expected "rgb(0, 0, 255)" but got "rgb(128, 128, 128)"
-PASS remove tree div>div.test after third_sibling_descendant
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/virtual/shared_array_buffer_on_desktop/crypto/random-values-expected.txt b/third_party/blink/web_tests/virtual/shared_array_buffer_on_desktop/crypto/random-values-expected.txt
deleted file mode 100644
index a72656e1..0000000
--- a/third_party/blink/web_tests/virtual/shared_array_buffer_on_desktop/crypto/random-values-expected.txt
+++ /dev/null
@@ -1,13 +0,0 @@
-Tests crypto.randomValues.
-
-On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
-
-PASS 'crypto' in self is true
-PASS 'getRandomValues' in self.crypto is true
-PASS self.crypto.__proto__.hasOwnProperty('getRandomValues') is true
-PASS matchingBytes < 100 is true
-PASS crypto.getRandomValues(new Uint8Array(new SharedArrayBuffer(100))) threw exception TypeError: Failed to execute 'getRandomValues' on 'Crypto': The provided ArrayBufferView value must not be shared..
-PASS successfullyParsed is true
-
-TEST COMPLETE
-
diff --git a/third_party/blink/web_tests/virtual/shared_array_buffer_on_desktop/crypto/worker-random-values-expected.txt b/third_party/blink/web_tests/virtual/shared_array_buffer_on_desktop/crypto/worker-random-values-expected.txt
deleted file mode 100644
index 588b55a2..0000000
--- a/third_party/blink/web_tests/virtual/shared_array_buffer_on_desktop/crypto/worker-random-values-expected.txt
+++ /dev/null
@@ -1,14 +0,0 @@
-[Worker] Tests crypto.randomValues.
-
-On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
-
-Starting worker: random-values.js
-PASS [Worker] 'crypto' in self is true
-PASS [Worker] 'getRandomValues' in self.crypto is true
-PASS [Worker] self.crypto.__proto__.hasOwnProperty('getRandomValues') is true
-PASS [Worker] matchingBytes < 100 is true
-PASS [Worker] crypto.getRandomValues(new Uint8Array(new SharedArrayBuffer(100))) threw exception TypeError: Failed to execute 'getRandomValues' on 'Crypto': The provided ArrayBufferView value must not be shared..
-PASS successfullyParsed is true
-
-TEST COMPLETE
-
diff --git a/tools/metrics/histograms/metadata/cros/histograms.xml b/tools/metrics/histograms/metadata/cros/histograms.xml
index bccd0dc..b13fa04 100644
--- a/tools/metrics/histograms/metadata/cros/histograms.xml
+++ b/tools/metrics/histograms/metadata/cros/histograms.xml
@@ -35,7 +35,7 @@
 </histogram>
 
 <histogram name="CrosDisks.ArchiveType" enum="CrosDisksArchiveType"
-    expires_after="2022-04-03">
+    expires_after="2023-04-01">
   <owner>fdegros@chromium.org</owner>
   <owner>src/ui/file_manager/OWNERS</owner>
   <summary>
@@ -45,7 +45,7 @@
 </histogram>
 
 <histogram name="CrosDisks.DeviceMediaType" enum="CrosDisksDeviceMediaType"
-    expires_after="2022-04-24">
+    expires_after="2023-04-01">
   <owner>fdegros@chromium.org</owner>
   <owner>src/ui/file_manager/OWNERS</owner>
   <summary>
@@ -55,7 +55,7 @@
 </histogram>
 
 <histogram name="CrosDisks.FilesystemType" enum="CrosDisksFilesystemType"
-    expires_after="2022-04-24">
+    expires_after="2023-04-01">
   <owner>fdegros@chromium.org</owner>
   <owner>src/ui/file_manager/OWNERS</owner>
   <summary>
@@ -65,7 +65,7 @@
 </histogram>
 
 <histogram name="CrosDisks.Fuse.FuseArchive" enum="FuseArchiveError"
-    expires_after="2022-04-17">
+    expires_after="2023-04-01">
   <owner>fdegros@chromium.org</owner>
   <owner>src/ui/file_manager/OWNERS</owner>
   <summary>
@@ -75,7 +75,7 @@
 </histogram>
 
 <histogram name="CrosDisks.Fuse.FuseZip" enum="FuseZipError"
-    expires_after="2022-04-10">
+    expires_after="2023-04-01">
   <owner>fdegros@chromium.org</owner>
   <owner>src/ui/file_manager/OWNERS</owner>
   <summary>
@@ -85,7 +85,7 @@
 </histogram>
 
 <histogram name="CrosDisks.Fuse.Rar2fs" enum="Rar2fsError"
-    expires_after="2022-04-03">
+    expires_after="2023-04-01">
   <owner>fdegros@chromium.org</owner>
   <owner>src/ui/file_manager/OWNERS</owner>
   <summary>
@@ -97,6 +97,7 @@
 <histogram name="CrosDisksClient.FormatCompletedError"
     enum="CrosDisksClientFormatError" expires_after="2022-07-24">
   <owner>austinct@chromium.org</owner>
+  <owner>src/ui/file_manager/OWNERS</owner>
   <summary>
     The error code of disk format signals received from the Chrome OS cros-disks
     daemon.
@@ -106,6 +107,7 @@
 <histogram name="CrosDisksClient.FormatTime" units="ms"
     expires_after="2022-07-03">
   <owner>austinct@chromium.org</owner>
+  <owner>src/ui/file_manager/OWNERS</owner>
   <summary>
     Time taken for the Chrome OS cros-disks daemon to perform a format
     operation.
@@ -113,7 +115,7 @@
 </histogram>
 
 <histogram name="CrosDisksClient.MountCompletedError"
-    enum="CrosDisksClientMountError" expires_after="2022-10-19">
+    enum="CrosDisksClientMountError" expires_after="2023-04-01">
   <owner>fdegros@chromium.org</owner>
   <owner>src/ui/file_manager/OWNERS</owner>
   <summary>
@@ -123,7 +125,7 @@
 </histogram>
 
 <histogram name="CrosDisksClient.MountErrorMountType"
-    enum="CrosDisksMountTypeError" expires_after="2022-10-19">
+    enum="CrosDisksMountTypeError" expires_after="2023-04-01">
   <owner>fdegros@chromium.org</owner>
   <owner>src/ui/file_manager/OWNERS</owner>
   <summary>
@@ -133,7 +135,7 @@
 </histogram>
 
 <histogram name="CrosDisksClient.MountTime" units="ms"
-    expires_after="2022-10-19">
+    expires_after="2023-04-01">
   <owner>fdegros@chromium.org</owner>
   <owner>src/ui/file_manager/OWNERS</owner>
   <summary>
@@ -142,7 +144,7 @@
 </histogram>
 
 <histogram name="CrosDisksClient.UnmountError" enum="CrosDisksClientMountError"
-    expires_after="2022-10-19">
+    expires_after="2023-04-01">
   <owner>fdegros@chromium.org</owner>
   <owner>src/ui/file_manager/OWNERS</owner>
   <summary>
@@ -152,7 +154,7 @@
 </histogram>
 
 <histogram name="CrosDisksClient.UnmountTime" units="ms"
-    expires_after="2022-10-19">
+    expires_after="2023-04-01">
   <owner>fdegros@chromium.org</owner>
   <owner>src/ui/file_manager/OWNERS</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/login/histograms.xml b/tools/metrics/histograms/metadata/login/histograms.xml
index 9c90148..1769001 100644
--- a/tools/metrics/histograms/metadata/login/histograms.xml
+++ b/tools/metrics/histograms/metadata/login/histograms.xml
@@ -67,8 +67,9 @@
 </histogram>
 
 <histogram name="Login.BrowserShutdownTime" units="ms"
-    expires_after="2022-04-03">
+    expires_after="2023-02-22">
   <owner>xiyuan@chromium.org</owner>
+  <owner>cros-oac@google.com</owner>
   <summary>
     Tracks the browser process shutdown time from when SIGTERM is sent to the
     browser process to when the browser process group exits (or gets killed by
diff --git a/tools/metrics/histograms/metadata/signin/histograms.xml b/tools/metrics/histograms/metadata/signin/histograms.xml
index bc34010..a6d2d12 100644
--- a/tools/metrics/histograms/metadata/signin/histograms.xml
+++ b/tools/metrics/histograms/metadata/signin/histograms.xml
@@ -452,15 +452,6 @@
   </summary>
 </histogram>
 
-<histogram name="Signin.GetAccessTokenRetry" enum="GoogleServiceAuthError"
-    expires_after="2022-01-23">
-  <owner>droger@chromium.org</owner>
-  <owner>msarda@chromium.org</owner>
-  <summary>
-    Retry reason of failed access token requests during Chrome reconcile.
-  </summary>
-</histogram>
-
 <histogram name="Signin.Intercept.AccountInfoFetchDuration" units="ms"
     expires_after="2022-05-01">
   <owner>alexilin@chromium.org</owner>